feat(mobile): in app language selector (#8574)

* feat(mobile): select locale in the mobile app

* add additional locale

* use the same locale variable across the app

* using different data structure

* drop down with button

* update pull locales

* open app ios

* remove dependency

* format fix
This commit is contained in:
Alex 2024-04-06 21:58:35 -05:00 committed by GitHub
parent 335c03d0b8
commit 82aeb3292a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 584 additions and 489 deletions

View File

@ -66,8 +66,8 @@ download:
locale_code: es-MX
- file: mobile/assets/i18n/sv-FI.json
locale_code: sv-FI
- file: mobile/assets/i18n/ca.json
locale_code: ca
- file: mobile/assets/i18n/ca-CA.json
locale_code: ca-CA
- file: mobile/assets/i18n/hu-HU.json
locale_code: hu-HU
- file: mobile/assets/i18n/lv-LV.json
@ -76,3 +76,19 @@ download:
locale_code: zh-Hans
- file: mobile/assets/i18n/th-TH.json
locale_code: th-TH
- file: mobile/assets/i18n/lt-LT.json
locale_code: lt-LT
- file: mobile/assets/i18n/el-GR.json
locale_code: el-GR
- file: mobile/assets/i18n/fr-CA.json
locale_code: fr-CA
- file: mobile/assets/i18n/es-US.json
locale_code: es-US
- file: mobile/assets/i18n/sl-SI.json
locale_code: sl-SI
- file: mobile/assets/i18n/ar-JO.json
locale_code: ar-JO
- file: mobile/assets/i18n/he-IL.json
locale_code: he-IL
- file: mobile/assets/i18n/ro-RO.json
locale_code: ro-RO

View File

@ -400,7 +400,9 @@
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings",
"setting_languages_title": "Languages",
"settings_require_restart": "Please restart Immich to apply this setting",
"setting_languages_apply": "Apply",
"share_add": "Add",
"share_add_photos": "Add photos",
"share_add_title": "Add a title",

View File

@ -1,121 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>app.alextran.immich.backgroundFetch</string>
<string>app.alextran.immich.backgroundProcessing</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Immich</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>de</string>
<string>da</string>
<string>it</string>
<string>es</string>
<string>vi</string>
<string>fr</string>
<string>ja</string>
<string>pl</string>
<string>fi</string>
<string>pt</string>
<string>cs</string>
<string>uk</string>
<string>ru</string>
<string>zh</string>
<string>sk</string>
<string>nl</string>
<string>nb</string>
<string>sv</string>
<string>mn</string>
<string>ko</string>
<string>sr</string>
<string>hi</string>
<string>ca</string>
<string>hu</string>
<string>lv</string>
<string>th</string>
<string>sl</string>
</array>
<key>CFBundleName</key>
<string>immich_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.101.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>147</string>
<key>FLTEnableImpeller</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>app.alextran.immich.backgroundFetch</string>
<string>app.alextran.immich.backgroundProcessing</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Immich</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ar</string>
<string>ca</string>
<string>cs</string>
<string>da</string>
<string>de</string>
<string>es</string>
<string>fi</string>
<string>fr</string>
<string>he</string>
<string>hi</string>
<string>hu</string>
<string>it</string>
<string>ja</string>
<string>ko</string>
<string>lv</string>
<string>mn</string>
<string>nb</string>
<string>nl</string>
<string>pl</string>
<string>pt</string>
<string>ro</string>
<string>ru</string>
<string>sk</string>
<string>sl</string>
<string>sr</string>
<string>sv</string>
<string>th</string>
<string>uk</string>
<string>vi</string>
<string>zh</string>
</array>
<key>CFBundleName</key>
<string>immich_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.101.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>147</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true />
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true />
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false />
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true />
<key>io.flutter.embedded_views_preview</key>
<true />
</dict>
</plist>

View File

@ -1,43 +1,48 @@
import 'dart:ui';
const List<Locale> locales = [
const Map<String, Locale> locales = {
// Default locale
Locale('en', 'US'),
'English (en_US)': Locale('en', 'US'),
// Additional locales
Locale('de', 'DE'),
Locale('da', 'DK'),
Locale('it', 'IT'),
Locale('es', 'ES'),
Locale('vi', 'VN'),
Locale('fr', 'CA'),
Locale('fr', 'FR'),
Locale('ja', 'JP'),
Locale('pl', 'PL'),
Locale('fi', 'FI'),
Locale('pt', 'PT'),
Locale('cs', 'CZ'),
Locale('uk', 'UA'),
Locale('ru', 'RU'),
Locale('zh', 'CN'),
Locale('sk', 'SK'),
Locale('nl', 'NL'),
Locale('nb', 'NO'),
Locale('sv', 'SE'),
Locale('mn', 'MN'),
Locale('ko', 'KR'),
Locale('sr', 'Latn'),
Locale('sr', 'Cyrl'),
Locale('hi', 'IN'),
Locale('es', 'PE'),
Locale('es', 'MX'),
Locale('es', 'US'),
Locale('sv', 'FI'),
Locale('ca', 'CA'),
Locale('hu', 'HU'),
Locale('lv', 'LV'),
Locale('zh', 'Hans'),
Locale('th', 'TH'),
Locale('sl', 'SI'),
];
'Arabic (ar_JO)': Locale('ar', 'JO'),
'Catalan (ca_CA)': Locale('ca', 'CA'),
'Chinese (zh_CN)': Locale('zh', 'CN'),
'Chinese Simplified (zh_Hans)': Locale('zh', 'Hans'),
'Czech (cs_CZ)': Locale('cs', 'CZ'),
'Danish (da_DK)': Locale('da', 'DK'),
'Dutch (nl_NL)': Locale('nl', 'NL'),
'Finnish (fi_FI)': Locale('fi', 'FI'),
'French (fr_CA)': Locale('fr', 'CA'),
'French (fr_FR)': Locale('fr', 'FR'),
'German (de_DE)': Locale('de', 'DE'),
'Greek (el_GR)': Locale('el', 'GR'),
'Hebrew (he_IL)': Locale('he', 'IL'),
'Hindi (hi_IN)': Locale('hi', 'IN'),
'Hungarian (hu_HU)': Locale('hu', 'HU'),
'Italian (it_IT)': Locale('it', 'IT'),
'Japanese (ja_JP)': Locale('ja', 'JP'),
'Korean (ko_KR)': Locale('ko', 'KR'),
'Latvian (lv_LV)': Locale('lv', 'LV'),
'Lithuanian (lt_LT)': Locale('lt', 'LT'),
'Mongolian (mn_MN)': Locale('mn', 'MN'),
'Norwegian Bokmål (nb_NO)': Locale('nb', 'NO'),
'Polish (pl_PL)': Locale('pl', 'PL'),
'Portuguese (pt_PT)': Locale('pt', 'PT'),
'Romanian (ro_RO)': Locale('ro', 'RO'),
'Russian (ru_RU)': Locale('ru', 'RU'),
'Serbian Cyrillic (sr_Cyrl)': Locale('sr', 'Cyrl'),
'Serbian Latin (sr_Latn)': Locale('sr', 'Latn'),
'Slovak (sk_SK)': Locale('sk', 'SK'),
'Slovenian (sl_SI)': Locale('sl', 'SI'),
'Spanish (es_ES)': Locale('es', 'ES'),
'Spanish (es_MX)': Locale('es', 'MX'),
'Spanish (es_PE)': Locale('es', 'PE'),
'Spanish (es_US)': Locale('es', 'US'),
'Swedish (sv_FI)': Locale('sv', 'FI'),
'Swedish (sv_SE)': Locale('sv', 'SE'),
'Thai (th_TH)': Locale('th', 'TH'),
'Ukrainian (uk_UA)': Locale('uk', 'UA'),
'Vietnamese (vi_VN)': Locale('vi', 'VN'),
};
const String translationsPath = 'assets/i18n';

View File

@ -215,10 +215,10 @@ class MainWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return EasyLocalization(
supportedLocales: locales,
supportedLocales: locales.values.toList(),
path: translationsPath,
useFallbackTranslations: true,
fallbackLocale: locales.first,
fallbackLocale: locales.values.first,
child: const ImmichApp(),
);
}

