From 1a7bfdc7162ba0f0099afff3a045732d1b7170e1 Mon Sep 17 00:00:00 2001 From: Romavic dos Anjos Date: Mon, 23 Jun 2025 08:44:41 +0100 Subject: [PATCH 1/3] feat: Add easy_localization for internationalization This commit integrates the `easy_localization` package to support internationalization. Key changes: - Added `easy_localization` dependency to `pubspec.yaml`. - Initialized `EasyLocalization` in `main.dart` with support for English (US) and Portuguese (PT). - Configured `MaterialApp` to use localization delegates, supported locales, and the current locale from `EasyLocalization`. - Added empty translation files for `en-US.json` and `pt-PT.json` in `assets/translations/`. This setup lays the groundwork for localizing the application's UI elements. --- assets/translations/en-US.json | 0 assets/translations/pt-PT.json | 0 lib/main.dart | 199 ++++++++++++++++++++++----------- pubspec.lock | 29 +++++ pubspec.yaml | 1 + 5 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 assets/translations/en-US.json create mode 100644 assets/translations/pt-PT.json diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json new file mode 100644 index 0000000..e69de29 diff --git a/assets/translations/pt-PT.json b/assets/translations/pt-PT.json new file mode 100644 index 0000000..e69de29 diff --git a/lib/main.dart b/lib/main.dart index ff1a242..9f38e0b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:yaru/yaru.dart'; @@ -12,11 +13,17 @@ import 'providers/participants_provider.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + await EasyLocalization.ensureInitialized(); await YaruWindowTitleBar.ensureInitialized(); setWindowTitle('Daily Standup Timer'); await DesktopWindow.setWindowSize(const Size(1200, 800)); - runApp(const ProviderScope(child: MyApp())); + EasyLocalization( + supportedLocales: [Locale('en', 'US'), Locale('pt', 'PT')], + path: 'assets/translations', + fallbackLocale: Locale('en', 'US'), + child: ProviderScope(child: MyApp()), + ); } class MyApp extends StatelessWidget { @@ -31,6 +38,9 @@ class MyApp extends StatelessWidget { darkTheme: yaruDark, themeMode: ThemeMode.system, debugShowCheckedModeBanner: false, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, home: const TimerPage(), ), ); @@ -44,7 +54,8 @@ class TimerPage extends ConsumerStatefulWidget { ConsumerState createState() => _TimerPageState(); } -class _TimerPageState extends ConsumerState with WidgetsBindingObserver { +class _TimerPageState extends ConsumerState + with WidgetsBindingObserver { final TextEditingController _nameController = TextEditingController(); final GlobalKey _timerSectionKey = GlobalKey(); @@ -71,24 +82,28 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv void _addPerson() { if (_nameController.text.trim().isNotEmpty) { - ref.read(participantsProvider.notifier).addPerson(_nameController.text.trim()); + ref + .read(participantsProvider.notifier) + .addPerson(_nameController.text.trim()); _nameController.clear(); } } Future _pasteParticipantList() async { try { - final addedCount = await ref.read(participantsProvider.notifier).pasteParticipantList(); + final addedCount = + await ref.read(participantsProvider.notifier).pasteParticipantList(); if (mounted) { String message; if (addedCount > 0) { - message = 'Added $addedCount new ${addedCount == 1 ? 'participant' : 'participants'}'; + message = + 'Added $addedCount new ${addedCount == 1 ? 'participant' : 'participants'}'; // Reset timer when new participants are added ref.read(timerProvider.notifier).resetTimer(); } else { message = 'No new participants added (duplicates were skipped)'; } - + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), @@ -135,7 +150,8 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv void _nextPerson() { final participantsState = ref.read(participantsProvider); - if (participantsState.currentPersonIndex < participantsState.people.length - 1) { + if (participantsState.currentPersonIndex < + participantsState.people.length - 1) { ref.read(participantsProvider.notifier).nextPerson(); ref.read(timerProvider.notifier).restartTimer(); } @@ -144,7 +160,7 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv void _toggleTimer() { final participantsState = ref.read(participantsProvider); if (participantsState.people.isEmpty) return; - + ref.read(timerProvider.notifier).toggleTimer(); } @@ -157,7 +173,7 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv Widget build(BuildContext context) { final timerState = ref.watch(timerProvider); final participantsState = ref.watch(participantsProvider); - + return Focus( autofocus: true, onKeyEvent: (node, event) { @@ -181,7 +197,8 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv return KeyEventResult.handled; } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) { final participantsState = ref.read(participantsProvider); - if (participantsState.currentPersonIndex < participantsState.people.length - 1) { + if (participantsState.currentPersonIndex < + participantsState.people.length - 1) { _nextPerson(); } return KeyEventResult.handled; @@ -214,33 +231,49 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv controller: timerState.controller, duration: timerState.duration, isRunning: timerState.isRunning, - currentPersonIndex: participantsState.currentPersonIndex, + currentPersonIndex: + participantsState.currentPersonIndex, people: participantsState.people, currentTime: timerState.currentTime, showTeamMembersHeader: isNarrow && !showPeople, - onToggleTimer: _toggleTimer, - onResetTimer: _resetTimer, - onPreviousPerson: _previousPerson, - onNextPerson: _nextPerson, - onPersonSelected: (index) { - ref.read(participantsProvider.notifier).setCurrentPersonIndex(index); - ref.read(timerProvider.notifier).restartTimer(); - }, - onTimerComplete: () { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - final currentParticipants = ref.read(participantsProvider); - if (currentParticipants.currentPersonIndex < currentParticipants.people.length - 1) { - ref.read(participantsProvider.notifier).nextPerson(); - ref.read(timerProvider.notifier).restartTimer(); - } else { - // Last person finished - move to end state to show comic - ref.read(participantsProvider.notifier).setCurrentPersonIndex(currentParticipants.people.length); - ref.read(timerProvider.notifier).setRunning(false); + onToggleTimer: _toggleTimer, + onResetTimer: _resetTimer, + onPreviousPerson: _previousPerson, + onNextPerson: _nextPerson, + onPersonSelected: (index) { + ref + .read(participantsProvider.notifier) + .setCurrentPersonIndex(index); + ref.read(timerProvider.notifier).restartTimer(); + }, + onTimerComplete: () { + WidgetsBinding.instance + .addPostFrameCallback((_) { + if (mounted) { + final currentParticipants = + ref.read(participantsProvider); + if (currentParticipants.currentPersonIndex < + currentParticipants.people.length - 1) { + ref + .read(participantsProvider.notifier) + .nextPerson(); + ref + .read(timerProvider.notifier) + .restartTimer(); + } else { + // Last person finished - move to end state to show comic + ref + .read(participantsProvider.notifier) + .setCurrentPersonIndex( + currentParticipants + .people.length); + ref + .read(timerProvider.notifier) + .setRunning(false); + } } - } - }); - }, + }); + }, ), ), ), @@ -253,28 +286,42 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv Expanded( child: PeopleSection( people: participantsState.people, - currentPersonIndex: participantsState.currentPersonIndex, - showAddPerson: participantsState.showAddPerson, - hasValidClipboardContent: - participantsState.hasValidClipboardContent, + currentPersonIndex: + participantsState.currentPersonIndex, + showAddPerson: + participantsState.showAddPerson, + hasValidClipboardContent: participantsState + .hasValidClipboardContent, nameController: _nameController, onAddPerson: _addPerson, onRemovePerson: _removePerson, onToggleAddPerson: () { - ref.read(participantsProvider.notifier).setShowAddPerson(true); + ref + .read(participantsProvider.notifier) + .setShowAddPerson(true); }, onCancelAddPerson: () { - ref.read(participantsProvider.notifier).setShowAddPerson(false); + ref + .read(participantsProvider.notifier) + .setShowAddPerson(false); _nameController.clear(); }, - onPasteParticipantList: _pasteParticipantList, - onClearAllParticipants: _clearAllParticipants, + onPasteParticipantList: + _pasteParticipantList, + onClearAllParticipants: + _clearAllParticipants, onShuffleParticipants: _shuffleParticipants, onPersonSelected: (index) { - final currentIndex = ref.read(participantsProvider).currentPersonIndex; + final currentIndex = ref + .read(participantsProvider) + .currentPersonIndex; if (index != currentIndex) { - ref.read(participantsProvider.notifier).setCurrentPersonIndex(index); - ref.read(timerProvider.notifier).restartTimer(); + ref + .read(participantsProvider.notifier) + .setCurrentPersonIndex(index); + ref + .read(timerProvider.notifier) + .restartTimer(); } }, ), @@ -299,27 +346,40 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv Expanded( child: PeopleSection( people: participantsState.people, - currentPersonIndex: participantsState.currentPersonIndex, - showAddPerson: participantsState.showAddPerson, - hasValidClipboardContent: - participantsState.hasValidClipboardContent, + currentPersonIndex: + participantsState.currentPersonIndex, + showAddPerson: + participantsState.showAddPerson, + hasValidClipboardContent: participantsState + .hasValidClipboardContent, nameController: _nameController, onAddPerson: _addPerson, onRemovePerson: _removePerson, onToggleAddPerson: () { - ref.read(participantsProvider.notifier).setShowAddPerson(true); + ref + .read(participantsProvider.notifier) + .setShowAddPerson(true); }, onCancelAddPerson: () { - ref.read(participantsProvider.notifier).setShowAddPerson(false); + ref + .read(participantsProvider.notifier) + .setShowAddPerson(false); _nameController.clear(); }, - onPasteParticipantList: _pasteParticipantList, - onClearAllParticipants: _clearAllParticipants, + onPasteParticipantList: + _pasteParticipantList, + onClearAllParticipants: + _clearAllParticipants, onShuffleParticipants: _shuffleParticipants, onPersonSelected: (index) { - print('Main: onPersonSelected called with index $index'); - ref.read(participantsProvider.notifier).setCurrentPersonIndex(index); - ref.read(timerProvider.notifier).restartTimer(); + print( + 'Main: onPersonSelected called with index $index'); + ref + .read(participantsProvider.notifier) + .setCurrentPersonIndex(index); + ref + .read(timerProvider.notifier) + .restartTimer(); print('Main: Person selection completed'); }, ), @@ -333,7 +393,8 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv ] else ...[ SizedBox( width: 320, - child: SessionInfo(people: participantsState.people), + child: + SessionInfo(people: participantsState.people), ), const SizedBox(width: 24), ], @@ -344,7 +405,8 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv controller: timerState.controller, duration: timerState.duration, isRunning: timerState.isRunning, - currentPersonIndex: participantsState.currentPersonIndex, + currentPersonIndex: + participantsState.currentPersonIndex, people: participantsState.people, currentTime: timerState.currentTime, showTeamMembersHeader: !showPeople, @@ -353,18 +415,29 @@ class _TimerPageState extends ConsumerState with WidgetsBindingObserv onPreviousPerson: _previousPerson, onNextPerson: _nextPerson, onPersonSelected: (index) { - ref.read(participantsProvider.notifier).setCurrentPersonIndex(index); + ref + .read(participantsProvider.notifier) + .setCurrentPersonIndex(index); ref.read(timerProvider.notifier).restartTimer(); }, onTimerComplete: () { - final currentParticipants = ref.read(participantsProvider); - if (currentParticipants.currentPersonIndex < currentParticipants.people.length - 1) { - ref.read(participantsProvider.notifier).nextPerson(); + final currentParticipants = + ref.read(participantsProvider); + if (currentParticipants.currentPersonIndex < + currentParticipants.people.length - 1) { + ref + .read(participantsProvider.notifier) + .nextPerson(); ref.read(timerProvider.notifier).restartTimer(); } else { // Last person finished - move to end state to show comic - ref.read(participantsProvider.notifier).setCurrentPersonIndex(currentParticipants.people.length); - ref.read(timerProvider.notifier).setRunning(false); + ref + .read(participantsProvider.notifier) + .setCurrentPersonIndex( + currentParticipants.people.length); + ref + .read(timerProvider.notifier) + .setRunning(false); } }, ), diff --git a/pubspec.lock b/pubspec.lock index 78447d7..71c652b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,6 +137,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12" + url: "https://pub.dev" + source: hosted + version: "3.0.7+1" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" fake_async: dependency: transitive description: @@ -174,6 +190,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_riverpod: dependency: "direct main" description: @@ -232,6 +253,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.4" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" json_annotation: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 63d38cd..258bd2c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: shared_preferences: ^2.2.2 flutter_riverpod: ^2.5.1 confetti: ^0.8.0 + easy_localization: ^3.0.7+1 dev_dependencies: flutter_test: From e145cda69630a03723ddd87413daffc0016e37a8 Mon Sep 17 00:00:00 2001 From: Romavic dos Anjos Date: Mon, 23 Jun 2025 10:05:43 +0100 Subject: [PATCH 2/3] feat: implement internationalization for the app This commit introduces internationalization (i18n) to the application using the `easy_localization` package. Key changes include: - Added English (en-US) and Portuguese (pt-PT) translation files. - Integrated `EasyLocalization` into the main application setup, setting Portuguese as the default start locale. - Updated various widgets to use translated strings for UI elements such as button labels, section headers, messages, and tooltips. - Specifically, translated text in `TimerControls`, `TimerPage`, `CurrentSpeaker`, `ComicScreen`, `PeopleSection`, `NavigationControls`, `AddPersonWidget`, `CelebrationScreen`, and `SessionInfo` widgets. - Added a language selection dropdown to `PeopleSection` allowing users to switch between English and Portuguese. - Ensured that dynamic text, such as participant counts and error messages, is correctly formatted with localized strings. --- assets/translations/en-US.json | 39 ++++++ assets/translations/pt-PT.json | 39 ++++++ lib/comic.dart | 46 +++++-- lib/main.dart | 29 ++-- lib/widgets/add_person_widget.dart | 9 +- lib/widgets/celebration_screen.dart | 41 +++--- lib/widgets/current_speaker.dart | 14 +- lib/widgets/navigation_controls.dart | 23 ++-- lib/widgets/people_section.dart | 191 ++++++++++++++++----------- lib/widgets/session_info.dart | 31 +++-- lib/widgets/timer_controls.dart | 23 +++- pubspec.yaml | 3 + 12 files changed, 336 insertions(+), 152 deletions(-) diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index e69de29..72674df 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -0,0 +1,39 @@ +{ + "enter_team_member_name_add_person_widget": "Enter team member name", + "add_member_add_person_widget": "Add Member", + "cancel_add_member_add_person_widget": "Cancel", + "standup_complete_celebration_screen": "Standup Complete!", + "great_job_celebration_screen": "Great job everyone! 🎉", + "participants_celebration_screen": "Participants ({count})", + "start_new_session_celebration_screen": "Start New Session", + "please_add_participants_current_speaker": "Please add participants", + "team_members_current_speaker": "Team Members", + "person_position_current_speaker": "Person {current} of {total}", + "previous_navigation_controls": "Previous", + "next_navigation_controls": "Next", + "finish_navigation_controls": "Finish", + "english_people_section": "English", + "portuguese_people_section": "Portuguese", + "team_members_people_section": "Team Members", + "clear_all_people_section": "Clear all", + "shuffle_order_people_section": "Shuffle order", + "team_options_people_section": "Team options", + "no_team_members_added_people_section": "No team members added", + "click_to_add_or_paste_people_section": "Click + to add members or paste a participant list", + "session_info": "Session Info", + "total_members_session_info": "Total members:", + "expected_duration_session_info": "Expected duration:", + "time_per_person_session_info": "Time per person:", + "timer_configuration_session_info": "Timer Configuration", + "reset_timer_controls": "Reset", + "start_timer_timer_controls": "Start Timer", + "pause_timer_controls": "Pause", + "error_prefix_comic_screen": "Error: {error}", + "failed_to_load_image_comic_screen": "Failed to load image", + "no_data_available_comic_screen": "No data available", + "added_participants_timer_page": "Added {count} new {entity}", + "participant_timer_page": "participant", + "participants_timer_page": "participants", + "no_new_participants_timer_page": "No new participants added (duplicates were skipped)", + "failed_paste_timer_page": "Failed to paste from clipboard" +} diff --git a/assets/translations/pt-PT.json b/assets/translations/pt-PT.json index e69de29..0e300fb 100644 --- a/assets/translations/pt-PT.json +++ b/assets/translations/pt-PT.json @@ -0,0 +1,39 @@ +{ + "enter_team_member_name_add_person_widget": "Introduza o nome do membro da equipa", + "add_member_add_person_widget": "Adicionar Membro", + "cancel_add_member_add_person_widget": "Cancelar", + "standup_complete_celebration_screen": "Standup Concluído!", + "great_job_celebration_screen": "Bom trabalho a todos! 🎉", + "participants_celebration_screen": "Participantes ({count})", + "start_new_session_celebration_screen": "Iniciar Nova Sessão", + "please_add_participants_current_speaker": "Por favor adicione participantes", + "team_members_current_speaker": "Membros da Equipa", + "person_position_current_speaker": "Pessoa {current} de {total}", + "previous_navigation_controls": "Anterior", + "next_navigation_controls": "Próximo", + "finish_navigation_controls": "Concluir", + "english_people_section": "Inglês", + "portuguese_people_section": "Português", + "team_members_people_section": "Membros da Equipa", + "clear_all_people_section": "Limpar tudo", + "shuffle_order_people_section": "Embaralhar ordem", + "team_options_people_section": "Opções da equipa", + "no_team_members_added_people_section": "Nenhum membro adicionado", + "click_to_add_or_paste_people_section": "Clique + para adicionar membros ou cole uma lista de participantes", + "session_info": "Informação da Sessão", + "total_members_session_info": "Total de membros:", + "expected_duration_session_info": "Duração esperada:", + "time_per_person_session_info": "Tempo por pessoa:", + "timer_configuration_session_info": "Configuração do Temporizador", + "reset_timer_controls": "Reiniciar", + "start_timer_timer_controls": "Iniciar Temporizador", + "pause_timer_controls": "Pausar", + "error_prefix_comic_screen": "Erro: {error}", + "failed_to_load_image_comic_screen": "Falha ao carregar imagem", + "no_data_available_comic_screen": "Sem dados disponíveis", + "added_participants_timer_page": "Adicionado(s) {count} novo(s) {entity}", + "participant_timer_page": "participante", + "participants_timer_page": "participantes", + "no_new_participants_timer_page": "Nenhum novo participante adicionado (duplicados foram ignorados)", + "failed_paste_timer_page": "Falha ao colar da área de transferência" +} \ No newline at end of file diff --git a/lib/comic.dart b/lib/comic.dart index 0c0adbd..cd69a83 100644 --- a/lib/comic.dart +++ b/lib/comic.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:math'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'widgets/timer_controls.dart'; @@ -22,7 +23,9 @@ class Comic { Future fetchComic() async { final weekday = DateTime.now().weekday; // Monday=1, Tuesday=2, etc. - if (weekday == DateTime.monday || weekday == DateTime.wednesday || weekday == DateTime.friday) { + if (weekday == DateTime.monday || + weekday == DateTime.wednesday || + weekday == DateTime.friday) { // Fetch the latest XKCD comic on Monday, Wednesday, or Friday. final response = await http.get(Uri.parse("https://xkcd.com/info.0.json")); if (response.statusCode == 200) { @@ -33,13 +36,15 @@ Future fetchComic() async { } else { // Otherwise, fetch a random safe-for-work XKCD comic. // First, get the latest comic to know the maximum comic number. - final latestResponse = await http.get(Uri.parse("https://xkcd.com/info.0.json")); + final latestResponse = + await http.get(Uri.parse("https://xkcd.com/info.0.json")); if (latestResponse.statusCode == 200) { final latestJson = json.decode(latestResponse.body); final maxNum = latestJson['num']; // Generate a random comic number between 1 and maxNum. final randomComicNum = Random().nextInt(maxNum) + 1; - final randomResponse = await http.get(Uri.parse("https://xkcd.com/$randomComicNum/info.0.json")); + final randomResponse = await http + .get(Uri.parse("https://xkcd.com/$randomComicNum/info.0.json")); if (randomResponse.statusCode == 200) { return Comic.fromJson(json.decode(randomResponse.body)); } else { @@ -75,7 +80,11 @@ class ComicScreen extends StatelessWidget { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); + return Center( + child: Text( + 'error_prefix_comic_screen' + .tr(namedArgs: {'error': '${snapshot.error}'}), + )); } else if (snapshot.hasData) { final comic = snapshot.data!; return LayoutBuilder( @@ -85,27 +94,35 @@ class ComicScreen extends StatelessWidget { Padding( padding: const EdgeInsets.all(16), child: Text( - comic.title, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + comic.title, + style: const TextStyle( + fontSize: 20, fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), ), Expanded( child: Container( width: constraints.maxWidth, - height: constraints.maxHeight - (showTimerControls ? 180 : 120), // Reserve space for controls if needed + height: constraints.maxHeight - + (showTimerControls + ? 180 + : 120), // Reserve space for controls if needed padding: const EdgeInsets.symmetric(horizontal: 16), child: Image.network( comic.img, fit: BoxFit.contain, width: constraints.maxWidth - 32, - height: constraints.maxHeight - (showTimerControls ? 180 : 120), + height: constraints.maxHeight - + (showTimerControls ? 180 : 120), loadingBuilder: (context, child, progress) { if (progress == null) return child; - return const Center(child: CircularProgressIndicator()); + return const Center( + child: CircularProgressIndicator()); }, errorBuilder: (context, error, stackTrace) { - return const Center(child: Text('Failed to load image')); + return Center( + child: Text( + 'failed_to_load_image_comic_screen'.tr())); }, ), ), @@ -115,7 +132,8 @@ class ComicScreen extends StatelessWidget { padding: const EdgeInsets.all(16), child: Text( comic.alt, - style: const TextStyle(fontSize: 14, fontStyle: FontStyle.italic), + style: const TextStyle( + fontSize: 14, fontStyle: FontStyle.italic), textAlign: TextAlign.center, ), ), @@ -135,9 +153,11 @@ class ComicScreen extends StatelessWidget { }, ); } else { - return const Center(child: Text('No data available')); + return Center( + child: Text('no_data_available_comic_screen').tr(), + ); } }, ); } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 9f38e0b..709e021 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,11 +18,14 @@ Future main() async { setWindowTitle('Daily Standup Timer'); await DesktopWindow.setWindowSize(const Size(1200, 800)); - EasyLocalization( - supportedLocales: [Locale('en', 'US'), Locale('pt', 'PT')], - path: 'assets/translations', - fallbackLocale: Locale('en', 'US'), - child: ProviderScope(child: MyApp()), + runApp( + EasyLocalization( + supportedLocales: [Locale('en', 'US'), Locale('pt', 'PT')], + path: 'assets/translations', + fallbackLocale: Locale('en', 'US'), + startLocale: Locale('pt', 'PT'), + child: ProviderScope(child: MyApp()), + ), ); } @@ -96,12 +99,16 @@ class _TimerPageState extends ConsumerState if (mounted) { String message; if (addedCount > 0) { - message = - 'Added $addedCount new ${addedCount == 1 ? 'participant' : 'participants'}'; - // Reset timer when new participants are added + message = 'added_participants_timer_page'.tr(namedArgs: { + 'count': addedCount.toString(), + 'entity': addedCount == 1 + ? 'participant_timer_page'.tr() + : 'participants_timer_page'.tr() + }); + ref.read(timerProvider.notifier).resetTimer(); } else { - message = 'No new participants added (duplicates were skipped)'; + message = 'no_new_participants_timer_page'.tr(); } ScaffoldMessenger.of(context).showSnackBar( @@ -114,8 +121,8 @@ class _TimerPageState extends ConsumerState } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to paste from clipboard'), + SnackBar( + content: Text('failed_paste_timer_page'.tr()), duration: Duration(seconds: 2), ), ); diff --git a/lib/widgets/add_person_widget.dart b/lib/widgets/add_person_widget.dart index d9b6119..a8ddc3e 100644 --- a/lib/widgets/add_person_widget.dart +++ b/lib/widgets/add_person_widget.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class AddPersonWidget extends StatelessWidget { @@ -37,7 +38,7 @@ class AddPersonWidget extends StatelessWidget { controller: nameController, style: TextStyle(color: inputText), decoration: InputDecoration( - hintText: 'Enter team member name', + hintText: 'enter_team_member_name_add_person_widget'.tr(), hintStyle: TextStyle(color: placeholderText), filled: true, fillColor: inputBg, @@ -75,7 +76,7 @@ class AddPersonWidget extends StatelessWidget { ), ), child: Text( - 'Add Member', + 'add_member_add_person_widget'.tr(), style: TextStyle( color: theme.colorScheme.onPrimary, fontSize: 14, @@ -95,7 +96,7 @@ class AddPersonWidget extends StatelessWidget { ), ), child: Text( - 'Cancel', + 'cancel_add_member_add_person_widget'.tr(), style: TextStyle( color: buttonSecondaryText, fontSize: 14, @@ -109,4 +110,4 @@ class AddPersonWidget extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/celebration_screen.dart b/lib/widgets/celebration_screen.dart index 027e2e8..4894211 100644 --- a/lib/widgets/celebration_screen.dart +++ b/lib/widgets/celebration_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class CelebrationScreen extends StatelessWidget { @@ -26,7 +27,7 @@ class CelebrationScreen extends StatelessWidget { ), SizedBox(height: isNarrow ? 16 : 24), Text( - 'Standup Complete!', + 'standup_complete_celebration_screen'.tr(), style: theme.textTheme.headlineMedium?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, @@ -35,7 +36,7 @@ class CelebrationScreen extends StatelessWidget { ), SizedBox(height: isNarrow ? 8 : 12), Text( - 'Great job everyone! 🎉', + 'great_job_celebration_screen'.tr(), style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onSurface, ), @@ -51,7 +52,9 @@ class CelebrationScreen extends StatelessWidget { child: Column( children: [ Text( - 'Participants (${people.length})', + 'participants_celebration_screen'.tr(namedArgs: { + 'count': people.length.toString(), + }), style: theme.textTheme.titleSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500, @@ -61,18 +64,20 @@ class CelebrationScreen extends StatelessWidget { Wrap( spacing: 8, runSpacing: 4, - children: people.map((name) => Chip( - label: Text( - name, - style: TextStyle( - fontSize: isNarrow ? 12 : 14, - ), - ), - backgroundColor: theme.colorScheme.primaryContainer, - labelStyle: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - ), - )).toList(), + children: people + .map((name) => Chip( + label: Text( + name, + style: TextStyle( + fontSize: isNarrow ? 12 : 14, + ), + ), + backgroundColor: theme.colorScheme.primaryContainer, + labelStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + ), + )) + .toList(), ), ], ), @@ -81,7 +86,9 @@ class CelebrationScreen extends StatelessWidget { ElevatedButton.icon( onPressed: onResetTimer, icon: const Icon(Icons.refresh), - label: const Text('Start New Session'), + label: Text( + 'start_new_session_celebration_screen'.tr(), + ), style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: theme.colorScheme.onPrimary, @@ -94,4 +101,4 @@ class CelebrationScreen extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/lib/widgets/current_speaker.dart b/lib/widgets/current_speaker.dart index 8e47cd0..7e41cee 100644 --- a/lib/widgets/current_speaker.dart +++ b/lib/widgets/current_speaker.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class CurrentSpeaker extends StatelessWidget { @@ -17,7 +18,7 @@ class CurrentSpeaker extends StatelessWidget { final theme = Theme.of(context); final textPrimary = theme.colorScheme.onSurface; final textSecondary = theme.colorScheme.onSurfaceVariant; - + final screenWidth = MediaQuery.of(context).size.width; final isNarrow = screenWidth < 800; @@ -30,7 +31,7 @@ class CurrentSpeaker extends StatelessWidget { child: Text( people.isNotEmpty ? people[currentPersonIndex] - : 'Please add participants', + : 'please_add_participants_current_speaker'.tr(), style: TextStyle( fontSize: isNarrow ? 24 : 32, fontWeight: FontWeight.w500, @@ -44,9 +45,14 @@ class CurrentSpeaker extends StatelessWidget { SizedBox(height: isNarrow ? 6 : 8), Text( (isNarrow && showTeamMembersHeader) - ? 'Team Members' + ? 'team_members_current_speaker'.tr() : (people.isNotEmpty - ? 'Person ${currentPersonIndex + 1} of ${people.length}' + ? 'person_position_current_speaker'.tr( + namedArgs: { + 'current': '${currentPersonIndex + 1}', + 'total': '${people.length}' + }, + ) : ''), style: TextStyle( color: textSecondary, diff --git a/lib/widgets/navigation_controls.dart b/lib/widgets/navigation_controls.dart index 493e58f..0271523 100644 --- a/lib/widgets/navigation_controls.dart +++ b/lib/widgets/navigation_controls.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class NavigationControls extends StatelessWidget { @@ -51,7 +52,7 @@ class NavigationControls extends StatelessWidget { if (!isNarrow) ...[ const SizedBox(width: 4), Text( - 'Previous', + 'previous_navigation_controls'.tr(), style: TextStyle( color: currentPersonIndex > 0 ? buttonGhostText @@ -79,7 +80,8 @@ class NavigationControls extends StatelessWidget { backgroundColor: theme.colorScheme.primary, foregroundColor: theme.colorScheme.onPrimary, padding: EdgeInsets.symmetric( - horizontal: isNarrow ? 12 : 16, vertical: isNarrow ? 8 : 10), + horizontal: isNarrow ? 12 : 16, + vertical: isNarrow ? 8 : 10), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -91,20 +93,23 @@ class NavigationControls extends StatelessWidget { ), if (!isNarrow) ...[ const SizedBox(width: 6), - const Text( - 'Finish', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + Text( + 'finish_navigation_controls'.tr(), + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.w500), ), ], ], ), ) : TextButton( - onPressed: - currentPersonIndex < peopleCount - 1 ? onNextPerson : null, + onPressed: currentPersonIndex < peopleCount - 1 + ? onNextPerson + : null, style: TextButton.styleFrom( padding: EdgeInsets.symmetric( - horizontal: isNarrow ? 4 : 8, vertical: isNarrow ? 6 : 8), + horizontal: isNarrow ? 4 : 8, + vertical: isNarrow ? 6 : 8), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -112,7 +117,7 @@ class NavigationControls extends StatelessWidget { children: [ if (!isNarrow) ...[ Text( - 'Next', + 'next_navigation_controls'.tr(), style: TextStyle( color: currentPersonIndex < peopleCount - 1 ? buttonGhostText diff --git a/lib/widgets/people_section.dart b/lib/widgets/people_section.dart index 497fc76..fab4c37 100644 --- a/lib/widgets/people_section.dart +++ b/lib/widgets/people_section.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'add_person_widget.dart'; @@ -38,7 +39,6 @@ class PeopleSection extends StatefulWidget { } class _PeopleSectionState extends State { - @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -82,9 +82,10 @@ class _PeopleSectionState extends State { ); } - Widget _buildHeader(BuildContext context, Color textPrimary, Color textSecondary, Color borderColor) { + Widget _buildHeader(BuildContext context, Color textPrimary, + Color textSecondary, Color borderColor) { final theme = Theme.of(context); - + return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -104,7 +105,7 @@ class _PeopleSectionState extends State { const SizedBox(width: 8), Flexible( child: Text( - 'Team Members', + 'team_members_people_section'.tr(), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, @@ -115,89 +116,91 @@ class _PeopleSectionState extends State { ), const SizedBox(width: 8), PopupMenuButton( - onSelected: (value) { - if (value == 'clear') { - widget.onClearAllParticipants(); - } else if (value == 'shuffle') { - widget.onShuffleParticipants(); - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: 'clear', - enabled: widget.people.isNotEmpty, - child: Row( - children: [ - Icon( - Icons.clear_all, - size: 16, - color: widget.people.isNotEmpty - ? theme.colorScheme.onSurface - : theme.colorScheme.outline, - ), - const SizedBox(width: 8), - Text( - 'Clear all', - style: TextStyle( - color: widget.people.isNotEmpty + onSelected: (value) { + if (value == 'clear_all_people_section'.tr()) { + widget.onClearAllParticipants(); + } else if (value == 'shuffle_order_people_section'.tr()) { + widget.onShuffleParticipants(); + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'clear_all_people_section'.tr(), + enabled: widget.people.isNotEmpty, + child: Row( + children: [ + Icon( + Icons.clear_all, + size: 16, + color: widget.people.isNotEmpty ? theme.colorScheme.onSurface : theme.colorScheme.outline, ), - ), - ], + const SizedBox(width: 8), + Text( + 'clear_all_people_section'.tr(), + style: TextStyle( + color: widget.people.isNotEmpty + ? theme.colorScheme.onSurface + : theme.colorScheme.outline, + ), + ), + ], + ), ), - ), - PopupMenuItem( - value: 'shuffle', - enabled: widget.people.length > 1, - child: Row( - children: [ - Icon( - Icons.shuffle, - size: 16, - color: widget.people.length > 1 - ? theme.colorScheme.onSurface - : theme.colorScheme.outline, - ), - const SizedBox(width: 8), - Text( - 'Shuffle order', - style: TextStyle( - color: widget.people.length > 1 + PopupMenuItem( + value: 'shuffle_order_people_section'.tr(), + enabled: widget.people.length > 1, + child: Row( + children: [ + Icon( + Icons.shuffle, + size: 16, + color: widget.people.length > 1 ? theme.colorScheme.onSurface : theme.colorScheme.outline, ), - ), - ], + const SizedBox(width: 8), + Text( + 'shuffle_order_people_section'.tr(), + style: TextStyle( + color: widget.people.length > 1 + ? theme.colorScheme.onSurface + : theme.colorScheme.outline, + ), + ), + ], + ), ), + ], + icon: Icon( + Icons.more_vert, + size: 16, + color: textSecondary, ), - ], - icon: Icon( - Icons.more_vert, - size: 16, - color: textSecondary, - ), - style: IconButton.styleFrom( - padding: const EdgeInsets.all(6), + style: IconButton.styleFrom( + padding: const EdgeInsets.all(6), + ), + tooltip: 'team_options_people_section'.tr(), ), - tooltip: 'Team options', - ), ], ), ), Row( children: [ IconButton( - onPressed: widget.hasValidClipboardContent ? widget.onPasteParticipantList : null, + onPressed: widget.hasValidClipboardContent + ? widget.onPasteParticipantList + : null, style: IconButton.styleFrom( - backgroundColor: widget.hasValidClipboardContent + backgroundColor: widget.hasValidClipboardContent ? theme.colorScheme.secondaryContainer : theme.colorScheme.surfaceContainerHighest, padding: const EdgeInsets.all(6), ), icon: Icon( Icons.content_paste, - color: widget.hasValidClipboardContent + color: widget.hasValidClipboardContent ? theme.colorScheme.onSecondaryContainer : theme.colorScheme.outline, size: 16, @@ -216,6 +219,41 @@ class _PeopleSectionState extends State { size: 16, ), ), + const SizedBox(width: 8), + PopupMenuButton( + onSelected: (value) { + if (value == 'english_people_section'.tr()) { + context.setLocale(Locale('en', 'US')); + } else if (value == 'portuguese_people_section'.tr()) { + context.setLocale(Locale('pt', 'PT')); + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'english_people_section'.tr(), + enabled: true, + child: Text( + 'english_people_section'.tr(), + ), + ), + PopupMenuItem( + value: 'portuguese_people_section'.tr(), + child: Text( + 'portuguese_people_section'.tr(), + style: TextStyle(), + ), + ), + ], + icon: Icon( + Icons.translate, + color: theme.colorScheme.onPrimary, + size: 16, + ), + style: IconButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + padding: const EdgeInsets.all(6), + ), + ), ], ), ], @@ -223,9 +261,10 @@ class _PeopleSectionState extends State { ); } - Widget _buildPeopleList(BuildContext context, Color textPrimary, Color textSecondary) { + Widget _buildPeopleList( + BuildContext context, Color textPrimary, Color textSecondary) { final theme = Theme.of(context); - + return ListView.builder( itemCount: widget.people.length, itemBuilder: (context, index) { @@ -233,12 +272,10 @@ class _PeopleSectionState extends State { final itemBg = isSelected ? theme.colorScheme.surfaceContainerHighest : theme.colorScheme.primaryContainer; - final itemBorder = isSelected - ? theme.colorScheme.primary - : Colors.transparent; - final itemText = isSelected - ? textPrimary - : theme.colorScheme.onPrimaryContainer; + final itemBorder = + isSelected ? theme.colorScheme.primary : Colors.transparent; + final itemText = + isSelected ? textPrimary : theme.colorScheme.onPrimaryContainer; return Container( margin: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), @@ -251,7 +288,9 @@ class _PeopleSectionState extends State { children: [ Expanded( child: GestureDetector( - onTap: widget.onPersonSelected != null ? () => widget.onPersonSelected!(index) : null, + onTap: widget.onPersonSelected != null + ? () => widget.onPersonSelected!(index) + : null, behavior: HitTestBehavior.opaque, child: Container( padding: const EdgeInsets.symmetric(vertical: 4), @@ -299,7 +338,7 @@ class _PeopleSectionState extends State { ), const SizedBox(height: 12), Text( - 'No team members added', + 'no_team_members_added_people_section'.tr(), style: TextStyle( color: textMuted, fontSize: 14, @@ -307,7 +346,7 @@ class _PeopleSectionState extends State { ), const SizedBox(height: 4), Text( - 'Click + to add members or paste a participant list', + 'click_to_add_or_paste_people_section'.tr(), style: TextStyle( color: textMuted, fontSize: 12, @@ -317,4 +356,4 @@ class _PeopleSectionState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/session_info.dart b/lib/widgets/session_info.dart index 5127a1b..cbf003f 100644 --- a/lib/widgets/session_info.dart +++ b/lib/widgets/session_info.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/timer_provider.dart'; @@ -42,7 +43,7 @@ class _SessionInfoState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Session Info', + 'session_info'.tr(), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, @@ -62,17 +63,23 @@ class _SessionInfoState extends ConsumerState { ], ), const SizedBox(height: 12), - _buildInfoRow('Total members:', '${widget.people.length}', textSecondary, textPrimary), + _buildInfoRow('total_members_session_info'.tr(), + '${widget.people.length}', textSecondary, textPrimary), const SizedBox(height: 8), - _buildInfoRow('Expected duration:', '${widget.people.length * durationMinutes} min', textSecondary, textPrimary), + _buildInfoRow( + 'expected_duration_session_info'.tr(), + '${widget.people.length * durationMinutes} min', + textSecondary, + textPrimary), const SizedBox(height: 8), - _buildInfoRow('Time per person:', '${timerState.duration}s', textSecondary, textPrimary), + _buildInfoRow('time_per_person_session_info'.tr(), + '${timerState.duration}s', textSecondary, textPrimary), if (_isExpanded) ...[ const SizedBox(height: 16), Divider(color: borderColor), const SizedBox(height: 12), Text( - 'Timer Configuration', + 'timer_configuration_session_info'.tr(), style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, @@ -86,16 +93,18 @@ class _SessionInfoState extends ConsumerState { children: _durationOptions.map((seconds) { final isSelected = seconds == timerState.duration; return GestureDetector( - onTap: () => ref.read(timerProvider.notifier).setDuration(seconds), + onTap: () => + ref.read(timerProvider.notifier).setDuration(seconds), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: isSelected + color: isSelected ? theme.colorScheme.primaryContainer : theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all( - color: isSelected + color: isSelected ? theme.colorScheme.primary : Colors.transparent, ), @@ -103,7 +112,7 @@ class _SessionInfoState extends ConsumerState { child: Text( '${seconds}s', style: TextStyle( - color: isSelected + color: isSelected ? theme.colorScheme.onPrimaryContainer : textPrimary, fontSize: 12, @@ -143,4 +152,4 @@ class _SessionInfoState extends ConsumerState { ], ); } -} \ No newline at end of file +} diff --git a/lib/widgets/timer_controls.dart b/lib/widgets/timer_controls.dart index d0a9195..769aa28 100644 --- a/lib/widgets/timer_controls.dart +++ b/lib/widgets/timer_controls.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; class TimerControls extends StatelessWidget { @@ -30,7 +31,8 @@ class TimerControls extends StatelessWidget { onPressed: onResetTimer, style: ElevatedButton.styleFrom( backgroundColor: buttonSecondaryBg, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + padding: + const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -45,7 +47,7 @@ class TimerControls extends StatelessWidget { ), const SizedBox(width: 8), Text( - 'Reset', + 'reset_timer_controls'.tr(), style: TextStyle( color: buttonSecondaryText, fontWeight: FontWeight.w500, @@ -63,7 +65,8 @@ class TimerControls extends StatelessWidget { child: ElevatedButton( onPressed: isDisabled ? null : onToggleTimer, style: ElevatedButton.styleFrom( - backgroundColor: isDisabled ? theme.disabledColor : theme.colorScheme.primary, + backgroundColor: + isDisabled ? theme.disabledColor : theme.colorScheme.primary, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), @@ -74,14 +77,20 @@ class TimerControls extends StatelessWidget { children: [ Icon( isRunning ? Icons.pause : Icons.play_arrow, - color: isDisabled ? theme.colorScheme.onSurface.withOpacity(0.38) : theme.colorScheme.onPrimary, + color: isDisabled + ? theme.colorScheme.onSurface.withOpacity(0.38) + : theme.colorScheme.onPrimary, size: 20, ), const SizedBox(width: 8), Text( - isRunning ? 'Pause' : 'Start Timer', + isRunning + ? 'pause_timer_controls'.tr() + : 'start_timer_timer_controls'.tr(), style: TextStyle( - color: isDisabled ? theme.colorScheme.onSurface.withOpacity(0.38) : theme.colorScheme.onPrimary, + color: isDisabled + ? theme.colorScheme.onSurface.withOpacity(0.38) + : theme.colorScheme.onPrimary, fontWeight: FontWeight.w500, fontSize: 16, ), @@ -93,4 +102,4 @@ class TimerControls extends StatelessWidget { ], ); } -} \ No newline at end of file +} diff --git a/pubspec.yaml b/pubspec.yaml index 258bd2c..5734fd1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,9 @@ flutter: # the material Icons class. uses-material-design: true + assets: + - assets/translations/ + # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg From b1dfc296512dee5883f3dd5c72e8c9138794a7ac Mon Sep 17 00:00:00 2001 From: Romavic dos Anjos Date: Mon, 23 Jun 2025 19:51:15 +0100 Subject: [PATCH 3/3] fix: improve comic screen error message The error message on the comic screen has been updated to be more user-friendly and informative. It now guides users to check their internet connection or try again later if an error occurs. --- assets/translations/en-US.json | 2 +- assets/translations/pt-PT.json | 2 +- lib/comic.dart | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index 72674df..5a74d9e 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -28,7 +28,7 @@ "reset_timer_controls": "Reset", "start_timer_timer_controls": "Start Timer", "pause_timer_controls": "Pause", - "error_prefix_comic_screen": "Error: {error}", + "error_prefix_comic_screen": "An error occurred while trying to connect to the server.\nPlease check your connection or try again later.", "failed_to_load_image_comic_screen": "Failed to load image", "no_data_available_comic_screen": "No data available", "added_participants_timer_page": "Added {count} new {entity}", diff --git a/assets/translations/pt-PT.json b/assets/translations/pt-PT.json index 0e300fb..5d397ef 100644 --- a/assets/translations/pt-PT.json +++ b/assets/translations/pt-PT.json @@ -28,7 +28,7 @@ "reset_timer_controls": "Reiniciar", "start_timer_timer_controls": "Iniciar Temporizador", "pause_timer_controls": "Pausar", - "error_prefix_comic_screen": "Erro: {error}", + "error_prefix_comic_screen": "Ocorreu um erro ao tentar ligar ao servidor. \nVerifique a sua ligação ou tente novamente mais tarde.", "failed_to_load_image_comic_screen": "Falha ao carregar imagem", "no_data_available_comic_screen": "Sem dados disponíveis", "added_participants_timer_page": "Adicionado(s) {count} novo(s) {entity}", diff --git a/lib/comic.dart b/lib/comic.dart index 9790acb..1b48fc0 100644 --- a/lib/comic.dart +++ b/lib/comic.dart @@ -82,8 +82,8 @@ class ComicScreen extends StatelessWidget { } else if (snapshot.hasError) { return Center( child: Text( - 'error_prefix_comic_screen' - .tr(namedArgs: {'error': '${snapshot.error}'}), + 'error_prefix_comic_screen'.tr(), + textAlign: TextAlign.center, )); } else if (snapshot.hasData) { final comic = snapshot.data!;