diff --git a/lib/app/app.dart b/lib/app/app.dart index dbe46219e..56b0a0c2f 100755 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -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, diff --git a/lib/app/models.dart b/lib/app/models.dart index 1a9838a8c..a384a35da 100755 --- a/lib/app/models.dart +++ b/lib/app/models.dart @@ -170,3 +170,10 @@ class _ColorConverter implements JsonConverter { @override int? toJson(Color? object) => object?.toInt32; } + +class AppLocale { + final Locale locale; + final bool systemDefault; + + const AppLocale(this.locale, this.systemDefault); +} diff --git a/lib/app/state.dart b/lib/app/state.dart index dc306a6d5..4fa84df82 100755 --- a/lib/app/state.dart +++ b/lib/app/state.dart @@ -16,7 +16,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; @@ -71,53 +70,50 @@ final supportedThemesProvider = StateProvider>( (ref) => throw UnimplementedError(), ); -final communityTranslationsProvider = - StateNotifierProvider( - (ref) => CommunityTranslationsNotifier(ref.watch(prefProvider))); +final currentLocaleProvider = + StateNotifierProvider( + (ref) => CurrentLocaleProvider(ref.watch(prefProvider)), +); -class CommunityTranslationsNotifier extends StateNotifier { - static const String _key = 'APP_STATE_ENABLE_COMMUNITY_TRANSLATIONS'; +class CurrentLocaleProvider extends StateNotifier { + 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>((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( - (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( - (ref) => lookupAppLocalizations(ref.watch(currentLocaleProvider)), + (ref) => lookupAppLocalizations(ref.watch(currentLocaleProvider).locale), ); final themeModeProvider = StateNotifierProvider( diff --git a/lib/app/views/settings_page.dart b/lib/app/views/settings_page.dart index 9f40f8017..3366ef160 100755 --- a/lib/app/views/settings_page.dart +++ b/lib/app/views/settings_page.dart @@ -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'; @@ -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; @@ -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(); @@ -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), + ); } } @@ -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), @@ -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() ], ), ); diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9b1584502..b8f43453d 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -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", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2f4971966..362051772 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 7d54f1e49..e842519d2 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -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", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 0c92868b1..a97318cb5 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -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": "アプリケーションテーマ", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b0e25e43c..7148d6ba6 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -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", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 774ea2fef..aed9b07a6 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -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", diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index d32ecdf2a..85e6acb79 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -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", diff --git a/lib/piv/views/cert_info_view.dart b/lib/piv/views/cert_info_view.dart index 945b1a7cf..4fb926763 100644 --- a/lib/piv/views/cert_info_view.dart +++ b/lib/piv/views/cert_info_view.dart @@ -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;