diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 82b49f83b5..be7d1aa12c 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -40,6 +40,7 @@ import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/cache/widgets_binding.dart'; import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:immich_mobile/utils/fallback_localizations.dart'; import 'package:immich_mobile/utils/licenses.dart'; import 'package:immich_mobile/utils/migration.dart'; import 'package:immich_mobile/wm_executor.dart'; @@ -283,7 +284,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve title: 'Immich', debugShowCheckedModeBanner: true, scaffoldMessengerKey: scaffoldMessengerKey, - localizationsDelegates: context.localizationDelegates, + localizationsDelegates: [...context.localizationDelegates, ...fallbackLocalizationDelegates], supportedLocales: context.supportedLocales, locale: context.locale, themeMode: ref.watch(appConfigProvider.select((config) => config.theme.mode)), diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index d961341f39..0ea9a6bae1 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -24,6 +24,7 @@ import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; import 'package:immich_mobile/theme/theme_data.dart'; +import 'package:immich_mobile/utils/fallback_localizations.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; import 'package:immich_mobile/widgets/common/immich_title_text.dart'; import 'package:logging/logging.dart'; @@ -49,7 +50,7 @@ class BootstrapErrorWidget extends StatelessWidget { builder: (lCtx) => MaterialApp( title: 'Immich', debugShowCheckedModeBanner: true, - localizationsDelegates: lCtx.localizationDelegates, + localizationsDelegates: [...lCtx.localizationDelegates, ...fallbackLocalizationDelegates], supportedLocales: lCtx.supportedLocales, locale: lCtx.locale, themeMode: ThemeMode.system, diff --git a/mobile/lib/utils/fallback_localizations.dart b/mobile/lib/utils/fallback_localizations.dart new file mode 100644 index 0000000000..c39c468d43 --- /dev/null +++ b/mobile/lib/utils/fallback_localizations.dart @@ -0,0 +1,23 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +// Flutter ships no Material or Cupertino strings for some of our languages, use English for those +const List fallbackLocalizationDelegates = [ + _FallbackLocalizationsDelegate(DefaultMaterialLocalizations.delegate), + _FallbackLocalizationsDelegate(DefaultCupertinoLocalizations.delegate), +]; + +class _FallbackLocalizationsDelegate extends LocalizationsDelegate { + final LocalizationsDelegate delegate; + + const _FallbackLocalizationsDelegate(this.delegate); + + @override + bool isSupported(Locale locale) => true; + + @override + Future load(Locale locale) => delegate.load(locale); + + @override + bool shouldReload(_FallbackLocalizationsDelegate old) => false; +} diff --git a/mobile/test/utils/fallback_localizations_test.dart b/mobile/test/utils/fallback_localizations_test.dart new file mode 100644 index 0000000000..1f2d118327 --- /dev/null +++ b/mobile/test/utils/fallback_localizations_test.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../widget_tester_extensions.dart'; + +void main() { + testWidgets('Material and Cupertino widgets work in languages Flutter has no translations for', (tester) async { + await tester.pumpConsumerWidget( + const Column(children: [BackButton(), CupertinoSearchTextField()]), + locale: const Locale('mi'), + ); + + expect(find.byTooltip('Back'), findsOneWidget); + expect(find.text('Search'), findsOneWidget); + }); +} diff --git a/mobile/test/widget_tester_extensions.dart b/mobile/test/widget_tester_extensions.dart index 5f43a80b89..39fda62e9d 100644 --- a/mobile/test/widget_tester_extensions.dart +++ b/mobile/test/widget_tester_extensions.dart @@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; +import 'package:immich_mobile/utils/fallback_localizations.dart'; extension PumpConsumerWidget on WidgetTester { /// Wraps the provided [widget] with a localized Material app such that it @@ -20,12 +21,13 @@ extension PumpConsumerWidget on WidgetTester { Duration? duration, EnginePhase phase = EnginePhase.sendSemanticsUpdate, List overrides = const [], + Locale? locale, }) async { await pumpWidget( EasyLocalization( supportedLocales: locales.values.toList(), path: translationsPath, - startLocale: locales.values.first, + startLocale: locale ?? locales.values.first, fallbackLocale: locales.values.first, saveLocale: false, useFallbackTranslations: true, @@ -35,7 +37,7 @@ extension PumpConsumerWidget on WidgetTester { child: Builder( builder: (context) => MaterialApp( debugShowCheckedModeBanner: false, - localizationsDelegates: context.localizationDelegates, + localizationsDelegates: [...context.localizationDelegates, ...fallbackLocalizationDelegates], supportedLocales: context.supportedLocales, locale: context.locale, home: Material(child: widget),