Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 7 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -278,12 +301,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 @@ -306,6 +331,7 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate<Localization> {
fallbackTranslations: localizationController!.fallbackTranslations,
useFallbackTranslationsForEmptyResources:
useFallbackTranslationsForEmptyResources,
ignorePluralRules: ignorePluralRules,
);
return Future.value(Localization.instance);
}
Expand Down
15 changes: 12 additions & 3 deletions 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);
bw-flagship marked this conversation as resolved.
Show resolved Hide resolved
}
startRuleEvaluation(howMany);
return pluralRules[locale];
}
Expand All @@ -139,11 +147,11 @@ class Localization {
String? name,
NumberFormat? format,
}) {

late String res;

final pluralRule = _pluralRule(_locale.languageCode, value);
final pluralCase = pluralRule != null ? pluralRule() : _pluralCaseFallback(value);
final pluralCase =
mauriziopinotti marked this conversation as resolved.
Show resolved Hide resolved
pluralRule != null ? pluralRule() : _pluralCaseFallback(value);

switch (pluralCase) {
case PluralCase.ZERO:
Expand Down Expand Up @@ -186,7 +194,8 @@ class Localization {
if (subKey == 'other') return _resolve('$key.other');

final tag = '$key.$subKey';
var resource = _resolve(tag, logging: false, fallback: _fallbackTranslations != null);
var resource =
mauriziopinotti marked this conversation as resolved.
Show resolved Hide resolved
_resolve(tag, logging: false, fallback: _fallbackTranslations != null);
if (resource == tag) {
resource = _resolve('$key.other');
}
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
Loading