diff --git a/.gitignore b/.gitignore index ca455ab4..d5d4fa75 100644 --- a/.gitignore +++ b/.gitignore @@ -148,6 +148,7 @@ output.json !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### VisualStudioCode ### +.vscode/ .vscode/* !.vscode/settings.json !.vscode/tasks.json @@ -203,6 +204,7 @@ obj/ /out/ # User-specific configurations +.idea/ .idea/caches/ .idea/libraries/ .idea/shelf/ diff --git a/example/lib/main.dart b/example/lib/main.dart index 53bc748f..402c0611 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -23,6 +23,7 @@ void main() async { // startLocale: Locale('de', 'DE'), // saveLocale: false, // useOnlyLangCode: true, + // ignorePluralRules: false, // optional assetLoader default used is RootBundleAssetLoader which uses flutter's assetloader // install easy_localization_loader for enable custom loaders diff --git a/example/resources/langs/en-US.json b/example/resources/langs/en-US.json index 7cee30b5..5a717468 100644 --- a/example/resources/langs/en-US.json +++ b/example/resources/langs/en-US.json @@ -11,12 +11,10 @@ } }, "clicked": { - "zero": "You clicked {} times!", - "one": "You clicked {} time!", - "two": "You clicked {} times!", - "few": "You clicked {} times!", - "many": "You clicked {} times!", - "other": "You clicked {} times!" + "zero": "You didn't click yet!", + "few": "You clicked a few times ({})!", + "many": "You clicked many times ({})!", + "other": "You clicked {} time(s)!" }, "amount": { "zero": "Your amount : {} ", diff --git a/lib/src/easy_localization_app.dart b/lib/src/easy_localization_app.dart index 21629f49..e6acd2c1 100644 --- a/lib/src/easy_localization_app.dart +++ b/lib/src/easy_localization_app.dart @@ -63,6 +63,26 @@ class EasyLocalization extends StatefulWidget { /// ``` final bool useFallbackTranslationsForEmptyResources; + /// Ignore usage of plural strings for languages that do not use plural rules. + /// @Default value false + /// Example: + /// ``` + /// // Default behavior, use "zero" rule for 0 even if the language doesn't + /// // use it by default (e.g. "en"). If "zero" localization for that string + /// // doesn't exist, "other" is still used as fallback. + /// // "nTimes": "{count, plural, =0{never} =1{once} other{{count} times}}" + /// // Text(AppLocalizations.of(context)!.nTimes(_counter)), + /// // will print "never, once, 2 times" for ALL languages. + /// ignorePluralRules: true + /// // Use "zero" rule for 0 only if the language is set to do so (e.g. for + /// "lt" but not for "en"). + /// // "nTimes": "{count, plural, =0{never} =1{once} other{{count} times}}" + /// // Text(AppLocalizations.of(context)!.nTimes(_counter)), + /// // will print "never, once, 2 times" ONLY for languages with plural rules. + /// ignorePluralRules: false + /// ``` + final bool ignorePluralRules; + /// Path to your folder with localization files. /// Example: /// ```dart @@ -117,6 +137,7 @@ class EasyLocalization extends StatefulWidget { this.useOnlyLangCode = false, this.useFallbackTranslations = false, this.useFallbackTranslationsForEmptyResources = false, + this.ignorePluralRules = true, this.assetLoader = const RootBundleAssetLoader(), this.extraAssetLoaders, this.saveLocale = true, @@ -198,6 +219,7 @@ class _EasyLocalizationState extends State { supportedLocales: widget.supportedLocales, useFallbackTranslationsForEmptyResources: widget.useFallbackTranslationsForEmptyResources, + ignorePluralRules: widget.ignorePluralRules, ), ); } @@ -243,6 +265,7 @@ class _EasyLocalizationProvider extends InheritedWidget { /// Get fallback locale Locale? get fallbackLocale => parent.fallbackLocale; + // Locale get startLocale => parent.startLocale; /// Change app locale @@ -279,12 +302,14 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate { final List? supportedLocales; final EasyLocalizationController? localizationController; final bool useFallbackTranslationsForEmptyResources; + final bool ignorePluralRules; /// * use only the lang code to generate i18n file path like en.json or ar.json // final bool useOnlyLangCode; _EasyLocalizationDelegate({ required this.useFallbackTranslationsForEmptyResources, + this.ignorePluralRules = true, this.localizationController, this.supportedLocales, }) { @@ -307,6 +332,7 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate { fallbackTranslations: localizationController!.fallbackTranslations, useFallbackTranslationsForEmptyResources: useFallbackTranslationsForEmptyResources, + ignorePluralRules: ignorePluralRules, ); return Future.value(Localization.instance); } diff --git a/lib/src/localization.dart b/lib/src/localization.dart index b153e5b1..55ef2f3c 100644 --- a/lib/src/localization.dart +++ b/lib/src/localization.dart @@ -20,11 +20,14 @@ class Localization { }; bool _useFallbackTranslationsForEmptyResources = false; + bool _ignorePluralRules = false; Localization(); static Localization? _instance; + static Localization get instance => _instance ?? (_instance = Localization()); + static Localization? of(BuildContext context) => Localizations.of(context, Localization); @@ -33,12 +36,14 @@ class Localization { Translations? translations, Translations? fallbackTranslations, bool useFallbackTranslationsForEmptyResources = false, + bool ignorePluralRules = true, }) { instance._locale = locale; instance._translations = translations; instance._fallbackTranslations = fallbackTranslations; instance._useFallbackTranslationsForEmptyResources = useFallbackTranslationsForEmptyResources; + instance._ignorePluralRules = ignorePluralRules; return translations == null ? false : true; } @@ -114,6 +119,9 @@ class Localization { } static PluralRule? _pluralRule(String? locale, num howMany) { + if (instance._ignorePluralRules) { + return () => _pluralCaseFallback(howMany); + } startRuleEvaluation(howMany); return pluralRules[locale]; } @@ -139,7 +147,6 @@ class Localization { String? name, NumberFormat? format, }) { - late String res; final pluralRule = _pluralRule(_locale.languageCode, value); diff --git a/test/easy_localization_language_specific_test.dart b/test/easy_localization_language_specific_test.dart index 2dce5467..27e3f6e3 100644 --- a/test/easy_localization_language_specific_test.dart +++ b/test/easy_localization_language_specific_test.dart @@ -8,58 +8,86 @@ import 'package:flutter_test/flutter_test.dart'; import 'utils/test_asset_loaders.dart'; void main() { - group('language-specific-plurals', () { - var r = EasyLocalizationController( - forceLocale: const Locale('fb'), - supportedLocales: [const Locale('en'), const Locale('ru'), const Locale('fb')], - fallbackLocale: const Locale('fb'), - path: 'path', - useOnlyLangCode: true, - useFallbackTranslations: true, - onLoadError: (FlutterError e) { - log(e.toString()); - }, - saveLocale: false, - assetLoader: const JsonAssetLoader()); + group('language-specific-plurals', () { + var r = EasyLocalizationController( + forceLocale: const Locale('fb'), + supportedLocales: [ + const Locale('en'), + const Locale('ru'), + const 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(); - - }); + setUpAll(() async { + await r.loadTranslations(); + }); - test('english one', () async { - Localization.load(const Locale('en'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); - expect(Localization.instance.plural('hat', 1), 'one hat'); - }); - test('english other', () async { - Localization.load(const Locale('en'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); - expect(Localization.instance.plural('hat', 2), 'other hats'); - expect(Localization.instance.plural('hat', 0), 'other hats'); - expect(Localization.instance.plural('hat', 3), 'other hats'); - }); - test('russian one', () async { - Localization.load(const Locale('ru'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); - expect(Localization.instance.plural('hat', 1), 'one hat'); - }); - test('russian few', () async { - Localization.load(const Locale('ru'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); - expect(Localization.instance.plural('hat', 2), 'few hats'); - expect(Localization.instance.plural('hat', 3), 'few hats'); - }); - test('russian many', () async { - Localization.load(const Locale('ru'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); - expect(Localization.instance.plural('hat', 0), 'many hats'); - expect(Localization.instance.plural('hat', 5), 'many hats'); - }); + test('english one', () async { + Localization.load(const Locale('en'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 1), 'one hat'); + }); + test('english other (default)', () async { + Localization.load(const Locale('en'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 2), 'two hats'); + expect(Localization.instance.plural('hat', 0), 'no hats'); + expect(Localization.instance.plural('hat', 3), 'other hats'); + }); + test('english other (with ignorePluralRules)', () async { + Localization.load(const Locale('en'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations, + ignorePluralRules: false); + expect(Localization.instance.plural('hat', 2), 'other hats'); + expect(Localization.instance.plural('hat', 0), 'other hats'); + expect(Localization.instance.plural('hat', 3), 'other hats'); + }); + test('russian one', () async { + Localization.load(const Locale('ru'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 1), 'one hat'); + }); + test('russian few (default)', () async { + Localization.load(const Locale('ru'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 2), 'two hats'); + expect(Localization.instance.plural('hat', 3), 'other hats'); + }); + test('russian few (with ignorePluralRules)', () async { + Localization.load(const Locale('ru'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations, + ignorePluralRules: false); + expect(Localization.instance.plural('hat', 2), 'few hats'); + expect(Localization.instance.plural('hat', 3), 'few hats'); + }); + test('russian many (default)', () async { + Localization.load(const Locale('ru'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 0), 'no hats'); + expect(Localization.instance.plural('hat', 5), 'other hats'); + }); + test('russian many (with ignorePluralRules)', () async { + Localization.load(const Locale('ru'), + translations: r.translations, + fallbackTranslations: r.fallbackTranslations, + ignorePluralRules: false); + expect(Localization.instance.plural('hat', 0), 'many hats'); + expect(Localization.instance.plural('hat', 5), 'many hats'); }); -} \ No newline at end of file + }); +} diff --git a/test/easy_localization_widget_test.dart b/test/easy_localization_widget_test.dart index 3444837d..895ecbfa 100644 --- a/test/easy_localization_widget_test.dart +++ b/test/easy_localization_widget_test.dart @@ -5,9 +5,9 @@ import 'package:easy_localization/src/exceptions.dart'; import 'package:easy_localization/src/localization.dart'; import 'package:easy_logger/easy_logger.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; + import 'utils/test_asset_loaders.dart'; late BuildContext _context; @@ -285,6 +285,7 @@ void main() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], + ignorePluralRules: false, child: const MyApp(), )); @@ -297,8 +298,10 @@ void main() async { await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US'), const Locale('ar', 'DZ')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [ + const Locale('en', 'US'), + const Locale('ar', 'DZ'), + ]); expect(EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); var trFinder = find.text('اختبار');