Skip to content

Commit

Permalink
Show full language names (settings page, menu)
Browse files Browse the repository at this point in the history
Language names are localized according to the app language. Only
exception is the app language setting, there we have the autonyms
(English, Deutsch).
  • Loading branch information
holybiber committed Oct 25, 2023
1 parent 8d803d5 commit e26a9df
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 42 deletions.
15 changes: 10 additions & 5 deletions lib/data/app_language.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,29 @@ class AppLanguage {
/// [str] can be a language code ('en', 'de', ...) or 'system'
/// In case of 'system': Use the [defaultLangCode]
static fromString(String str, String defaultLangCode) {
if (!availableAppLanguages.contains(str)) str = 'system';
if (!availableAppLanguages.keys.contains(str)) str = 'system';
bool isSystemLanguage = str == 'system';

String languageCode = str;
if (isSystemLanguage) {
languageCode = defaultLangCode;
}
if ((languageCode == 'system') ||
!availableAppLanguages.contains(languageCode)) {
!availableAppLanguages.keys.contains(languageCode)) {
// English is default in case we can't make sense of our parameters
languageCode = 'en';
}
return AppLanguage(isSystemLanguage, languageCode);
}

/// App Languages (settings)
// TODO get the list from the repository - maybe create applanguage class
static const List<String> availableAppLanguages = ["system", "en", "de"];
/// The languages available for the whole app:
/// identifier (language code) => description shown (autonym of language)
/// TODO get this from supportedLocales?
static const Map<String, String> availableAppLanguages = {
'system': 'System default',
'en': 'English (en)',
'de': 'Deutsch (de)'
};
}

/// This class also takes care of persistance and reads/writes
Expand Down
51 changes: 51 additions & 0 deletions lib/l10n/l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,54 @@ export 'package:flutter_gen/gen_l10n/app_localizations.dart';
extension AppLocalizationsExt on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this);
}

