1
0
mirror of https://github.com/immich-app/immich.git synced 2025-01-04 13:14:29 +02:00

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 locale_code: es-MX
- file: mobile/assets/i18n/sv-FI.json - file: mobile/assets/i18n/sv-FI.json
locale_code: sv-FI locale_code: sv-FI
- file: mobile/assets/i18n/ca.json - file: mobile/assets/i18n/ca-CA.json
locale_code: ca locale_code: ca-CA
- file: mobile/assets/i18n/hu-HU.json - file: mobile/assets/i18n/hu-HU.json
locale_code: hu-HU locale_code: hu-HU
- file: mobile/assets/i18n/lv-LV.json - file: mobile/assets/i18n/lv-LV.json
@ -76,3 +76,19 @@ download:
locale_code: zh-Hans locale_code: zh-Hans
- file: mobile/assets/i18n/th-TH.json - file: mobile/assets/i18n/th-TH.json
locale_code: th-TH 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_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Show background backup total progress", "setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings", "setting_pages_app_bar_settings": "Settings",
"setting_languages_title": "Languages",
"settings_require_restart": "Please restart Immich to apply this setting", "settings_require_restart": "Please restart Immich to apply this setting",
"setting_languages_apply": "Apply",
"share_add": "Add", "share_add": "Add",
"share_add_photos": "Add photos", "share_add_photos": "Add photos",
"share_add_title": "Add a title", "share_add_title": "Add a title",

View File

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

View File

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

View File

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

View File

@ -11,14 +11,14 @@ Future<bool> loadTranslations() async {
await EasyLocalizationController.initEasyLocation(); await EasyLocalizationController.initEasyLocation();
final controller = EasyLocalizationController( final controller = EasyLocalizationController(
supportedLocales: locales, supportedLocales: locales.values.toList(),
useFallbackTranslations: true, useFallbackTranslations: true,
saveLocale: true, saveLocale: true,
assetLoader: const RootBundleAssetLoader(), assetLoader: const RootBundleAssetLoader(),
path: translationsPath, path: translationsPath,
useOnlyLangCode: false, useOnlyLangCode: false,
onLoadError: (e) => debugPrint(e.toString()), onLoadError: (e) => debugPrint(e.toString()),
fallbackLocale: locales.first, fallbackLocale: locales.values.first,
); );
await controller.loadTranslations(); 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:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/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/advanced_settings.dart';
import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_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'; import 'package:immich_mobile/modules/settings/ui/backup_settings/backup_settings.dart';
@ -16,6 +19,7 @@ enum SettingSection {
'setting_notifications_title', 'setting_notifications_title',
Icons.notifications_none_rounded, Icons.notifications_none_rounded,
), ),
languages('setting_languages_title', Icons.language),
preferences('preferences_settings_title', Icons.interests_outlined), preferences('preferences_settings_title', Icons.interests_outlined),
backup('backup_controller_page_backup', Icons.cloud_upload_outlined), backup('backup_controller_page_backup', Icons.cloud_upload_outlined),
timeline('asset_list_settings_title', Icons.auto_awesome_mosaic_outlined), timeline('asset_list_settings_title', Icons.auto_awesome_mosaic_outlined),
@ -27,6 +31,7 @@ enum SettingSection {
Widget get widget => switch (this) { Widget get widget => switch (this) {
SettingSection.notifications => const NotificationSetting(), SettingSection.notifications => const NotificationSetting(),
SettingSection.languages => const LanguageSettings(),
SettingSection.preferences => const PreferenceSetting(), SettingSection.preferences => const PreferenceSetting(),
SettingSection.backup => const BackupSettings(), SettingSection.backup => const BackupSettings(),
SettingSection.timeline => const AssetListSettings(), SettingSection.timeline => const AssetListSettings(),
@ -37,6 +42,70 @@ enum SettingSection {
const SettingSection(this.title, this.icon); 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() @RoutePage()
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
const SettingsPage({super.key}); const SettingsPage({super.key});