diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..8d546bf7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,65 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "easy_localization", + "request": "launch", + "type": "dart" + }, + { + "name": "easy_localization (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "easy_localization (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "example", + "cwd": "example", + "request": "launch", + "type": "dart" + }, + { + "name": "example (profile mode)", + "cwd": "example", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "example (release mode)", + "cwd": "example", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "easy_logger", + "cwd": "packages/easy_logger", + "request": "launch", + "type": "dart" + }, + { + "name": "easy_logger (profile mode)", + "cwd": "packages/easy_logger", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "easy_logger (release mode)", + "cwd": "packages/easy_logger", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/example/lib/lang_view.dart b/example/lib/lang_view.dart index a81274aa..18b35147 100644 --- a/example/lib/lang_view.dart +++ b/example/lib/lang_view.dart @@ -36,27 +36,41 @@ class LanguageView extends StatelessWidget { ), ), ), - _SwitchListTileMenuItem( - title: 'عربي', - subtitle: 'عربي', - locale: - context.supportedLocales[1] //BuildContext extension method + _SwitchListTileMenuItem(title: 'عربي', subtitle: 'عربي', locale: context.supportedLocales[1] //BuildContext extension method ), _Divider(), - _SwitchListTileMenuItem( - title: 'English', - subtitle: 'English', - locale: context.supportedLocales[0]), + _SwitchListTileMenuItem(title: 'English', subtitle: 'English', locale: context.supportedLocales[0]), _Divider(), - _SwitchListTileMenuItem( - title: 'German', - subtitle: 'German', - locale: context.supportedLocales[2]), + _SwitchListTileMenuItem(title: 'German', subtitle: 'German', locale: context.supportedLocales[2]), _Divider(), - _SwitchListTileMenuItem( - title: 'Русский', - subtitle: 'Русский', - locale: context.supportedLocales[3]), + _SwitchListTileMenuItem(title: 'Русский', subtitle: 'Русский', locale: context.supportedLocales[3]), + _Divider(), + SizedBox( + height: 250, + ), + Container( + padding: EdgeInsets.only(top: 26), + margin: EdgeInsets.symmetric( + horizontal: 24, + ), + child: Text( + 'Choose sub language', + style: TextStyle( + color: Colors.blue, + fontFamily: 'Montserrat', + fontWeight: FontWeight.w700, + fontSize: 18, + ), + ), + ), + _SwitchSubListTileMenuItem(title: 'عربي', subtitle: 'عربي', subLocale: context.supportedLocales[1] //BuildContext extension method + ), + _Divider(), + _SwitchSubListTileMenuItem(title: 'English', subtitle: 'English', subLocale: context.supportedLocales[0]), + _Divider(), + _SwitchSubListTileMenuItem(title: 'German', subtitle: 'German', subLocale: context.supportedLocales[2]), + _Divider(), + _SwitchSubListTileMenuItem(title: 'Русский', subtitle: 'Русский', subLocale: context.supportedLocales[3]), _Divider(), ], ), @@ -100,8 +114,7 @@ class _SwitchListTileMenuItem extends StatelessWidget { return Container( margin: EdgeInsets.only(left: 10, right: 10, top: 5), decoration: BoxDecoration( - border: - isSelected(context) ? Border.all(color: Colors.blueAccent) : null, + border: isSelected(context) ? Border.all(color: Colors.blueAccent) : null, ), child: ListTile( dense: true, @@ -120,3 +133,42 @@ class _SwitchListTileMenuItem extends StatelessWidget { ); } } + +class _SwitchSubListTileMenuItem extends StatelessWidget { + const _SwitchSubListTileMenuItem({ + Key? key, + required this.title, + required this.subtitle, + required this.subLocale, + }) : super(key: key); + + final String title; + final String subtitle; + final Locale subLocale; + + bool isSelected(BuildContext context) => subLocale == context.subLocale; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(left: 10, right: 10, top: 5), + decoration: BoxDecoration( + border: isSelected(context) ? Border.all(color: Colors.blueAccent) : null, + ), + child: ListTile( + dense: true, + // isThreeLine: true, + title: Text( + title, + ), + subtitle: Text( + subtitle, + ), + onTap: () async { + log(subLocale.toString(), name: toString()); + await context.setSubLocale(subLocale); //BuildContext extension method + Navigator.pop(context); + }), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 53bc748f..88cba7f2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -11,12 +11,7 @@ void main() async { await EasyLocalization.ensureInitialized(); runApp(EasyLocalization( - supportedLocales: [ - Locale('en', 'US'), - Locale('ar', 'DZ'), - Locale('de', 'DE'), - Locale('ru', 'RU') - ], + supportedLocales: [Locale('en', 'US'), Locale('ar', 'DZ'), Locale('de', 'DE'), Locale('ru', 'RU')], path: 'resources/langs', child: MyApp(), // fallbackLocale: Locale('en', 'US'), @@ -88,13 +83,12 @@ class _MyHomePageState extends State { onPressed: () { Navigator.push( context, - MaterialPageRoute( - builder: (_) => LanguageView(), fullscreenDialog: true), + MaterialPageRoute(builder: (_) => LanguageView(), fullscreenDialog: true), ); }, child: Icon( Icons.language, - color: Colors.white, + color: Colors.black, ), ), ], @@ -108,17 +102,11 @@ class _MyHomePageState extends State { ), Text( LocaleKeys.gender_with_arg, - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 19, - fontWeight: FontWeight.bold), + style: TextStyle(color: Colors.grey.shade600, fontSize: 19, fontWeight: FontWeight.bold), ).tr(args: ['aissat'], gender: _gender ? 'female' : 'male'), Text( tr(LocaleKeys.gender, gender: _gender ? 'female' : 'male'), - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 15, - fontWeight: FontWeight.bold), + style: TextStyle(color: Colors.grey.shade600, fontSize: 15, fontWeight: FontWeight.bold), ), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -132,8 +120,9 @@ class _MyHomePageState extends State { flex: 1, ), Text(LocaleKeys.msg).tr(args: ['aissat', 'Flutter']), - Text(LocaleKeys.msg_named) - .tr(namedArgs: {'lang': 'Dart'}, args: ['Easy localization']), + Text(LocaleKeys.msg_named).tr(namedArgs: {'lang': 'Dart'}, args: ['Easy localization']), + Text("Sub language:"), + Text(LocaleKeys.msg).trSub(args: ['aissat', 'Flutter']), Text(LocaleKeys.clicked).plural(counter), TextButton( onPressed: () { @@ -144,14 +133,8 @@ class _MyHomePageState extends State { SizedBox( height: 15, ), - Text( - plural(LocaleKeys.amount, counter, - format: NumberFormat.currency( - locale: Intl.defaultLocale, symbol: '€')), - style: TextStyle( - color: Colors.grey.shade900, - fontSize: 18, - fontWeight: FontWeight.bold)), + Text(plural(LocaleKeys.amount, counter, format: NumberFormat.currency(locale: Intl.defaultLocale, symbol: '€')), + style: TextStyle(color: Colors.grey.shade900, fontSize: 18, fontWeight: FontWeight.bold)), SizedBox( height: 20, ), diff --git a/lib/src/easy_localization_app.dart b/lib/src/easy_localization_app.dart index e5c9ec8c..172b51e1 100644 --- a/lib/src/easy_localization_app.dart +++ b/lib/src/easy_localization_app.dart @@ -37,6 +37,9 @@ class EasyLocalization extends StatefulWidget { /// Overrides device locale. final Locale? startLocale; + /// Overrides device locale. + final Locale? startSubLocale; + /// Trigger for using only language code for reading localization files. /// @Default value false /// Example: @@ -72,6 +75,10 @@ class EasyLocalization extends StatefulWidget { /// @Default value true final bool saveLocale; + /// Save sub locale in device storage. + /// @Default value true + final bool saveSubLocale; + /// Shows a custom error widget when an error is encountered instead of the default error widget. /// @Default value `errorWidget = ErrorWidget()` final Widget Function(FlutterError? message)? errorWidget; @@ -83,10 +90,12 @@ class EasyLocalization extends StatefulWidget { required this.path, this.fallbackLocale, this.startLocale, + this.startSubLocale, this.useOnlyLangCode = false, this.useFallbackTranslations = false, this.assetLoader = const RootBundleAssetLoader(), this.saveLocale = true, + this.saveSubLocale = true, this.errorWidget, }) : assert(supportedLocales.isNotEmpty), assert(path.isNotEmpty), @@ -99,14 +108,12 @@ class EasyLocalization extends StatefulWidget { _EasyLocalizationState createState() => _EasyLocalizationState(); // ignore: library_private_types_in_public_api - static _EasyLocalizationProvider? of(BuildContext context) => - _EasyLocalizationProvider.of(context); + static _EasyLocalizationProvider? of(BuildContext context) => _EasyLocalizationProvider.of(context); /// ensureInitialized needs to be called in main /// so that savedLocale is loaded and used from the /// start. - static Future ensureInitialized() async => - await EasyLocalizationController.initEasyLocation(); + static Future ensureInitialized() async => await EasyLocalizationController.initEasyLocation(); /// Customizable logger static EasyLogger logger = EasyLogger(name: '🌎 Easy Localization'); @@ -122,9 +129,11 @@ class _EasyLocalizationState extends State { EasyLocalization.logger.debug('Init state'); localizationController = EasyLocalizationController( saveLocale: widget.saveLocale, + saveSubLocale: widget.saveSubLocale, fallbackLocale: widget.fallbackLocale, supportedLocales: widget.supportedLocales, startLocale: widget.startLocale, + startSubLocale: widget.startSubLocale, assetLoader: widget.assetLoader, useOnlyLangCode: widget.useOnlyLangCode, useFallbackTranslations: widget.useFallbackTranslations, @@ -152,9 +161,7 @@ class _EasyLocalizationState extends State { Widget build(BuildContext context) { EasyLocalization.logger.debug('Build'); if (translationsLoadError != null) { - return widget.errorWidget != null - ? widget.errorWidget!(translationsLoadError) - : ErrorWidget(translationsLoadError!); + return widget.errorWidget != null ? widget.errorWidget!(translationsLoadError) : ErrorWidget(translationsLoadError!); } return _EasyLocalizationProvider( widget, @@ -171,6 +178,7 @@ class _EasyLocalizationProvider extends InheritedWidget { final EasyLocalization parent; final EasyLocalizationController _localeState; final Locale? currentLocale; + final Locale? currentSubLocale; final _EasyLocalizationDelegate delegate; /// {@macro flutter.widgets.widgetsApp.localizationsDelegates} @@ -195,9 +203,9 @@ class _EasyLocalizationProvider extends InheritedWidget { // _EasyLocalizationDelegate get delegate => parent.delegate; - _EasyLocalizationProvider(this.parent, this._localeState, - {Key? key, required this.delegate}) + _EasyLocalizationProvider(this.parent, this._localeState, {Key? key, required this.delegate}) : currentLocale = _localeState.locale, + currentSubLocale = _localeState.subLocale, super(key: key, child: parent.child) { EasyLocalization.logger.debug('Init provider'); } @@ -205,6 +213,8 @@ class _EasyLocalizationProvider extends InheritedWidget { /// Get current locale Locale get locale => _localeState.locale; + Locale get subLocale => _localeState.subLocale; + /// Get fallback locale Locale? get fallbackLocale => parent.fallbackLocale; // Locale get startLocale => parent.startLocale; @@ -218,6 +228,14 @@ class _EasyLocalizationProvider extends InheritedWidget { } } + Future setSubLocale(Locale subLocale) async { + // Check old locale + if (subLocale != _localeState.subLocale) { + assert(parent.supportedLocales.contains(subLocale)); + await _localeState.setSubLocale(subLocale); + } + } + /// Clears a saved locale from device storage Future deleteSaveLocale() async { await _localeState.deleteSaveLocale(); @@ -234,8 +252,7 @@ class _EasyLocalizationProvider extends InheritedWidget { return oldWidget.currentLocale != locale; } - static _EasyLocalizationProvider? of(BuildContext context) => - context.dependOnInheritedWidgetOfExactType<_EasyLocalizationProvider>(); + static _EasyLocalizationProvider? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<_EasyLocalizationProvider>(); } class _EasyLocalizationDelegate extends LocalizationsDelegate { @@ -245,8 +262,7 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate { /// * 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({this.localizationController, this.supportedLocales}) { EasyLocalization.logger.debug('Init Localization Delegate'); } @@ -259,10 +275,15 @@ class _EasyLocalizationDelegate extends LocalizationsDelegate { if (localizationController!.translations == null) { await localizationController!.loadTranslations(); } + if (localizationController!.subTranslations == null) { + await localizationController!.loadSubTranslations(); + } - Localization.load(value, + Localization.load(value, localizationController!.subLocale, translations: localizationController!.translations, - fallbackTranslations: localizationController!.fallbackTranslations); + subTranslations: localizationController!.subTranslations, + fallbackTranslations: localizationController!.fallbackTranslations, + subFallbackTranslations: localizationController!.subFallbackTranslations); return Future.value(Localization.instance); } diff --git a/lib/src/easy_localization_controller.dart b/lib/src/easy_localization_controller.dart index 2f63f0c8..e431f4bd 100644 --- a/lib/src/easy_localization_controller.dart +++ b/lib/src/easy_localization_controller.dart @@ -1,16 +1,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl_standalone.dart' - if (dart.library.html) 'package:intl/intl_browser.dart'; +import 'package:intl/intl_standalone.dart' if (dart.library.html) 'package:intl/intl_browser.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'translations.dart'; class EasyLocalizationController extends ChangeNotifier { static Locale? _savedLocale; + static Locale? _savedSubLocale; static late Locale _deviceLocale; late Locale _locale; + late Locale _subLocale; Locale? _fallbackLocale; final Function(FlutterError e) onLoadError; @@ -19,32 +20,37 @@ class EasyLocalizationController extends ChangeNotifier { final String path; final bool useFallbackTranslations; final bool saveLocale; + final bool saveSubLocale; final bool useOnlyLangCode; - Translations? _translations, _fallbackTranslations; + Translations? _translations, _subTranslations, _fallbackTranslations, _subFallbackTranslations; Translations? get translations => _translations; + Translations? get subTranslations => _subTranslations; Translations? get fallbackTranslations => _fallbackTranslations; + Translations? get subFallbackTranslations => _subFallbackTranslations; - EasyLocalizationController({ - required List supportedLocales, - required this.useFallbackTranslations, - required this.saveLocale, - required this.assetLoader, - required this.path, - required this.useOnlyLangCode, - required this.onLoadError, - Locale? startLocale, - Locale? fallbackLocale, - Locale? forceLocale, // used for testing - }) { + EasyLocalizationController( + {required List supportedLocales, + required this.useFallbackTranslations, + required this.saveLocale, + required this.saveSubLocale, + required this.assetLoader, + required this.path, + required this.useOnlyLangCode, + required this.onLoadError, + Locale? startLocale, + Locale? startSubLocale, + Locale? fallbackLocale, + Locale? forceLocale, // used for testing + Locale? forceSubLocale // used for testing + }) { _fallbackLocale = fallbackLocale; + if (forceLocale != null) { _locale = forceLocale; } else if (_savedLocale == null && startLocale != null) { _locale = _getFallbackLocale(supportedLocales, startLocale); EasyLocalization.logger('Start locale loaded ${_locale.toString()}'); - } - // If saved locale then get - else if (saveLocale && _savedLocale != null) { + } else if (saveLocale && _savedLocale != null) { EasyLocalization.logger('Saved locale loaded ${_savedLocale.toString()}'); _locale = selectLocaleFrom( supportedLocales, @@ -59,6 +65,27 @@ class EasyLocalizationController extends ChangeNotifier { fallbackLocale: fallbackLocale, ); } + + if (forceSubLocale != null) { + _subLocale = forceSubLocale; + } else if (_savedLocale == null && startSubLocale != null) { + _subLocale = _getFallbackLocale(supportedLocales, startSubLocale); + EasyLocalization.logger('Sub start locale loaded ${_subLocale.toString()}'); + } else if (saveSubLocale && _savedSubLocale != null) { + EasyLocalization.logger('Saved sub locale loaded ${_savedSubLocale.toString()}'); + _subLocale = selectLocaleFrom( + supportedLocales, + _savedSubLocale!, + fallbackLocale: fallbackLocale, + ); + } else { + // From Device Locale + _subLocale = selectLocaleFrom( + supportedLocales, + _deviceLocale, + fallbackLocale: fallbackLocale, + ); + } } @visibleForTesting @@ -75,8 +102,7 @@ class EasyLocalizationController extends ChangeNotifier { } //Get fallback Locale - static Locale _getFallbackLocale( - List supportedLocales, Locale? fallbackLocale) { + static Locale _getFallbackLocale(List supportedLocales, Locale? fallbackLocale) { //If fallbackLocale not set then return first from supportedLocales if (fallbackLocale != null) { return fallbackLocale; @@ -90,13 +116,16 @@ class EasyLocalizationController extends ChangeNotifier { try { data = Map.from(await loadTranslationData(_locale)); _translations = Translations(data); + if (useFallbackTranslations && _fallbackLocale != null) { Map? baseLangData; + if (_locale.countryCode != null && _locale.countryCode!.isNotEmpty) { - baseLangData = - await loadBaseLangTranslationData(Locale(locale.languageCode)); + baseLangData = await loadBaseLangTranslationData(Locale(_locale.languageCode)); } + data = Map.from(await loadTranslationData(_fallbackLocale!)); + if (baseLangData != null) { try { data.addAll(baseLangData); @@ -104,6 +133,7 @@ class EasyLocalizationController extends ChangeNotifier { data = Map.of(data)..addAll(baseLangData); } } + _fallbackTranslations = Translations(data); } } on FlutterError catch (e) { @@ -113,8 +143,39 @@ class EasyLocalizationController extends ChangeNotifier { } } - Future?> loadBaseLangTranslationData( - Locale locale) async { + Future loadSubTranslations() async { + Map subData; + try { + subData = Map.from(await loadTranslationData(_subLocale)); + _subTranslations = Translations(subData); + + if (useFallbackTranslations && _fallbackLocale != null) { + Map? baseLangData; + + if (_subLocale.countryCode != null && _subLocale.countryCode!.isNotEmpty) { + baseLangData = await loadBaseLangTranslationData(Locale(_subLocale.languageCode)); + } + + subData = Map.from(await loadTranslationData(_fallbackLocale!)); + + if (baseLangData != null) { + try { + subData.addAll(baseLangData); + } on UnsupportedError { + subData = Map.of(subData)..addAll(baseLangData); + } + } + + _subFallbackTranslations = Translations(subData); + } + } on FlutterError catch (e) { + onLoadError(e); + } catch (e) { + onLoadError(FlutterError(e.toString())); + } + } + + Future?> loadBaseLangTranslationData(Locale locale) async { try { return await loadTranslationData(Locale(locale.languageCode)); } on FlutterError catch (e) { @@ -140,6 +201,8 @@ class EasyLocalizationController extends ChangeNotifier { Locale get locale => _locale; + Locale get subLocale => _subLocale; + Future setLocale(Locale l) async { _locale = l; await loadTranslations(); @@ -148,6 +211,14 @@ class EasyLocalizationController extends ChangeNotifier { await _saveLocale(_locale); } + Future setSubLocale(Locale l) async { + _subLocale = l; + await loadSubTranslations(); + notifyListeners(); + EasyLocalization.logger('Sub locale $subLocale changed'); + await _saveSubLocale(_subLocale); + } + Future _saveLocale(Locale? locale) async { if (!saveLocale) return; final preferences = await SharedPreferences.getInstance(); @@ -155,10 +226,19 @@ class EasyLocalizationController extends ChangeNotifier { EasyLocalization.logger('Locale $locale saved'); } + Future _saveSubLocale(Locale? locale) async { + if (!saveSubLocale) return; + final preferences = await SharedPreferences.getInstance(); + await preferences.setString('subLocale', subLocale.toString()); + EasyLocalization.logger('Sub Locale $subLocale saved'); + } + static Future initEasyLocation() async { final preferences = await SharedPreferences.getInstance(); final strLocale = preferences.getString('locale'); _savedLocale = strLocale?.toLocale(); + final strSubLocale = preferences.getString('subLocale'); + _savedSubLocale = strSubLocale?.toLocale(); final foundPlatformLocale = await findSystemLocale(); _deviceLocale = foundPlatformLocale.toLocale(); EasyLocalization.logger.debug('Localization initialized'); @@ -166,8 +246,10 @@ class EasyLocalizationController extends ChangeNotifier { Future deleteSaveLocale() async { _savedLocale = null; + _savedSubLocale = null; final preferences = await SharedPreferences.getInstance(); await preferences.remove('locale'); + await preferences.remove('subLocale'); EasyLocalization.logger('Saved locale deleted'); } @@ -177,6 +259,7 @@ class EasyLocalizationController extends ChangeNotifier { EasyLocalization.logger('Reset locale to platform locale $_deviceLocale'); await setLocale(_deviceLocale); + await setSubLocale(_deviceLocale); } } @@ -189,9 +272,7 @@ extension LocaleExtension on Locale { if (languageCode != locale.languageCode) { return false; } - if (countryCode != null && - countryCode!.isNotEmpty && - countryCode != locale.countryCode) { + if (countryCode != null && countryCode!.isNotEmpty && countryCode != locale.countryCode) { return false; } if (scriptCode != null && scriptCode != locale.scriptCode) { diff --git a/lib/src/localization.dart b/lib/src/localization.dart index 57742ee3..ed7a736e 100644 --- a/lib/src/localization.dart +++ b/lib/src/localization.dart @@ -5,12 +5,12 @@ import 'plural_rules.dart'; import 'translations.dart'; class Localization { - Translations? _translations, _fallbackTranslations; + Translations? _translations, _subTranslations, _fallbackTranslations, _subFallbackTranslations; late Locale _locale; + late Locale _subLocale; final RegExp _replaceArgRegex = RegExp('{}'); - final RegExp _linkKeyMatcher = - RegExp(r'(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))'); + final RegExp _linkKeyMatcher = RegExp(r'(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))'); final RegExp _linkKeyPrefixMatcher = RegExp(r'^@(?:\.([a-z]+))?:'); final RegExp _bracketsMatcher = RegExp('[()]'); final _modifiers = { @@ -23,17 +23,24 @@ class Localization { static Localization? _instance; static Localization get instance => _instance ?? (_instance = Localization()); - static Localization? of(BuildContext context) => - Localizations.of(context, Localization); + static Localization? of(BuildContext context) => Localizations.of(context, Localization); static bool load( - Locale locale, { + Locale locale, + Locale? subLocale, { Translations? translations, + Translations? subTranslations, Translations? fallbackTranslations, + Translations? subFallbackTranslations, }) { instance._locale = locale; instance._translations = translations; + instance._subTranslations = subTranslations; instance._fallbackTranslations = fallbackTranslations; + instance._subFallbackTranslations = subFallbackTranslations; + if (subLocale != null) { + instance._subLocale = subLocale; + } return translations == null ? false : true; } @@ -58,6 +65,27 @@ class Localization { return _replaceArgs(res, args); } + String trSub( + String key, { + List? args, + Map? namedArgs, + String? gender, + }) { + late String res; + + if (gender != null) { + res = _genderSub(key, gender: gender); + } else { + res = _resolveSub(key); + } + + res = _replaceLinks(res); + + res = _replaceNamedArgs(res, namedArgs); + + return _replaceArgs(res, args); + } + String _replaceLinks(String res, {bool logging = true}) { // TODO: add recursion detection and a resolve stack. final matches = _linkKeyMatcher.allMatches(res); @@ -70,8 +98,7 @@ class Localization { final formatterName = linkPrefixMatches.first[1]; // Remove the leading @:, @.case: and the brackets - final linkPlaceholder = - link.replaceAll(linkPrefix, '').replaceAll(_bracketsMatcher, ''); + final linkPlaceholder = link.replaceAll(linkPrefix, '').replaceAll(_bracketsMatcher, ''); var translated = _resolve(linkPlaceholder); @@ -80,14 +107,12 @@ class Localization { translated = _modifiers[formatterName]!(translated); } else { if (logging) { - EasyLocalization.logger.warning( - 'Undefined modifier $formatterName, available modifiers: ${_modifiers.keys.toString()}'); + EasyLocalization.logger.warning('Undefined modifier $formatterName, available modifiers: ${_modifiers.keys.toString()}'); } } } - result = - translated.isEmpty ? result : result.replaceAll(link, translated); + result = translated.isEmpty ? result : result.replaceAll(link, translated); } return result; @@ -103,8 +128,7 @@ class Localization { String _replaceNamedArgs(String res, Map? args) { if (args == null || args.isEmpty) return res; - args.forEach((String key, String value) => - res = res.replaceAll(RegExp('{$key}'), value)); + args.forEach((String key, String value) => res = res.replaceAll(RegExp('{$key}'), value)); return res; } @@ -134,7 +158,6 @@ class Localization { String? name, NumberFormat? format, }) { - late String res; final pluralRule = _pluralRule(_locale.languageCode, value); @@ -173,10 +196,60 @@ class Localization { return _replaceArgs(res, args ?? [formattedValue]); } + String pluralSub( + String key, + num value, { + List? args, + Map? namedArgs, + String? name, + NumberFormat? format, + }) { + late String res; + + final pluralRule = _pluralRule(_subLocale.languageCode, value); + final pluralCase = pluralRule != null ? pluralRule() : _pluralCaseFallback(value); + + switch (pluralCase) { + case PluralCase.ZERO: + res = _resolvePluralSub(key, 'zero'); + break; + case PluralCase.ONE: + res = _resolvePluralSub(key, 'one'); + break; + case PluralCase.TWO: + res = _resolvePluralSub(key, 'two'); + break; + case PluralCase.FEW: + res = _resolvePluralSub(key, 'few'); + break; + case PluralCase.MANY: + res = _resolvePluralSub(key, 'many'); + break; + case PluralCase.OTHER: + res = _resolvePluralSub(key, 'other'); + break; + default: + throw ArgumentError.value(value, 'howMany', 'Invalid plural argument'); + } + + final formattedValue = format == null ? '$value' : format.format(value); + + if (name != null) { + namedArgs = {...?namedArgs, name: formattedValue}; + } + res = _replaceNamedArgs(res, namedArgs); + + return _replaceArgs(res, args ?? [formattedValue]); + } + String _gender(String key, {required String gender}) { return _resolve('$key.$gender'); } + String _genderSub(String key, {required String gender}) { + return _resolveSub('$key.$gender'); + } + String _resolvePlural(String key, String subKey) { if (subKey == 'other') return _resolve('$key.other'); @@ -188,6 +261,17 @@ class Localization { return resource; } + String _resolvePluralSub(String key, String subKey) { + if (subKey == 'other') return _resolve('$key.other'); + + final tag = '$key.$subKey'; + var resource = _resolveSub(tag, logging: false, fallback: _subFallbackTranslations != null); + if (resource == tag) { + resource = _resolveSub('$key.other'); + } + return resource; + } + String _resolve(String key, {bool logging = true, bool fallback = true}) { var resource = _translations?.get(key); if (resource == null) { @@ -200,8 +284,7 @@ class Localization { resource = _fallbackTranslations?.get(key); if (resource == null) { if (logging) { - EasyLocalization.logger - .warning('Fallback localization key [$key] not found'); + EasyLocalization.logger.warning('Fallback localization key [$key] not found'); } return key; } @@ -210,7 +293,32 @@ class Localization { return resource; } - bool exists(String key){ + String _resolveSub(String key, {bool logging = true, bool fallback = true}) { + var resource = _subTranslations?.get(key); + if (resource == null) { + if (logging) { + EasyLocalization.logger.warning('Localization key [$key] not found'); + } + if (_subFallbackTranslations == null || !fallback) { + return key; + } else { + resource = _subFallbackTranslations?.get(key); + if (resource == null) { + if (logging) { + EasyLocalization.logger.warning('Fallback localization key [$key] not found'); + } + return key; + } + } + } + return resource; + } + + bool exists(String key) { return _translations?.get(key) != null; } + + bool existsSub(String key) { + return _subTranslations?.get(key) != null; + } } diff --git a/lib/src/public.dart b/lib/src/public.dart index ba419496..233850f7 100644 --- a/lib/src/public.dart +++ b/lib/src/public.dart @@ -38,16 +38,54 @@ String tr( Map? namedArgs, String? gender, }) { - return context != null - ? Localization.of(context)! - .tr(key, args: args, namedArgs: namedArgs, gender: gender) - : Localization.instance - .tr(key, args: args, namedArgs: namedArgs, gender: gender); + return context != null ? Localization.of(context)!.tr(key, args: args, namedArgs: namedArgs, gender: gender) : Localization.instance.tr(key, args: args, namedArgs: namedArgs, gender: gender); } bool trExists(String key) { - return Localization.instance - .exists(key); + return Localization.instance.exists(key); +} + +/// {@template trSub} +/// Main function for translate your language keys +/// [key] Localization key +/// [BuildContext] The location in the tree where this widget builds +/// [args] List of localized strings. Replaces {} left to right +/// [namedArgs] Map of localized strings. Replaces the name keys {key_name} according to its name +/// [gender] Gender switcher. Changes the localized string based on gender string +/// +/// Example: +/// +/// ```json +/// { +/// "msg":"{} are written in the {} language", +/// "msg_named":"Easy localization is written in the {lang} language", +/// "msg_mixed":"{} are written in the {lang} language", +/// "gender":{ +/// "male":"Hi man ;) {}", +/// "female":"Hello girl :) {}", +/// "other":"Hello {}" +/// } +/// } +/// ``` +/// ```dart +/// Text('msg').trSub(args: ['Easy localization', 'Dart']), // args +/// Text('msg_named').trSub(namedArgs: {'lang': 'Dart'}), // namedArgs +/// Text('msg_mixed').trSub(args: ['Easy localization'], namedArgs: {'lang': 'Dart'}), // args and namedArgs +/// Text('gender').trSub(gender: _gender ? "female" : "male"), // gender +/// ``` +/// {@endtemplate} +String trSub( + String key, { + BuildContext? context, + List? args, + Map? namedArgs, + String? gender, +}) { + return context != null ? Localization.of(context)!.trSub(key, args: args, namedArgs: namedArgs, gender: gender) : Localization.instance.trSub(key, args: args, namedArgs: namedArgs, gender: gender); +} + +bool trSubExists(String key) { + return Localization.instance.existsSub(key); } /// {@template plural} @@ -110,8 +148,70 @@ String plural( NumberFormat? format, }) { return context != null - ? Localization.of(context)!.plural(key, value, - args: args, namedArgs: namedArgs, name: name, format: format) - : Localization.instance.plural(key, value, - args: args, namedArgs: namedArgs, name: name, format: format); + ? Localization.of(context)!.plural(key, value, args: args, namedArgs: namedArgs, name: name, format: format) + : Localization.instance.plural(key, value, args: args, namedArgs: namedArgs, name: name, format: format); +} + +/// {@template pluralSub} +/// function translate with pluralization +/// [key] Localization key +/// [value] Number value for pluralization +/// [BuildContext] The location in the tree where this widget builds +/// [args] List of localized strings. Replaces {} left to right +/// [namedArgs] Map of localized strings. Replaces the name keys {key_name} according to its name +/// [name] Name of number value. Replaces {$name} to value +/// [format] Formats a numeric value using a [NumberFormat](https://pub.dev/documentation/intl/latest/intl/NumberFormat-class.html) class +/// +/// Example: +///```json +/// { +/// "day": { +/// "zero":"{} дней", +/// "one": "{} день", +/// "two": "{} дня", +/// "few": "{} дня", +/// "many": "{} дней", +/// "other": "{} дней" +/// }, +/// "money": { +/// "zero": "You not have money", +/// "one": "You have {} dollar", +/// "many": "You have {} dollars", +/// "other": "You have {} dollars" +/// }, +/// "money_args": { +/// "zero": "{} has no money", +/// "one": "{} has {} dollar", +/// "many": "{} has {} dollars", +/// "other": "{} has {} dollars" +/// }, +/// "money_named_args": { +/// "zero": "{name} has no money", +/// "one": "{name} has {money} dollar", +/// "many": "{name} has {money} dollars", +/// "other": "{name} has {money} dollars" +/// } +/// } +/// ``` +/// +///```dart +/// Text('money').pluralSub(1000000, format: NumberFormat.compact(locale: context.locale.toString())) // output: You have 1M dollars +/// print('day'.pluralSub(21)); // output: 21 день +/// var money = pluralSub('money', 10.23) // output: You have 10.23 dollars +/// var money = pluralSub('money_args', 10.23, args: ['John', '10.23']) // output: John has 10.23 dollars +/// var money = pluralSub('money_named_args', 10.23, namedArgs: {'name': 'Jane'}, name: 'money') // output: Jane has 10.23 dollars +/// ``` +/// {@endtemplate} +String pluralSub( + String key, + num value, { + List? args, + BuildContext? context, + Map? namedArgs, + String? name, + NumberFormat? format, +}) { + return context != null + ? Localization.of(context)!.pluralSub(key, value, args: args, namedArgs: namedArgs, name: name, format: format) + : Localization.instance.pluralSub(key, value, args: args, namedArgs: namedArgs, name: name, format: format); } diff --git a/lib/src/public_ext.dart b/lib/src/public_ext.dart index 876099a1..5e18bc9c 100644 --- a/lib/src/public_ext.dart +++ b/lib/src/public_ext.dart @@ -14,18 +14,67 @@ import 'public.dart' as ez; /// ``` extension TextTranslateExtension on Text { /// {@macro tr} - Text tr( - {List? args, - BuildContext? context, - Map? namedArgs, - String? gender}) => + Text tr({List? args, BuildContext? context, Map? namedArgs, String? gender}) => Text( + ez.tr( + data ?? '', + context: context, + args: args, + namedArgs: namedArgs, + gender: gender, + ), + key: key, + style: style, + strutStyle: strutStyle, + textAlign: textAlign, + textDirection: textDirection, + locale: locale, + softWrap: softWrap, + overflow: overflow, + textScaleFactor: textScaleFactor, + maxLines: maxLines, + semanticsLabel: semanticsLabel, + textWidthBasis: textWidthBasis); + + /// {@macro trSub} + Text trSub({List? args, BuildContext? context, Map? namedArgs, String? gender}) => Text( + ez.trSub( + data ?? '', + context: context, + args: args, + namedArgs: namedArgs, + gender: gender, + ), + key: key, + style: style, + strutStyle: strutStyle, + textAlign: textAlign, + textDirection: textDirection, + locale: locale, + softWrap: softWrap, + overflow: overflow, + textScaleFactor: textScaleFactor, + maxLines: maxLines, + semanticsLabel: semanticsLabel, + textWidthBasis: textWidthBasis); + + /// {@macro plural} + Text plural( + num value, { + BuildContext? context, + List? args, + Map? namedArgs, + String? name, + NumberFormat? format, + }) => Text( - ez.tr( + ez.plural( data ?? '', + value, context: context, args: args, namedArgs: namedArgs, - gender: gender, + name: name, + format: format, ), key: key, style: style, @@ -40,8 +89,8 @@ extension TextTranslateExtension on Text { semanticsLabel: semanticsLabel, textWidthBasis: textWidthBasis); - /// {@macro plural} - Text plural( + /// {@macro pluralSub} + Text pluralSub( num value, { BuildContext? context, List? args, @@ -50,7 +99,7 @@ extension TextTranslateExtension on Text { NumberFormat? format, }) => Text( - ez.plural( + ez.pluralSub( data ?? '', value, context: context, @@ -87,11 +136,21 @@ extension StringTranslateExtension on String { String? gender, BuildContext? context, }) => - ez.tr(this, - context: context, args: args, namedArgs: namedArgs, gender: gender); + ez.tr(this, context: context, args: args, namedArgs: namedArgs, gender: gender); + + /// {@macro trSub} + String trSub({ + List? args, + Map? namedArgs, + String? gender, + BuildContext? context, + }) => + ez.trSub(this, context: context, args: args, namedArgs: namedArgs, gender: gender); bool trExists() => ez.trExists(this); + bool trSubExists() => ez.trSubExists(this); + /// {@macro plural} String plural( num value, { @@ -110,6 +169,25 @@ extension StringTranslateExtension on String { name: name, format: format, ); + + /// {@macro pluralSub} + String pluralSub( + num value, { + List? args, + BuildContext? context, + Map? namedArgs, + String? name, + NumberFormat? format, + }) => + ez.pluralSub( + this, + value, + context: context, + args: args, + namedArgs: namedArgs, + name: name, + format: format, + ); } /// BuildContext extension method for access to [locale], [supportedLocales], [fallbackLocale], [delegates] and [deleteSaveLocale()] @@ -129,19 +207,20 @@ extension BuildContextEasyLocalizationExtension on BuildContext { /// Get current locale Locale get locale => EasyLocalization.of(this)!.locale; + Locale get subLocale => EasyLocalization.of(this)!.subLocale; + /// Change app locale - Future setLocale(Locale val) async => - EasyLocalization.of(this)!.setLocale(val); + Future setLocale(Locale val) async => EasyLocalization.of(this)!.setLocale(val); + + Future setSubLocale(Locale val) async => EasyLocalization.of(this)!.setSubLocale(val); /// Old Change app locale - @Deprecated( - 'This is the func used in the old version of EasyLocalization. The modern func is `setLocale(val)` . ' + @Deprecated('This is the func used in the old version of EasyLocalization. The modern func is `setLocale(val)` . ' 'This feature was deprecated after v3.0.0') set locale(Locale val) => EasyLocalization.of(this)!.setLocale(val); /// Get List of supported locales. - List get supportedLocales => - EasyLocalization.of(this)!.supportedLocales; + List get supportedLocales => EasyLocalization.of(this)!.supportedLocales; /// Get fallback locale Locale? get fallbackLocale => EasyLocalization.of(this)!.fallbackLocale; @@ -156,12 +235,10 @@ extension BuildContextEasyLocalizationExtension on BuildContext { /// GlobalCupertinoLocalizations.delegate /// ], /// ``` - List get localizationDelegates => - EasyLocalization.of(this)!.delegates; + List get localizationDelegates => EasyLocalization.of(this)!.delegates; /// Clears a saved locale from device storage - Future deleteSaveLocale() => - EasyLocalization.of(this)!.deleteSaveLocale(); + Future deleteSaveLocale() => EasyLocalization.of(this)!.deleteSaveLocale(); /// Getting device locale from platform Locale get deviceLocale => EasyLocalization.of(this)!.deviceLocale; @@ -218,6 +295,26 @@ extension BuildContextEasyLocalizationExtension on BuildContext { ); } + String trSub( + String key, { + List? args, + Map? namedArgs, + String? gender, + }) { + final localization = Localization.of(this); + + if (localization == null) { + throw const LocalizationNotFoundException(); + } + + return localization.trSub( + key, + args: args, + namedArgs: namedArgs, + gender: gender, + ); + } + String plural( String key, num number, { @@ -241,4 +338,28 @@ extension BuildContextEasyLocalizationExtension on BuildContext { format: format, ); } + + String pluralSub( + String key, + num number, { + List? args, + Map? namedArgs, + String? name, + NumberFormat? format, + }) { + final localization = Localization.of(this); + + if (localization == null) { + throw const LocalizationNotFoundException(); + } + + return localization.pluralSub( + key, + number, + args: args, + namedArgs: namedArgs, + name: name, + format: format, + ); + } } diff --git a/lib/src/widgets.dart b/lib/src/widgets.dart index 0993a1a6..996aeec1 100644 --- a/lib/src/widgets.dart +++ b/lib/src/widgets.dart @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; class FutureErrorWidget extends StatelessWidget { final String msg; - const FutureErrorWidget({Key? key, this.msg = 'Loading ...'}) - : super(key: key); + const FutureErrorWidget({Key? key, this.msg = 'Loading ...'}) : super(key: key); @override Widget build(BuildContext context) { return Container( @@ -20,16 +19,14 @@ class FutureErrorWidget extends StatelessWidget { 'Easy Localization:', textAlign: TextAlign.center, textDirection: TextDirection.ltr, - style: TextStyle( - fontWeight: FontWeight.w700, color: Colors.red, fontSize: 25.0), + style: TextStyle(fontWeight: FontWeight.w700, color: Colors.red, fontSize: 25.0), ), const SizedBox(height: 10), Text( '"$msg"', textAlign: TextAlign.center, textDirection: TextDirection.ltr, - style: const TextStyle( - fontWeight: FontWeight.w500, color: Colors.red, fontSize: 14.0), + style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.red, fontSize: 14.0), ), const SizedBox(height: 30), // Center( diff --git a/test/easy_localization_context_test.dart b/test/easy_localization_context_test.dart index c0348a97..2ba3f0db 100644 --- a/test/easy_localization_context_test.dart +++ b/test/easy_localization_context_test.dart @@ -87,9 +87,7 @@ void main() async { saveLocale: false, useOnlyLangCode: true, // fallbackLocale:Locale('en') , - supportedLocales: const [ - Locale('ar') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('ar')], // Locale('en', 'US'), Locale('ar','DZ') child: const MyApp(), )); // await tester.idle(); @@ -117,10 +115,7 @@ void main() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', // fallbackLocale:Locale('en') , - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') child: const MyApp(), )); // await tester.idle(); @@ -140,10 +135,7 @@ void main() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', // fallbackLocale:Locale('en') , - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') child: const MyApp(), )); // await tester.idle(); @@ -161,10 +153,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') child: const MyApp(), )); // await tester.idle(); @@ -182,10 +171,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') startLocale: const Locale('ar', 'DZ'), child: const MyApp(), )); @@ -208,10 +194,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') child: const MyApp(), )); await tester.idle(); @@ -229,10 +212,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') startLocale: const Locale('ar', 'DZ'), child: const MyApp(), )); diff --git a/test/easy_localization_language_specific_test.dart b/test/easy_localization_language_specific_test.dart index 2dce5467..63444979 100644 --- a/test/easy_localization_language_specific_test.dart +++ b/test/easy_localization_language_specific_test.dart @@ -8,58 +8,50 @@ 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'), + forceSubLocale: const Locale('ru'), + 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, + saveSubLocale: true, + assetLoader: const JsonAssetLoader()); - setUpAll(() async { - await r.loadTranslations(); - - }); + setUpAll(() async { + await r.loadTranslations(); + await r.loadSubTranslations(); + }); - 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'), const Locale('ru'), translations: r.translations, subTranslations: r.subTranslations, fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 1), 'one hat'); + }); + test('english other', () async { + Localization.load(const Locale('en'), const Locale('ru'), translations: r.translations, subTranslations: r.subTranslations, 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'), const Locale('en'), translations: r.translations, subTranslations: r.subTranslations, fallbackTranslations: r.fallbackTranslations); + expect(Localization.instance.plural('hat', 1), 'one hat'); + }); + test('russian few', () async { + Localization.load(const Locale('ru'), const Locale('en'), translations: r.translations, subTranslations: r.subTranslations, 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'), const Locale('en'), translations: r.translations, subTranslations: r.subTranslations, fallbackTranslations: r.fallbackTranslations); + 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_test.dart b/test/easy_localization_test.dart index 5dff2aa3..a57fd6a3 100644 --- a/test/easy_localization_test.dart +++ b/test/easy_localization_test.dart @@ -15,17 +15,20 @@ void main() { group('localization', () { var r1 = EasyLocalizationController( forceLocale: const Locale('en'), + forceSubLocale: const Locale('en'), path: 'path/en.json', supportedLocales: const [Locale('en')], useOnlyLangCode: true, useFallbackTranslations: false, saveLocale: false, + saveSubLocale: false, onLoadError: (FlutterError e) { log(e.toString()); }, assetLoader: const JsonAssetLoader()); var r2 = EasyLocalizationController( forceLocale: const Locale('en', 'us'), + forceSubLocale: const Locale('en', 'us'), supportedLocales: const [Locale('en', 'us')], path: 'path/en-us.json', useOnlyLangCode: false, @@ -34,6 +37,7 @@ void main() { log(e.toString()); }, saveLocale: false, + saveSubLocale: false, assetLoader: const JsonAssetLoader()); setUpAll(() async { EasyLocalization.logger.enableLevels = [ @@ -42,8 +46,10 @@ void main() { ]; await r1.loadTranslations(); + await r1.loadSubTranslations(); await r2.loadTranslations(); - Localization.load(const Locale('en'), translations: r1.translations); + await r2.loadSubTranslations(); + Localization.load(const Locale('en'), const Locale('en'), translations: r1.translations, subTranslations: r1.subTranslations); }); test('is a localization object', () { expect(Localization.instance, isInstanceOf()); @@ -57,28 +63,18 @@ void main() { }); test('load() succeeds', () async { - expect( - Localization.load(const Locale('en'), translations: r1.translations), - true); + expect(Localization.load(const Locale('en'), const Locale('en'), translations: r1.translations, subTranslations: r1.subTranslations), true); }); test('load() with fallback succeeds', () async { - expect( - Localization.load(const Locale('en'), - translations: r1.translations, - fallbackTranslations: r2.translations), - true); + expect(Localization.load(const Locale('en'), const Locale('en'), translations: r1.translations, subTranslations: r1.subTranslations, fallbackTranslations: r2.translations), true); }); - test('merge fallbackLocale with locale without country code succeeds', - () async { - await EasyLocalizationController( + test('merge fallbackLocale with locale without country code succeeds', () async { + EasyLocalizationController( forceLocale: const Locale('es', 'AR'), - supportedLocales: const [ - Locale('en'), - Locale('es'), - Locale('es', 'AR') - ], + forceSubLocale: const Locale('es', 'AR'), + supportedLocales: const [Locale('en'), Locale('es'), Locale('es', 'AR')], path: 'path/en-us.json', useOnlyLangCode: false, useFallbackTranslations: true, @@ -87,24 +83,23 @@ void main() { throw e; }, saveLocale: false, + saveSubLocale: false, assetLoader: const ImmutableJsonAssetLoader(), - ).loadTranslations(); + ) + ..loadTranslations() + ..loadSubTranslations(); }); test('localeFromString() succeeds', () async { expect(const Locale('ar'), 'ar'.toLocale()); expect(const Locale('ar', 'DZ'), 'ar_DZ'.toLocale()); - expect(const Locale.fromSubtags(languageCode: 'ar', scriptCode: 'Arab'), - 'ar_Arab'.toLocale()); - expect( - const Locale.fromSubtags( - languageCode: 'ar', scriptCode: 'Arab', countryCode: 'DZ'), - 'ar_Arab_DZ'.toLocale()); + expect(const Locale.fromSubtags(languageCode: 'ar', scriptCode: 'Arab'), 'ar_Arab'.toLocale()); + expect(const Locale.fromSubtags(languageCode: 'ar', scriptCode: 'Arab', countryCode: 'DZ'), 'ar_Arab_DZ'.toLocale()); }); test('load() Failed assertion', () async { try { - Localization.load(const Locale('en'), translations: null); + Localization.load(const Locale('en'), const Locale('ru'), translations: null, subTranslations: null); } on AssertionError catch (e) { // throw AssertionError('Expected ArgumentError'); expect(e, isAssertionError); @@ -112,22 +107,15 @@ void main() { }); test('load() correctly sets locale path', () async { - expect( - Localization.load(const Locale('en'), translations: r1.translations), - true); + expect(Localization.load(const Locale('en'), const Locale('en'), translations: r1.translations, subTranslations: r1.subTranslations), true); expect(Localization.instance.tr('path'), 'path/en.json'); }); test('load() respects useOnlyLangCode', () async { - expect( - Localization.load(const Locale('en'), translations: r1.translations), - true); + expect(Localization.load(const Locale('en'), const Locale('en'), translations: r1.translations), true); expect(Localization.instance.tr('path'), 'path/en.json'); - expect( - Localization.load(const Locale('en', 'us'), - translations: r2.translations), - true); + expect(Localization.load(const Locale('en', 'us'), const Locale('en', 'us'), translations: r2.translations, subTranslations: r2.subTranslations), true); expect(Localization.instance.tr('path'), 'path/en-us.json'); }); @@ -146,6 +134,7 @@ void main() { log(e.toString()); }, saveLocale: true, + saveSubLocale: true, assetLoader: const JsonAssetLoader(), ); expect(controller.locale, const Locale('en')); @@ -154,8 +143,7 @@ void main() { }); /// E.g. if user saved a locale that was removed in a later version - test('controller loads fallback if saved locale is not supported', - () async { + test('controller loads fallback if saved locale is not supported', () async { SharedPreferences.setMockInitialValues({ 'locale': 'de', }); @@ -170,6 +158,7 @@ void main() { log(e.toString()); }, saveLocale: true, + saveSubLocale: true, assetLoader: const JsonAssetLoader(), ); expect(controller.locale, const Locale('fb')); @@ -191,12 +180,9 @@ void main() { const zh = Locale('zh', ''); const zh2 = Locale('zh', ''); const zhCN = Locale('zh', 'CN'); - const zhHans = - Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'); - const zhHant = - Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'); - const zhHansCN = Locale.fromSubtags( - languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'); + const zhHans = Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'); + const zhHant = Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'); + const zhHansCN = Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'); expect(zh.supports(zhHansCN), isTrue); expect(zh2.supports(zhHansCN), isTrue); expect(zhCN.supports(zhHansCN), isTrue); @@ -208,20 +194,16 @@ void main() { test('select locale from device locale', () { const en = Locale('en', ''); const zh = Locale('zh', ''); - const zhHans = - Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'); - const zhHant = - Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'); - const zhHansCN = Locale.fromSubtags( - languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'); + const zhHans = Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'); + const zhHant = Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'); + const zhHansCN = Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'); expect( EasyLocalizationController.selectLocaleFrom([en, zh], zhHansCN), zh, ); expect( - EasyLocalizationController.selectLocaleFrom( - [zhHant, zhHans], zhHansCN), + EasyLocalizationController.selectLocaleFrom([zhHant, zhHans], zhHansCN), zhHans, ); }); @@ -230,6 +212,7 @@ void main() { group('tr', () { var r = EasyLocalizationController( forceLocale: const Locale('en'), + forceSubLocale: const Locale('en'), supportedLocales: const [Locale('en'), Locale('fb')], fallbackLocale: const Locale('fb'), path: 'path', @@ -239,13 +222,12 @@ void main() { log(e.toString()); }, saveLocale: false, + saveSubLocale: false, assetLoader: const JsonAssetLoader()); setUpAll(() async { await r.loadTranslations(); - Localization.load(const Locale('en'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); + Localization.load(const Locale('en'), const Locale('en'), translations: r.translations, subTranslations: r.subTranslations, fallbackTranslations: r.fallbackTranslations); }); test('finds and returns resource', () { expect(Localization.instance.tr('test'), 'test'); @@ -283,35 +265,23 @@ void main() { }); test('can resolve linked locale messages and apply modifiers', () { - expect(Localization.instance.tr('linkAndModify'), - 'this is linked and MODIFIED'); + expect(Localization.instance.tr('linkAndModify'), 'this is linked and MODIFIED'); }); - test('can resolve multiple linked locale messages and apply modifiers', - () { + test('can resolve multiple linked locale messages and apply modifiers', () { expect(Localization.instance.tr('linkMany'), 'many Locale messages'); }); test('can resolve linked locale messages with brackets', () { - expect(Localization.instance.tr('linkedWithBrackets'), - 'linked with brackets.'); + expect(Localization.instance.tr('linkedWithBrackets'), 'linked with brackets.'); }); test('can resolve any number of nested arguments', () { - expect( - Localization.instance - .tr('nestedArguments', args: ['a', 'argument', '!']), - 'this is a nested argument!'); + expect(Localization.instance.tr('nestedArguments', args: ['a', 'argument', '!']), 'this is a nested argument!'); }); test('can resolve nested named arguments', () { - expect( - Localization.instance.tr('nestedNamedArguments', namedArgs: { - 'firstArg': 'this', - 'secondArg': 'named argument', - 'thirdArg': '!' - }), - 'this is a nested named argument!'); + expect(Localization.instance.tr('nestedNamedArguments', namedArgs: {'firstArg': 'this', 'secondArg': 'named argument', 'thirdArg': '!'}), 'this is a nested named argument!'); }); test('returns missing resource as provided', () { @@ -323,11 +293,9 @@ void main() { expect(Localization.instance.tr('test_missing'), 'test_missing'); final logIterator = printLog.iterator; logIterator.moveNext(); - expect(logIterator.current, - contains('Localization key [test_missing] not found')); + expect(logIterator.current, contains('Localization key [test_missing] not found')); logIterator.moveNext(); - expect(logIterator.current, - contains('Fallback localization key [test_missing] not found')); + expect(logIterator.current, contains('Fallback localization key [test_missing] not found')); })); test('uses fallback translations', overridePrint(() { @@ -338,8 +306,7 @@ void main() { test('reports missing resource with fallback', overridePrint(() { printLog = []; expect(Localization.instance.tr('test_missing_fallback'), 'fallback!'); - expect(printLog.first, - contains('Localization key [test_missing_fallback] not found')); + expect(printLog.first, contains('Localization key [test_missing_fallback] not found')); })); test('returns resource and replaces argument', () { @@ -350,8 +317,7 @@ void main() { }); test('returns resource and replaces argument in any nest level', () { expect( - Localization.instance - .tr('nested.super.duper.nested_with_arg', args: ['what a nest']), + Localization.instance.tr('nested.super.duper.nested_with_arg', args: ['what a nest']), 'nested.super.duper.nested_with_arg what a nest', ); }); @@ -363,25 +329,20 @@ void main() { ); }); - test( - 'should raise exception if provided arguments length is different from the count of {} in the resource', - () { + test('should raise exception if provided arguments length is different from the count of {} in the resource', () { // @TODO }); test('return resource and replaces named argument', () { expect( - Localization.instance.tr('test_replace_named', - namedArgs: {'arg1': 'one', 'arg2': 'two'}), + Localization.instance.tr('test_replace_named', namedArgs: {'arg1': 'one', 'arg2': 'two'}), 'test named replace one two', ); }); - test('returns resource and replaces named argument in any nest level', - () { + test('returns resource and replaces named argument in any nest level', () { expect( - Localization.instance.tr('nested.super.duper.nested_with_named_arg', - namedArgs: {'arg': 'what a nest'}), + Localization.instance.tr('nested.super.duper.nested_with_named_arg', namedArgs: {'arg': 'what a nest'}), 'nested.super.duper.nested_with_named_arg what a nest', ); }); @@ -399,13 +360,11 @@ void main() { test('gender returns the correct resource and replaces args', () { expect( - Localization.instance - .tr('gender_and_replace', gender: 'male', args: ['one']), + Localization.instance.tr('gender_and_replace', gender: 'male', args: ['one']), 'Hi one man ;)', ); expect( - Localization.instance - .tr('gender_and_replace', gender: 'female', args: ['one']), + Localization.instance.tr('gender_and_replace', gender: 'female', args: ['one']), 'Hello one girl :)', ); }); @@ -414,6 +373,7 @@ void main() { group('plural', () { var r = EasyLocalizationController( forceLocale: const Locale('fb'), + forceSubLocale: const Locale('fb'), supportedLocales: [const Locale('fb')], fallbackLocale: const Locale('fb'), path: 'path', @@ -423,13 +383,12 @@ void main() { log(e.toString()); }, saveLocale: false, + saveSubLocale: false, assetLoader: const JsonAssetLoader()); setUpAll(() async { await r.loadTranslations(); - Localization.load(const Locale('fb'), - translations: r.translations, - fallbackTranslations: r.fallbackTranslations); + Localization.load(const Locale('fb'), const Locale('fb'), translations: r.translations, subTranslations: r.subTranslations, fallbackTranslations: r.fallbackTranslations); }); test('zero', () { @@ -459,8 +418,7 @@ void main() { expect(Localization.instance.plural('hat_other', 1), 'other hats'); }); - test('two as fallback and fallback translations priority', - overridePrint(() { + test('two as fallback and fallback translations priority', overridePrint(() { printLog = []; expect( Localization.instance.plural('test_fallback_plurals', 2), @@ -470,55 +428,45 @@ void main() { })); test('with number format', () { - expect( - Localization.instance - .plural('day', 3, format: NumberFormat.currency()), - 'USD3.00 other days'); + expect(Localization.instance.plural('day', 3, format: NumberFormat.currency()), 'USD3.00 other days'); }); test('zero with args', () { - expect(Localization.instance.plural('money', 0, args: ['John', '0']), - 'John has no money'); + expect(Localization.instance.plural('money', 0, args: ['John', '0']), 'John has no money'); }); test('one with args', () { - expect(Localization.instance.plural('money', 1, args: ['John', '1']), - 'John has 1 dollar'); + expect(Localization.instance.plural('money', 1, args: ['John', '1']), 'John has 1 dollar'); }); test('other with args', () { - expect(Localization.instance.plural('money', 3, args: ['John', '3']), - 'John has 3 dollars'); + expect(Localization.instance.plural('money', 3, args: ['John', '3']), 'John has 3 dollars'); }); test('zero with named args', () { expect( - Localization.instance.plural('money_named_args', 0, - namedArgs: {'name': 'John', 'money': '0'}), + Localization.instance.plural('money_named_args', 0, namedArgs: {'name': 'John', 'money': '0'}), 'John has no money', ); }); test('one with named args', () { expect( - Localization.instance.plural('money_named_args', 1, - namedArgs: {'name': 'John', 'money': '1'}), + Localization.instance.plural('money_named_args', 1, namedArgs: {'name': 'John', 'money': '1'}), 'John has 1 dollar', ); }); test('other with named args', () { expect( - Localization.instance.plural('money_named_args', 3, - namedArgs: {'name': 'John', 'money': '3'}), + Localization.instance.plural('money_named_args', 3, namedArgs: {'name': 'John', 'money': '3'}), 'John has 3 dollars', ); }); test('named args and value name', () { expect( - Localization.instance.plural('money_named_args', 3, - namedArgs: {'name': 'John'}, name: 'money'), + Localization.instance.plural('money_named_args', 3, namedArgs: {'name': 'John'}, name: 'money'), 'John has 3 dollars', ); }); diff --git a/test/easy_localization_widget_test.dart b/test/easy_localization_widget_test.dart index 3444837d..fa46c01e 100644 --- a/test/easy_localization_widget_test.dart +++ b/test/easy_localization_widget_test.dart @@ -50,6 +50,23 @@ class MyWidget extends StatelessWidget { } } +class MySubWidget extends StatelessWidget { + const MySubWidget({Key? key}) : super(key: key); + + @override + Widget build(context) { + _context = context; + return Scaffold( + body: Column( + children: [ + const Text('test').trSub(), + const Text('day').pluralSub(1), + ], + ), + ); + } +} + class MyLocalizedWidget extends StatelessWidget { const MyLocalizedWidget({Key? key}) : super(key: key); @@ -95,8 +112,7 @@ void main() async { expect(Localization.of(_context), isInstanceOf()); expect(Localization.instance, isInstanceOf()); expect(Localization.instance, Localization.of(_context)); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); final trFinder = find.text('test'); @@ -125,8 +141,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); final trFinder = find.text('test'); @@ -151,8 +166,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); final trFinder = find.text('test'); @@ -176,15 +190,14 @@ void main() async { // await tester.idle(); // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - final trFinder = - find.byWidgetPredicate((widget) => widget is ErrorWidget); + final trFinder = find.byWidgetPredicate((widget) => widget is ErrorWidget); expect(trFinder, findsOneWidget); await tester.pump(); }); }, ); testWidgets( - '[EasyLocalization] change loacle test', + '[EasyLocalization] change locale test', (WidgetTester tester) async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( @@ -196,8 +209,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); var l = const Locale('en', 'US'); @@ -224,7 +236,46 @@ void main() async { ); testWidgets( - '[EasyLocalization] change loacle test', + '[EasyLocalization] change sub locale test', + (WidgetTester tester) async { + await tester.runAsync(() async { + await tester.pumpWidget(EasyLocalization( + path: '../../i18n', + supportedLocales: const [Locale('en', 'US')], + child: const MyApp(), + )); + // await tester.idle(); + // The async delegator load will require build on the next frame. Thus, pump + await tester.pump(); + + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US')]); + expect(EasyLocalization.of(_context)!.subLocale, const Locale('en', 'US')); + + var l = const Locale('en', 'US'); + await EasyLocalization.of(_context)!.setSubLocale(l); + await tester.pump(); + expect(EasyLocalization.of(_context)!.subLocale, const Locale('en', 'US')); + + final trFinder = find.text('test'); + expect(trFinder, findsOneWidget); + final pluralFinder = find.text('1 day'); + expect(pluralFinder, findsOneWidget); + + expect(tr('test'), 'test'); + expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); + + l = const Locale('ar', 'DZ'); + expect(() async { + await EasyLocalization.of(_context)!.setSubLocale(l); + }, throwsAssertionError); + await tester.pump(); + expect(EasyLocalization.of(_context)!.subLocale, const Locale('en', 'US')); + }); + }, + ); + + testWidgets( + '[EasyLocalization] change locale test', (WidgetTester tester) async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( @@ -237,8 +288,7 @@ void main() async { await tester.pump(); expect(Localization.of(_context), isInstanceOf()); - 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('en', 'US')); var trFinder = find.text('test'); @@ -266,8 +316,7 @@ void main() async { expect(EasyLocalization.of(_context)!.locale, l); l = const Locale('en', 'UK'); - expect(() async => {await EasyLocalization.of(_context)!.setLocale(l)}, - throwsAssertionError); + expect(() async => {await EasyLocalization.of(_context)!.setLocale(l)}, throwsAssertionError); l = const Locale('ar', 'DZ'); await EasyLocalization.of(_context)!.setLocale(l); @@ -279,7 +328,60 @@ void main() async { ); testWidgets( - '[EasyLocalization] loacle ar_DZ test', + '[EasyLocalization] change locale test', + (WidgetTester tester) async { + await tester.runAsync(() async { + await tester.pumpWidget(EasyLocalization( + path: '../../i18n', + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], + child: const MyApp(), + )); + // await tester.idle(); + // The async delegator load will require build on the next frame. Thus, pump + await tester.pump(); + + expect(Localization.of(_context), isInstanceOf()); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US'), const Locale('ar', 'DZ')]); + expect(EasyLocalization.of(_context)!.subLocale, const Locale('en', 'US')); + + var trFinder = find.text('test'); + expect(trFinder, findsOneWidget); + var pluralFinder = find.text('1 day'); + expect(pluralFinder, findsOneWidget); + + expect(tr('test'), 'test'); + + var l = const Locale('en', 'US'); + await EasyLocalization.of(_context)!.setSubLocale(l); + await tester.pump(); + expect(EasyLocalization.of(_context)!.subLocale, l); + + l = const Locale('ar', 'DZ'); + await EasyLocalization.of(_context)!.setSubLocale(l); + // await tester.idle(); + await tester.pump(); + expect(EasyLocalization.of(_context)!.subLocale, l); + + l = const Locale('en', 'US'); + await EasyLocalization.of(_context)!.setSubLocale(l); + // await tester.idle(); + await tester.pump(); + expect(EasyLocalization.of(_context)!.subLocale, l); + + l = const Locale('en', 'UK'); + expect(() async => {await EasyLocalization.of(_context)!.setSubLocale(l)}, throwsAssertionError); + + l = const Locale('ar', 'DZ'); + await EasyLocalization.of(_context)!.setSubLocale(l); + // await tester.idle(); + await tester.pump(); + expect(EasyLocalization.of(_context)!.subLocale, l); + }); + }, + ); + + testWidgets( + '[EasyLocalization] locale ar_DZ test', (WidgetTester tester) async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( @@ -292,13 +394,11 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - await EasyLocalization.of(_context)! - .setLocale(const Locale('ar', 'DZ')); + await EasyLocalization.of(_context)!.setLocale(const Locale('ar', 'DZ')); 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('اختبار'); @@ -334,8 +434,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en'), const Locale('ar')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en'), const Locale('ar')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en')); var l = const Locale('en'); @@ -360,8 +459,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en'), const Locale('ar')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en'), const Locale('ar')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en')); var l = const Locale('en'); @@ -387,11 +485,9 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('ar')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('ar')]); expect(EasyLocalization.of(_context)!.locale, const Locale('ar')); - expect( - EasyLocalization.of(_context)!.fallbackLocale, const Locale('ar')); + expect(EasyLocalization.of(_context)!.fallbackLocale, const Locale('ar')); }); }, ); @@ -412,8 +508,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('ar')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('ar')]); expect(EasyLocalization.of(_context)!.locale, const Locale('ar')); expect(EasyLocalization.of(_context)!.fallbackLocale, null); }); @@ -442,8 +537,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en'), const Locale('ar')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en'), const Locale('ar')]); expect(EasyLocalization.of(_context)!.locale, const Locale('en')); expect(EasyLocalization.of(_context)!.fallbackLocale, null); }); @@ -464,10 +558,8 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US'), const Locale('ar', 'DZ')]); - expect( - EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US'), const Locale('ar', 'DZ')]); + expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); expect(EasyLocalization.of(_context)!.fallbackLocale, null); }); }, @@ -488,10 +580,8 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US'), const Locale('ar', 'DZ')]); - expect( - EasyLocalization.of(_context)!.locale, 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')); expect(EasyLocalization.of(_context)!.fallbackLocale, null); }); }, @@ -522,8 +612,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en'), const Locale('ar')]); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en'), const Locale('ar')]); expect(EasyLocalization.of(_context)!.locale, const Locale('ar')); expect(EasyLocalization.of(_context)!.fallbackLocale, null); }); @@ -554,10 +643,8 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US'), const Locale('ar', 'DZ')]); - expect( - EasyLocalization.of(_context)!.locale, 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')); expect(EasyLocalization.of(_context)!.fallbackLocale, null); }); }, @@ -578,13 +665,10 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.supportedLocales, - [const Locale('en', 'US'), const Locale('ar', 'DZ')]); - expect( - EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); + expect(EasyLocalization.of(_context)!.supportedLocales, [const Locale('en', 'US'), const Locale('ar', 'DZ')]); + expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); - await EasyLocalization.of(_context)! - .setLocale(const Locale('en', 'US')); + await EasyLocalization.of(_context)!.setLocale(const Locale('en', 'US')); }); }, ); @@ -610,8 +694,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect( - EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); + expect(EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); await EasyLocalization.of(_context)!.deleteSaveLocale(); }); }, @@ -631,8 +714,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect( - EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); + expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); }); }, ); @@ -650,8 +732,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect(EasyLocalization.of(_context)!.deviceLocale.toString(), - Platform.localeName); + expect(EasyLocalization.of(_context)!.deviceLocale.toString(), Platform.localeName); }); }, ); @@ -662,10 +743,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') startLocale: const Locale('ar', 'DZ'), child: const MyApp(), )); @@ -673,13 +751,11 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pump(); - expect( - EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); + expect(EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); // reset to device locale await _context.resetLocale(); await tester.pump(); - expect( - EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); + expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); }); }, ); @@ -697,8 +773,7 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pumpAndSettle(); - expect(EasyLocalization.of(_context)!.deviceLocale.toString(), - Platform.localeName); + expect(EasyLocalization.of(_context)!.deviceLocale.toString(), Platform.localeName); }); }, ); @@ -709,10 +784,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') startLocale: const Locale('ar', 'DZ'), child: const MyApp(), )); @@ -720,13 +792,11 @@ void main() async { // The async delegator load will require build on the next frame. Thus, pump await tester.pumpAndSettle(); - expect( - EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); + expect(EasyLocalization.of(_context)!.locale, const Locale('ar', 'DZ')); // reset to device locale await _context.resetLocale(); await tester.pumpAndSettle(); - expect( - EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); + expect(EasyLocalization.of(_context)!.locale, const Locale('en', 'US')); }); }, ); @@ -735,11 +805,9 @@ void main() async { group('Context extensions tests', () { final testWidget = EasyLocalization( path: '../../i18n', - supportedLocales: const [ - Locale('en', 'US'), - Locale('ar', 'DZ') - ], // Locale('en', 'US'), Locale('ar','DZ') + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'DZ')], // Locale('en', 'US'), Locale('ar','DZ') startLocale: const Locale('en', 'US'), + startSubLocale: const Locale('en', 'US'), child: const MyApp( child: MyLocalizedWidget(), ), @@ -764,7 +832,7 @@ void main() async { await tester.runAsync(() async { await tester.pumpWidget(testWidget); - await tester.idle(); + await tester.pump(); // The async delegator load will require build on the next frame. Thus, pump await tester.pumpAndSettle(); @@ -813,13 +881,11 @@ void main() async { await tester.pumpAndSettle(); expect( - initialTranslationValue != _contextTranslationValue && - _contextTranslationValue == expectedArTranslateTextWidgetValue, + initialTranslationValue != _contextTranslationValue && _contextTranslationValue == expectedArTranslateTextWidgetValue, true, ); expect( - initialPluralValue != _contextPluralValue && - _contextPluralValue == expectedArPluralTextWidgetValue, + initialPluralValue != _contextPluralValue && _contextPluralValue == expectedArPluralTextWidgetValue, true, ); });