From 53a4a0d15a63ceba6bc06d6146b5013fb4562a68 Mon Sep 17 00:00:00 2001 From: dvbthien Date: Thu, 5 Jun 2025 20:50:31 +0700 Subject: [PATCH] Revert "re-write localization service and add translation extension" This reverts commit fdd7386020f638b92ad4f4691667d67e8c2935fc. --- .../extensions/translation_extensions.dart | 16 -- mobile/lib/main.dart | 48 +++--- mobile/lib/services/background.service.dart | 26 +-- mobile/lib/services/localization.service.dart | 159 ++++-------------- .../widgets/settings/language_settings.dart | 18 +- 5 files changed, 77 insertions(+), 190 deletions(-) delete mode 100644 mobile/lib/extensions/translation_extensions.dart diff --git a/mobile/lib/extensions/translation_extensions.dart b/mobile/lib/extensions/translation_extensions.dart deleted file mode 100644 index 9ef3741e75..0000000000 --- a/mobile/lib/extensions/translation_extensions.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_mobile/services/localization.service.dart'; - -final _translationService = EasyLocalizationService(); - -extension StringTranslation on String { - String t([Map? args]) { - return _translationService.translate(this, args); - } -} - -extension BuildContextTranslation on BuildContext { - String t(String key, [Map? args]) { - return _translationService.translate(key, args); - } -} diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 19aba77cee..32bb025916 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -11,7 +11,6 @@ import 'package:flutter_displaymode/flutter_displaymode.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/translation_extensions.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; @@ -23,7 +22,6 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/app_navigation_observer.dart'; import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/local_notification.service.dart'; -import 'package:immich_mobile/services/localization.service.dart'; import 'package:immich_mobile/theme/dynamic_theme.dart'; import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; @@ -160,12 +158,12 @@ class ImmichAppState extends ConsumerState void _configureFileDownloaderNotifications() { FileDownloader().configureNotification( running: TaskNotification( - 'downloading_media'.t(), - '${'file_name'.t()}: {filename}', + 'downloading_media'.tr(), + '${'file_name'.tr()}: {filename}', ), complete: TaskNotification( - 'download_finished'.t(), - '${'file_name'.t()}: {filename}', + 'download_finished'.tr(), + '${'file_name'.tr()}: {filename}', ), progressBar: true, ); @@ -207,34 +205,34 @@ class ImmichAppState extends ConsumerState overrides: [ localeProvider.overrideWithValue(context.locale), ], - child: MaterialApp.router( - title: 'Immich', - debugShowCheckedModeBanner: true, + child: MaterialApp( localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - themeMode: ref.watch(immichThemeModeProvider), - darkTheme: getThemeData( - colorScheme: immichTheme.dark, - locale: context.locale, + debugShowCheckedModeBanner: true, + home: MaterialApp.router( + title: 'Immich', + debugShowCheckedModeBanner: false, + themeMode: ref.watch(immichThemeModeProvider), + darkTheme: getThemeData( + colorScheme: immichTheme.dark, + locale: context.locale, + ), + theme: getThemeData( + colorScheme: immichTheme.light, + locale: context.locale, + ), + routeInformationParser: router.defaultRouteParser(), + routerDelegate: router.delegate( + navigatorObservers: () => [AppNavigationObserver(ref: ref)], + ), ), - theme: getThemeData( - colorScheme: immichTheme.light, - locale: context.locale, - ), - routeInformationParser: router.defaultRouteParser(), - routerDelegate: router.delegate( - navigatorObservers: () => [AppNavigationObserver(ref: ref)], - ), - builder: (context, child) { - EasyLocalizationService.setContext(context); - return child!; - }, ), ); } } +// ignore: prefer-single-widget-per-file class MainWidget extends StatelessWidget { const MainWidget({super.key}); diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart index 1b59b7a563..335f71acab 100644 --- a/mobile/lib/services/background.service.dart +++ b/mobile/lib/services/background.service.dart @@ -6,6 +6,7 @@ import 'dart:ui' show DartPluginRegistrant, IsolateNameServer, PluginUtilities; import 'package:cancellation_token_http/http.dart'; import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -33,7 +34,6 @@ import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:path_provider_foundation/path_provider_foundation.dart'; -import 'package:immich_mobile/extensions/translation_extensions.dart'; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; final backgroundServiceProvider = Provider((ref) => BackgroundService()); @@ -76,7 +76,8 @@ class BackgroundService { Future enableService({bool immediate = false}) async { try { final callback = PluginUtilities.getCallbackHandle(_nativeEntry)!; - final String title = 'backup_background_service_default_notification'.t(); + final String title = + "backup_background_service_default_notification".tr(); final bool ok = await _foregroundChannel .invokeMethod('enable', [callback.toRawHandle(), title, immediate]); return ok; @@ -324,8 +325,7 @@ class BackgroundService { return false; } - final translationsOk = - await EasyLocalizationService().loadTranslations(); + final translationsOk = await loadTranslations(); if (!translationsOk) { debugPrint("[_callHandler] could not load translations"); } @@ -452,8 +452,8 @@ class BackgroundService { toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); } catch (e) { _showErrorNotification( - title: 'backup_background_service_error_title'.t(), - content: 'backup_background_service_connection_failed_message'.t(), + title: "backup_background_service_error_title".tr(), + content: "backup_background_service_connection_failed_message".tr(), ); return false; } @@ -468,7 +468,7 @@ class BackgroundService { _assetsToUploadCount = toUpload.length; _uploadedAssetsCount = 0; _updateNotification( - title: 'backup_background_service_in_progress_notification'.t(), + title: "backup_background_service_in_progress_notification".tr(), content: notifyTotalProgress ? formatAssetBackupProgress( _uploadedAssetsCount, @@ -502,8 +502,8 @@ class BackgroundService { if (!ok && !_cancellationToken!.isCancelled) { _showErrorNotification( - title: 'backup_background_service_error_title'.t(), - content: 'backup_background_service_backup_failed_message'.t(), + title: "backup_background_service_error_title".tr(), + content: "backup_background_service_backup_failed_message".tr(), ); } @@ -561,8 +561,8 @@ class BackgroundService { void _onBackupError(ErrorUploadAsset errorAssetInfo) { _showErrorNotification( - title: 'backup_background_service_upload_failure_notification' - .t({'filename': errorAssetInfo.fileName}), + title: "backup_background_service_upload_failure_notification" + .tr(namedArgs: {'filename': errorAssetInfo.fileName}), individualTag: errorAssetInfo.id, ); } @@ -576,8 +576,8 @@ class BackgroundService { } _throttledDetailNotify.title = - 'backup_background_service_current_upload_notification' - .t({'filename': currentUploadAsset.fileName}); + "backup_background_service_current_upload_notification" + .tr(namedArgs: {'filename': currentUploadAsset.fileName}); _throttledDetailNotify.progress = 0; _throttledDetailNotify.total = 0; } diff --git a/mobile/lib/services/localization.service.dart b/mobile/lib/services/localization.service.dart index af3fb746d4..8bee710544 100644 --- a/mobile/lib/services/localization.service.dart +++ b/mobile/lib/services/localization.service.dart @@ -1,128 +1,31 @@ -// ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:easy_localization/src/easy_localization_controller.dart'; -import 'package:easy_localization/src/localization.dart'; - -import 'package:intl/message_format.dart'; -import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/generated/codegen_loader.g.dart'; - -abstract class ILocalizationService { - String translate(String key, [Map? args]); - - Locale get currentLocale; - - bool get isInitialized; - - Future loadTranslations(); - - Future changeLocale(Locale localeCode); -} - -class EasyLocalizationService implements ILocalizationService { - EasyLocalizationService._internal(); - - static EasyLocalizationService? _instance; - static EasyLocalizationService get instance { - _instance ??= EasyLocalizationService._internal(); - return _instance!; - } - - factory EasyLocalizationService() => instance; - - static BuildContext? _context; - static EasyLocalizationController? _controller; - static bool _isInitialized = false; - - static void setContext(BuildContext context) { - if (_context != context) { - debugPrint('🔄 Updating EasyLocalization context'); - _context = context; - } - } - - static BuildContext? get context => _context; - - @override - String translate(String key, [Map? args]) { - if (_context != null) { - try { - String message = _context!.tr(key); - if (args != null) { - return MessageFormat(message, locale: Intl.defaultLocale ?? 'en') - .format(args); - } - return message; - } catch (e) { - debugPrint('❌ Translation error for key "$key": $e'); - return key; - } - } - return key; - } - - @override - Locale get currentLocale { - if (_controller != null) { - return _controller!.locale; - } - if (_context != null) { - return _context!.locale; - } - return Intl.defaultLocale != null - ? Locale(Intl.defaultLocale!) - : locales.values.first; - } - - @override - bool get isInitialized => _isInitialized; - - @override - Future changeLocale(Locale localeCode) async { - try { - if (_context != null) { - await _context!.setLocale(localeCode); - } - if (_controller != null) { - await _controller!.setLocale(localeCode); - } - debugPrint('✅ Locale changed to: $localeCode'); - } catch (e) { - debugPrint('❌ Failed to change locale to $localeCode: $e'); - } - } - - @override - Future loadTranslations() async { - if (_isInitialized) { - debugPrint('✅ Translations already loaded'); - return true; - } - try { - await EasyLocalizationController.initEasyLocation(); - _controller = EasyLocalizationController( - supportedLocales: locales.values.toList(), - useFallbackTranslations: true, - saveLocale: true, - assetLoader: const CodegenLoader(), - path: translationsPath, - useOnlyLangCode: false, - onLoadError: (e) => debugPrint(e.toString()), - fallbackLocale: locales.values.first, - ); - await _controller!.loadTranslations(); - final bool result = Localization.load( - _controller!.locale, - translations: _controller!.translations, - fallbackTranslations: _controller!.fallbackTranslations, - ); - _isInitialized = result; - debugPrint('✅ Translations loaded: $result'); - return result; - } catch (e) { - debugPrint('❌ Error loading translations: $e'); - return false; - } - } -} +// ignore_for_file: implementation_imports + +import 'package:easy_localization/src/easy_localization_controller.dart'; +import 'package:easy_localization/src/localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/constants/locales.dart'; +import 'package:immich_mobile/generated/codegen_loader.g.dart'; + +/// Workaround to manually load translations in another Isolate +Future loadTranslations() async { + await EasyLocalizationController.initEasyLocation(); + + final controller = EasyLocalizationController( + supportedLocales: locales.values.toList(), + useFallbackTranslations: true, + saveLocale: true, + assetLoader: const CodegenLoader(), + path: translationsPath, + useOnlyLangCode: false, + onLoadError: (e) => debugPrint(e.toString()), + fallbackLocale: locales.values.first, + ); + + await controller.loadTranslations(); + + return Localization.load( + controller.locale, + translations: controller.translations, + fallbackTranslations: controller.fallbackTranslations, + ); +} diff --git a/mobile/lib/widgets/settings/language_settings.dart b/mobile/lib/widgets/settings/language_settings.dart index f3e4d7c25b..7dc7f89ea1 100644 --- a/mobile/lib/widgets/settings/language_settings.dart +++ b/mobile/lib/widgets/settings/language_settings.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/services/localization.service.dart'; -import 'package:immich_mobile/extensions/translation_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; @@ -12,14 +12,15 @@ class LanguageSettings extends HookConsumerWidget { const LanguageSettings({super.key}); Future _applyLanguageChange( + BuildContext context, ValueNotifier selectedLocale, ValueNotifier isLoading, ) async { isLoading.value = true; await Future.delayed(const Duration(milliseconds: 500)); try { - await EasyLocalizationService().changeLocale(selectedLocale.value); - await EasyLocalizationService().loadTranslations(); + await context.setLocale(selectedLocale.value); + await loadTranslations(); } finally { isLoading.value = false; } @@ -28,7 +29,7 @@ class LanguageSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final localeEntries = useMemoized(() => locales.entries.toList(), const []); - final currentLocale = EasyLocalizationService().currentLocale; + final currentLocale = context.locale; final filteredLocaleEntries = useState>>(localeEntries); final selectedLocale = useState(currentLocale); @@ -114,6 +115,7 @@ class LanguageSettings extends HookConsumerWidget { isDisabled: isButtonDisabled, isLoading: isLoading.value, onPressed: () => _applyLanguageChange( + context, selectedLocale, isLoading, ), @@ -160,7 +162,7 @@ class _LanguageSearchBar extends StatelessWidget { child: SearchField( autofocus: false, contentPadding: const EdgeInsets.all(12), - hintText: 'language_search_hint'.t(), + hintText: 'language_search_hint'.tr(), prefixIcon: const Icon(Icons.search_rounded), suffixIcon: controller.text.isNotEmpty ? IconButton( @@ -194,14 +196,14 @@ class _LanguageNotFound extends StatelessWidget { ), const SizedBox(height: 8), Text( - 'language_no_results_title'.t(), + 'language_no_results_title'.tr(), style: context.textTheme.titleMedium?.copyWith( color: context.colorScheme.onSurface, ), ), const SizedBox(height: 4), Text( - 'language_no_results_subtitle'.t(), + 'language_no_results_subtitle'.tr(), style: context.textTheme.bodyMedium?.copyWith( color: context.colorScheme.onSurface.withValues(alpha: 0.8), ), @@ -244,7 +246,7 @@ class _LanguageApplyButton extends StatelessWidget { ), ) : Text( - 'setting_languages_apply'.t(), + 'setting_languages_apply'.tr(), style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 16.0,