Skip to content

Commit

Permalink
Allow changing app language
Browse files Browse the repository at this point in the history
  • Loading branch information
elibon99 committed Jan 24, 2025
1 parent c96b1fb commit f9b5f72
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 61 deletions.
4 changes: 2 additions & 2 deletions lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class YubicoAuthenticatorApp extends StatelessWidget {
themeMode: ref.watch(themeModeProvider),
home: page,
debugShowCheckedModeBanner: false,
locale: ref.watch(currentLocaleProvider),
supportedLocales: ref.watch(supportedLocalesProvider),
locale: ref.watch(currentLocaleProvider).locale,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
Expand Down
7 changes: 7 additions & 0 deletions lib/app/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,10 @@ class _ColorConverter implements JsonConverter<Color?, int?> {
@override
int? toJson(Color? object) => object?.toInt32;
}

class AppLocale {
final Locale locale;
final bool systemDefault;

const AppLocale(this.locale, this.systemDefault);
}
64 changes: 30 additions & 34 deletions lib/app/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui';

import 'package:flutter/material.dart';
Expand Down Expand Up @@ -71,53 +70,50 @@ final supportedThemesProvider = StateProvider<List<ThemeMode>>(
(ref) => throw UnimplementedError(),
);

final communityTranslationsProvider =
StateNotifierProvider<CommunityTranslationsNotifier, bool>(
(ref) => CommunityTranslationsNotifier(ref.watch(prefProvider)));
final currentLocaleProvider =
StateNotifierProvider<CurrentLocaleProvider, AppLocale>(
(ref) => CurrentLocaleProvider(ref.watch(prefProvider)),
);

class CommunityTranslationsNotifier extends StateNotifier<bool> {
static const String _key = 'APP_STATE_ENABLE_COMMUNITY_TRANSLATIONS';
class CurrentLocaleProvider extends StateNotifier<AppLocale> {
static const String _key = 'APP_LOCALE';
final SharedPreferences _prefs;

CommunityTranslationsNotifier(this._prefs)
: super(_prefs.getBool(_key) == true);
CurrentLocaleProvider(this._prefs) : super(_fromName(_prefs.getString(_key)));

void setEnableCommunityTranslations(bool value) {
state = value;
_prefs.setBool(_key, value);
void setLocale(Locale locale) {
_log.debug('Set locale to $locale');
state = AppLocale(locale, false);
_prefs.setString(_key, locale.languageCode);
}
}

final supportedLocalesProvider = Provider<List<Locale>>((ref) {
final locales = [...officialLocales];
final localeStr = Platform.environment['_YA_LOCALE'];
if (localeStr != null) {
// Force locale
final locale = Locale(localeStr, '');
locales.add(locale);
void resetLocale() {
_log.debug('Resetting locale to system default');
state = _getDefaultLocale();
_prefs.remove(_key);
}
return ref.watch(communityTranslationsProvider)
? AppLocalizations.supportedLocales
: locales;
});

final currentLocaleProvider = Provider<Locale>(
(ref) {
final localeStr = Platform.environment['_YA_LOCALE'];
static AppLocale _getDefaultLocale() => AppLocale(
basicLocaleListResolution(PlatformDispatcher.instance.locales,
AppLocalizations.supportedLocales),
true,
);

static AppLocale _fromName(String? localeStr) {
if (localeStr != null) {
// Force locale
final locale = Locale(localeStr, '');
return basicLocaleListResolution(
[locale], AppLocalizations.supportedLocales);
return AppLocale(
basicLocaleListResolution([locale], AppLocalizations.supportedLocales),
false,
);
}
// Choose from supported
return basicLocaleListResolution(PlatformDispatcher.instance.locales,
ref.watch(supportedLocalesProvider));
},
);
return _getDefaultLocale();
}
}

final l10nProvider = Provider<AppLocalizations>(
(ref) => lookupAppLocalizations(ref.watch(currentLocaleProvider)),
(ref) => lookupAppLocalizations(ref.watch(currentLocaleProvider).locale),
);

final themeModeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>(
Expand Down
91 changes: 67 additions & 24 deletions lib/app/views/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand All @@ -25,6 +23,7 @@ import '../../android/views/settings_views.dart';
import '../../core/state.dart';
import '../../widgets/list_title.dart';
import '../../widgets/responsive_dialog.dart';
import '../models.dart';
import '../state.dart';
import 'keys.dart' as keys;

Expand All @@ -36,6 +35,19 @@ extension on ThemeMode {
};
}

extension on Locale {
String getDisplayName(AppLocalizations l10n) => switch (languageCode) {
'en' => l10n.s_english,
'de' => l10n.s_german,
'fr' => l10n.s_french,
'ja' => l10n.s_japanese,
'pl' => l10n.s_polish,
'sk' => l10n.s_slovak,
'vi' => l10n.s_vietnamese,
_ => languageCode
};
}

class _ThemeModeView extends ConsumerWidget {
const _ThemeModeView();

Expand Down Expand Up @@ -80,23 +92,61 @@ class _ThemeModeView extends ConsumerWidget {
}
}

class _CommunityTranslationsView extends ConsumerWidget {
const _CommunityTranslationsView();
class _LanguageView extends ConsumerWidget {
const _LanguageView();

void _selectLocale(
BuildContext context,
WidgetRef ref,
AppLocale currentLocale,
) async {
final groupValue =
currentLocale.systemDefault ? null : currentLocale.locale;
await showDialog(
context: context,
builder: (context) {
final l10n = AppLocalizations.of(context)!;
return SimpleDialog(
title: Text('Choose language'),
children: [
RadioListTile(
title: Text(l10n.s_system_default),
value: null,
groupValue: groupValue,
toggleable: true,
onChanged: (_) {
ref.read(currentLocaleProvider.notifier).resetLocale();
Navigator.pop(context);
},
),
...AppLocalizations.supportedLocales.map(
(e) => RadioListTile(
title: Text(e.getDisplayName(l10n)),
value: e,
groupValue: groupValue,
toggleable: true,
onChanged: (value) {
ref.read(currentLocaleProvider.notifier).setLocale(e);
Navigator.pop(context);
},
),
)
],
);
},
);
}

@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final enableTranslations = ref.watch(communityTranslationsProvider);
return SwitchListTile(
title: Text(l10n.l_enable_community_translations),
subtitle: Text(l10n.p_community_translations_desc),
isThreeLine: true,
value: enableTranslations,
onChanged: (value) {
ref
.read(communityTranslationsProvider.notifier)
.setEnableCommunityTranslations(value);
});
final currentLocale = ref.watch(currentLocaleProvider);
return ListTile(
title: Text(l10n.s_language),
subtitle: Text(currentLocale.locale.getDisplayName(l10n)),
key: keys.themeModeSetting,
onTap: () => _selectLocale(context, ref, currentLocale),
);
}
}

Expand All @@ -106,7 +156,6 @@ class SettingsPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final enableTranslations = ref.watch(communityTranslationsProvider);

return ResponsiveDialog(
title: Text(l10n.s_settings),
Expand All @@ -128,14 +177,8 @@ class SettingsPage extends ConsumerWidget {
],
ListTitle(l10n.s_appearance),
const _ThemeModeView(),
if (enableTranslations ||
basicLocaleListResolution(
PlatformDispatcher.instance.locales, officialLocales) !=
basicLocaleListResolution(PlatformDispatcher.instance.locales,
AppLocalizations.supportedLocales)) ...[
ListTitle(l10n.s_language),
const _CommunityTranslationsView(),
],
ListTitle(l10n.s_options),
const _LanguageView()
],
),
);
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "Sprache",
"l_enable_community_translations": "Community-Übersetzungen aktivieren",
"p_community_translations_desc": "Diese Übersetzungen werden von der Community bereitgestellt und gepflegt. Sie können Fehler enthalten oder unvollständig sein.",
"s_choose_language": null,
"s_english": null,
"s_french": null,
"s_german": null,
"s_polish": null,
"s_slovak": null,
"s_vietnamese": null,
"s_japanese": null,


"@_theme": {},
"s_app_theme": "App Theme",
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "Language",
"l_enable_community_translations": "Enable community translations",
"p_community_translations_desc": "These translations are provided and maintained by the community. They may contain errors or be incomplete.",
"s_choose_language": "Choose language",
"s_english": "English",
"s_french": "French",
"s_german": "German",
"s_polish": "Polish",
"s_slovak": "Slovak",
"s_vietnamese": "Vietnamese",
"s_japanese": "Japanese",


"@_theme": {},
"s_app_theme": "Application theme",
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "Langue",
"l_enable_community_translations": "Activer traductions communautaires",
"p_community_translations_desc": "Ces traductions sont fournies et gérées par la communauté. Elles peuvent être erronées ou incomplètes.",
"s_choose_language": null,
"s_english": null,
"s_french": null,
"s_german": null,
"s_polish": null,
"s_slovak": null,
"s_vietnamese": null,
"s_japanese": null,


"@_theme": {},
"s_app_theme": "Thème de l'application",
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_ja.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "言語",
"l_enable_community_translations": "コミュニティ翻訳を有効にする",
"p_community_translations_desc": "これらの翻訳はコミュニティによって提供され、更新されます。エラーが含まれているか、不完全である可能性があります。",
"s_choose_language": null,
"s_english": null,
"s_french": null,
"s_german": null,
"s_polish": null,
"s_slovak": null,
"s_vietnamese": null,
"s_japanese": null,


"@_theme": {},
"s_app_theme": "アプリケーションテーマ",
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_pl.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "Język",
"l_enable_community_translations": "Włącz tłumaczenia społecznościowe",
"p_community_translations_desc": "Tłumaczenia są dostarczane i utrzymywane przez społeczność. Mogą zawierać błędy lub być niekompletne.",
"s_choose_language": null,
"s_english": null,
"s_french": null,
"s_german": null,
"s_polish": null,
"s_slovak": null,
"s_vietnamese": null,
"s_japanese": null,


"@_theme": {},
"s_app_theme": "Motyw aplikacji",
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_sk.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "Jazyk",
"l_enable_community_translations": "Povoliť komunitné preklady",
"p_community_translations_desc": "Tieto preklady zabezpečuje a udržiava komunita. Môžu obsahovať chyby alebo byť neúplné.",
"s_choose_language": null,
"s_english": null,
"s_french": null,
"s_german": null,
"s_polish": null,
"s_slovak": null,
"s_vietnamese": null,
"s_japanese": null,


"@_theme": {},
"s_app_theme": "Téma aplikácie",
Expand Down
9 changes: 9 additions & 0 deletions lib/l10n/app_vi.arb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"s_language": "Ngôn ngữ",
"l_enable_community_translations": "Bật dịch thuật cộng đồng",
"p_community_translations_desc": "Các bản dịch này được cung cấp và duy trì bởi cộng đồng. Chúng có thể chứa lỗi hoặc chưa hoàn chỉnh.",
"s_choose_language": null,
"s_english": null,
"s_french": null,
"s_german": null,
"s_polish": null,
"s_slovak": null,
"s_vietnamese": null,
"s_japanese": null,


"@_theme": {},
"s_app_theme": "Chủ đề ứng dụng",
Expand Down
2 changes: 1 addition & 1 deletion lib/piv/views/cert_info_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CertInfoTable extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final dateFormat =
DateFormat.yMMMEd(ref.watch(currentLocaleProvider).toString());
DateFormat.yMMMEd(ref.watch(currentLocaleProvider).locale.toString());

final certInfo = this.certInfo;
final metadata = this.metadata;
Expand Down

0 comments on commit f9b5f72

Please sign in to comment.