Skip to content

Commit

Permalink
Fix #648: allow using "old" (e.g. pre-3.0.4) plural rules evaluation (#…
Browse files Browse the repository at this point in the history
…668)

* Add .vscode/ and .idea/ folders to gitignore

* Fix #648: allow using "old" (e.g. pre-3.0.4) plural rules evaluation

Add forcePluralCaseFallback option to force evaluation of fallback
plural rules, i.e.

* forcePluralCaseFallback: false
Default behavior, will use "zero" rule for 0 only if the language
is set to do so (e.g. for "lt" but not for "en").

* forcePluralCaseFallback: true
Force using "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.

* Rename forcePluralCaseFallback to ignorePluralRules and reverse logic

Parameter forcePluralCaseFallback has been renamed to ignorePluralRules
for clarity as suggested by bw-flagship. Also, the logic is now reversed
(e.g. false will implement the old behavior and true will implement the
new behavior). Finally, the default value is now false (old behavior).

* Fix unit tests

* Re-reverse logic of ignorePluralRules

ignorePluralRules=true, "old" beahvior (default)
ignorePluralRules=false, "new" beahvior

* Revert some formatting changes

---------

Co-authored-by: Maurizio Pinotti <[email protected]>
  • Loading branch information
mauriziopinotti and mauriziopinotti authored May 9, 2024
1 parent 4518a9c commit 977a72f
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 62 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ output.json
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

### VisualStudioCode ###
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
Expand Down Expand Up @@ -203,6 +204,7 @@ obj/
/out/

# User-specific configurations
.idea/
.idea/caches/
.idea/libraries/
.idea/shelf/
Expand Down
1 change: 1 addition & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 4 additions & 6 deletions example/resources/langs/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 : {} ",
Expand Down
26 changes: 26 additions & 0 deletions lib/src/easy_localization_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -198,6 +219,7 @@ class _EasyLocalizationState extends State<EasyLocalization> {
supportedLocales: widget.supportedLocales,
useFallbackTranslationsForEmptyResources:
widget.useFallbackTranslationsForEmptyResources,
ignorePluralRules: widget.ignorePluralRules,
),
);
}
Expand Down Expand Up @@ -243,6 +265,7 @@ class _EasyLocalizationProvider extends InheritedWidget {

/// Get fallback locale
Locale? get fallbackLocale => parent.fallbackLocale;

// Locale get startLocale => parent.startLocale;

/// Change app locale
Expand Down Expand Up @@ -279,12 +302,14 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate<Localization> {
final List<Locale>? 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,
}) {
Expand All @@ -307,6 +332,7 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate<Localization> {
fallbackTranslations: localizationController!.fallbackTranslations,
useFallbackTranslationsForEmptyResources:
useFallbackTranslationsForEmptyResources,
ignorePluralRules: ignorePluralRules,
);
return Future.value(Localization.instance);
}
Expand Down
9 changes: 8 additions & 1 deletion lib/src/localization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Localization>(context, Localization);

Expand All @@ -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;
}

Expand Down Expand Up @@ -114,6 +119,9 @@ class Localization {
}

static PluralRule? _pluralRule(String? locale, num howMany) {
if (instance._ignorePluralRules) {
return () => _pluralCaseFallback(howMany);
}
startRuleEvaluation(howMany);
return pluralRules[locale];
}
Expand All @@ -139,7 +147,6 @@ class Localization {
String? name,
NumberFormat? format,
}) {

late String res;

final pluralRule = _pluralRule(_locale.languageCode, value);
Expand Down
132 changes: 80 additions & 52 deletions test/easy_localization_language_specific_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
}
});
}
9 changes: 6 additions & 3 deletions test/easy_localization_widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
));

Expand All @@ -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('اختبار');
Expand Down

0 comments on commit 977a72f

Please sign in to comment.