View File

@ -11,14 +11,14 @@ Future<bool> loadTranslations() async {
await EasyLocalizationController.initEasyLocation();
final controller = EasyLocalizationController(
supportedLocales: locales,
supportedLocales: locales.values.toList(),
useFallbackTranslations: true,
saveLocale: true,
assetLoader: const RootBundleAssetLoader(),
path: translationsPath,
useOnlyLangCode: false,
onLoadError: (e) => debugPrint(e.toString()),
fallbackLocale: locales.first,
fallbackLocale: locales.values.first,
);
await controller.loadTranslations();

View File

@ -2,7 +2,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/backup/background_service/localization.dart';
import 'package:immich_mobile/modules/settings/ui/advanced_settings.dart';
import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_settings.dart';
import 'package:immich_mobile/modules/settings/ui/backup_settings/backup_settings.dart';
@ -16,6 +19,7 @@ enum SettingSection {
'setting_notifications_title',
Icons.notifications_none_rounded,
),
languages('setting_languages_title', Icons.language),
preferences('preferences_settings_title', Icons.interests_outlined),
backup('backup_controller_page_backup', Icons.cloud_upload_outlined),
timeline('asset_list_settings_title', Icons.auto_awesome_mosaic_outlined),
@ -27,6 +31,7 @@ enum SettingSection {
Widget get widget => switch (this) {
SettingSection.notifications => const NotificationSetting(),
SettingSection.languages => const LanguageSettings(),
SettingSection.preferences => const PreferenceSetting(),
SettingSection.backup => const BackupSettings(),
SettingSection.timeline => const AssetListSettings(),
@ -37,6 +42,70 @@ enum SettingSection {
const SettingSection(this.title, this.icon);
}
class LanguageSettings extends HookConsumerWidget {
const LanguageSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentLocale = context.locale;
final textController = useTextEditingController(
text: locales.keys.firstWhere(
(countryName) => locales[countryName] == currentLocale,
),
);
final selectedLocale = useState<Locale>(currentLocale);
return ListView(
padding: const EdgeInsets.all(16),
children: [
DropdownMenu(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
contentPadding: const EdgeInsets.only(left: 16),
),
menuStyle: MenuStyle(
shape: MaterialStatePropertyAll<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
menuHeight: context.height * 0.5,
hintText: "Languages",
label: const Text('Languages'),
dropdownMenuEntries: locales.keys
.map(
(countryName) => DropdownMenuEntry(
value: locales[countryName],
label: countryName,
),
)
.toList(),
controller: textController,
onSelected: (value) {
if (value != null) {
selectedLocale.value = value;
}
},
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: selectedLocale.value == currentLocale
? null
: () {
context.setLocale(selectedLocale.value);
loadTranslations();
},
child: const Text('setting_languages_apply').tr(),
),
],
);
}
}
@RoutePage()
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});