From 534d19efc3dfd6fd961ddb9e68466ae0a8cd1042 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:21:09 +0100 Subject: [PATCH] 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) {