From 39d091e3e1ea70cec33fc37db22f66a33091b377 Mon Sep 17 00:00:00 2001 From: Shturma Vladislav Date: Mon, 22 Apr 2024 12:38:01 +0400 Subject: [PATCH] Add useFallbackTranslationsForEmptyResources parameter to use fallback if translation lookup returns an empty string (#663) * Add useFallbackTranslationsForEmptyResources parameter * Remove formatting changes in readme. Increase the version in pubspec --- CHANGELOG.md | 4 + README.md | 1 + lib/src/easy_localization_app.dart | 30 +++++-- lib/src/localization.dart | 13 ++- pubspec.yaml | 2 +- test/easy_localization_test.dart | 132 ++++++++++++++++++++++++++++- test/utils/test_asset_loaders.dart | 36 ++++++++ 7 files changed, 207 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab50ac5c..3f4e7c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### [3.0.6] + +- add 'useFallbackTranslationsForEmptyResources' to be able to use fallback locales for empty resources. + ### [3.0.5] - add 'extraAssetLoaders' to add more assets loaders if it is needed, for example, if you want to add packages localizations to your project. diff --git a/README.md b/README.md index 03a653fb..c74f5ac4 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ class MyApp extends StatelessWidget { | startLocale | false | | Overrides device locale. | | saveLocale | false | `true` | Save locale in device storage. | | useFallbackTranslations | false | `false` | If a localization key is not found in the locale file, try to use the fallbackLocale file. | +| useFallbackTranslationsForEmptyResources | false | `false` | If translation is empty in the locale file, try to use the fallbackLocale file. Does not take effect if `useFallbackTranslations` is false. | | useOnlyLangCode | false | `false` | Trigger for using only language code for reading localization files.

Example:
`en.json //useOnlyLangCode: true`
`en-US.json //useOnlyLangCode: false` | | errorWidget | false | `FutureErrorWidget()` | Shows a custom error widget when an error occurs. | diff --git a/lib/src/easy_localization_app.dart b/lib/src/easy_localization_app.dart index 2ce7ec7f..0a49e9a0 100644 --- a/lib/src/easy_localization_app.dart +++ b/lib/src/easy_localization_app.dart @@ -54,6 +54,15 @@ class EasyLocalization extends StatefulWidget { /// ``` final bool useFallbackTranslations; + /// If a localization key is empty in the locale file, try to use the fallbackLocale file. + /// Does not take effect if [useFallbackTranslations] is false. + /// @Default value false + /// Example: + /// ``` + /// useFallbackTranslationsForEmptyResources: true + /// ``` + final bool useFallbackTranslationsForEmptyResources; + /// Path to your folder with localization files. /// Example: /// ```dart @@ -107,6 +116,7 @@ class EasyLocalization extends StatefulWidget { this.startLocale, this.useOnlyLangCode = false, this.useFallbackTranslations = false, + this.useFallbackTranslationsForEmptyResources = false, this.assetLoader = const RootBundleAssetLoader(), this.extraAssetLoaders, this.saveLocale = true, @@ -186,6 +196,8 @@ class _EasyLocalizationState extends State { delegate: _EasyLocalizationDelegate( localizationController: localizationController, supportedLocales: widget.supportedLocales, + useFallbackTranslationsForEmptyResources: + widget.useFallbackTranslationsForEmptyResources, ), ); } @@ -265,12 +277,16 @@ class _EasyLocalizationProvider extends InheritedWidget { class _EasyLocalizationDelegate extends LocalizationsDelegate { final List? supportedLocales; final EasyLocalizationController? localizationController; + final bool useFallbackTranslationsForEmptyResources; /// * use only the lang code to generate i18n file path like en.json or ar.json // final bool useOnlyLangCode; - _EasyLocalizationDelegate( - {this.localizationController, this.supportedLocales}) { + _EasyLocalizationDelegate({ + required this.useFallbackTranslationsForEmptyResources, + this.localizationController, + this.supportedLocales, + }) { EasyLocalization.logger.debug('Init Localization Delegate'); } @@ -284,9 +300,13 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate { await localizationController!.loadTranslations(); } - Localization.load(value, - translations: localizationController!.translations, - fallbackTranslations: localizationController!.fallbackTranslations); + Localization.load( + value, + translations: localizationController!.translations, + fallbackTranslations: localizationController!.fallbackTranslations, + useFallbackTranslationsForEmptyResources: + useFallbackTranslationsForEmptyResources, + ); return Future.value(Localization.instance); } diff --git a/lib/src/localization.dart b/lib/src/localization.dart index 57742ee3..b153e5b1 100644 --- a/lib/src/localization.dart +++ b/lib/src/localization.dart @@ -19,6 +19,8 @@ class Localization { 'capitalize': (String? val) => '${val![0].toUpperCase()}${val.substring(1)}' }; + bool _useFallbackTranslationsForEmptyResources = false; + Localization(); static Localization? _instance; @@ -30,10 +32,13 @@ class Localization { Locale locale, { Translations? translations, Translations? fallbackTranslations, + bool useFallbackTranslationsForEmptyResources = false, }) { instance._locale = locale; instance._translations = translations; instance._fallbackTranslations = fallbackTranslations; + instance._useFallbackTranslationsForEmptyResources = + useFallbackTranslationsForEmptyResources; return translations == null ? false : true; } @@ -190,7 +195,8 @@ class Localization { String _resolve(String key, {bool logging = true, bool fallback = true}) { var resource = _translations?.get(key); - if (resource == null) { + if (resource == null || + (_useFallbackTranslationsForEmptyResources && resource.isEmpty)) { if (logging) { EasyLocalization.logger.warning('Localization key [$key] not found'); } @@ -198,7 +204,8 @@ class Localization { return key; } else { resource = _fallbackTranslations?.get(key); - if (resource == null) { + if (resource == null || + (_useFallbackTranslationsForEmptyResources && resource.isEmpty)) { if (logging) { EasyLocalization.logger .warning('Fallback localization key [$key] not found'); @@ -210,7 +217,7 @@ class Localization { return resource; } - bool exists(String key){ + bool exists(String key) { return _translations?.get(key) != null; } } diff --git a/pubspec.yaml b/pubspec.yaml index 45fe0b03..5252648b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/aissat/easy_localization issue_tracker: https://github.com/aissat/easy_localization/issues # publish_to: none -version: 3.0.5 +version: 3.0.6 environment: sdk: '>=2.12.0 <4.0.0' diff --git a/test/easy_localization_test.dart b/test/easy_localization_test.dart index 5dff2aa3..59e21213 100644 --- a/test/easy_localization_test.dart +++ b/test/easy_localization_test.dart @@ -342,12 +342,18 @@ void main() { contains('Localization key [test_missing_fallback] not found')); })); + test('uses empty translation, not using fallback', overridePrint(() { + printLog = []; + expect(Localization.instance.tr('test_empty_fallback'), ''); + })); + test('returns resource and replaces argument', () { expect( Localization.instance.tr('test_replace_one', args: ['one']), 'test replace one', ); }); + test('returns resource and replaces argument in any nest level', () { expect( Localization.instance @@ -411,10 +417,59 @@ void main() { }); }); + group('tr useFallbackTranslationsForEmptyResources', () { + var r = EasyLocalizationController( + forceLocale: const Locale('en'), + supportedLocales: const [Locale('en'), Locale('fb')], + fallbackLocale: const Locale('fb'), + path: 'path', + useOnlyLangCode: true, + useFallbackTranslations: true, + onLoadError: (FlutterError e) { + log(e.toString()); + }, + saveLocale: false, + assetLoader: const JsonAssetLoader()); + + setUpAll(() async { + await r.loadTranslations(); + Localization.load( + const Locale('en'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations, + useFallbackTranslationsForEmptyResources: true, + ); + }); + + test('uses fallback translations for empty resource', overridePrint(() { + printLog = []; + expect(Localization.instance.tr('test_empty_fallback'), 'fallback!'); + })); + + test('reports empty resource with fallback', overridePrint(() { + printLog = []; + expect(Localization.instance.tr('test_empty_fallback'), 'fallback!'); + expect(printLog.first, + contains('Localization key [test_empty_fallback] not found')); + })); + + test('reports empty resource', overridePrint(() { + printLog = []; + expect(Localization.instance.tr('test_empty'), 'test_empty'); + final logIterator = printLog.iterator; + logIterator.moveNext(); + expect(logIterator.current, + contains('Localization key [test_empty] not found')); + logIterator.moveNext(); + expect(logIterator.current, + contains('Fallback localization key [test_empty] not found')); + })); + }); + group('plural', () { var r = EasyLocalizationController( - forceLocale: const Locale('fb'), - supportedLocales: [const Locale('fb')], + forceLocale: const Locale('en'), + supportedLocales: const [Locale('en'), Locale('fb')], fallbackLocale: const Locale('fb'), path: 'path', useOnlyLangCode: true, @@ -469,6 +524,16 @@ void main() { expect(printLog, isEmpty); })); + test('two as fallback and fallback translations priority', + overridePrint(() { + printLog = []; + expect( + Localization.instance.plural('test_empty_fallback_plurals', 2), + '', + ); + expect(printLog, isEmpty); + })); + test('with number format', () { expect( Localization.instance @@ -524,6 +589,69 @@ void main() { }); }); + group('plural useFallbackTranslationsForEmptyResources', () { + var r = EasyLocalizationController( + forceLocale: const Locale('en'), + supportedLocales: const [Locale('en'), Locale('fb')], + fallbackLocale: const Locale('fb'), + path: 'path', + useOnlyLangCode: true, + useFallbackTranslations: true, + onLoadError: (FlutterError e) { + log(e.toString()); + }, + saveLocale: false, + assetLoader: const JsonAssetLoader()); + + setUpAll(() async { + await r.loadTranslations(); + Localization.load( + const Locale('fb'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations, + useFallbackTranslationsForEmptyResources: true, + ); + }); + + test('two as fallback for empty resource and fallback translations priority', + overridePrint(() { + printLog = []; + expect( + Localization.instance.plural('test_empty_fallback_plurals', 2), + 'fallback two', + ); + expect(printLog, isEmpty); + })); + + test('reports empty plural resource with fallback', + overridePrint(() { + printLog = []; + expect( + Localization.instance.plural('test_empty_fallback_plurals', -1), + 'fallback other', + ); + expect( + printLog.first, + contains( + 'Localization key [test_empty_fallback_plurals.other] not found')); + })); + + test('reports empty plural resource', overridePrint(() { + printLog = []; + expect( + Localization.instance.plural('test_empty_plurals', -1), + 'test_empty_plurals.other', + ); + final logIterator = printLog.iterator; + logIterator.moveNext(); + expect(logIterator.current, + contains('Localization key [test_empty_plurals.other] not found')); + logIterator.moveNext(); + expect(logIterator.current, + contains('Fallback localization key [test_empty_plurals.other] not found')); + })); + }); + group('extensions', () { // setUpAll(() async { // await Localization.load(Locale('en'), diff --git a/test/utils/test_asset_loaders.dart b/test/utils/test_asset_loaders.dart index 7ca79006..d6c3e2d3 100644 --- a/test/utils/test_asset_loaders.dart +++ b/test/utils/test_asset_loaders.dart @@ -20,6 +20,7 @@ class JsonAssetLoader extends AssetLoader { Future> load(String fullPath, Locale locale) { return Future.value({ 'test': 'test', + 'test_empty': '', 'test_replace_one': 'test replace {}', 'test_replace_two': 'test replace {} {}', 'test_replace_named': 'test named replace {arg1} {arg2}', @@ -87,6 +88,7 @@ class JsonAssetLoader extends AssetLoader { 'path': fullPath, 'test_missing_fallback': (locale.languageCode == 'fb' ? 'fallback!' : null), + 'test_empty_fallback': (locale.languageCode == 'fb' ? 'fallback!' : ''), 'test_fallback_plurals': (locale.languageCode == 'fb' ? { 'zero': 'fallback zero', @@ -100,6 +102,40 @@ class JsonAssetLoader extends AssetLoader { 'one': '{} second', 'other': '{} seconds', }), + 'test_empty_fallback_plurals': (locale.languageCode == 'fb' + ? { + 'zero': 'fallback zero', + 'one': 'fallback one', + 'two': 'fallback two', + 'few': 'fallback few', + 'many': 'fallback many', + 'other': 'fallback other', + } + : { + 'zero': '', + 'one': '', + 'two': '', + 'few': '', + 'many': '', + 'other': '', + }), + 'test_empty_plurals': (locale.languageCode == 'fb' + ? { + 'zero': '', + 'one': '', + 'two': '', + 'few': '', + 'many': '', + 'other': '', + } + : { + 'zero': '', + 'one': '', + 'two': '', + 'few': '', + 'many': '', + 'other': '', + }) }); } }