From 179ac2fe213ae22c2129275a75a4d7d2caa81f2f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:16 +0100 Subject: [PATCH 01/32] Update localization dependencies and enable code generation --- pubspec.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 07e4df2..866f662 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,10 @@ dependencies: json_schema: ^5.2.2 file_saver: ^0.3.1 clock: ^1.1.2 - intl: ^0.18.0 + intl: any + flutter_localizations: + sdk: flutter + auto_localize: ^0.0.5 dev_dependencies: flutter_test: @@ -35,5 +38,6 @@ dev_dependencies: flutter: uses-material-design: true + generate: true assets: - assets/schema.json -- 2.49.1 From 8e05e9d61b71e1591b7ab7a7821ce408e821fafe Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:22 +0100 Subject: [PATCH 02/32] Added l10n.yaml for localization configuration --- l10n.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 l10n.yaml diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..f5730dc --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n/arb +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +output-dir: lib/l10n/generated \ No newline at end of file -- 2.49.1 From d9a26a8cf7df889298cc3e80a3e2435be4edd313 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:44 +0100 Subject: [PATCH 03/32] Added English and German localization files containing all strings used in app --- lib/l10n/arb/app_de.arb | 84 ++++++++++ lib/l10n/arb/app_en.arb | 357 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 lib/l10n/arb/app_de.arb create mode 100644 lib/l10n/arb/app_en.arb diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb new file mode 100644 index 0000000..3e090e3 --- /dev/null +++ b/lib/l10n/arb/app_de.arb @@ -0,0 +1,84 @@ +{ + "@@locale": "de", + "choose_group": "Gruppe wählen", + "create_new_match": "Neues Match erstellen", + "choose_ruleset": "Regelwerk wählen", + "choose_game": "Spiel wählen", + "select_winner": "Gewinner wählen:", + "no_recent_matches_available": "Keine letzten Matches verfügbar", + "no_second_match_available": "Kein zweites Match verfügbar", + "delete_all_data": "Alle Daten löschen?", + "cancel": "Abbrechen", + "delete": "Löschen", + "create_new_group": "Neue Gruppe erstellen", + "error_while_creating_group_please_try_again": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "selected_players": "Ausgewählte Spieler: {count}", + "no_players_selected": "Keine Spieler ausgewählt", + "all_players": "Alle Spieler:", + "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt.", + "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden.", + "winner": "Gewinner: {winnerName}", + "players": "Spieler", + "player_name": "Spielername", + "no_data_available": "Keine Daten verfügbar.", + "matches": "Matches", + "groups": "Gruppen", + "recent_matches": "Letzte Matches", + "quick_create": "Schnellzugriff", + "winner_label": "Gewinner", + "ruleset_label": "Regelwerk", + "match_in_progress": "Match läuft...", + "menu": "Menü", + "settings": "Einstellungen", + "export_data": "Daten exportieren", + "import_data": "Daten importieren", + "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", + "data_successfully_deleted": "Daten erfolgreich gelöscht", + "data_successfully_imported": "Daten erfolgreich importiert", + "invalid_schema": "Ungültiges Schema", + "error_reading_file": "Fehler beim Lesen der Datei", + "import_canceled": "Import abgebrochen", + "format_exception": "Formatfehler (siehe Konsole)", + "unknown_exception": "Unbekannter Fehler (siehe Konsole)", + "data_successfully_exported": "Daten erfolgreich exportiert", + "export_canceled": "Export abgebrochen", + "undo": "Rückgängig", + "wins": "Siege", + "winrate": "Siegquote", + "amount_of_matches": "Anzahl der Matches", + "info": "Info", + "no_groups_created_yet": "Noch keine Gruppen erstellt", + "create_group": "Gruppe erstellen", + "group_name": "Gruppenname", + "no_matches_created_yet": "Noch keine Matches erstellt", + "create_game": "Match erstellen", + "match_name": "Matchname", + "game": "Spiel", + "ruleset": "Regelwerk", + "group": "Gruppe", + "none": "Keine", + "create_match": "Match erstellen", + "search_for_players": "Nach Spielern suchen", + "search_for_groups": "Nach Gruppen suchen", + "no_players_created_yet": "Noch keine Spieler erstellt", + "all_players_selected": "Alle Spieler ausgewählt", + "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", + "today_at": "Heute um {time}", + "yesterday_at": "Gestern um {time}", + "days_ago": "vor {count} Tagen", + "home": "Startseite", + "statistics": "Statistiken", + "stats": "Statistiken", + "players_count": "{count} Spieler", + "you_have_no_groups_created_yet": "Du hast noch keine Gruppen erstellt", + "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", + "game_name": "Spielname", + "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "ruleset_single_loser_desc": "Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_most_points_desc": "Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.", + "ruleset_least_points_desc": "Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.", + "single_winner": "Ein Gewinner", + "single_loser": "Ein Verlierer", + "most_points": "Höchste Punkte", + "least_points": "Niedrigste Punkte" +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb new file mode 100644 index 0000000..44a8f67 --- /dev/null +++ b/lib/l10n/arb/app_en.arb @@ -0,0 +1,357 @@ +{ + "@@locale": "en", + "choose_group": "Choose Group", + "@choose_group": { + "description": "Label for choosing a group" + }, + "create_new_match": "Create new match", + "@create_new_match": { + "description": "Button text to create a new match" + }, + "choose_ruleset": "Choose Ruleset", + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "choose_game": "Choose Game", + "@choose_game": { + "description": "Label for choosing a game" + }, + "select_winner": "Select Winner:", + "@select_winner": { + "description": "Label to select the winner" + }, + "no_recent_matches_available": "No recent matches available", + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "no_second_match_available": "No second match available", + "@no_second_matcb_available": { + "description": "Message when no second match exists" + }, + "delete_all_data": "Delete all data?", + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button text" + }, + "delete": "Delete", + "@delete": { + "description": "Delete button text" + }, + "create_new_group": "Create new group", + "@create_new_group": { + "description": "Button text to create a new group" + }, + "error_while_creating_group_please_try_again": "Error while creating group, please try again", + "@error_while_creating_group_please_try_again": { + "description": "Error message when group creation fails" + }, + "selected_players": "Selected players: {count}", + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "no_players_selected": "No players selected", + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "all_players": "All players:", + "@all_players": { + "description": "Label for all players list" + }, + "successfully_added_player": "Successfully added player {playerName}.", + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "could_not_add_player": "Could not add player {playerName}.", + "@could_not_add_player": { + "description": "Error message when adding a player fails", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "winner": "Winner: {winnerName}", + "@winner": { + "description": "Shows the winner's name", + "placeholders": { + "winnerName": { + "type": "String", + "example": "John" + } + } + }, + "players": "Players", + "@players": { + "description": "Players label" + }, + "no_data_available": "No data available.", + "@no_data_available": { + "description": "Message when no data is available" + }, + "matches": "Matches", + "@matches": { + "description": "Label for matches" + }, + "groups": "Groups", + "@groups": { + "description": "Label for groups" + }, + "recent_matches": "Recent Matches", + "@recent_matches": { + "description": "Title for recent matches section" + }, + "quick_create": "Quick Create", + "@quick_create": { + "description": "Title for quick create section" + }, + "winner_label": "Winner", + "@winner_label": { + "description": "Label for winner field" + }, + "ruleset_label": "Ruleset", + "@ruleset_label": { + "description": "Label for ruleset field" + }, + "match_in_progress": "Match in progress...", + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "menu": "Menu", + "@menu": { + "description": "Menu label" + }, + "settings": "Settings", + "@settings": { + "description": "Settings label" + }, + "export_data": "Export data", + "@export_data": { + "description": "Export data menu item" + }, + "import_data": "Import data", + "@import_data": { + "description": "Import data menu item" + }, + "this_cannot_be_undone": "This can't be undone", + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "data_successfully_deleted": "Data successfully deleted", + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "data_successfully_imported": "Data successfully imported", + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "invalid_schema": "Invalid Schema", + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "error_reading_file": "Error reading file", + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "import_canceled": "Import canceled", + "@import_canceled": { + "description": "Message when import is canceled" + }, + "format_exception": "Format Exception (see console)", + "@format_exception": { + "description": "Error message for format exceptions" + }, + "unknown_exception": "Unknown Exception (see console)", + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "data_successfully_exported": "Data successfully exported", + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "export_canceled": "Export canceled", + "@export_canceled": { + "description": "Message when export is canceled" + }, + "undo": "Undo", + "@undo": { + "description": "Undo button text" + }, + "wins": "Wins", + "@wins": { + "description": "Label for wins statistic" + }, + "winrate": "Winrate", + "@winrate": { + "description": "Label for winrate statistic" + }, + "amount_of_matches": "Amount of Matches", + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "info": "Info", + "@info": { + "description": "Info label" + }, + "no_groups_created_yet": "No groups created yet", + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "no_players_created_yet": "No players created yet", + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "create_group": "Create Group", + "@create_group": { + "description": "Button text to create a group" + }, + "group_name": "Group name", + "@group_name": { + "description": "Placeholder for group name input" + }, + "player_name": "Player name", + "@player_name": { + "description": "Placeholder for player name input" + }, + "no_matches_created_yet": "No matches created yet", + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "match_name": "Match name", + "@match_name": { + "description": "Placeholder for match name input" + }, + "game": "Game", + "@game": { + "description": "Game label" + }, + "ruleset": "Ruleset", + "@ruleset": { + "description": "Ruleset label" + }, + "group": "Group", + "@group": { + "description": "Group label" + }, + "none": "None", + "@none": { + "description": "None option label" + }, + "create_match": "Create match", + "@create_match": { + "description": "Button text to create a match" + }, + "no_players_found_with_that_name": "No players found with that name", + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "all_players_selected": "All players selected", + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "today_at": "Today at {time}", + "@today_at": { + "description": "Date format for today", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "yesterday_at": "Yesterday at {time}", + "@yesterday_at": { + "description": "Date format for yesterday", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "days_ago": "{count} days ago", + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "home": "Home", + "@home": { + "description": "Home tab label" + }, + "statistics": "Statistics", + "@statistics": { + "description": "Statistics tab label" + }, + "stats": "Stats", + "@stats": { + "description": "Stats tab label (short)" + }, + "players_count": "{count} Players", + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "there_is_no_group_matching_your_search": "There is no group matching your search", + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "game_name": "Game Name", + "@game_name": { + "description": "Placeholder for game name search" + }, + "ruleset_single_winner_desc": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "@ruleset_single_winner_desc": { + "description": "Description for single winner ruleset" + }, + "ruleset_single_loser_desc": "Exactly one loser is determined; last place receives the penalty or consequence.", + "@ruleset_single_loser_desc": { + "description": "Description for single loser ruleset" + }, + "ruleset_most_points_desc": "Traditional ruleset: the player with the most points wins.", + "@ruleset_most_points_desc": { + "description": "Description for most points ruleset" + }, + "ruleset_least_points_desc": "Inverse scoring: the player with the fewest points wins.", + "@ruleset_least_points_desc": { + "description": "Description for least points ruleset" + }, + "single_winner": "Single Winner", + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "single_loser": "Single Loser", + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "most_points": "Most Points", + "@most_points": { + "description": "Title for most points ruleset" + }, + "least_points": "Least Points", + "@least_points": { + "description": "Title for least points ruleset" + } +} -- 2.49.1 From 534d19efc3dfd6fd961ddb9e68466ae0a8cd1042 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:21:09 +0100 Subject: [PATCH 04/32] Implemented localization across the application. --- lib/core/enums.dart | 15 +-- lib/main.dart | 3 + .../main_menu/custom_navigation_bar.dart | 17 ++-- .../group_view/create_group_view.dart | 17 ++-- .../main_menu/group_view/groups_view.dart | 9 +- .../views/main_menu/home_view.dart | 49 +++++++--- .../create_match/choose_game_view.dart | 13 ++- .../create_match/choose_group_view.dart | 22 +++-- .../create_match/choose_ruleset_view.dart | 10 +- .../create_match/create_match_view.dart | 92 +++++++++---------- .../match_view/match_result_view.dart | 5 +- .../main_menu/match_view/match_view.dart | 9 +- .../views/main_menu/settings_view.dart | 83 +++++++++++------ .../views/main_menu/statistics_view.dart | 7 +- .../widgets/player_selection.dart | 31 +++++-- .../widgets/tiles/game_history_tile.dart | 17 ++-- .../widgets/tiles/statistics_tile.dart | 5 +- 17 files changed, 247 insertions(+), 157 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 737882e..fc1ac91 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; + /// Button types used for styling the [CustomWidthButton] enum ButtonType { primary, secondary, tertiary } @@ -30,16 +33,16 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.leastPoints]: The player with the fewest points wins. enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } -/// Translates a [Ruleset] enum value to its corresponding string representation. -String translateRulesetToString(Ruleset ruleset) { +/// Translates a [Ruleset] enum value to its corresponding localized string. +String translateRulesetToString(Ruleset ruleset, BuildContext context) { switch (ruleset) { case Ruleset.singleWinner: - return 'Single Winner'; + return AppLocalizations.of(context)!.single_winner; case Ruleset.singleLoser: - return 'Single Loser'; + return AppLocalizations.of(context)!.single_loser; case Ruleset.mostPoints: - return 'Most Points'; + return AppLocalizations.of(context)!.most_points; case Ruleset.leastPoints: - return 'Least Points'; + return AppLocalizations.of(context)!.least_points; } } diff --git a/lib/main.dart b/lib/main.dart index 98c40f8..0f3b6b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -20,6 +21,8 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, debugShowCheckedModeBanner: false, title: 'Game Tracker', darkTheme: ThemeData.dark(), diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2ec28fa..980bf1a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; @@ -89,28 +90,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: 'Home', + label: AppLocalizations.of(context)!.home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: 'Matches', + label: AppLocalizations.of(context)!.matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: 'Groups', + label: AppLocalizations.of(context)!.groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: 'Stats', + label: AppLocalizations.of(context)!.statistics, onTabTapped: onTabTapped, ), ], @@ -131,13 +132,13 @@ class _CustomNavigationBarState extends State String _currentTabTitle() { switch (currentIndex) { case 0: - return 'Home'; + return AppLocalizations.of(context)!.home; case 1: - return 'Matches'; + return AppLocalizations.of(context)!.matches; case 2: - return 'Groups'; + return AppLocalizations.of(context)!.groups; case 3: - return 'Statistics'; + return AppLocalizations.of(context)!.statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index f20fb4e..bceb3bd 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; @@ -43,8 +44,8 @@ class _CreateGroupViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Create new group', + title: Text( + AppLocalizations.of(context)!.create_new_group, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -57,7 +58,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: 'Group name', + hintText: AppLocalizations.of(context)!.group_name, onChanged: (value) { setState(() {}); }, @@ -73,7 +74,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: 'Create group', + text: AppLocalizations.of(context)!.create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -94,10 +95,12 @@ class _CreateGroupViewState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, - content: const Center( + content: Center( child: Text( - 'Error while creating group, please try again', - style: TextStyle(color: Colors.white), + AppLocalizations.of( + context, + )!.error_while_creating_group_please_try_again, + style: const TextStyle(color: Colors.white), ), ), ), diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index b2243bc..0e9ddbb 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -49,11 +50,11 @@ class _GroupsViewState extends State { enabled: isLoading, child: Visibility( visible: groups.isNotEmpty, - replacement: const Center( + replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'No groups created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_groups_created_yet, ), ), child: ListView.builder( @@ -73,7 +74,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Group', + text: AppLocalizations.of(context)!.create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3e221e7..1f0f233 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; @@ -86,7 +87,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Matches', + title: AppLocalizations.of(context)!.matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -94,7 +95,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Groups', + title: AppLocalizations.of(context)!.groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -104,15 +105,19 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: 'Recent Matches', + title: AppLocalizations.of(context)!.recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: Visibility( visible: !isLoading && loadedRecentMatches.isNotEmpty, - replacement: const Center( + replacement: Center( heightFactor: 12, - child: Text('No recent games available'), + child: Text( + AppLocalizations.of( + context, + )!.no_recent_matches_available, + ), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -120,11 +125,15 @@ class _HomeViewState extends State { children: [ MatchTile( matchTitle: recentMatches[0].name, - game: 'Winner', - ruleset: 'Ruleset', + game: AppLocalizations.of(context)!.winner_label, + ruleset: AppLocalizations.of( + context, + )!.ruleset_label, players: _getPlayerText(recentMatches[0]), winner: recentMatches[0].winner == null - ? 'Match in progress...' + ? AppLocalizations.of( + context, + )!.match_in_progress : recentMatches[0].winner!.name, ), const Padding( @@ -134,18 +143,28 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchTile( matchTitle: recentMatches[1].name, - game: 'Winner', - ruleset: 'Ruleset', + game: AppLocalizations.of( + context, + )!.winner_label, + ruleset: AppLocalizations.of( + context, + )!.ruleset_label, players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null - ? 'Game in progress...' + ? AppLocalizations.of( + context, + )!.match_in_progress : recentMatches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ - const Center( + Center( heightFactor: 5.35, - child: Text('No second game available'), + child: Text( + AppLocalizations.of( + context, + )!.no_second_match_available, + ), ), ], ], @@ -156,7 +175,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: 'Quick Create', + title: AppLocalizations.of(context)!.quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -213,7 +232,7 @@ class _HomeViewState extends State { String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return '$playerCount Players'; + return AppLocalizations.of(context)!.players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 53a4fcb..9ffe5b2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; @@ -41,8 +42,8 @@ class _ChooseGameViewState extends State { Navigator.of(context).pop(selectedGameIndex); }, ), - title: const Text( - 'Choose Game', + title: Text( + AppLocalizations.of(context)!.choose_game, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -53,7 +54,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: 'Game Name', + hintText: AppLocalizations.of(context)!.game_name, ), ), const SizedBox(height: 5), @@ -64,8 +65,10 @@ class _ChooseGameViewState extends State { return TitleDescriptionListTile( title: widget.games[index].$1, description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, + badgeText: translateRulesetToString( + widget.games[index].$3, + context, + ), onPressed: () async { setState(() { if (selectedGameIndex == index) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 37096b9..2f5dc75 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -22,7 +23,6 @@ class ChooseGroupView extends StatefulWidget { class _ChooseGroupViewState extends State { late String selectedGroupId; final TextEditingController controller = TextEditingController(); - final String hintText = 'Group Name'; late final List filteredGroups; @override @@ -51,8 +51,8 @@ class _ChooseGroupViewState extends State { ); }, ), - title: const Text( - 'Choose Group', + title: Text( + AppLocalizations.of(context)!.choose_group, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: hintText, + hintText: AppLocalizations.of(context)!.group_name, onChanged: (value) { setState(() { filterGroups(value); @@ -76,15 +76,17 @@ class _ChooseGroupViewState extends State { visible: filteredGroups.isNotEmpty, replacement: Visibility( visible: widget.groups.isNotEmpty, - replacement: const TopCenteredMessage( + replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'You have no groups created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_groups_created_yet, ), - child: const TopCenteredMessage( + child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of( + context, + )!.there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 537f749..ff6ab83 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { @@ -46,8 +47,8 @@ class _ChooseRulesetViewState extends State { ); }, ), - title: const Text( - 'Choose Ruleset', + title: Text( + AppLocalizations.of(context)!.choose_ruleset, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -66,7 +67,10 @@ class _ChooseRulesetViewState extends State { } }); }, - title: translateRulesetToString(widget.rulesets[index].$1), + title: translateRulesetToString( + widget.rulesets[index].$1, + context, + ), description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f3b4d79..62da943 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -6,6 +6,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart'; @@ -64,34 +65,6 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; - /// List of available rulesets with their descriptions - /// as tuples of (Ruleset, String) - /// TODO: Replace when rulesets are implemented - List<(Ruleset, String)> rulesets = [ - ( - Ruleset.singleWinner, - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', - ), - ( - Ruleset.singleLoser, - 'Exactly one loser is determined; last place receives the penalty or consequence.', - ), - ( - Ruleset.mostPoints, - 'Traditional ruleset: the player with the most points wins.', - ), - ( - Ruleset.leastPoints, - 'Inverse scoring: the player with the fewest points wins.', - ), - ]; - - // TODO: Replace when games are implemented - List<(String, String, Ruleset)> games = [ - ('Example Game 1', 'This is a discription', Ruleset.leastPoints), - ('Example Game 2', '', Ruleset.singleWinner), - ]; - @override void initState() { super.initState(); @@ -111,6 +84,33 @@ class _CreateMatchViewState extends State { filteredPlayerList = List.from(playerList); } + List<(Ruleset, String)> _getRulesets(BuildContext context) { + return [ + ( + Ruleset.singleWinner, + AppLocalizations.of(context)!.ruleset_single_winner_desc, + ), + ( + Ruleset.singleLoser, + AppLocalizations.of(context)!.ruleset_single_loser_desc, + ), + ( + Ruleset.mostPoints, + AppLocalizations.of(context)!.ruleset_most_points_desc, + ), + ( + Ruleset.leastPoints, + AppLocalizations.of(context)!.ruleset_least_points_desc, + ), + ]; + } + + // TODO: Replace when games are implemented + List<(String, String, Ruleset)> games = [ + ('Example Game 1', 'This is a description', Ruleset.leastPoints), + ('Example Game 2', '', Ruleset.singleWinner), + ]; + @override Widget build(BuildContext context) { return Scaffold( @@ -118,8 +118,8 @@ class _CreateMatchViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Create new match', + title: Text( + AppLocalizations.of(context)!.create_new_match, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -132,13 +132,13 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: 'Match name', + hintText: AppLocalizations.of(context)!.match_name, ), ), ChooseTile( - title: 'Game', + title: AppLocalizations.of(context)!.game, trailingText: selectedGameIndex == -1 - ? 'None' + ? AppLocalizations.of(context)!.none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -152,9 +152,9 @@ class _CreateMatchViewState extends State { setState(() { if (selectedGameIndex != -1) { selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); + selectedRulesetIndex = _getRulesets( + context, + ).indexWhere((r) => r.$1 == selectedRuleset); } else { selectedRuleset = null; } @@ -162,30 +162,30 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: 'Ruleset', + title: AppLocalizations.of(context)!.ruleset, trailingText: selectedRuleset == null - ? 'None' - : translateRulesetToString(selectedRuleset!), + ? AppLocalizations.of(context)!.none + : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: rulesets, + rulesets: _getRulesets(context), initialRulesetIndex: selectedRulesetIndex, ), ), ); - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); + selectedRulesetIndex = _getRulesets( + context, + ).indexWhere((r) => r.$1 == selectedRuleset); selectedGameIndex = -1; setState(() {}); }, ), ChooseTile( - title: 'Group', + title: AppLocalizations.of(context)!.group, trailingText: selectedGroup == null - ? 'None' + ? AppLocalizations.of(context)!.none : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -222,7 +222,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: 'Create match', + text: AppLocalizations.of(context)!.create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e8075f6..1639dce 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; @@ -79,8 +80,8 @@ class _MatchResultViewState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Select Winner:', + Text( + AppLocalizations.of(context)!.select_winner, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..af7f7ed 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -8,6 +8,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; @@ -58,11 +59,11 @@ class _MatchViewState extends State { enabled: isLoading, child: Visibility( visible: matches.isNotEmpty, - replacement: const Center( + replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: 'Info', - message: 'No games created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_matches_created_yet, ), ), child: ListView.builder( @@ -96,7 +97,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Game', + text: AppLocalizations.of(context)!.create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 6ebb7fb..8899e40 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; @@ -24,30 +25,33 @@ class _SettingsViewState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Padding( - padding: EdgeInsets.fromLTRB(24, 0, 24, 10), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - 'Menu', - style: TextStyle( + AppLocalizations.of(context)!.menu, + style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 10, + ), child: Text( textAlign: TextAlign.start, - 'Settings', - style: TextStyle( + AppLocalizations.of(context)!.settings, + style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), ), SettingsListTile( - title: 'Export data', + title: AppLocalizations.of(context)!.export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -62,7 +66,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: 'Import data', + title: AppLocalizations.of(context)!.import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -74,23 +78,27 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: 'Delete all data', + title: AppLocalizations.of(context)!.delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Delete all data?'), - content: const Text('This can\'t be undone'), + title: Text( + AppLocalizations.of(context)!.delete_all_data, + ), + content: Text( + AppLocalizations.of(context)!.this_cannot_be_undone, + ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: const Text('Abbrechen'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: const Text('Löschen'), + child: Text(AppLocalizations.of(context)!.delete), ), ], ), @@ -99,7 +107,9 @@ class _SettingsViewState extends State { DataTransferService.deleteAllData(context); showSnackbar( context: context, - message: 'Daten erfolgreich gelöscht', + message: AppLocalizations.of( + context, + )!.data_successfully_deleted, ); } }); @@ -122,22 +132,34 @@ class _SettingsViewState extends State { }) { switch (result) { case ImportResult.success: - showSnackbar(context: context, message: 'Data successfully imported'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.data_successfully_imported, + ); case ImportResult.invalidSchema: - showSnackbar(context: context, message: 'Invalid Schema'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.invalid_schema, + ); case ImportResult.fileReadError: - showSnackbar(context: context, message: 'Error reading file'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.error_reading_file, + ); case ImportResult.canceled: - showSnackbar(context: context, message: 'Import canceled'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.import_canceled, + ); case ImportResult.formatException: showSnackbar( context: context, - message: 'Format Exception (see console)', + message: AppLocalizations.of(context)!.format_exception, ); case ImportResult.unknownException: showSnackbar( context: context, - message: 'Unknown Exception (see console)', + message: AppLocalizations.of(context)!.unknown_exception, ); } } @@ -152,13 +174,19 @@ class _SettingsViewState extends State { }) { switch (result) { case ExportResult.success: - showSnackbar(context: context, message: 'Data successfully exported'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.data_successfully_exported, + ); case ExportResult.canceled: - showSnackbar(context: context, message: 'Export canceled'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.export_canceled, + ); case ExportResult.unknownException: showSnackbar( context: context, - message: 'Unknown Exception (see console)', + message: AppLocalizations.of(context)!.unknown_exception, ); } } @@ -183,7 +211,10 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.onBoxColor, duration: duration, action: action != null - ? SnackBarAction(label: 'Rückgängig', onPressed: action) + ? SnackBarAction( + label: AppLocalizations.of(context)!.undo, + onPressed: action, + ) : null, ), ); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e94f2b6..07c27aa 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:provider/provider.dart'; @@ -60,7 +61,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.01), StatisticsTile( icon: Icons.sports_score, - title: 'Wins', + title: AppLocalizations.of(context)!.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -69,7 +70,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate', + title: AppLocalizations.of(context)!.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -78,7 +79,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Amount of Matches', + title: AppLocalizations.of(context)!.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eb70ae0..a827cc0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; @@ -129,14 +130,20 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - 'Selected players: (${selectedPlayers.length})', + AppLocalizations.of( + context, + )!.selected_players(selectedPlayers.length), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), SizedBox( height: 50, child: selectedPlayers.isEmpty - ? const Center(child: Text('No players selected')) + ? Center( + child: Text( + AppLocalizations.of(context)!.no_players_selected, + ), + ) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -171,8 +178,8 @@ class _PlayerSelectionState extends State { ), ), const SizedBox(height: 10), - const Text( - 'All players:', + Text( + AppLocalizations.of(context)!.all_players, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -186,12 +193,14 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', + title: AppLocalizations.of(context)!.info, message: allPlayers.isEmpty - ? 'No players created yet' + ? AppLocalizations.of(context)!.no_players_created_yet : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add' - : 'No players found with that name', + ? AppLocalizations.of(context)!.all_players_selected + : AppLocalizations.of( + context, + )!.no_players_found_with_that_name, ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -243,7 +252,9 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - 'Successfully added player $playerName.', + AppLocalizations.of( + context, + )!.successfully_added_player(playerName), style: const TextStyle(color: Colors.white), ), ), @@ -255,7 +266,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - 'Could not add player $playerName.', + AppLocalizations.of(context)!.could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index f79edc3..4f40fc6 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; @@ -97,7 +98,7 @@ class _GameHistoryTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - 'Winner: ${winner.name}', + AppLocalizations.of(context)!.winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -113,8 +114,8 @@ class _GameHistoryTileState extends State { ], if (allPlayers.isNotEmpty) ...[ - const Text( - 'Players', + Text( + AppLocalizations.of(context)!.players, style: TextStyle( fontSize: 13, color: Colors.grey, @@ -141,11 +142,15 @@ class _GameHistoryTileState extends State { final difference = now.difference(dateTime); if (difference.inDays == 0) { - return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + return AppLocalizations.of( + context, + )!.today_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays == 1) { - return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + return AppLocalizations.of( + context, + )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return '${difference.inDays} days ago'; + return AppLocalizations.of(context)!.days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 582cf66..e783ab4 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; class StatisticsTile extends StatelessWidget { @@ -33,9 +34,9 @@ class StatisticsTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Visibility( visible: values.isNotEmpty, - replacement: const Center( + replacement: Center( heightFactor: 4, - child: Text('No data available.'), + child: Text(AppLocalizations.of(context)!.no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { -- 2.49.1 From ed9e3af76869efa9027d75be1bc874dbcea9d3c2 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:34:55 +0100 Subject: [PATCH 05/32] merged dev into branch and fixed missing comma --- lib/presentation/views/main_menu/statistics_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e66a2ce..dabfff5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -100,6 +100,7 @@ class _StatisticsViewState extends State { title: 'Info', message: 'No statistics available', ), + ), StatisticsTile( icon: Icons.sports_score, title: AppLocalizations.of(context)!.wins, -- 2.49.1 From 73aea0d0f56853f1da87522191b1e6c5cb63c5cc Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:39:15 +0100 Subject: [PATCH 06/32] added missings consts --- .../views/main_menu/group_view/create_group_view.dart | 2 +- .../main_menu/match_view/create_match/choose_game_view.dart | 2 +- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- .../main_menu/match_view/create_match/choose_ruleset_view.dart | 2 +- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- .../views/main_menu/match_view/match_result_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- lib/presentation/widgets/tiles/game_history_tile.dart | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index bceb3bd..e9e5fe1 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -46,7 +46,7 @@ class _CreateGroupViewState extends State { scrolledUnderElevation: 0, title: Text( AppLocalizations.of(context)!.create_new_group, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 9ffe5b2..6a75ae1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -44,7 +44,7 @@ class _ChooseGameViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_game, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 2f5dc75..7308dce 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -53,7 +53,7 @@ class _ChooseGroupViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_group, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index ff6ab83..a6129cc 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -49,7 +49,7 @@ class _ChooseRulesetViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_ruleset, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 62da943..8eeaca6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -120,7 +120,7 @@ class _CreateMatchViewState extends State { scrolledUnderElevation: 0, title: Text( AppLocalizations.of(context)!.create_new_match, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 1639dce..f13ef87 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -82,7 +82,7 @@ class _MatchResultViewState extends State { children: [ Text( AppLocalizations.of(context)!.select_winner, - style: TextStyle( + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index a827cc0..9a1ffd1 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -180,7 +180,7 @@ class _PlayerSelectionState extends State { const SizedBox(height: 10), Text( AppLocalizations.of(context)!.all_players, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), /* diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 4f40fc6..091935e 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -116,7 +116,7 @@ class _GameHistoryTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( AppLocalizations.of(context)!.players, - style: TextStyle( + style: const TextStyle( fontSize: 13, color: Colors.grey, fontWeight: FontWeight.w500, -- 2.49.1 From 86dbc9afb0cc04584448c83948e092f16025428c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:42:21 +0100 Subject: [PATCH 07/32] Optimized ruleset selection in CreateMatchView --- .../match_view/create_match/create_match_view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 8eeaca6..5e35f41 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -167,17 +167,19 @@ class _CreateMatchViewState extends State { ? AppLocalizations.of(context)!.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { + final rulesets = _getRulesets(context); selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: _getRulesets(context), + rulesets: rulesets, initialRulesetIndex: selectedRulesetIndex, ), ), ); - selectedRulesetIndex = _getRulesets( - context, - ).indexWhere((r) => r.$1 == selectedRuleset); + if (!mounted) return; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); selectedGameIndex = -1; setState(() {}); }, -- 2.49.1 From 0bfaba42259470d758fb7e81f7932f6fe8c84c8b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:40:36 +0100 Subject: [PATCH 08/32] Refactored German localization strings by removing redundant entries --- lib/l10n/arb/app_de.arb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 3e090e3..a922996 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -51,15 +51,12 @@ "create_group": "Gruppe erstellen", "group_name": "Gruppenname", "no_matches_created_yet": "Noch keine Matches erstellt", - "create_game": "Match erstellen", "match_name": "Matchname", "game": "Spiel", "ruleset": "Regelwerk", "group": "Gruppe", "none": "Keine", "create_match": "Match erstellen", - "search_for_players": "Nach Spielern suchen", - "search_for_groups": "Nach Gruppen suchen", "no_players_created_yet": "Noch keine Spieler erstellt", "all_players_selected": "Alle Spieler ausgewählt", "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", @@ -70,7 +67,6 @@ "statistics": "Statistiken", "stats": "Statistiken", "players_count": "{count} Spieler", - "you_have_no_groups_created_yet": "Du hast noch keine Gruppen erstellt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "game_name": "Spielname", "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", -- 2.49.1 From 7bf03ec3889c372bba27225e8948766c124159f0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:42:38 +0100 Subject: [PATCH 09/32] added auto-generated files to git --- lib/l10n/generated/app_localizations.dart | 596 +++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 270 +++++++++ lib/l10n/generated/app_localizations_en.dart | 269 +++++++++ 3 files changed, 1135 insertions(+) create mode 100644 lib/l10n/generated/app_localizations.dart create mode 100644 lib/l10n/generated/app_localizations_de.dart create mode 100644 lib/l10n/generated/app_localizations_en.dart diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart new file mode 100644 index 0000000..71091ab --- /dev/null +++ b/lib/l10n/generated/app_localizations.dart @@ -0,0 +1,596 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_de.dart'; +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'generated/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('de'), + Locale('en'), + ]; + + /// Label for choosing a group + /// + /// In en, this message translates to: + /// **'Choose Group'** + String get choose_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Label for choosing a game + /// + /// In en, this message translates to: + /// **'Choose Game'** + String get choose_game; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Message when no recent matches exist + /// + /// In en, this message translates to: + /// **'No recent matches available'** + String get no_recent_matches_available; + + /// No description provided for @no_second_match_available. + /// + /// In en, this message translates to: + /// **'No second match available'** + String get no_second_match_available; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_while_creating_group_please_try_again; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + + /// Label for all players list + /// + /// In en, this message translates to: + /// **'All players:'** + String get all_players; + + /// Success message when adding a player + /// + /// In en, this message translates to: + /// **'Successfully added player {playerName}.'** + String successfully_added_player(String playerName); + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}.'** + String could_not_add_player(String playerName); + + /// Shows the winner's name + /// + /// In en, this message translates to: + /// **'Winner: {winnerName}'** + String winner(String winnerName); + + /// Players label + /// + /// In en, this message translates to: + /// **'Players'** + String get players; + + /// Message when no data is available + /// + /// In en, this message translates to: + /// **'No data available.'** + String get no_data_available; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Title for recent matches section + /// + /// In en, this message translates to: + /// **'Recent Matches'** + String get recent_matches; + + /// Title for quick create section + /// + /// In en, this message translates to: + /// **'Quick Create'** + String get quick_create; + + /// Label for winner field + /// + /// In en, this message translates to: + /// **'Winner'** + String get winner_label; + + /// Label for ruleset field + /// + /// In en, this message translates to: + /// **'Ruleset'** + String get ruleset_label; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Warning message for irreversible actions + /// + /// In en, this message translates to: + /// **'This can\'t be undone'** + String get this_cannot_be_undone; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Error message for unknown exceptions + /// + /// In en, this message translates to: + /// **'Unknown Exception (see console)'** + String get unknown_exception; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Undo button text + /// + /// In en, this message translates to: + /// **'Undo'** + String get undo; + + /// Label for wins statistic + /// + /// In en, this message translates to: + /// **'Wins'** + String get wins; + + /// Label for winrate statistic + /// + /// In en, this message translates to: + /// **'Winrate'** + String get winrate; + + /// Label for amount of matches statistic + /// + /// In en, this message translates to: + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Placeholder for player name input + /// + /// In en, this message translates to: + /// **'Player name'** + String get player_name; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Ruleset label + /// + /// In en, this message translates to: + /// **'Ruleset'** + String get ruleset; + + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// None option label + /// + /// In en, this message translates to: + /// **'None'** + String get none; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when all players are added to selection + /// + /// In en, this message translates to: + /// **'All players selected'** + String get all_players_selected; + + /// Date format for today + /// + /// In en, this message translates to: + /// **'Today at {time}'** + String today_at(String time); + + /// Date format for yesterday + /// + /// In en, this message translates to: + /// **'Yesterday at {time}'** + String yesterday_at(String time); + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Statistics tab label + /// + /// In en, this message translates to: + /// **'Statistics'** + String get statistics; + + /// Stats tab label (short) + /// + /// In en, this message translates to: + /// **'Stats'** + String get stats; + + /// Shows the number of players + /// + /// In en, this message translates to: + /// **'{count} Players'** + String players_count(int count); + + /// Message when search returns no groups + /// + /// In en, this message translates to: + /// **'There is no group matching your search'** + String get there_is_no_group_matching_your_search; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner_desc; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser_desc; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points_desc; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points_desc; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['de', 'en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'de': + return AppLocalizationsDe(); + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart new file mode 100644 index 0000000..77476f2 --- /dev/null +++ b/lib/l10n/generated/app_localizations_de.dart @@ -0,0 +1,270 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class AppLocalizationsDe extends AppLocalizations { + AppLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get choose_group => 'Gruppe wählen'; + + @override + String get create_new_match => 'Neues Match erstellen'; + + @override + String get choose_ruleset => 'Regelwerk wählen'; + + @override + String get choose_game => 'Spiel wählen'; + + @override + String get select_winner => 'Gewinner wählen:'; + + @override + String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; + + @override + String get no_second_match_available => 'Kein zweites Match verfügbar'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get cancel => 'Abbrechen'; + + @override + String get delete => 'Löschen'; + + @override + String get create_new_group => 'Neue Gruppe erstellen'; + + @override + String get error_while_creating_group_please_try_again => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Ausgewählte Spieler: $countString'; + } + + @override + String get no_players_selected => 'Keine Spieler ausgewählt'; + + @override + String get all_players => 'Alle Spieler:'; + + @override + String successfully_added_player(String playerName) { + return 'Spieler $playerName erfolgreich hinzugefügt.'; + } + + @override + String could_not_add_player(String playerName) { + return 'Spieler $playerName konnte nicht hinzugefügt werden.'; + } + + @override + String winner(String winnerName) { + return 'Gewinner: $winnerName'; + } + + @override + String get players => 'Spieler'; + + @override + String get no_data_available => 'Keine Daten verfügbar.'; + + @override + String get matches => 'Matches'; + + @override + String get groups => 'Gruppen'; + + @override + String get recent_matches => 'Letzte Matches'; + + @override + String get quick_create => 'Schnellzugriff'; + + @override + String get winner_label => 'Gewinner'; + + @override + String get ruleset_label => 'Regelwerk'; + + @override + String get match_in_progress => 'Match läuft...'; + + @override + String get menu => 'Menü'; + + @override + String get settings => 'Einstellungen'; + + @override + String get export_data => 'Daten exportieren'; + + @override + String get import_data => 'Daten importieren'; + + @override + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; + + @override + String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + + @override + String get data_successfully_imported => 'Daten erfolgreich importiert'; + + @override + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get error_reading_file => 'Fehler beim Lesen der Datei'; + + @override + String get import_canceled => 'Import abgebrochen'; + + @override + String get format_exception => 'Formatfehler (siehe Konsole)'; + + @override + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; + + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + + @override + String get export_canceled => 'Export abgebrochen'; + + @override + String get undo => 'Rückgängig'; + + @override + String get wins => 'Siege'; + + @override + String get winrate => 'Siegquote'; + + @override + String get amount_of_matches => 'Anzahl der Matches'; + + @override + String get info => 'Info'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler erstellt'; + + @override + String get create_group => 'Gruppe erstellen'; + + @override + String get group_name => 'Gruppenname'; + + @override + String get player_name => 'Spielername'; + + @override + String get no_matches_created_yet => 'Noch keine Matches erstellt'; + + @override + String get match_name => 'Matchname'; + + @override + String get game => 'Spiel'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get group => 'Gruppe'; + + @override + String get none => 'Keine'; + + @override + String get create_match => 'Match erstellen'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler mit diesem Namen gefunden'; + + @override + String get all_players_selected => 'Alle Spieler ausgewählt'; + + @override + String today_at(String time) { + return 'Heute um $time'; + } + + @override + String yesterday_at(String time) { + return 'Gestern um $time'; + } + + @override + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get home => 'Startseite'; + + @override + String get statistics => 'Statistiken'; + + @override + String get stats => 'Statistiken'; + + @override + String players_count(int count) { + return '$count Spieler'; + } + + @override + String get there_is_no_group_matching_your_search => + 'Es gibt keine Gruppe, die deiner Suche entspricht'; + + @override + String get game_name => 'Spielname'; + + @override + String get ruleset_single_winner_desc => + 'Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get ruleset_single_loser_desc => + 'Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_most_points_desc => + 'Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_least_points_desc => + 'Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.'; + + @override + String get single_winner => 'Ein Gewinner'; + + @override + String get single_loser => 'Ein Verlierer'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get least_points => 'Niedrigste Punkte'; +} diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart new file mode 100644 index 0000000..f6a5b5f --- /dev/null +++ b/lib/l10n/generated/app_localizations_en.dart @@ -0,0 +1,269 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get choose_group => 'Choose Group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String get choose_game => 'Choose Game'; + + @override + String get select_winner => 'Select Winner:'; + + @override + String get no_recent_matches_available => 'No recent matches available'; + + @override + String get no_second_match_available => 'No second match available'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get cancel => 'Cancel'; + + @override + String get delete => 'Delete'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get error_while_creating_group_please_try_again => + 'Error while creating group, please try again'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Selected players: $countString'; + } + + @override + String get no_players_selected => 'No players selected'; + + @override + String get all_players => 'All players:'; + + @override + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName.'; + } + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName.'; + } + + @override + String winner(String winnerName) { + return 'Winner: $winnerName'; + } + + @override + String get players => 'Players'; + + @override + String get no_data_available => 'No data available.'; + + @override + String get matches => 'Matches'; + + @override + String get groups => 'Groups'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get quick_create => 'Quick Create'; + + @override + String get winner_label => 'Winner'; + + @override + String get ruleset_label => 'Ruleset'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get menu => 'Menu'; + + @override + String get settings => 'Settings'; + + @override + String get export_data => 'Export data'; + + @override + String get import_data => 'Import data'; + + @override + String get this_cannot_be_undone => 'This can\'t be undone'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get unknown_exception => 'Unknown Exception (see console)'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get undo => 'Undo'; + + @override + String get wins => 'Wins'; + + @override + String get winrate => 'Winrate'; + + @override + String get amount_of_matches => 'Amount of Matches'; + + @override + String get info => 'Info'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get create_group => 'Create Group'; + + @override + String get group_name => 'Group name'; + + @override + String get player_name => 'Player name'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get match_name => 'Match name'; + + @override + String get game => 'Game'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get group => 'Group'; + + @override + String get none => 'None'; + + @override + String get create_match => 'Create match'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get all_players_selected => 'All players selected'; + + @override + String today_at(String time) { + return 'Today at $time'; + } + + @override + String yesterday_at(String time) { + return 'Yesterday at $time'; + } + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get home => 'Home'; + + @override + String get statistics => 'Statistics'; + + @override + String get stats => 'Stats'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get there_is_no_group_matching_your_search => + 'There is no group matching your search'; + + @override + String get game_name => 'Game Name'; + + @override + String get ruleset_single_winner_desc => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get ruleset_single_loser_desc => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_most_points_desc => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_least_points_desc => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get single_winner => 'Single Winner'; + + @override + String get single_loser => 'Single Loser'; + + @override + String get most_points => 'Most Points'; + + @override + String get least_points => 'Least Points'; +} -- 2.49.1 From f97c341b81c050574c739216c09759de70f7bc26 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:43:24 +0100 Subject: [PATCH 10/32] added devtools_options.yaml to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e000548..72eb56e 100644 --- a/.gitignore +++ b/.gitignore @@ -195,3 +195,4 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/devtools_options.yaml -- 2.49.1 From 81f63c1c070145cfa5b3252518a2bdc152ef0a44 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:09:50 +0100 Subject: [PATCH 11/32] made statistics_view.dart use localization and fix merge error --- .../views/main_menu/statistics_view.dart | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index dabfff5..8fad901 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: 'Wins', + title: AppLocalizations.of(context)!.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate', + title: AppLocalizations.of(context)!.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -87,7 +87,9 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Amount of Matches', + title: AppLocalizations.of( + context, + )!.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -95,38 +97,12 @@ class _StatisticsViewState extends State { ), ], ), - child: const TopCenteredMessage( + child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'No statistics available', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_data_available, ), ), - StatisticsTile( - icon: Icons.sports_score, - title: AppLocalizations.of(context)!.wins, - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: AppLocalizations.of(context)!.winrate, - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: AppLocalizations.of(context)!.amount_of_matches, - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), -- 2.49.1 From 633a21d829d2556ef3a07befc31c12735c6ae2f5 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:08 +0100 Subject: [PATCH 12/32] Updated search bar hint texts to use localization for group and player searches --- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 7308dce..bfc0fed 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context)!.group_name, + hintText: AppLocalizations.of(context)!.search_for_groups, onChanged: (value) { setState(() { filterGroups(value); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 9a1ffd1..939b211 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -97,7 +97,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: 'Search for players', + hintText: AppLocalizations.of(context)!.search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, -- 2.49.1 From ad87dca67429366c9da1d2c2950aeb7d458ebd86 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:24 +0100 Subject: [PATCH 13/32] add generated files --- lib/l10n/generated/app_localizations.dart | 14 +++++++++++++- lib/l10n/generated/app_localizations_de.dart | 6 ++++++ lib/l10n/generated/app_localizations_en.dart | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 71091ab..05ce4b6 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -134,7 +134,7 @@ abstract class AppLocalizations { /// **'No recent matches available'** String get no_recent_matches_available; - /// No description provided for @no_second_match_available. + /// Message when no second match exists /// /// In en, this message translates to: /// **'No second match available'** @@ -559,6 +559,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Least Points'** String get least_points; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 77476f2..070cb9a 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -267,4 +267,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get least_points => 'Niedrigste Punkte'; + + @override + String get search_for_players => 'Nach Spielern suchen'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index f6a5b5f..8c78e86 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -266,4 +266,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get least_points => 'Least Points'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get search_for_groups => 'Search for groups'; } -- 2.49.1 From bc01a6de9a2ab9a3dd5c757de7b7aa423a0c4145 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:44 +0100 Subject: [PATCH 14/32] Add localization for search input fields in English and German --- lib/l10n/arb/app_de.arb | 4 +++- lib/l10n/arb/app_en.arb | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index a922996..f5e4855 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -76,5 +76,7 @@ "single_winner": "Ein Gewinner", "single_loser": "Ein Verlierer", "most_points": "Höchste Punkte", - "least_points": "Niedrigste Punkte" + "least_points": "Niedrigste Punkte", + "search_for_players": "Nach Spielern suchen", + "search_for_groups": "Nach Gruppen suchen" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 44a8f67..ae3fa16 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -25,7 +25,7 @@ "description": "Message when no recent matches exist" }, "no_second_match_available": "No second match available", - "@no_second_matcb_available": { + "@no_second_match_available": { "description": "Message when no second match exists" }, "delete_all_data": "Delete all data?", @@ -353,5 +353,13 @@ "least_points": "Least Points", "@least_points": { "description": "Title for least points ruleset" + }, + "search_for_players": "Search for players", + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "search_for_groups": "Search for groups", + "@search_for_groups": { + "description": "Hint text for group search input field" } } -- 2.49.1 From 7103765054aa2f60605d02efdaef48c505e46632 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:17:53 +0100 Subject: [PATCH 15/32] Update statistics_view.dart to use localized message no statistics available instead of no data available --- lib/presentation/views/main_menu/statistics_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 8fad901..fc7825a 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -100,7 +100,9 @@ class _StatisticsViewState extends State { child: TopCenteredMessage( icon: Icons.info, title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_data_available, + message: AppLocalizations.of( + context, + )!.no_statistics_available, ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), -- 2.49.1 From 8afba5680b830dfc762b4b8730fbf7f9ffad0a6d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:18:14 +0100 Subject: [PATCH 16/32] Add localization for no statistics available message and removed dots from all messages --- lib/l10n/arb/app_de.arb | 9 +++++---- lib/l10n/arb/app_en.arb | 12 ++++++++---- lib/l10n/generated/app_localizations.dart | 14 ++++++++++---- lib/l10n/generated/app_localizations_de.dart | 9 ++++++--- lib/l10n/generated/app_localizations_en.dart | 9 ++++++--- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index f5e4855..4228671 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -15,12 +15,12 @@ "selected_players": "Ausgewählte Spieler: {count}", "no_players_selected": "Keine Spieler ausgewählt", "all_players": "Alle Spieler:", - "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt.", - "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden.", + "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt", + "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden", "winner": "Gewinner: {winnerName}", "players": "Spieler", "player_name": "Spielername", - "no_data_available": "Keine Daten verfügbar.", + "no_statistics_available": "Keine Statistiken verfügbar", "matches": "Matches", "groups": "Gruppen", "recent_matches": "Letzte Matches", @@ -78,5 +78,6 @@ "most_points": "Höchste Punkte", "least_points": "Niedrigste Punkte", "search_for_players": "Nach Spielern suchen", - "search_for_groups": "Nach Gruppen suchen" + "search_for_groups": "Nach Gruppen suchen", + "no_data_available": "Keine Daten verfügbar" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ae3fa16..72e307c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -66,7 +66,7 @@ "@all_players": { "description": "Label for all players list" }, - "successfully_added_player": "Successfully added player {playerName}.", + "successfully_added_player": "Successfully added player {playerName}", "@successfully_added_player": { "description": "Success message when adding a player", "placeholders": { @@ -76,7 +76,7 @@ } } }, - "could_not_add_player": "Could not add player {playerName}.", + "could_not_add_player": "Could not add player {playerName}", "@could_not_add_player": { "description": "Error message when adding a player fails", "placeholders": { @@ -100,9 +100,13 @@ "@players": { "description": "Players label" }, - "no_data_available": "No data available.", + "no_statistics_available": "No statistics available", + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "no_data_available": "No data available", "@no_data_available": { - "description": "Message when no data is available" + "description": "Message when no data in the statistic tiles is given" }, "matches": "Matches", "@matches": { diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 05ce4b6..edd14df 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -191,13 +191,13 @@ abstract class AppLocalizations { /// Success message when adding a player /// /// In en, this message translates to: - /// **'Successfully added player {playerName}.'** + /// **'Successfully added player {playerName}'** String successfully_added_player(String playerName); /// Error message when adding a player fails /// /// In en, this message translates to: - /// **'Could not add player {playerName}.'** + /// **'Could not add player {playerName}'** String could_not_add_player(String playerName); /// Shows the winner's name @@ -212,10 +212,16 @@ abstract class AppLocalizations { /// **'Players'** String get players; - /// Message when no data is available + /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: - /// **'No data available.'** + /// **'No statistics available'** + String get no_statistics_available; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** String get no_data_available; /// Label for matches diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 070cb9a..df46454 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -63,12 +63,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String successfully_added_player(String playerName) { - return 'Spieler $playerName erfolgreich hinzugefügt.'; + return 'Spieler $playerName erfolgreich hinzugefügt'; } @override String could_not_add_player(String playerName) { - return 'Spieler $playerName konnte nicht hinzugefügt werden.'; + return 'Spieler $playerName konnte nicht hinzugefügt werden'; } @override @@ -80,7 +80,10 @@ class AppLocalizationsDe extends AppLocalizations { String get players => 'Spieler'; @override - String get no_data_available => 'Keine Daten verfügbar.'; + String get no_statistics_available => 'Keine Statistiken verfügbar'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; @override String get matches => 'Matches'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 8c78e86..7f000e5 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -63,12 +63,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String successfully_added_player(String playerName) { - return 'Successfully added player $playerName.'; + return 'Successfully added player $playerName'; } @override String could_not_add_player(String playerName) { - return 'Could not add player $playerName.'; + return 'Could not add player $playerName'; } @override @@ -80,7 +80,10 @@ class AppLocalizationsEn extends AppLocalizations { String get players => 'Players'; @override - String get no_data_available => 'No data available.'; + String get no_statistics_available => 'No statistics available'; + + @override + String get no_data_available => 'No data available'; @override String get matches => 'Matches'; -- 2.49.1 From 3c3bf506cbe58c9d74c29969490c53b0da0d248c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:31:37 +0100 Subject: [PATCH 17/32] Add TODO for implementing quick create functionality in home_view.dart --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1f0f233..1f8240d 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -173,6 +173,7 @@ class _HomeViewState extends State { ), ), ), + // TODO: Implement quick create functionality InfoTile( width: constraints.maxWidth * 0.95, title: AppLocalizations.of(context)!.quick_create, @@ -221,7 +222,6 @@ class _HomeViewState extends State { ], ), ), - ], ), ), ); -- 2.49.1 From d77b5f20f99aa2e077bbed9b073d3ae8777d1687 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:31:53 +0100 Subject: [PATCH 18/32] Update statistics_view.dart to use localized 'not available' message for players --- lib/presentation/views/main_menu/statistics_view.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index fc7825a..ed90a84 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -152,7 +152,10 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: 'N.a.'), + orElse: () => Player( + id: playerId, + name: AppLocalizations.of(context)!.not_available, + ), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -214,7 +217,10 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: 'N.a.'), + orElse: () => Player( + id: playerId, + name: AppLocalizations.of(context)!.not_available, + ), ); matchCounts[i] = (player.name, matchCounts[i].$2); } -- 2.49.1 From cc23c03f6b44d896e53967bd20224f77ee242a2d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:32:01 +0100 Subject: [PATCH 19/32] Add localization for 'not available' message in English and German --- lib/l10n/arb/app_de.arb | 3 ++- lib/l10n/arb/app_en.arb | 4 ++++ lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 3 +++ lib/l10n/generated/app_localizations_en.dart | 3 +++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4228671..c3605da 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -79,5 +79,6 @@ "least_points": "Niedrigste Punkte", "search_for_players": "Nach Spielern suchen", "search_for_groups": "Nach Gruppen suchen", - "no_data_available": "Keine Daten verfügbar" + "no_data_available": "Keine Daten verfügbar", + "not_available": "Nicht verfügbar" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 72e307c..a16d327 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -365,5 +365,9 @@ "search_for_groups": "Search for groups", "@search_for_groups": { "description": "Hint text for group search input field" + }, + "not_available": "Not available", + "@not_available": { + "description": "Abbreviation for not available" } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index edd14df..c7036d8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -577,6 +577,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Search for groups'** String get search_for_groups; + + /// Abbreviation for not available + /// + /// In en, this message translates to: + /// **'Not available'** + String get not_available; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index df46454..247e3e3 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -276,4 +276,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get not_available => 'Nicht verfügbar'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 7f000e5..a4f62e0 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -275,4 +275,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get search_for_groups => 'Search for groups'; + + @override + String get not_available => 'Not available'; } -- 2.49.1 From 00519901685e782f58def671444b3d966b66d6b4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:43:06 +0100 Subject: [PATCH 20/32] Remove auto_localize dependency from pubspec.yaml --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 866f662..e79ca17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: intl: any flutter_localizations: sdk: flutter - auto_localize: ^0.0.5 dev_dependencies: flutter_test: -- 2.49.1 From 25fe10eb9a3896434d6051a6b389d4baf858724c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:44:28 +0100 Subject: [PATCH 21/32] added missing square bracket --- lib/presentation/views/main_menu/home_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1f8240d..680afde 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -222,6 +222,7 @@ class _HomeViewState extends State { ], ), ), + ], ), ), ); -- 2.49.1 From f22595e6782e3ebff54e121befe5073bb1e92860 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:44:43 +0100 Subject: [PATCH 22/32] Add localization for 'none_group' option in English and German --- lib/l10n/arb/app_de.arb | 3 ++- lib/l10n/arb/app_en.arb | 4 ++++ lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 5 ++++- lib/l10n/generated/app_localizations_en.dart | 3 +++ .../match_view/create_match/create_match_view.dart | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index c3605da..26ed145 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -55,7 +55,8 @@ "game": "Spiel", "ruleset": "Regelwerk", "group": "Gruppe", - "none": "Keine", + "none": "Kein", + "none_group": "Keine", "create_match": "Match erstellen", "no_players_created_yet": "Noch keine Spieler erstellt", "all_players_selected": "Alle Spieler ausgewählt", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a16d327..0a68994 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -256,6 +256,10 @@ "@none": { "description": "None option label" }, + "none_group": "None", + "@none_group": { + "description": "None group option label" + }, "create_match": "Create match", "@create_match": { "description": "Button text to create a match" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index c7036d8..5152962 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -446,6 +446,12 @@ abstract class AppLocalizations { /// **'None'** String get none; + /// None group option label + /// + /// In en, this message translates to: + /// **'None'** + String get none_group; + /// Button text to create a match /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 247e3e3..a270c82 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -195,7 +195,10 @@ class AppLocalizationsDe extends AppLocalizations { String get group => 'Gruppe'; @override - String get none => 'Keine'; + String get none => 'Kein'; + + @override + String get none_group => 'Keine'; @override String get create_match => 'Match erstellen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index a4f62e0..63a7d0e 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -196,6 +196,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get none => 'None'; + @override + String get none_group => 'None'; + @override String get create_match => 'Create match'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 5e35f41..b72a4fa 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -187,7 +187,7 @@ class _CreateMatchViewState extends State { ChooseTile( title: AppLocalizations.of(context)!.group, trailingText: selectedGroup == null - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context)!.none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( -- 2.49.1 From 3c22b084d63ece1fce8e512a1c72dc3882f65a45 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 19:01:44 +0100 Subject: [PATCH 23/32] made getters non nullable and removed all null assertion operators --- l10n.yaml | 3 +- lib/core/enums.dart | 8 ++-- lib/l10n/generated/app_localizations.dart | 4 +- .../main_menu/custom_navigation_bar.dart | 16 ++++---- .../group_view/create_group_view.dart | 6 +-- .../main_menu/group_view/groups_view.dart | 6 +-- .../views/main_menu/home_view.dart | 12 +++--- .../create_match/choose_game_view.dart | 4 +- .../create_match/choose_group_view.dart | 10 ++--- .../create_match/choose_ruleset_view.dart | 2 +- .../create_match/create_match_view.dart | 24 ++++++------ .../match_view/match_result_view.dart | 2 +- .../main_menu/match_view/match_view.dart | 6 +-- .../views/main_menu/settings_view.dart | 38 +++++++++---------- .../views/main_menu/statistics_view.dart | 10 ++--- .../widgets/player_selection.dart | 14 +++---- .../widgets/tiles/match_tile.dart | 6 +-- .../widgets/tiles/statistics_tile.dart | 2 +- 18 files changed, 87 insertions(+), 86 deletions(-) diff --git a/l10n.yaml b/l10n.yaml index f5730dc..f7805c8 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,5 @@ arb-dir: lib/l10n/arb template-arb-file: app_en.arb output-localization-file: app_localizations.dart -output-dir: lib/l10n/generated \ No newline at end of file +output-dir: lib/l10n/generated +nullable-getter: false \ No newline at end of file diff --git a/lib/core/enums.dart b/lib/core/enums.dart index fc1ac91..74ae023 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -37,12 +37,12 @@ enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } String translateRulesetToString(Ruleset ruleset, BuildContext context) { switch (ruleset) { case Ruleset.singleWinner: - return AppLocalizations.of(context)!.single_winner; + return AppLocalizations.of(context).single_winner; case Ruleset.singleLoser: - return AppLocalizations.of(context)!.single_loser; + return AppLocalizations.of(context).single_loser; case Ruleset.mostPoints: - return AppLocalizations.of(context)!.most_points; + return AppLocalizations.of(context).most_points; case Ruleset.leastPoints: - return AppLocalizations.of(context)!.least_points; + return AppLocalizations.of(context).least_points; } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 5152962..e3acecb 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -67,8 +67,8 @@ abstract class AppLocalizations { final String localeName; - static AppLocalizations? of(BuildContext context) { - return Localizations.of(context, AppLocalizations); + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations)!; } static const LocalizationsDelegate delegate = diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 980bf1a..2faef8b 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -90,28 +90,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: AppLocalizations.of(context)!.home, + label: AppLocalizations.of(context).home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: AppLocalizations.of(context)!.matches, + label: AppLocalizations.of(context).matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: AppLocalizations.of(context)!.groups, + label: AppLocalizations.of(context).groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: AppLocalizations.of(context)!.statistics, + label: AppLocalizations.of(context).statistics, onTabTapped: onTabTapped, ), ], @@ -132,13 +132,13 @@ class _CustomNavigationBarState extends State String _currentTabTitle() { switch (currentIndex) { case 0: - return AppLocalizations.of(context)!.home; + return AppLocalizations.of(context).home; case 1: - return AppLocalizations.of(context)!.matches; + return AppLocalizations.of(context).matches; case 2: - return AppLocalizations.of(context)!.groups; + return AppLocalizations.of(context).groups; case 3: - return AppLocalizations.of(context)!.statistics; + return AppLocalizations.of(context).statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index e9e5fe1..eddcf6d 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -45,7 +45,7 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context)!.create_new_group, + AppLocalizations.of(context).create_new_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -58,7 +58,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: AppLocalizations.of(context)!.group_name, + hintText: AppLocalizations.of(context).group_name, onChanged: (value) { setState(() {}); }, @@ -74,7 +74,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context)!.create_group, + text: AppLocalizations.of(context).create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 0e9ddbb..4d42417 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -53,8 +53,8 @@ class _GroupsViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_groups_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_groups_created_yet, ), ), child: ListView.builder( @@ -74,7 +74,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context)!.create_group, + text: AppLocalizations.of(context).create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3fe85fd..e1dd9f8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -87,7 +87,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context)!.matches, + title: AppLocalizations.of(context).matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -95,7 +95,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context)!.groups, + title: AppLocalizations.of(context).groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -105,7 +105,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context)!.recent_matches, + title: AppLocalizations.of(context).recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -125,7 +125,7 @@ class _HomeViewState extends State { children: [ MatchSummaryTile( matchTitle: recentMatches[0].name, - game: AppLocalizations.of(context)!.winner_label, + game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, )!.ruleset_label, @@ -175,7 +175,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context)!.quick_create, + title: AppLocalizations.of(context).quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -232,7 +232,7 @@ class _HomeViewState extends State { String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return AppLocalizations.of(context)!.players_count(playerCount); + return AppLocalizations.of(context).players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 6a75ae1..8e40ab8 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -43,7 +43,7 @@ class _ChooseGameViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_game, + AppLocalizations.of(context).choose_game, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -54,7 +54,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: AppLocalizations.of(context)!.game_name, + hintText: AppLocalizations.of(context).game_name, ), ), const SizedBox(height: 5), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index bfc0fed..83997e7 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -52,7 +52,7 @@ class _ChooseGroupViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_group, + AppLocalizations.of(context).choose_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context)!.search_for_groups, + hintText: AppLocalizations.of(context).search_for_groups, onChanged: (value) { setState(() { filterGroups(value); @@ -78,12 +78,12 @@ class _ChooseGroupViewState extends State { visible: widget.groups.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_groups_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_groups_created_yet, ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, )!.there_is_no_group_matching_your_search, diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index a6129cc..9383be2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -48,7 +48,7 @@ class _ChooseRulesetViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_ruleset, + AppLocalizations.of(context).choose_ruleset, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 63da16d..03aa6c0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -91,19 +91,19 @@ class _CreateMatchViewState extends State { return [ ( Ruleset.singleWinner, - AppLocalizations.of(context)!.ruleset_single_winner_desc, + AppLocalizations.of(context).ruleset_single_winner_desc, ), ( Ruleset.singleLoser, - AppLocalizations.of(context)!.ruleset_single_loser_desc, + AppLocalizations.of(context).ruleset_single_loser_desc, ), ( Ruleset.mostPoints, - AppLocalizations.of(context)!.ruleset_most_points_desc, + AppLocalizations.of(context).ruleset_most_points_desc, ), ( Ruleset.leastPoints, - AppLocalizations.of(context)!.ruleset_least_points_desc, + AppLocalizations.of(context).ruleset_least_points_desc, ), ]; } @@ -122,7 +122,7 @@ class _CreateMatchViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context)!.create_new_match, + AppLocalizations.of(context).create_new_match, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -139,9 +139,9 @@ class _CreateMatchViewState extends State { ), ), ChooseTile( - title: AppLocalizations.of(context)!.game, + title: AppLocalizations.of(context).game, trailingText: selectedGameIndex == -1 - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context).none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -167,9 +167,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context)!.ruleset, + title: AppLocalizations.of(context).ruleset, trailingText: selectedRuleset == null - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context).none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { final rulesets = _getRulesets(context); @@ -190,9 +190,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context)!.group, + title: AppLocalizations.of(context).group, trailingText: selectedGroup == null - ? AppLocalizations.of(context)!.none_group + ? AppLocalizations.of(context).none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -229,7 +229,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context)!.create_match, + text: AppLocalizations.of(context).create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index f13ef87..e99ce85 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -81,7 +81,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context)!.select_winner, + AppLocalizations.of(context).select_winner, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 30804de..2b1b110 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -62,8 +62,8 @@ class _MatchViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_matches_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_matches_created_yet, ), ), child: ListView.builder( @@ -97,7 +97,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context)!.create_match, + text: AppLocalizations.of(context).create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 8899e40..3ab401f 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -29,7 +29,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context)!.menu, + AppLocalizations.of(context).menu, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -43,7 +43,7 @@ class _SettingsViewState extends State { ), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context)!.settings, + AppLocalizations.of(context).settings, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -51,7 +51,7 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: AppLocalizations.of(context)!.export_data, + title: AppLocalizations.of(context).export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -66,7 +66,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context)!.import_data, + title: AppLocalizations.of(context).import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -78,7 +78,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context)!.delete_all_data, + title: AppLocalizations.of(context).delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { @@ -86,19 +86,19 @@ class _SettingsViewState extends State { context: context, builder: (context) => AlertDialog( title: Text( - AppLocalizations.of(context)!.delete_all_data, + AppLocalizations.of(context).delete_all_data, ), content: Text( - AppLocalizations.of(context)!.this_cannot_be_undone, + AppLocalizations.of(context).this_cannot_be_undone, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(AppLocalizations.of(context)!.cancel), + child: Text(AppLocalizations.of(context).cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(AppLocalizations.of(context)!.delete), + child: Text(AppLocalizations.of(context).delete), ), ], ), @@ -134,32 +134,32 @@ class _SettingsViewState extends State { case ImportResult.success: showSnackbar( context: context, - message: AppLocalizations.of(context)!.data_successfully_imported, + message: AppLocalizations.of(context).data_successfully_imported, ); case ImportResult.invalidSchema: showSnackbar( context: context, - message: AppLocalizations.of(context)!.invalid_schema, + message: AppLocalizations.of(context).invalid_schema, ); case ImportResult.fileReadError: showSnackbar( context: context, - message: AppLocalizations.of(context)!.error_reading_file, + message: AppLocalizations.of(context).error_reading_file, ); case ImportResult.canceled: showSnackbar( context: context, - message: AppLocalizations.of(context)!.import_canceled, + message: AppLocalizations.of(context).import_canceled, ); case ImportResult.formatException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.format_exception, + message: AppLocalizations.of(context).format_exception, ); case ImportResult.unknownException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.unknown_exception, + message: AppLocalizations.of(context).unknown_exception, ); } } @@ -176,17 +176,17 @@ class _SettingsViewState extends State { case ExportResult.success: showSnackbar( context: context, - message: AppLocalizations.of(context)!.data_successfully_exported, + message: AppLocalizations.of(context).data_successfully_exported, ); case ExportResult.canceled: showSnackbar( context: context, - message: AppLocalizations.of(context)!.export_canceled, + message: AppLocalizations.of(context).export_canceled, ); case ExportResult.unknownException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.unknown_exception, + message: AppLocalizations.of(context).unknown_exception, ); } } @@ -212,7 +212,7 @@ class _SettingsViewState extends State { duration: duration, action: action != null ? SnackBarAction( - label: AppLocalizations.of(context)!.undo, + label: AppLocalizations.of(context).undo, onPressed: action, ) : null, diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index ed90a84..d752119 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: AppLocalizations.of(context)!.wins, + title: AppLocalizations.of(context).wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: AppLocalizations.of(context)!.winrate, + title: AppLocalizations.of(context).winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -99,7 +99,7 @@ class _StatisticsViewState extends State { ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, )!.no_statistics_available, @@ -154,7 +154,7 @@ class _StatisticsViewState extends State { (p) => p.id == playerId, orElse: () => Player( id: playerId, - name: AppLocalizations.of(context)!.not_available, + name: AppLocalizations.of(context).not_available, ), ); winCounts[i] = (player.name, winCounts[i].$2); @@ -219,7 +219,7 @@ class _StatisticsViewState extends State { (p) => p.id == playerId, orElse: () => Player( id: playerId, - name: AppLocalizations.of(context)!.not_available, + name: AppLocalizations.of(context).not_available, ), ); matchCounts[i] = (player.name, matchCounts[i].$2); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index dc8731c..d33d83e 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -97,7 +97,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: AppLocalizations.of(context)!.search_for_players, + hintText: AppLocalizations.of(context).search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, @@ -141,7 +141,7 @@ class _PlayerSelectionState extends State { child: selectedPlayers.isEmpty ? Center( child: Text( - AppLocalizations.of(context)!.no_players_selected, + AppLocalizations.of(context).no_players_selected, ), ) : SingleChildScrollView( @@ -185,7 +185,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of(context)!.all_players, + AppLocalizations.of(context).all_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -199,11 +199,11 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: allPlayers.isEmpty - ? AppLocalizations.of(context)!.no_players_created_yet + ? AppLocalizations.of(context).no_players_created_yet : (selectedPlayers.length == allPlayers.length) - ? AppLocalizations.of(context)!.all_players_selected + ? AppLocalizations.of(context).all_players_selected : AppLocalizations.of( context, )!.no_players_found_with_that_name, @@ -276,7 +276,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - AppLocalizations.of(context)!.could_not_add_player(playerName), + AppLocalizations.of(context).could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 9eedec6..93e6f32 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -98,7 +98,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - AppLocalizations.of(context)!.winner(winner.name), + AppLocalizations.of(context).winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -115,7 +115,7 @@ class _MatchTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( - AppLocalizations.of(context)!.players, + AppLocalizations.of(context).players, style: const TextStyle( fontSize: 13, color: Colors.grey, @@ -150,7 +150,7 @@ class _MatchTileState extends State { context, )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return AppLocalizations.of(context)!.days_ago(difference.inDays); + return AppLocalizations.of(context).days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index e783ab4..8d81270 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -36,7 +36,7 @@ class StatisticsTile extends StatelessWidget { visible: values.isNotEmpty, replacement: Center( heightFactor: 4, - child: Text(AppLocalizations.of(context)!.no_data_available), + child: Text(AppLocalizations.of(context).no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { -- 2.49.1 From 22fcff73bd3aa0ec2b3e4c1239c1fe6716860a8e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:11:37 +0100 Subject: [PATCH 24/32] configure en locale as fallback --- lib/main.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 0f3b6b0..a232256 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,9 +20,21 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { + print(AppLocalizations.supportedLocales.first); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, + localeResolutionCallback: (locale, supportedLocales) { + for (final supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return supportedLocales.firstWhere( + (locale) => locale.languageCode == 'en', + orElse: () => supportedLocales.first, + ); + }, debugShowCheckedModeBanner: false, title: 'Game Tracker', darkTheme: ThemeData.dark(), -- 2.49.1 From a038c22ba69d78ad50d4bd6460e55730c9c838ff Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:13:00 +0100 Subject: [PATCH 25/32] removed else in fallback --- lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index a232256..8ef243a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -32,7 +32,6 @@ class GameTracker extends StatelessWidget { } return supportedLocales.firstWhere( (locale) => locale.languageCode == 'en', - orElse: () => supportedLocales.first, ); }, debugShowCheckedModeBanner: false, -- 2.49.1 From 678ab90af37e73f7c901684f2a7b427aa49eba57 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:16:13 +0100 Subject: [PATCH 26/32] removed unneccessary null assertion operators --- .../main_menu/group_view/create_group_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 16 +++++++--------- .../create_match/choose_group_view.dart | 2 +- .../views/main_menu/settings_view.dart | 2 +- .../views/main_menu/statistics_view.dart | 6 ++---- lib/presentation/widgets/player_selection.dart | 6 +++--- lib/presentation/widgets/tiles/match_tile.dart | 4 ++-- 7 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index eddcf6d..0d8c28f 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -99,7 +99,7 @@ class _CreateGroupViewState extends State { child: Text( AppLocalizations.of( context, - )!.error_while_creating_group_please_try_again, + ).error_while_creating_group_please_try_again, style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index e1dd9f8..6446ea8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -116,7 +116,7 @@ class _HomeViewState extends State { child: Text( AppLocalizations.of( context, - )!.no_recent_matches_available, + ).no_recent_matches_available, ), ), child: Column( @@ -128,12 +128,12 @@ class _HomeViewState extends State { game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, - )!.ruleset_label, + ).ruleset_label, players: _getPlayerText(recentMatches[0]), winner: recentMatches[0].winner == null ? AppLocalizations.of( context, - )!.match_in_progress + ).match_in_progress : recentMatches[0].winner!.name, ), const Padding( @@ -143,17 +143,15 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchSummaryTile( matchTitle: recentMatches[1].name, - game: AppLocalizations.of( - context, - )!.winner_label, + game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, - )!.ruleset_label, + ).ruleset_label, players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null ? AppLocalizations.of( context, - )!.match_in_progress + ).match_in_progress : recentMatches[1].winner!.name, ), const SizedBox(height: 8), @@ -163,7 +161,7 @@ class _HomeViewState extends State { child: Text( AppLocalizations.of( context, - )!.no_second_match_available, + ).no_second_match_available, ), ), ], diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 83997e7..d05fff9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -86,7 +86,7 @@ class _ChooseGroupViewState extends State { title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, - )!.there_is_no_group_matching_your_search, + ).there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 3ab401f..c554950 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -109,7 +109,7 @@ class _SettingsViewState extends State { context: context, message: AppLocalizations.of( context, - )!.data_successfully_deleted, + ).data_successfully_deleted, ); } }); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index d752119..43de037 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -87,9 +87,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: AppLocalizations.of( - context, - )!.amount_of_matches, + title: AppLocalizations.of(context).amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -102,7 +100,7 @@ class _StatisticsViewState extends State { title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, - )!.no_statistics_available, + ).no_statistics_available, ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d33d83e..af95081 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -132,7 +132,7 @@ class _PlayerSelectionState extends State { Text( AppLocalizations.of( context, - )!.selected_players(selectedPlayers.length), + ).selected_players(selectedPlayers.length), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -206,7 +206,7 @@ class _PlayerSelectionState extends State { ? AppLocalizations.of(context).all_players_selected : AppLocalizations.of( context, - )!.no_players_found_with_that_name, + ).no_players_found_with_that_name, ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -264,7 +264,7 @@ class _PlayerSelectionState extends State { child: Text( AppLocalizations.of( context, - )!.successfully_added_player(playerName), + ).successfully_added_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 93e6f32..7727827 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -144,11 +144,11 @@ class _MatchTileState extends State { if (difference.inDays == 0) { return AppLocalizations.of( context, - )!.today_at(DateFormat('HH:mm').format(dateTime)); + ).today_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays == 1) { return AppLocalizations.of( context, - )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); + ).yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { return AppLocalizations.of(context).days_ago(difference.inDays); } else { -- 2.49.1 From 77095725de452c62f1cf994d8c133c491c18b569 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:26:54 +0100 Subject: [PATCH 27/32] fix game choose view highlighting not working --- .../main_menu/match_view/create_match/choose_game_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 8e40ab8..8e6eceb 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -69,6 +69,7 @@ class _ChooseGameViewState extends State { widget.games[index].$3, context, ), + isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { if (selectedGameIndex == index) { -- 2.49.1 From 132966f3d26145cbf4337818087b6bd13db4f96a Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:28:46 +0100 Subject: [PATCH 28/32] increase title_description_list_tile width to make german translation fully visible --- lib/presentation/widgets/tiles/title_description_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 7a138a0..465c94d 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -54,7 +54,7 @@ class TitleDescriptionListTile extends StatelessWidget { if (badgeText != null) ...[ const Spacer(), Container( - constraints: const BoxConstraints(maxWidth: 100), + constraints: const BoxConstraints(maxWidth: 115), margin: const EdgeInsets.only(top: 4), padding: const EdgeInsets.symmetric( vertical: 2, -- 2.49.1 From ec94e12ed7793e230b543c3e2f38ee099a0fe68e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 15:38:25 +0100 Subject: [PATCH 29/32] implement changes --- lib/core/enums.dart | 9 ++- lib/l10n/arb/app_de.arb | 48 ++++++----- lib/l10n/arb/app_en.arb | 32 ++++---- lib/l10n/generated/app_localizations.dart | 28 +++---- lib/l10n/generated/app_localizations_de.dart | 63 +++++++-------- lib/l10n/generated/app_localizations_en.dart | 19 ++--- lib/main.dart | 3 +- .../main_menu/custom_navigation_bar.dart | 27 +++---- .../group_view/create_group_view.dart | 11 ++- .../main_menu/group_view/groups_view.dart | 8 +- .../views/main_menu/home_view.dart | 32 ++++---- .../create_match/choose_game_view.dart | 12 +-- .../create_match/choose_group_view.dart | 15 ++-- .../create_match/choose_ruleset_view.dart | 9 ++- .../create_match/create_match_view.dart | 38 ++++----- .../match_view/match_result_view.dart | 3 +- .../main_menu/match_view/match_view.dart | 8 +- .../views/main_menu/settings_view.dart | 81 +++++++------------ .../views/main_menu/statistics_view.dart | 41 +++++----- .../widgets/player_selection.dart | 27 +++---- .../widgets/tiles/match_tile.dart | 12 +-- .../widgets/tiles/statistics_tile.dart | 3 +- 22 files changed, 247 insertions(+), 282 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 74ae023..ce06f85 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -35,14 +35,15 @@ enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } /// Translates a [Ruleset] enum value to its corresponding localized string. String translateRulesetToString(Ruleset ruleset, BuildContext context) { + final loc = AppLocalizations.of(context); switch (ruleset) { case Ruleset.singleWinner: - return AppLocalizations.of(context).single_winner; + return loc.single_winner; case Ruleset.singleLoser: - return AppLocalizations.of(context).single_loser; + return loc.single_loser; case Ruleset.mostPoints: - return AppLocalizations.of(context).most_points; + return loc.most_points; case Ruleset.leastPoints: - return AppLocalizations.of(context).least_points; + return loc.least_points; } } diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 26ed145..d8a9e7e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -3,30 +3,28 @@ "choose_group": "Gruppe wählen", "create_new_match": "Neues Match erstellen", "choose_ruleset": "Regelwerk wählen", - "choose_game": "Spiel wählen", - "select_winner": "Gewinner wählen:", + "choose_game": "Spielvorlage wählen", + "select_winner": "Gewinner:in wählen:", "no_recent_matches_available": "Keine letzten Matches verfügbar", "no_second_match_available": "Kein zweites Match verfügbar", "delete_all_data": "Alle Daten löschen?", "cancel": "Abbrechen", "delete": "Löschen", "create_new_group": "Neue Gruppe erstellen", - "error_while_creating_group_please_try_again": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", - "selected_players": "Ausgewählte Spieler: {count}", - "no_players_selected": "Keine Spieler ausgewählt", - "all_players": "Alle Spieler:", - "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt", - "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden", - "winner": "Gewinner: {winnerName}", - "players": "Spieler", - "player_name": "Spielername", + "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "selected_players": "Ausgewählte Spieler:in: {count}", + "no_players_selected": "Keine Spieler:in ausgewählt", + "all_players": "Alle Spieler:innen:", + "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", + "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "winner": "Gewinner:in: {winnerName}", + "players": "Spieler:in", + "player_name": "Spieler:innenname", "no_statistics_available": "Keine Statistiken verfügbar", "matches": "Matches", "groups": "Gruppen", "recent_matches": "Letzte Matches", "quick_create": "Schnellzugriff", - "winner_label": "Gewinner", - "ruleset_label": "Regelwerk", "match_in_progress": "Match läuft...", "menu": "Menü", "settings": "Einstellungen", @@ -52,15 +50,15 @@ "group_name": "Gruppenname", "no_matches_created_yet": "Noch keine Matches erstellt", "match_name": "Matchname", - "game": "Spiel", + "game": "Spielvorlage", "ruleset": "Regelwerk", "group": "Gruppe", "none": "Kein", "none_group": "Keine", "create_match": "Match erstellen", - "no_players_created_yet": "Noch keine Spieler erstellt", - "all_players_selected": "Alle Spieler ausgewählt", - "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", + "no_players_created_yet": "Noch keine Spieler:in erstellt", + "all_players_selected": "Alle Spieler:innen ausgewählt", + "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "today_at": "Heute um {time}", "yesterday_at": "Gestern um {time}", "days_ago": "vor {count} Tagen", @@ -69,16 +67,16 @@ "stats": "Statistiken", "players_count": "{count} Spieler", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", - "game_name": "Spielname", - "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", - "ruleset_single_loser_desc": "Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", - "ruleset_most_points_desc": "Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.", - "ruleset_least_points_desc": "Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.", - "single_winner": "Ein Gewinner", - "single_loser": "Ein Verlierer", + "game_name": "Spielvorlagenname", + "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", + "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", + "single_winner": "Ein:e Gewinner:in", + "single_loser": "Ein:e Verlierer:in", "most_points": "Höchste Punkte", "least_points": "Niedrigste Punkte", - "search_for_players": "Nach Spielern suchen", + "search_for_players": "Nach Spieler:innen suchen", "search_for_groups": "Nach Gruppen suchen", "no_data_available": "Keine Daten verfügbar", "not_available": "Nicht verfügbar" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 0a68994..8e6d63a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -20,6 +20,10 @@ "@select_winner": { "description": "Label to select the winner" }, + "game_tracker": "Game Tracker", + "@game_tracker": { + "description": "App Name" + }, "no_recent_matches_available": "No recent matches available", "@no_recent_matches_available": { "description": "Message when no recent matches exist" @@ -44,8 +48,8 @@ "@create_new_group": { "description": "Button text to create a new group" }, - "error_while_creating_group_please_try_again": "Error while creating group, please try again", - "@error_while_creating_group_please_try_again": { + "error_creating_group": "Error while creating group, please try again", + "@error_creating_group": { "description": "Error message when group creation fails" }, "selected_players": "Selected players: {count}", @@ -124,14 +128,6 @@ "@quick_create": { "description": "Title for quick create section" }, - "winner_label": "Winner", - "@winner_label": { - "description": "Label for winner field" - }, - "ruleset_label": "Ruleset", - "@ruleset_label": { - "description": "Label for ruleset field" - }, "match_in_progress": "Match in progress...", "@match_in_progress": { "description": "Message when match is in progress" @@ -330,20 +326,20 @@ "@game_name": { "description": "Placeholder for game name search" }, - "ruleset_single_winner_desc": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "@ruleset_single_winner_desc": { + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "@ruleset_single_winner": { "description": "Description for single winner ruleset" }, - "ruleset_single_loser_desc": "Exactly one loser is determined; last place receives the penalty or consequence.", - "@ruleset_single_loser_desc": { + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "@ruleset_single_loser": { "description": "Description for single loser ruleset" }, - "ruleset_most_points_desc": "Traditional ruleset: the player with the most points wins.", - "@ruleset_most_points_desc": { + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "@ruleset_most_points": { "description": "Description for most points ruleset" }, - "ruleset_least_points_desc": "Inverse scoring: the player with the fewest points wins.", - "@ruleset_least_points_desc": { + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "@ruleset_least_points": { "description": "Description for least points ruleset" }, "single_winner": "Single Winner", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index e3acecb..951ff22 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -128,6 +128,12 @@ abstract class AppLocalizations { /// **'Select Winner:'** String get select_winner; + /// App Name + /// + /// In en, this message translates to: + /// **'Game Tracker'** + String get game_tracker; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -168,7 +174,7 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Error while creating group, please try again'** - String get error_while_creating_group_please_try_again; + String get error_creating_group; /// Shows the number of selected players /// @@ -248,18 +254,6 @@ abstract class AppLocalizations { /// **'Quick Create'** String get quick_create; - /// Label for winner field - /// - /// In en, this message translates to: - /// **'Winner'** - String get winner_label; - - /// Label for ruleset field - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset_label; - /// Message when match is in progress /// /// In en, this message translates to: @@ -528,25 +522,25 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner_desc; + String get ruleset_single_winner; /// Description for single loser ruleset /// /// In en, this message translates to: /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser_desc; + String get ruleset_single_loser; /// Description for most points ruleset /// /// In en, this message translates to: /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points_desc; + String get ruleset_most_points; /// Description for least points ruleset /// /// In en, this message translates to: /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points_desc; + String get ruleset_least_points; /// Title for single winner ruleset /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index a270c82..3f3e36e 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -18,10 +18,13 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_ruleset => 'Regelwerk wählen'; @override - String get choose_game => 'Spiel wählen'; + String get choose_game => 'Spielvorlage wählen'; @override - String get select_winner => 'Gewinner wählen:'; + String get select_winner => 'Gewinner:in wählen:'; + + @override + String get game_tracker => 'Game Tracker'; @override String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; @@ -42,7 +45,7 @@ class AppLocalizationsDe extends AppLocalizations { String get create_new_group => 'Neue Gruppe erstellen'; @override - String get error_while_creating_group_please_try_again => + String get error_creating_group => 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @override @@ -52,32 +55,32 @@ class AppLocalizationsDe extends AppLocalizations { ); final String countString = countNumberFormat.format(count); - return 'Ausgewählte Spieler: $countString'; + return 'Ausgewählte Spieler:in: $countString'; } @override - String get no_players_selected => 'Keine Spieler ausgewählt'; + String get no_players_selected => 'Keine Spieler:in ausgewählt'; @override - String get all_players => 'Alle Spieler:'; + String get all_players => 'Alle Spieler:innen:'; @override String successfully_added_player(String playerName) { - return 'Spieler $playerName erfolgreich hinzugefügt'; + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override String could_not_add_player(String playerName) { - return 'Spieler $playerName konnte nicht hinzugefügt werden'; + return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @override String winner(String winnerName) { - return 'Gewinner: $winnerName'; + return 'Gewinner:in: $winnerName'; } @override - String get players => 'Spieler'; + String get players => 'Spieler:in'; @override String get no_statistics_available => 'Keine Statistiken verfügbar'; @@ -97,12 +100,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get quick_create => 'Schnellzugriff'; - @override - String get winner_label => 'Gewinner'; - - @override - String get ruleset_label => 'Regelwerk'; - @override String get match_in_progress => 'Match läuft...'; @@ -168,7 +165,7 @@ class AppLocalizationsDe extends AppLocalizations { String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; @override - String get no_players_created_yet => 'Noch keine Spieler erstellt'; + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @override String get create_group => 'Gruppe erstellen'; @@ -177,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations { String get group_name => 'Gruppenname'; @override - String get player_name => 'Spielername'; + String get player_name => 'Spieler:innenname'; @override String get no_matches_created_yet => 'Noch keine Matches erstellt'; @@ -186,7 +183,7 @@ class AppLocalizationsDe extends AppLocalizations { String get match_name => 'Matchname'; @override - String get game => 'Spiel'; + String get game => 'Spielvorlage'; @override String get ruleset => 'Regelwerk'; @@ -205,10 +202,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_players_found_with_that_name => - 'Keine Spieler mit diesem Namen gefunden'; + 'Keine Spieler:in mit diesem Namen gefunden'; @override - String get all_players_selected => 'Alle Spieler ausgewählt'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override String today_at(String time) { @@ -244,29 +241,29 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielname'; + String get game_name => 'Spielvorlagenname'; @override - String get ruleset_single_winner_desc => - 'Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; @override - String get ruleset_single_loser_desc => - 'Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; @override - String get ruleset_most_points_desc => - 'Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.'; + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; @override - String get ruleset_least_points_desc => - 'Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.'; + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; @override - String get single_winner => 'Ein Gewinner'; + String get single_winner => 'Ein:e Gewinner:in'; @override - String get single_loser => 'Ein Verlierer'; + String get single_loser => 'Ein:e Verlierer:in'; @override String get most_points => 'Höchste Punkte'; @@ -275,7 +272,7 @@ class AppLocalizationsDe extends AppLocalizations { String get least_points => 'Niedrigste Punkte'; @override - String get search_for_players => 'Nach Spielern suchen'; + String get search_for_players => 'Nach Spieler:innen suchen'; @override String get search_for_groups => 'Nach Gruppen suchen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 63a7d0e..263714c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -23,6 +23,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select_winner => 'Select Winner:'; + @override + String get game_tracker => 'Game Tracker'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -42,7 +45,7 @@ class AppLocalizationsEn extends AppLocalizations { String get create_new_group => 'Create new group'; @override - String get error_while_creating_group_please_try_again => + String get error_creating_group => 'Error while creating group, please try again'; @override @@ -97,12 +100,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get quick_create => 'Quick Create'; - @override - String get winner_label => 'Winner'; - - @override - String get ruleset_label => 'Ruleset'; - @override String get match_in_progress => 'Match in progress...'; @@ -246,19 +243,19 @@ class AppLocalizationsEn extends AppLocalizations { String get game_name => 'Game Name'; @override - String get ruleset_single_winner_desc => + String get ruleset_single_winner => 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; @override - String get ruleset_single_loser_desc => + String get ruleset_single_loser => 'Exactly one loser is determined; last place receives the penalty or consequence.'; @override - String get ruleset_most_points_desc => + String get ruleset_most_points => 'Traditional ruleset: the player with the most points wins.'; @override - String get ruleset_least_points_desc => + String get ruleset_least_points => 'Inverse scoring: the player with the fewest points wins.'; @override diff --git a/lib/main.dart b/lib/main.dart index 8ef243a..c1ed977 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,6 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { - print(AppLocalizations.supportedLocales.first); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, @@ -35,7 +34,7 @@ class GameTracker extends StatelessWidget { ); }, debugShowCheckedModeBanner: false, - title: 'Game Tracker', + onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, darkTheme: ThemeData.dark(), themeMode: ThemeMode.dark, // forces dark mode diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2faef8b..1e38808 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -20,13 +20,9 @@ class _CustomNavigationBarState extends State int currentIndex = 0; int tabKeyCount = 0; - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); // Pretty ugly but works final List tabs = [ KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), @@ -47,7 +43,7 @@ class _CustomNavigationBarState extends State appBar: AppBar( centerTitle: true, title: Text( - _currentTabTitle(), + _currentTabTitle(context), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), backgroundColor: CustomTheme.backgroundColor, @@ -90,28 +86,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: AppLocalizations.of(context).home, + label: loc.home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: AppLocalizations.of(context).matches, + label: loc.matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: AppLocalizations.of(context).groups, + label: loc.groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: AppLocalizations.of(context).statistics, + label: loc.statistics, onTabTapped: onTabTapped, ), ], @@ -129,16 +125,17 @@ class _CustomNavigationBarState extends State }); } - String _currentTabTitle() { + String _currentTabTitle(context) { + final loc = AppLocalizations.of(context); switch (currentIndex) { case 0: - return AppLocalizations.of(context).home; + return loc.home; case 1: - return AppLocalizations.of(context).matches; + return loc.matches; case 2: - return AppLocalizations.of(context).groups; + return loc.groups; case 3: - return AppLocalizations.of(context).statistics; + return loc.statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 0d8c28f..cba22ef 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -20,11 +20,13 @@ class CreateGroupView extends StatefulWidget { class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); late final AppDatabase db; + List selectedPlayers = []; @override void initState() { super.initState(); + db = Provider.of(context, listen: false); _groupNameController.addListener(() { setState(() {}); @@ -39,13 +41,14 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context).create_new_group, + loc.create_new_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -58,7 +61,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: AppLocalizations.of(context).group_name, + hintText: loc.group_name, onChanged: (value) { setState(() {}); }, @@ -74,7 +77,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context).create_group, + text: loc.create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -99,7 +102,7 @@ class _CreateGroupViewState extends State { child: Text( AppLocalizations.of( context, - ).error_while_creating_group_please_try_again, + ).error_creating_group, style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 4d42417..3505a3c 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -35,12 +35,14 @@ class _GroupsViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); loadGroups(); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( @@ -53,8 +55,8 @@ class _GroupsViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_groups_created_yet, + title: loc.info, + message: loc.no_groups_created_yet, ), ), child: ListView.builder( @@ -74,7 +76,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context).create_group, + text: loc.create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 6446ea8..96280ce 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -72,6 +72,7 @@ class _HomeViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return AppSkeleton( @@ -87,7 +88,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context).matches, + title: loc.matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -95,7 +96,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context).groups, + title: loc.groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -105,7 +106,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context).recent_matches, + title: loc.recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -125,11 +126,12 @@ class _HomeViewState extends State { children: [ MatchSummaryTile( matchTitle: recentMatches[0].name, - game: AppLocalizations.of(context).winner_label, - ruleset: AppLocalizations.of( + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText( + recentMatches[0], context, - ).ruleset_label, - players: _getPlayerText(recentMatches[0]), + ), winner: recentMatches[0].winner == null ? AppLocalizations.of( context, @@ -143,11 +145,12 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchSummaryTile( matchTitle: recentMatches[1].name, - game: AppLocalizations.of(context).winner_label, - ruleset: AppLocalizations.of( + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText( + recentMatches[1], context, - ).ruleset_label, - players: _getPlayerText(recentMatches[1]), + ), winner: recentMatches[1].winner == null ? AppLocalizations.of( context, @@ -173,7 +176,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context).quick_create, + title: loc.quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -227,10 +230,11 @@ class _HomeViewState extends State { ); } - String _getPlayerText(Match game) { + String _getPlayerText(Match game, context) { + final loc = AppLocalizations.of(context); if (game.group == null) { final playerCount = game.players?.length ?? 0; - return AppLocalizations.of(context).players_count(playerCount); + return loc.players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 5ed9d95..18e1e9d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -21,6 +21,7 @@ class ChooseGameView extends StatefulWidget { class _ChooseGameViewState extends State { late int selectedGameIndex; + final TextEditingController searchBarController = TextEditingController(); @override @@ -31,6 +32,7 @@ class _ChooseGameViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -43,7 +45,7 @@ class _ChooseGameViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_game, + loc.choose_game, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -64,7 +66,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: AppLocalizations.of(context).game_name, + hintText: loc.game_name, ), ), const SizedBox(height: 5), @@ -76,9 +78,9 @@ class _ChooseGameViewState extends State { title: widget.games[index].$1, description: widget.games[index].$2, badgeText: translateRulesetToString( - widget.games[index].$3, - context, - ), + widget.games[index].$3, + context, + ), isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 8a3f0f2..5101db6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -34,6 +34,7 @@ class _ChooseGroupViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -52,7 +53,7 @@ class _ChooseGroupViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_group, + loc.choose_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -79,7 +80,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context).search_for_groups, + hintText: loc.search_for_groups, onChanged: (value) { setState(() { filterGroups(value); @@ -94,15 +95,15 @@ class _ChooseGroupViewState extends State { visible: widget.groups.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_groups_created_yet, + title: loc.info, + message: loc.no_groups_created_yet, ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, + title: loc.info, message: AppLocalizations.of( - context, - ).there_is_no_group_matching_your_search, + context, + ).there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index f47b535..7a41417 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -29,6 +29,7 @@ class _ChooseRulesetViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return DefaultTabController( length: 2, initialIndex: 0, @@ -48,7 +49,7 @@ class _ChooseRulesetViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_ruleset, + loc.choose_ruleset, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -82,9 +83,9 @@ class _ChooseRulesetViewState extends State { }); }, title: translateRulesetToString( - widget.rulesets[index].$1, - context, - ), + widget.rulesets[index].$1, + context, + ), description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 77fa4e4..d3a23ae 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -90,23 +90,12 @@ class _CreateMatchViewState extends State { } List<(Ruleset, String)> _getRulesets(BuildContext context) { + final loc = AppLocalizations.of(context); return [ - ( - Ruleset.singleWinner, - AppLocalizations.of(context).ruleset_single_winner_desc, - ), - ( - Ruleset.singleLoser, - AppLocalizations.of(context).ruleset_single_loser_desc, - ), - ( - Ruleset.mostPoints, - AppLocalizations.of(context).ruleset_most_points_desc, - ), - ( - Ruleset.leastPoints, - AppLocalizations.of(context).ruleset_least_points_desc, - ), + (Ruleset.singleWinner, loc.ruleset_single_winner), + (Ruleset.singleLoser, loc.ruleset_single_loser), + (Ruleset.mostPoints, loc.ruleset_most_points), + (Ruleset.leastPoints, loc.ruleset_least_points), ]; } @@ -118,13 +107,14 @@ class _CreateMatchViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context).create_new_match, + loc.create_new_match, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -141,9 +131,9 @@ class _CreateMatchViewState extends State { ), ), ChooseTile( - title: AppLocalizations.of(context).game, + title: loc.game, trailingText: selectedGameIndex == -1 - ? AppLocalizations.of(context).none + ? loc.none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -169,9 +159,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context).ruleset, + title: loc.ruleset, trailingText: selectedRuleset == null - ? AppLocalizations.of(context).none + ? loc.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { final rulesets = _getRulesets(context); @@ -192,9 +182,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context).group, + title: loc.group, trailingText: selectedGroup == null - ? AppLocalizations.of(context).none_group + ? loc.none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -231,7 +221,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context).create_match, + text: loc.create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e99ce85..8e178bf 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -36,6 +36,7 @@ class _MatchResultViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -81,7 +82,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context).select_winner, + loc.select_winner, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 2b1b110..73f596f 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -44,12 +44,14 @@ class _MatchViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); loadGames(); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( @@ -62,8 +64,8 @@ class _MatchViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_matches_created_yet, + title: loc.info, + message: loc.no_matches_created_yet, ), ), child: ListView.builder( @@ -97,7 +99,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context).create_match, + text: loc.create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index c554950..8f1e68a 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -13,8 +13,14 @@ class SettingsView extends StatefulWidget { } class _SettingsViewState extends State { + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), backgroundColor: CustomTheme.backgroundColor, @@ -29,7 +35,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context).menu, + loc.menu, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -43,7 +49,7 @@ class _SettingsViewState extends State { ), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context).settings, + loc.settings, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -51,7 +57,7 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: AppLocalizations.of(context).export_data, + title: loc.export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -66,7 +72,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context).import_data, + title: loc.import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -78,27 +84,23 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context).delete_all_data, + title: loc.delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( - title: Text( - AppLocalizations.of(context).delete_all_data, - ), - content: Text( - AppLocalizations.of(context).this_cannot_be_undone, - ), + title: Text(loc.delete_all_data), + content: Text(loc.this_cannot_be_undone), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(AppLocalizations.of(context).cancel), + child: Text(loc.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(AppLocalizations.of(context).delete), + child: Text(loc.delete), ), ], ), @@ -130,37 +132,20 @@ class _SettingsViewState extends State { required BuildContext context, required ImportResult result, }) { + final loc = AppLocalizations.of(context); switch (result) { case ImportResult.success: - showSnackbar( - context: context, - message: AppLocalizations.of(context).data_successfully_imported, - ); + showSnackbar(context: context, message: loc.data_successfully_imported); case ImportResult.invalidSchema: - showSnackbar( - context: context, - message: AppLocalizations.of(context).invalid_schema, - ); + showSnackbar(context: context, message: loc.invalid_schema); case ImportResult.fileReadError: - showSnackbar( - context: context, - message: AppLocalizations.of(context).error_reading_file, - ); + showSnackbar(context: context, message: loc.error_reading_file); case ImportResult.canceled: - showSnackbar( - context: context, - message: AppLocalizations.of(context).import_canceled, - ); + showSnackbar(context: context, message: loc.import_canceled); case ImportResult.formatException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).format_exception, - ); + showSnackbar(context: context, message: loc.format_exception); case ImportResult.unknownException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).unknown_exception, - ); + showSnackbar(context: context, message: loc.unknown_exception); } } @@ -172,22 +157,14 @@ class _SettingsViewState extends State { required BuildContext context, required ExportResult result, }) { + final loc = AppLocalizations.of(context); switch (result) { case ExportResult.success: - showSnackbar( - context: context, - message: AppLocalizations.of(context).data_successfully_exported, - ); + showSnackbar(context: context, message: loc.data_successfully_exported); case ExportResult.canceled: - showSnackbar( - context: context, - message: AppLocalizations.of(context).export_canceled, - ); + showSnackbar(context: context, message: loc.export_canceled); case ExportResult.unknownException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).unknown_exception, - ); + showSnackbar(context: context, message: loc.unknown_exception); } } @@ -203,6 +180,7 @@ class _SettingsViewState extends State { Duration duration = const Duration(seconds: 3), VoidCallback? action, }) { + final loc = AppLocalizations.of(context); final messenger = ScaffoldMessenger.of(context); messenger.hideCurrentSnackBar(); messenger.showSnackBar( @@ -211,10 +189,7 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.onBoxColor, duration: duration, action: action != null - ? SnackBarAction( - label: AppLocalizations.of(context).undo, - onPressed: action, - ) + ? SnackBarAction(label: loc.undo, onPressed: action) : null, ), ); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 43de037..6c30483 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -25,6 +25,7 @@ class _StatisticsViewState extends State { @override void initState() { super.initState(); + final db = Provider.of(context, listen: false); Future.wait([ @@ -32,21 +33,25 @@ class _StatisticsViewState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) async { + if (!mounted) return; final matches = results[0] as List; final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(matches, players); - matchCounts = _calculateMatchAmountsForAllPlayers(matches, players); + winCounts = _calculateWinsForAllPlayers(matches, players, context); + matchCounts = _calculateMatchAmountsForAllPlayers( + matches, + players, + context, + ); winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); - if (mounted) { - setState(() { - isLoading = false; - }); - } + setState(() { + isLoading = false; + }); }); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return SingleChildScrollView( @@ -69,7 +74,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: AppLocalizations.of(context).wins, + title: loc.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +83,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: AppLocalizations.of(context).winrate, + title: loc.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -87,7 +92,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: AppLocalizations.of(context).amount_of_matches, + title: loc.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -97,7 +102,7 @@ class _StatisticsViewState extends State { ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, + title: loc.info, message: AppLocalizations.of( context, ).no_statistics_available, @@ -118,8 +123,10 @@ class _StatisticsViewState extends State { List<(String, int)> _calculateWinsForAllPlayers( List matches, List players, + BuildContext context, ) { List<(String, int)> winCounts = []; + final loc = AppLocalizations.of(context); // Getting the winners for (var match in matches) { @@ -150,10 +157,7 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player( - id: playerId, - name: AppLocalizations.of(context).not_available, - ), + orElse: () => Player(id: playerId, name: loc.not_available), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -168,8 +172,10 @@ class _StatisticsViewState extends State { List<(String, int)> _calculateMatchAmountsForAllPlayers( List matches, List players, + BuildContext context, ) { List<(String, int)> matchCounts = []; + final loc = AppLocalizations.of(context); // Counting matches for each player for (var match in matches) { @@ -215,10 +221,7 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player( - id: playerId, - name: AppLocalizations.of(context).not_available, - ), + orElse: () => Player(id: playerId, name: loc.not_available), ); matchCounts[i] = (player.name, matchCounts[i].$2); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e1761b1..eac4480 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -87,6 +87,7 @@ class _PlayerSelectionState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), @@ -97,7 +98,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: AppLocalizations.of(context).search_for_players, + hintText: loc.search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, @@ -139,11 +140,7 @@ class _PlayerSelectionState extends State { SizedBox( height: 50, child: selectedPlayers.isEmpty - ? Center( - child: Text( - AppLocalizations.of(context).no_players_selected, - ), - ) + ? Center(child: Text(loc.no_players_selected)) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -185,7 +182,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of(context).all_players, + loc.all_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -196,8 +193,8 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: _getInfoText(), + title: loc.info, + message: _getInfoText(context), ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -234,6 +231,7 @@ class _PlayerSelectionState extends State { /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { + final loc = AppLocalizations.of(context); String playerName = _searchBarController.text.trim(); Player createdPlayer = Player(name: playerName); bool success = await db.playerDao.addPlayer(player: createdPlayer); @@ -267,7 +265,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - AppLocalizations.of(context).could_not_add_player(playerName), + loc.could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), @@ -278,18 +276,19 @@ class _PlayerSelectionState extends State { /// Determines the appropriate info text to display when no players /// are available in the suggested players list. - String _getInfoText() { + String _getInfoText(BuildContext context) { + final loc = AppLocalizations.of(context); if (allPlayers.isEmpty) { // No players exist in the database - return AppLocalizations.of(context).no_players_created_yet; + return loc.no_players_created_yet; } else if (selectedPlayers.length == allPlayers.length || widget.availablePlayers?.isEmpty == true) { // All players have been selected or // available players list is provided but empty - return AppLocalizations.of(context).all_players_selected; + return loc.all_players_selected; } else { // No players match the search query - return AppLocalizations.of(context).no_players_found_with_that_name; + return loc.no_players_found_with_that_name; } } } diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 7727827..291a256 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -21,6 +21,7 @@ class _MatchTileState extends State { final group = widget.match.group; final winner = widget.match.winner; final allPlayers = _getAllPlayers(); + final loc = AppLocalizations.of(context); return GestureDetector( onTap: widget.onTap, @@ -49,7 +50,7 @@ class _MatchTileState extends State { ), ), Text( - _formatDate(widget.match.createdAt), + _formatDate(widget.match.createdAt, context), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], @@ -98,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - AppLocalizations.of(context).winner(winner.name), + loc.winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -115,7 +116,7 @@ class _MatchTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( - AppLocalizations.of(context).players, + loc.players, style: const TextStyle( fontSize: 13, color: Colors.grey, @@ -137,9 +138,10 @@ class _MatchTileState extends State { ); } - String _formatDate(DateTime dateTime) { + String _formatDate(DateTime dateTime, BuildContext context) { final now = DateTime.now(); final difference = now.difference(dateTime); + final loc = AppLocalizations.of(context); if (difference.inDays == 0) { return AppLocalizations.of( @@ -150,7 +152,7 @@ class _MatchTileState extends State { context, ).yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return AppLocalizations.of(context).days_ago(difference.inDays); + return loc.days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 8d81270..598fad0 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -25,6 +25,7 @@ class StatisticsTile extends StatelessWidget { @override Widget build(BuildContext context) { final maxBarWidth = MediaQuery.of(context).size.width * 0.65; + final loc = AppLocalizations.of(context); return InfoTile( width: width, @@ -36,7 +37,7 @@ class StatisticsTile extends StatelessWidget { visible: values.isNotEmpty, replacement: Center( heightFactor: 4, - child: Text(AppLocalizations.of(context).no_data_available), + child: Text(loc.no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { -- 2.49.1 From 69effa2b7d287f47945dfc6f43b04cd9b23c0aa8 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 11:49:10 +0100 Subject: [PATCH 30/32] change winner localization to not include placeholder --- lib/l10n/arb/app_en.arb | 10 ++-------- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8e6d63a..e3666b3 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -90,15 +90,9 @@ } } }, - "winner": "Winner: {winnerName}", + "winner": "Winner", "@winner": { - "description": "Shows the winner's name", - "placeholders": { - "winnerName": { - "type": "String", - "example": "John" - } - } + "description": "Winner label", }, "players": "Players", "@players": { diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 2b94cf2..b037af2 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -99,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - loc.winner(winner.name), + "${loc.winner}: ${winner.name}", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, -- 2.49.1 From a487e4071f9500d439501a80e70a7193c03546b0 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 12:00:25 +0100 Subject: [PATCH 31/32] Sort files --- lib/l10n/arb/app_de.arb | 144 ++++---- lib/l10n/arb/app_en.arb | 732 ++++++++++++++++++++-------------------- 2 files changed, 438 insertions(+), 438 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index d8a9e7e..89354bd 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,83 +1,83 @@ { "@@locale": "de", - "choose_group": "Gruppe wählen", - "create_new_match": "Neues Match erstellen", - "choose_ruleset": "Regelwerk wählen", + "all_players": "Alle Spieler:innen:", + "all_players_selected": "Alle Spieler:innen ausgewählt", + "amount_of_matches": "Anzahl der Matches", + "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", - "select_winner": "Gewinner:in wählen:", + "choose_group": "Gruppe wählen", + "choose_ruleset": "Regelwerk wählen", + "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "create_group": "Gruppe erstellen", + "create_match": "Match erstellen", + "create_new_group": "Neue Gruppe erstellen", + "create_new_match": "Neues Match erstellen", + "data_successfully_deleted": "Daten erfolgreich gelöscht", + "data_successfully_exported": "Daten erfolgreich exportiert", + "data_successfully_imported": "Daten erfolgreich importiert", + "days_ago": "vor {count} Tagen", + "delete": "Löschen", + "delete_all_data": "Alle Daten löschen?", + "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "error_reading_file": "Fehler beim Lesen der Datei", + "export_canceled": "Export abgebrochen", + "export_data": "Daten exportieren", + "format_exception": "Formatfehler (siehe Konsole)", + "game": "Spielvorlage", + "game_name": "Spielvorlagenname", + "group": "Gruppe", + "group_name": "Gruppenname", + "groups": "Gruppen", + "home": "Startseite", + "import_canceled": "Import abgebrochen", + "import_data": "Daten importieren", + "info": "Info", + "invalid_schema": "Ungültiges Schema", + "least_points": "Niedrigste Punkte", + "match_in_progress": "Match läuft...", + "match_name": "Matchname", + "matches": "Matches", + "menu": "Menü", + "most_points": "Höchste Punkte", + "no_data_available": "Keine Daten verfügbar", + "no_groups_created_yet": "Noch keine Gruppen erstellt", + "no_matches_created_yet": "Noch keine Matches erstellt", + "no_players_created_yet": "Noch keine Spieler:in erstellt", + "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", + "no_players_selected": "Keine Spieler:in ausgewählt", "no_recent_matches_available": "Keine letzten Matches verfügbar", "no_second_match_available": "Kein zweites Match verfügbar", - "delete_all_data": "Alle Daten löschen?", - "cancel": "Abbrechen", - "delete": "Löschen", - "create_new_group": "Neue Gruppe erstellen", - "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", - "selected_players": "Ausgewählte Spieler:in: {count}", - "no_players_selected": "Keine Spieler:in ausgewählt", - "all_players": "Alle Spieler:innen:", - "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", - "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", - "winner": "Gewinner:in: {winnerName}", - "players": "Spieler:in", - "player_name": "Spieler:innenname", "no_statistics_available": "Keine Statistiken verfügbar", - "matches": "Matches", - "groups": "Gruppen", - "recent_matches": "Letzte Matches", - "quick_create": "Schnellzugriff", - "match_in_progress": "Match läuft...", - "menu": "Menü", - "settings": "Einstellungen", - "export_data": "Daten exportieren", - "import_data": "Daten importieren", - "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", - "data_successfully_deleted": "Daten erfolgreich gelöscht", - "data_successfully_imported": "Daten erfolgreich importiert", - "invalid_schema": "Ungültiges Schema", - "error_reading_file": "Fehler beim Lesen der Datei", - "import_canceled": "Import abgebrochen", - "format_exception": "Formatfehler (siehe Konsole)", - "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "data_successfully_exported": "Daten erfolgreich exportiert", - "export_canceled": "Export abgebrochen", - "undo": "Rückgängig", - "wins": "Siege", - "winrate": "Siegquote", - "amount_of_matches": "Anzahl der Matches", - "info": "Info", - "no_groups_created_yet": "Noch keine Gruppen erstellt", - "create_group": "Gruppe erstellen", - "group_name": "Gruppenname", - "no_matches_created_yet": "Noch keine Matches erstellt", - "match_name": "Matchname", - "game": "Spielvorlage", - "ruleset": "Regelwerk", - "group": "Gruppe", "none": "Kein", "none_group": "Keine", - "create_match": "Match erstellen", - "no_players_created_yet": "Noch keine Spieler:in erstellt", - "all_players_selected": "Alle Spieler:innen ausgewählt", - "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", - "today_at": "Heute um {time}", - "yesterday_at": "Gestern um {time}", - "days_ago": "vor {count} Tagen", - "home": "Startseite", + "not_available": "Nicht verfügbar", + "player_name": "Spieler:innenname", + "players": "Spieler:in", + "players_count": "{count} Spieler", + "quick_create": "Schnellzugriff", + "recent_matches": "Letzte Matches", + "ruleset": "Regelwerk", + "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", + "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", + "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "search_for_groups": "Nach Gruppen suchen", + "search_for_players": "Nach Spieler:innen suchen", + "select_winner": "Gewinner:in wählen:", + "selected_players": "Ausgewählte Spieler:in: {count}", + "settings": "Einstellungen", + "single_loser": "Ein:e Verlierer:in", + "single_winner": "Ein:e Gewinner:in", "statistics": "Statistiken", "stats": "Statistiken", - "players_count": "{count} Spieler", + "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", - "game_name": "Spielvorlagenname", - "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", - "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", - "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", - "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", - "single_winner": "Ein:e Gewinner:in", - "single_loser": "Ein:e Verlierer:in", - "most_points": "Höchste Punkte", - "least_points": "Niedrigste Punkte", - "search_for_players": "Nach Spieler:innen suchen", - "search_for_groups": "Nach Gruppen suchen", - "no_data_available": "Keine Daten verfügbar", - "not_available": "Nicht verfügbar" -} + "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", + "today_at": "Heute um {time}", + "undo": "Rückgängig", + "unknown_exception": "Unbekannter Fehler (siehe Konsole)", + "winner": "Gewinner:in: {winnerName}", + "winrate": "Siegquote", + "wins": "Siege", + "yesterday_at": "Gestern um {time}" +} \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e3666b3..d567f50 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,367 +1,367 @@ { - "@@locale": "en", - "choose_group": "Choose Group", - "@choose_group": { - "description": "Label for choosing a group" - }, - "create_new_match": "Create new match", - "@create_new_match": { - "description": "Button text to create a new match" - }, - "choose_ruleset": "Choose Ruleset", - "@choose_ruleset": { - "description": "Label for choosing a ruleset" - }, - "choose_game": "Choose Game", - "@choose_game": { - "description": "Label for choosing a game" - }, - "select_winner": "Select Winner:", - "@select_winner": { - "description": "Label to select the winner" - }, - "game_tracker": "Game Tracker", - "@game_tracker": { - "description": "App Name" - }, - "no_recent_matches_available": "No recent matches available", - "@no_recent_matches_available": { - "description": "Message when no recent matches exist" - }, - "no_second_match_available": "No second match available", - "@no_second_match_available": { - "description": "Message when no second match exists" - }, - "delete_all_data": "Delete all data?", - "@delete_all_data": { - "description": "Confirmation dialog for deleting all data" - }, - "cancel": "Cancel", - "@cancel": { - "description": "Cancel button text" - }, - "delete": "Delete", - "@delete": { - "description": "Delete button text" - }, - "create_new_group": "Create new group", - "@create_new_group": { - "description": "Button text to create a new group" - }, - "error_creating_group": "Error while creating group, please try again", - "@error_creating_group": { - "description": "Error message when group creation fails" - }, - "selected_players": "Selected players: {count}", - "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } - }, - "no_players_selected": "No players selected", - "@no_players_selected": { - "description": "Message when no players are selected" - }, - "all_players": "All players:", - "@all_players": { - "description": "Label for all players list" - }, - "successfully_added_player": "Successfully added player {playerName}", - "@successfully_added_player": { - "description": "Success message when adding a player", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "could_not_add_player": "Could not add player {playerName}", - "@could_not_add_player": { - "description": "Error message when adding a player fails", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "winner": "Winner", - "@winner": { - "description": "Winner label", - }, - "players": "Players", - "@players": { - "description": "Players label" - }, - "no_statistics_available": "No statistics available", - "@no_statistics_available": { - "description": "Message when no statistics are available, because no matches were played yet" - }, - "no_data_available": "No data available", - "@no_data_available": { - "description": "Message when no data in the statistic tiles is given" - }, - "matches": "Matches", - "@matches": { - "description": "Label for matches" - }, - "groups": "Groups", - "@groups": { - "description": "Label for groups" - }, - "recent_matches": "Recent Matches", - "@recent_matches": { - "description": "Title for recent matches section" - }, - "quick_create": "Quick Create", - "@quick_create": { - "description": "Title for quick create section" - }, - "match_in_progress": "Match in progress...", - "@match_in_progress": { - "description": "Message when match is in progress" - }, - "menu": "Menu", - "@menu": { - "description": "Menu label" - }, - "settings": "Settings", - "@settings": { - "description": "Settings label" - }, - "export_data": "Export data", - "@export_data": { - "description": "Export data menu item" - }, - "import_data": "Import data", - "@import_data": { - "description": "Import data menu item" - }, - "this_cannot_be_undone": "This can't be undone", - "@this_cannot_be_undone": { - "description": "Warning message for irreversible actions" - }, - "data_successfully_deleted": "Data successfully deleted", - "@data_successfully_deleted": { - "description": "Success message after deleting data" - }, - "data_successfully_imported": "Data successfully imported", - "@data_successfully_imported": { - "description": "Success message after importing data" - }, - "invalid_schema": "Invalid Schema", - "@invalid_schema": { - "description": "Error message for invalid schema" - }, - "error_reading_file": "Error reading file", - "@error_reading_file": { - "description": "Error message when file cannot be read" - }, - "import_canceled": "Import canceled", - "@import_canceled": { - "description": "Message when import is canceled" - }, - "format_exception": "Format Exception (see console)", - "@format_exception": { - "description": "Error message for format exceptions" - }, - "unknown_exception": "Unknown Exception (see console)", - "@unknown_exception": { - "description": "Error message for unknown exceptions" - }, - "data_successfully_exported": "Data successfully exported", - "@data_successfully_exported": { - "description": "Success message after exporting data" - }, - "export_canceled": "Export canceled", - "@export_canceled": { - "description": "Message when export is canceled" - }, - "undo": "Undo", - "@undo": { - "description": "Undo button text" - }, - "wins": "Wins", - "@wins": { - "description": "Label for wins statistic" - }, - "winrate": "Winrate", - "@winrate": { - "description": "Label for winrate statistic" - }, - "amount_of_matches": "Amount of Matches", - "@amount_of_matches": { - "description": "Label for amount of matches statistic" - }, - "info": "Info", - "@info": { - "description": "Info label" - }, - "no_groups_created_yet": "No groups created yet", - "@no_groups_created_yet": { - "description": "Message when no groups exist" - }, - "no_players_created_yet": "No players created yet", - "@no_players_created_yet": { - "description": "Message when no players exist" - }, - "create_group": "Create Group", - "@create_group": { - "description": "Button text to create a group" - }, - "group_name": "Group name", - "@group_name": { - "description": "Placeholder for group name input" - }, - "player_name": "Player name", - "@player_name": { - "description": "Placeholder for player name input" - }, - "no_matches_created_yet": "No matches created yet", - "@no_matches_created_yet": { - "description": "Message when no matches exist" - }, - "match_name": "Match name", - "@match_name": { - "description": "Placeholder for match name input" - }, - "game": "Game", - "@game": { - "description": "Game label" - }, - "ruleset": "Ruleset", - "@ruleset": { - "description": "Ruleset label" - }, - "group": "Group", - "@group": { - "description": "Group label" - }, - "none": "None", - "@none": { - "description": "None option label" - }, - "none_group": "None", - "@none_group": { - "description": "None group option label" - }, - "create_match": "Create match", - "@create_match": { - "description": "Button text to create a match" - }, - "no_players_found_with_that_name": "No players found with that name", - "@no_players_found_with_that_name": { - "description": "Message when search returns no results" - }, - "all_players_selected": "All players selected", - "@all_players_selected": { - "description": "Message when all players are added to selection" - }, - "today_at": "Today at {time}", - "@today_at": { - "description": "Date format for today", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "yesterday_at": "Yesterday at {time}", - "@yesterday_at": { - "description": "Date format for yesterday", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "days_ago": "{count} days ago", - "@days_ago": { - "description": "Date format for days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "home": "Home", - "@home": { - "description": "Home tab label" - }, - "statistics": "Statistics", - "@statistics": { - "description": "Statistics tab label" - }, - "stats": "Stats", - "@stats": { - "description": "Stats tab label (short)" - }, - "players_count": "{count} Players", - "@players_count": { - "description": "Shows the number of players", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "there_is_no_group_matching_your_search": "There is no group matching your search", - "@there_is_no_group_matching_your_search": { - "description": "Message when search returns no groups" - }, - "game_name": "Game Name", - "@game_name": { - "description": "Placeholder for game name search" - }, - "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "@ruleset_single_winner": { - "description": "Description for single winner ruleset" - }, - "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", - "@ruleset_single_loser": { - "description": "Description for single loser ruleset" - }, - "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", - "@ruleset_most_points": { - "description": "Description for most points ruleset" - }, - "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", - "@ruleset_least_points": { - "description": "Description for least points ruleset" - }, - "single_winner": "Single Winner", - "@single_winner": { - "description": "Title for single winner ruleset" - }, - "single_loser": "Single Loser", - "@single_loser": { - "description": "Title for single loser ruleset" - }, - "most_points": "Most Points", - "@most_points": { - "description": "Title for most points ruleset" - }, - "least_points": "Least Points", - "@least_points": { - "description": "Title for least points ruleset" - }, - "search_for_players": "Search for players", - "@search_for_players": { - "description": "Hint text for player search input field" - }, - "search_for_groups": "Search for groups", - "@search_for_groups": { - "description": "Hint text for group search input field" - }, - "not_available": "Not available", - "@not_available": { - "description": "Abbreviation for not available" - } -} + "@@locale": "en", + "@all_players": { + "description": "Label for all players list" + }, + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "@cancel": { + "description": "Cancel button text" + }, + "@choose_game": { + "description": "Label for choosing a game" + }, + "@choose_group": { + "description": "Label for choosing a group" + }, + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "@could_not_add_player": { + "description": "Error message when adding a player fails", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@create_group": { + "description": "Button text to create a group" + }, + "@create_match": { + "description": "Button text to create a match" + }, + "@create_new_group": { + "description": "Button text to create a new group" + }, + "@create_new_match": { + "description": "Button text to create a new match" + }, + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@delete": { + "description": "Delete button text" + }, + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "@error_creating_group": { + "description": "Error message when group creation fails" + }, + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "@export_canceled": { + "description": "Message when export is canceled" + }, + "@export_data": { + "description": "Export data menu item" + }, + "@format_exception": { + "description": "Error message for format exceptions" + }, + "@game": { + "description": "Game label" + }, + "@game_name": { + "description": "Placeholder for game name search" + }, + "@game_tracker": { + "description": "App Name" + }, + "@group": { + "description": "Group label" + }, + "@group_name": { + "description": "Placeholder for group name input" + }, + "@groups": { + "description": "Label for groups" + }, + "@home": { + "description": "Home tab label" + }, + "@import_canceled": { + "description": "Message when import is canceled" + }, + "@import_data": { + "description": "Import data menu item" + }, + "@info": { + "description": "Info label" + }, + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "@least_points": { + "description": "Title for least points ruleset" + }, + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "@match_name": { + "description": "Placeholder for match name input" + }, + "@matches": { + "description": "Label for matches" + }, + "@menu": { + "description": "Menu label" + }, + "@most_points": { + "description": "Title for most points ruleset" + }, + "@no_data_available": { + "description": "Message when no data in the statistic tiles is given" + }, + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "@no_second_match_available": { + "description": "Message when no second match exists" + }, + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "@none": { + "description": "None option label" + }, + "@none_group": { + "description": "None group option label" + }, + "@not_available": { + "description": "Abbreviation for not available" + }, + "@player_name": { + "description": "Placeholder for player name input" + }, + "@players": { + "description": "Players label" + }, + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@quick_create": { + "description": "Title for quick create section" + }, + "@recent_matches": { + "description": "Title for recent matches section" + }, + "@ruleset": { + "description": "Ruleset label" + }, + "@ruleset_least_points": { + "description": "Description for least points ruleset" + }, + "@ruleset_most_points": { + "description": "Description for most points ruleset" + }, + "@ruleset_single_loser": { + "description": "Description for single loser ruleset" + }, + "@ruleset_single_winner": { + "description": "Description for single winner ruleset" + }, + "@search_for_groups": { + "description": "Hint text for group search input field" + }, + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "@select_winner": { + "description": "Label to select the winner" + }, + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "@settings": { + "description": "Settings label" + }, + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "@statistics": { + "description": "Statistics tab label" + }, + "@stats": { + "description": "Stats tab label (short)" + }, + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "@today_at": { + "description": "Date format for today", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "@undo": { + "description": "Undo button text" + }, + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "@winner": { + "description": "Winner label" + }, + "@winrate": { + "description": "Label for winrate statistic" + }, + "@wins": { + "description": "Label for wins statistic" + }, + "@yesterday_at": { + "description": "Date format for yesterday", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "all_players": "All players:", + "all_players_selected": "All players selected", + "amount_of_matches": "Amount of Matches", + "cancel": "Cancel", + "choose_game": "Choose Game", + "choose_group": "Choose Group", + "choose_ruleset": "Choose Ruleset", + "could_not_add_player": "Could not add player {playerName}", + "create_group": "Create Group", + "create_match": "Create match", + "create_new_group": "Create new group", + "create_new_match": "Create new match", + "data_successfully_deleted": "Data successfully deleted", + "data_successfully_exported": "Data successfully exported", + "data_successfully_imported": "Data successfully imported", + "days_ago": "{count} days ago", + "delete": "Delete", + "delete_all_data": "Delete all data?", + "error_creating_group": "Error while creating group, please try again", + "error_reading_file": "Error reading file", + "export_canceled": "Export canceled", + "export_data": "Export data", + "format_exception": "Format Exception (see console)", + "game": "Game", + "game_name": "Game Name", + "game_tracker": "Game Tracker", + "group": "Group", + "group_name": "Group name", + "groups": "Groups", + "home": "Home", + "import_canceled": "Import canceled", + "import_data": "Import data", + "info": "Info", + "invalid_schema": "Invalid Schema", + "least_points": "Least Points", + "match_in_progress": "Match in progress...", + "match_name": "Match name", + "matches": "Matches", + "menu": "Menu", + "most_points": "Most Points", + "no_data_available": "No data available", + "no_groups_created_yet": "No groups created yet", + "no_matches_created_yet": "No matches created yet", + "no_players_created_yet": "No players created yet", + "no_players_found_with_that_name": "No players found with that name", + "no_players_selected": "No players selected", + "no_recent_matches_available": "No recent matches available", + "no_second_match_available": "No second match available", + "no_statistics_available": "No statistics available", + "none": "None", + "none_group": "None", + "not_available": "Not available", + "player_name": "Player name", + "players": "Players", + "players_count": "{count} Players", + "quick_create": "Quick Create", + "recent_matches": "Recent Matches", + "ruleset": "Ruleset", + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "search_for_groups": "Search for groups", + "search_for_players": "Search for players", + "select_winner": "Select Winner:", + "selected_players": "Selected players: {count}", + "settings": "Settings", + "single_loser": "Single Loser", + "single_winner": "Single Winner", + "statistics": "Statistics", + "stats": "Stats", + "successfully_added_player": "Successfully added player {playerName}", + "there_is_no_group_matching_your_search": "There is no group matching your search", + "this_cannot_be_undone": "This can't be undone", + "today_at": "Today at {time}", + "undo": "Undo", + "unknown_exception": "Unknown Exception (see console)", + "winner": "Winner", + "winrate": "Winrate", + "wins": "Wins", + "yesterday_at": "Yesterday at {time}" +} \ No newline at end of file -- 2.49.1 From 1dc5286c6b4c21a7552a592a070413caa67d39bd Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 12:08:21 +0100 Subject: [PATCH 32/32] change double to single quotes --- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index b037af2..c455949 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -99,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - "${loc.winner}: ${winner.name}", + '${loc.winner}: ${winner.name}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, -- 2.49.1