/// Get the translated name of a language together with the language code
/// in parentheses, for example getLanguageName('de') returns 'German (de)'
/// We need to implement this manually as flutter currently doesn't
/// have a way to access translations with dynamic keys:
/// https://github.com/flutter/flutter/issues/105672
extension GetLanguageNameExt on AppLocalizations {
String getLanguageName(String languageCode) {
final languageMap = <String, String>{
'tr': language_tr,
'zh': language_zh,
'vi': language_vi,
'ro': language_ro,
'ky': language_ky,
'pl': language_pl,
'id': language_id,
'xh': language_xh,
'uz': language_uz,
'af': language_af,
'ta': language_ta,
'sr': language_sr,
'ms': language_ms,
'az': language_az,
'ti': language_ti,
'sw': language_sw,
'nb': language_nb,
'ku': language_ku,
'sv': language_sv,
'ml': language_ml,
'hi': language_hi,
'lg': language_lg,
'kn': language_kn,
'it': language_it,
'cs': language_cs,
'fa': language_fa,
'ar': language_ar,
'ru': language_ru,
'nl': language_nl,
'fr': language_fr,
'es': language_es,
'sq': language_sq,
'en': language_en,
'de': language_de
};
if (languageMap.containsKey(languageCode)) {
return "${languageMap[languageCode]!} ($languageCode)";
}
debugPrint("Warning: getLanguageName('$languageCode') is not defined");
return languageCode.toUpperCase();
}
}
38 changes: 36 additions & 2 deletions lib/l10n/locales/app_de.arb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "Einstellungen",
"appearance" : "Darstellung",
"appLanguage" : "App Sprache",
"appLanguage" : "App-Sprache",
"theme" : "Design",
"light" : "hell",
"dark" : "dunkel",
Expand All @@ -14,8 +14,42 @@
"lastTime" : "Letzte Überprüfung",
"checkNow" : "Jetzt überprüfen",
"languages" : "Sprachen",
"languagesText" : "Dies sind die verfügbaren Sprachen. Du kannst sie manuell herunterladen, löschen und aktualieren. Nutze das Häkchen, um einzustellen, welche Sprachen von der App automatisch aktualisiert werden sollen.",
"languagesText" : "Dies sind die verfügbaren Sprachen. Du kannst sie manuell herunterladen, aktualisieren und löschen.",
"language" : "Sprache",
"language_tr": "Türkisch",
"language_zh": "Chinesisch",
"language_vi": "Vietnamesisch",
"language_ro": "Rumänisch",
"language_ky": "Kirgisisch",
"language_pl": "Polnisch",
"language_id": "Indonesisch",
"language_xh": "Xhosa",
"language_uz": "Usbekisch",
"language_af": "Afrikaans",
"language_ta": "Tamil",
"language_sr": "Serbisch",
"language_ms": "Malaiisch",
"language_az": "Aserbaidschanisch",
"language_ti": "Tigrinya",
"language_sw": "Suaheli",
"language_nb": "Norwegisch",
"language_ku": "Kurmandschi",
"language_sv": "Schwedisch",
"language_ml": "Malayalam",
"language_hi": "Hindi",
"language_lg": "Luganda",
"language_kn": "Kannada",
"language_it": "Italienisch",
"language_cs": "Tschechisch",
"language_fa": "Persisch",
"language_ar": "Arabisch",
"language_ru": "Russisch",
"language_nl": "Niederländisch",
"language_fr": "Französisch",
"language_es": "Spanisch",
"language_sq": "Albanisch",
"language_en": "Englisch",
"language_de": "Deutsch",
"state" : "Status",
"diskUsage" : "Speicherverbrauch",
"warning" : "Warnung",
Expand Down
38 changes: 36 additions & 2 deletions lib/l10n/locales/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,45 @@
"lastTime" : " Last check:",
"checkNow" : "Check now",
"languages" : "Languages",
"languagesText" : "These are the languages available. You can download, delete or update them manually. Use the checkbox to set which languages should be updated automatically by the app.",
"languagesText" : "These are the languages available. You can download, update or delete them manually.",
"language" : "Language",
"language_tr": "Turkish",
"language_zh": "Chinese",
"language_vi": "Vietnamese",
"language_ro": "Romanian",
"language_ky": "Kyrgyz",
"language_pl": "Polish",
"language_id": "Indonesian",
"language_xh": "Xhosa",
"language_uz": "Uzbek",
"language_af": "Afrikaans",
"language_ta": "Tamil",
"language_sr": "Serbian",
"language_ms": "Malay",
"language_az": "Azerbaijani",
"language_ti": "Tigrinya",
"language_sw": "Swahili",
"language_nb": "Norwegian",
"language_ku": "Kurmanji",
"language_sv": "Swedish",
"language_ml": "Malayalam",
"language_hi": "Hindi",
"language_lg": "Luganda",
"language_kn": "Kannada",
"language_it": "Italian",
"language_cs": "Czech",
"language_fa": "Persian",
"language_ar": "Arabic",
"language_ru": "Russian",
"language_nl": "Dutch",
"language_fr": "French",
"language_es": "Spanish",
"language_sq": "Albanian",
"language_en": "English",
"language_de": "German",
"state" : "State",
"diskUsage" : "Disk usage",
"warning" : "Warning",
"cannotDelete" : "You can't delete the language, that's currently selected. Switch language and try again.",
"cannotDelete" : "You can't delete the language that is currently selected. Switch language and try again.",
"close" : "Close"
}
7 changes: 2 additions & 5 deletions lib/widgets/dropdownbutton_app_language.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ class DropdownButtonAppLanguage extends ConsumerWidget {
return DropdownButton(
value: appLanguage.toString(),
items: [
for (var value in AppLanguage.availableAppLanguages)
DropdownMenuItem<String>(
value: value,
child: Text(value.toUpperCase()),
)
for (MapEntry item in AppLanguage.availableAppLanguages.entries)
DropdownMenuItem<String>(value: item.key, child: Text(item.value))
],
onChanged: (String? value) {
ref.read(appLanguageProvider.notifier).setLocale(value!);
Expand Down
3 changes: 2 additions & 1 deletion lib/widgets/languages_table.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:app4training/l10n/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:app4training/data/globals.dart';
Expand Down Expand Up @@ -34,7 +35,7 @@ class LanguagesTable extends ConsumerWidget {
Container(
height: 32,
alignment: Alignment.centerLeft,
child: Text(languageCode.toUpperCase(),
child: Text(context.l10n.getLanguageName(languageCode),
style: Theme.of(context).textTheme.bodyMedium)),
Container(
height: 32,
Expand Down
3 changes: 2 additions & 1 deletion lib/widgets/main_drawer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:collection';

import 'package:app4training/data/globals.dart';
import 'package:app4training/l10n/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:app4training/widgets/upward_expansion_tile.dart';
Expand Down Expand Up @@ -66,7 +67,7 @@ class LanguageSelection extends ConsumerWidget {

for (var language in Globals.availableLanguages) {
if (!ref.watch(languageProvider(language)).downloaded) continue;
String title = language.toUpperCase();
String title = context.l10n.getLanguageName(language);
allLanguages.add(ListTile(
title: Text(title, style: Theme.of(context).textTheme.labelMedium),
onTap: () {
Expand Down
28 changes: 14 additions & 14 deletions test/dropdownbutton_app_language_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ void main() {

// Verify initial value
expect(prefs.getString('appLanguage'), 'system');
expect(find.text('SYSTEM'), findsOneWidget);
expect(find.text('DE'), findsNothing);
expect(find.text('System default'), findsOneWidget);
expect(find.text('Deutsch (de)'), findsNothing);

// Click on the button to expand it: Find the other options as well
await tester.tap(find.byType(DropdownButtonAppLanguage));
await tester.pump();
expect(find.text('DE'), findsOneWidget);
expect(find.text('EN'), findsOneWidget);
expect(find.text('Deutsch (de)'), findsOneWidget);
expect(find.text('English (en)'), findsOneWidget);

// Select German and verify correct UI and saving in SharedPreferences
await tester.tap(find.text('DE'));
await tester.tap(find.text('Deutsch (de)'));
await tester.pump();
expect(find.text('DE'), findsOneWidget);
expect(find.text('SYSTEM'), findsNothing);
expect(find.text('Deutsch (de)'), findsOneWidget);
expect(find.text('System default'), findsNothing);
expect(prefs.getString('appLanguage'), 'de');
});

Expand All @@ -45,20 +45,20 @@ void main() {

// Verify initial value
expect(prefs.getString('appLanguage'), 'de');
expect(find.text('DE'), findsOneWidget);
expect(find.text('SYSTEM'), findsNothing);
expect(find.text('Deutsch (de)'), findsOneWidget);
expect(find.text('System default'), findsNothing);

// Click on the button to expand it: Find the other options as well
await tester.tap(find.byType(DropdownButtonAppLanguage));
await tester.pump();
expect(find.text('SYSTEM'), findsOneWidget);
expect(find.text('EN'), findsOneWidget);
expect(find.text('System default'), findsOneWidget);
expect(find.text('English (en)'), findsOneWidget);

// Select SYSTEM and verify correct UI and saving in SharedPreferences
await tester.tap(find.text('SYSTEM'));
await tester.tap(find.text('System default'));
await tester.pump();
expect(find.text('SYSTEM'), findsOneWidget);
expect(find.text('DE'), findsNothing);
expect(find.text('System default'), findsOneWidget);
expect(find.text('Deutsch (de)'), findsNothing);
expect(prefs.getString('appLanguage'), 'system');
});
}
39 changes: 30 additions & 9 deletions test/languages_table_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:app4training/data/app_language.dart';
import 'package:app4training/l10n/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
Expand Down Expand Up @@ -28,9 +30,25 @@ class TestLanguageStatusNotifier extends LanguageStatusNotifier {
}
}

// To simplify testing the LanguagesTable widget in different locales
class TestLanguagesTable extends ConsumerWidget {
const TestLanguagesTable({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final AppLanguage appLanguage = ref.watch(appLanguageProvider);

return MaterialApp(
locale: appLanguage.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: LanguagesTable()));
}
}

void main() {
final int countLanguages = Globals.availableLanguages.length;
testWidgets('Basic test with no language downloaded',
testWidgets('Basic test with no language downloaded, English as appLanguage',
(WidgetTester tester) async {
final testLanguageProvider =
NotifierProvider.family<LanguageController, Language, String>(() {
Expand All @@ -43,16 +61,16 @@ void main() {
await tester.pumpWidget(ProviderScope(overrides: [
sharedPrefsProvider.overrideWithValue(prefs),
languageProvider.overrideWithProvider(testLanguageProvider)
], child: const MaterialApp(home: Scaffold(body: LanguagesTable()))));
], child: const TestLanguagesTable()));

expect(find.text('DE'), findsOneWidget);
expect(find.text('EN'), findsOneWidget);
expect(find.text('German (de)'), findsOneWidget);
expect(find.text('English (en)'), findsOneWidget);
expect(find.byIcon(Icons.check), findsNothing);
expect(find.byIcon(Icons.delete), findsNothing);
expect(find.byIcon(Icons.download), findsNWidgets(countLanguages));
expect(find.byIcon(Icons.refresh), findsNothing);
});
testWidgets('Basic test with only German downloaded',
testWidgets('Basic test with only German downloaded, German as appLanguage',
(WidgetTester tester) async {
final testLanguageProvider =
NotifierProvider.family<LanguageController, Language, String>(() {
Expand All @@ -67,14 +85,17 @@ void main() {
SharedPreferences.setMockInitialValues(
{'download_de': true, 'download_en': false});
final prefs = await SharedPreferences.getInstance();
await tester.pumpWidget(ProviderScope(overrides: [
final container = ProviderContainer(overrides: [
sharedPrefsProvider.overrideWithValue(prefs),
languageProvider.overrideWithProvider(testLanguageProvider),
languageStatusProvider.overrideWithProvider(testLanguageStatusProvider)
], child: const MaterialApp(home: Scaffold(body: LanguagesTable()))));
]);
container.read(appLanguageProvider.notifier).setLocale('de');
await tester.pumpWidget(UncontrolledProviderScope(
container: container, child: const TestLanguagesTable()));

expect(find.text('DE'), findsOneWidget);
expect(find.text('EN'), findsOneWidget);
expect(find.text('Deutsch (de)'), findsOneWidget);
expect(find.text('Englisch (en)'), findsOneWidget);
expect(find.byIcon(Icons.check), findsOneWidget);

expect(find.byIcon(Icons.delete), findsOneWidget);
Expand Down
4 changes: 1 addition & 3 deletions test/settings_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ void main() {
expect(prefs.getString('appLanguage'), 'en');
await tester.tap(find.byType(DropdownButtonAppLanguage));
await tester.pump();
// There is another text label 'DE' on the settings page...
expect(find.text('DE'), findsNWidgets(2));
await tester.tap(find.text('DE').at(1));
await tester.tap(find.text('Deutsch (de)'));
await tester.pump();
expect(AppLocalizations.of(context).appLanguage,
equals(AppLocalizationsDe().appLanguage));
Expand Down

0 comments on commit e26a9df

Please sign in to comment.