diff --git a/CHANGELOG.md b/CHANGELOG.md index 52fe0a4f..0f03e8b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + +- Add `supportedLocales` property that can be used to fill `MaterialApp`'s `supportedLocales` argument. + ## 2.2.1 - Fix compilation error occurring when non-standard name (not 'strings.i18n.json') is used for json files. diff --git a/README.md b/README.md index 1d89275e..75caf465 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Lightweight i18n solution. Use JSON files to create typesafe translations. ```yaml dependencies: - fast_i18n: ^2.2.1 + fast_i18n: ^2.3.0 dev_dependencies: build_runner: any diff --git a/example/README.md b/example/README.md index ab22676c..8ff9c4f4 100644 --- a/example/README.md +++ b/example/README.md @@ -4,7 +4,7 @@ ```yaml dependencies: - fast_i18n: ^2.2.1 + fast_i18n: ^2.3.0 dev_dependencies: build_runner: any @@ -67,6 +67,19 @@ void initState() { } ``` +## Step 4a: Override 'supportedLocales' + +```dart +MaterialApp( + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: LocaleSettings.supportedLocales, // <--- +) +``` + ## Step 4b: iOS-only ``` diff --git a/lib/builder.dart b/lib/builder.dart index a79f2acc..e513f2da 100644 --- a/lib/builder.dart +++ b/lib/builder.dart @@ -23,24 +23,27 @@ class I18nBuilder implements Builder { bool _generated = false; - String get inputFilePattern => options.config['input_file_pattern'] ?? defaultInputFilePattern; - String get outputFilePattern => options.config['output_file_pattern'] ?? defaultOutputFilePattern; + String get inputFilePattern => + options.config['input_file_pattern'] ?? defaultInputFilePattern; + String get outputFilePattern => + options.config['output_file_pattern'] ?? defaultOutputFilePattern; @override FutureOr build(BuildStep buildStep) async { - final String baseLocale = Utils.normalize(options.config['base_locale'] ?? defaultBaseLocale); + final String baseLocale = + Utils.normalize(options.config['base_locale'] ?? defaultBaseLocale); final String inputDirectory = options.config['input_directory']; final String outputDirectory = options.config['output_directory']; - final String translateVar = options.config['output_translate_var'] ?? defaultTranslateVar; + final String translateVar = + options.config['output_translate_var'] ?? defaultTranslateVar; final String keyCase = options.config['key_case']; final List maps = options.config['maps']?.cast() ?? []; - if (inputDirectory != null && !buildStep.inputId.path.contains(inputDirectory)) - return; + if (inputDirectory != null && + !buildStep.inputId.path.contains(inputDirectory)) return; // only generate once - if (_generated) - return; + if (_generated) return; _generated = true; @@ -53,7 +56,8 @@ class I18nBuilder implements Builder { : Glob('**$inputFilePattern'); await buildStep.findAssets(findAssetsPattern).forEach((assetId) { - final fileNameNoExtension = assetId.pathSegments.last.replaceAll(inputFilePattern, ''); + final fileNameNoExtension = + assetId.pathSegments.last.replaceAll(inputFilePattern, ''); final match = Utils.localeRegex.firstMatch(fileNameNoExtension); if (match != null) { @@ -77,12 +81,11 @@ class I18nBuilder implements Builder { // build config which applies to all locales final config = I18nConfig( - baseName: baseName ?? defaultBaseName, - baseLocale: baseLocale, - maps: maps, - keyCase: keyCase, - translateVariable: translateVar - ); + baseName: baseName ?? defaultBaseName, + baseLocale: baseLocale, + maps: maps, + keyCase: keyCase, + translateVariable: translateVar); // map each assetId to I18nData final localesWithData = Map(); @@ -96,23 +99,24 @@ class I18nBuilder implements Builder { // generate final String output = generate( - config: config, - translations: localesWithData.values.toList()..sort((a, b) => a.locale.compareTo(b.locale)) - ); + config: config, + translations: localesWithData.values.toList() + ..sort((a, b) => a.locale.compareTo(b.locale))); // write only to main locale - final AssetId baseId = localesWithData.entries - .firstWhere((element) => element.value.base) - .key; + final AssetId baseId = + localesWithData.entries.firstWhere((element) => element.value.base).key; - final finalOutputDirectory = outputDirectory ?? (baseId.pathSegments..removeLast()).join('/'); - final String outFilePath = '$finalOutputDirectory/$baseName$outputFilePattern'; + final finalOutputDirectory = + outputDirectory ?? (baseId.pathSegments..removeLast()).join('/'); + final String outFilePath = + '$finalOutputDirectory/$baseName$outputFilePattern'; File(outFilePath).writeAsStringSync(output); } @override get buildExtensions => { - inputFilePattern: [outputFilePattern], - }; + inputFilePattern: [outputFilePattern], + }; } diff --git a/lib/fast_i18n.dart b/lib/fast_i18n.dart index 8b3b063f..30170726 100644 --- a/lib/fast_i18n.dart +++ b/lib/fast_i18n.dart @@ -1,19 +1,20 @@ library fast_i18n; import 'dart:io'; +import 'dart:ui'; import 'package:fast_i18n/utils.dart'; class FastI18n { + static const _localePartsDelimiter = '-'; - /// returns the locale string used by the device - static String getDeviceLocale() { - return Platform.localeName; - } + /// Returns the locale string used by the device. + static String getDeviceLocale() => Platform.localeName; - /// returns the candidate (or part of it) if it is supported - /// fallback to base locale - static String selectLocale(String candidate, List supported, String baseLocale) { + /// Returns the candidate (or part of it) if it is supported. + /// Fallbacks to base locale. + static String selectLocale( + String candidate, List supported, String baseLocale) { // normalize candidate = Utils.normalize(candidate); @@ -23,7 +24,7 @@ class FastI18n { if (selected != null) return selected; // 2nd try: match the first part (language) - List deviceLocaleParts = candidate.split('-'); + List deviceLocaleParts = candidate.split(_localePartsDelimiter); selected = supported.firstWhere( (element) => element == deviceLocaleParts.first, orElse: () => null); @@ -38,4 +39,40 @@ class FastI18n { // fallback: default locale return baseLocale; } + + /// Converts the passed locales from [String] to [Locale]. + /// Puts the [baseLocale] into the the beginning of the list. + static List convertToLocales( + List locales, String baseLocale) { + final rawSupportedLocales = [ + baseLocale, + ...locales.where((locale) => locale != baseLocale), + ]; + + final supportedLocales = rawSupportedLocales.map((rawLocale) { + if (rawLocale.contains(_localePartsDelimiter)) { + final localeParts = rawLocale + .split(_localePartsDelimiter) + .where((part) => part.isNotEmpty) + .toList(); + if (localeParts.length == 2) { + return Locale.fromSubtags( + languageCode: localeParts[0], countryCode: localeParts[1]); + } else if (localeParts.length == 3) { + return Locale.fromSubtags( + languageCode: localeParts[0], + scriptCode: localeParts[1], + countryCode: localeParts[2], + ); + } else { + throw Exception( + "The locale '$rawLocale' is not in a supported format. Examples of the supported formats: 'en', 'en-US', 'zh-Hans-CN'."); + } + } else { + return Locale.fromSubtags(languageCode: rawLocale); + } + }).toList(); + + return supportedLocales; + } } diff --git a/lib/src/generator.dart b/lib/src/generator.dart index 5a7f228c..e1ae2227 100644 --- a/lib/src/generator.dart +++ b/lib/src/generator.dart @@ -36,7 +36,8 @@ String generate({I18nConfig config, List translations}) { /// generates the header of the .g.dart file /// contains the t function, LocaleSettings class and some global variables -void _generateHeader(StringBuffer buffer, I18nConfig config, List allLocales) { +void _generateHeader( + StringBuffer buffer, I18nConfig config, List allLocales) { // identifiers const String mapVar = '_strings'; const String baseLocaleVar = '_baseLocale'; @@ -77,8 +78,10 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL buffer.writeln(); buffer.writeln('/// Method A: Simple'); buffer.writeln('///'); - buffer.writeln('/// Widgets using this method will not be updated when locale changes during runtime.'); - buffer.writeln('/// Translation happens during initialization of the widget (call of t).'); + buffer.writeln( + '/// Widgets using this method will not be updated when locale changes during runtime.'); + buffer.writeln( + '/// Translation happens during initialization of the widget (call of t).'); buffer.writeln('///'); buffer.writeln('/// Usage:'); buffer.writeln('/// String translated = t.someKey.anotherKey;'); @@ -88,8 +91,10 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL buffer.writeln(); buffer.writeln('/// Method B: Advanced'); buffer.writeln('///'); - buffer.writeln('/// All widgets using this method will trigger a rebuild when locale changes.'); - buffer.writeln('/// Use this if you have e.g. a settings page where the user can select the locale during runtime.'); + buffer.writeln( + '/// All widgets using this method will trigger a rebuild when locale changes.'); + buffer.writeln( + '/// Use this if you have e.g. a settings page where the user can select the locale during runtime.'); buffer.writeln('///'); buffer.writeln('/// Step 1:'); buffer.writeln('/// wrap your App with'); @@ -98,13 +103,16 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL buffer.writeln('/// );'); buffer.writeln('///'); buffer.writeln('/// Step 2:'); - buffer.writeln('/// final t = $translationsClass.of(context); // get t variable'); - buffer.writeln('/// String translated = t.someKey.anotherKey; // use t variable'); + buffer.writeln( + '/// final t = $translationsClass.of(context); // get t variable'); + buffer.writeln( + '/// String translated = t.someKey.anotherKey; // use t variable'); buffer.writeln('class $translationsClass {'); buffer.writeln('\t$translationsClass._(); // no constructor'); buffer.writeln(); buffer.writeln('\tstatic $baseClassName of(BuildContext context) {'); - buffer.writeln('\t\treturn context.dependOnInheritedWidgetOfExactType<$inheritedClass>().translations;'); + buffer.writeln( + '\t\treturn context.dependOnInheritedWidgetOfExactType<$inheritedClass>().translations;'); buffer.writeln('\t}'); buffer.writeln('}'); @@ -114,7 +122,7 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL buffer.writeln('\t$settingsClass._(); // no constructor'); buffer.writeln(); - buffer.writeln('\t/// Use locale of the device, fallbacks to base locale.'); + buffer.writeln('\t/// Uses locale of the device, fallbacks to base locale.'); buffer.writeln('\t/// Returns the locale which has been set.'); buffer.writeln('\tstatic String useDeviceLocale() {'); buffer.writeln('\t\tString deviceLocale = FastI18n.getDeviceLocale();'); @@ -122,54 +130,67 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL buffer.writeln('\t}'); buffer.writeln(); - buffer.writeln('\t/// Set locale, fallbacks to base locale.'); + buffer.writeln('\t/// Sets locale, fallbacks to base locale.'); buffer.writeln('\t/// Returns the locale which has been set.'); buffer.writeln('\tstatic String setLocale(String locale) {'); - buffer.writeln('\t\t$localeVar = FastI18n.selectLocale(locale, $mapVar.keys.toList(), $baseLocaleVar);'); + buffer.writeln( + '\t\t$localeVar = FastI18n.selectLocale(locale, $mapVar.keys.toList(), $baseLocaleVar);'); buffer.writeln('\t\t$translateVar = $mapVar[$localeVar];'); buffer.writeln(); buffer.writeln('\t\tif ($translationProviderKey.currentState != null) {'); - buffer.writeln('\t\t\t$translationProviderKey.currentState.setLocale($localeVar);'); + buffer.writeln( + '\t\t\t$translationProviderKey.currentState.setLocale($localeVar);'); buffer.writeln('\t\t}'); buffer.writeln(); buffer.writeln('\t\treturn $localeVar;'); buffer.writeln('\t}'); buffer.writeln(); - buffer.writeln('\t/// Get current locale.'); + buffer.writeln('\t/// Gets current locale.'); buffer.writeln('\tstatic String get currentLocale {'); buffer.writeln('\t\treturn $localeVar;'); buffer.writeln('\t}'); buffer.writeln(); - buffer.writeln('\t/// Get base locale.'); + buffer.writeln('\t/// Gets base locale.'); buffer.writeln('\tstatic String get baseLocale {'); buffer.writeln('\t\treturn $baseLocaleVar;'); buffer.writeln('\t}'); buffer.writeln(); - buffer.writeln('\t/// Get supported locales.'); + buffer.writeln('\t/// Gets supported locales.'); buffer.writeln('\tstatic List get locales {'); buffer.writeln('\t\treturn $mapVar.keys.toList();'); buffer.writeln('\t}'); + buffer.writeln(); + buffer.writeln('\t/// Get supported locales with base locale sorted first.'); + buffer.writeln('\tstatic List get supportedLocales {'); + buffer.writeln( + '\t\treturn FastI18n.convertToLocales($mapVar.keys.toList(), $baseLocaleVar);'); + buffer.writeln('\t}'); + buffer.writeln('}'); // TranslationProvider buffer.writeln(); - buffer.writeln('GlobalKey<$translationProviderStateClass> $translationProviderKey = new GlobalKey<$translationProviderStateClass>();'); + buffer.writeln( + 'GlobalKey<$translationProviderStateClass> $translationProviderKey = new GlobalKey<$translationProviderStateClass>();'); buffer.writeln(); buffer.writeln('class $translationProviderClass extends StatefulWidget {'); - buffer.writeln('\t$translationProviderClass({@required this.child}) : super(key: $translationProviderKey);'); + buffer.writeln( + '\t$translationProviderClass({@required this.child}) : super(key: $translationProviderKey);'); buffer.writeln(); buffer.writeln('\tfinal Widget child;'); buffer.writeln(); buffer.writeln('\t@override'); - buffer.writeln('\t$translationProviderStateClass createState() => $translationProviderStateClass();'); + buffer.writeln( + '\t$translationProviderStateClass createState() => $translationProviderStateClass();'); buffer.writeln('}'); buffer.writeln(); - buffer.writeln('class $translationProviderStateClass extends State<$translationProviderClass> {'); + buffer.writeln( + 'class $translationProviderStateClass extends State<$translationProviderClass> {'); buffer.writeln('\tString locale = $localeVar;'); buffer.writeln(); buffer.writeln('\tvoid setLocale(String newLocale) {'); @@ -191,7 +212,8 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL buffer.writeln(); buffer.writeln('class $inheritedClass extends InheritedWidget {'); buffer.writeln('\tfinal $baseClassName translations;'); - buffer.writeln('\t$inheritedClass({this.translations, Widget child}) : super(child: child);'); + buffer.writeln( + '\t$inheritedClass({this.translations, Widget child}) : super(child: child);'); buffer.writeln(); buffer.writeln('\t@override'); buffer.writeln('\tbool updateShouldNotify($inheritedClass oldWidget) {'); @@ -203,7 +225,8 @@ void _generateHeader(StringBuffer buffer, I18nConfig config, List allL /// generates all classes of one locale /// all non-default locales has a postfix of their locale code /// e.g. Strings, StringsDe, StringsFr -void _generateLocale(StringBuffer buffer, I18nConfig config, I18nData localeData) { +void _generateLocale( + StringBuffer buffer, I18nConfig config, I18nData localeData) { Queue queue = Queue(); queue.add(ClassTask( @@ -221,7 +244,7 @@ void _generateLocale(StringBuffer buffer, I18nConfig config, I18nData localeData buffer, queue, task.className, - task.members + task.members, ); } while (queue.isNotEmpty); } @@ -235,11 +258,10 @@ void _generateClass( StringBuffer buffer, Queue queue, String className, - Map currMembers + Map currMembers, ) { - String finalClassName = base - ? className - : className + locale.capitalize().replaceAll('-', ''); + String finalClassName = + base ? className : className + locale.capitalize().replaceAll('-', ''); buffer.writeln(); @@ -265,7 +287,8 @@ void _generateClass( if (value.params.isEmpty) { buffer.writeln('String $key = \'${value.content}\';'); } else { - buffer.writeln('String $key${_toParameterList(value.params)} => \'${value.content}\';'); + buffer.writeln( + 'String $key${_toParameterList(value.params)} => \'${value.content}\';'); } } else if (value is ListNode) { String type = value.plainStrings ? 'String' : 'dynamic'; @@ -287,7 +310,8 @@ void _generateClass( ? childClassName : childClassName + locale.capitalize().replaceAll('-', ''); - buffer.writeln('$finalChildClassName get $key => $finalChildClassName._instance;'); + buffer.writeln( + '$finalChildClassName get $key => $finalChildClassName._instance;'); } } }); @@ -298,13 +322,14 @@ void _generateClass( /// generates a map of ONE locale /// similar to _generateClass but anonymous and accessible via key void _generateMap( - bool base, - String locale, - StringBuffer buffer, - Queue queue, - String className, - Map currMembers, - int depth) { + bool base, + String locale, + StringBuffer buffer, + Queue queue, + String className, + Map currMembers, + int depth, +) { buffer.writeln('{'); currMembers.forEach((key, value) { @@ -313,17 +338,20 @@ void _generateMap( if (value.params.isEmpty) { buffer.writeln('\'$key\': \'${value.content}\','); } else { - buffer.writeln('\'$key\': ${_toParameterList(value.params)} => \'${value.content}\','); + buffer.writeln( + '\'$key\': ${_toParameterList(value.params)} => \'${value.content}\','); } } else if (value is ListNode) { buffer.write('\'$key\': '); - _generateList(base, locale, buffer, queue, className, value.entries, depth + 1); + _generateList( + base, locale, buffer, queue, className, value.entries, depth + 1); } else if (value is ObjectNode) { String childClassName = className + key.capitalize(); if (value.mapMode) { // inline map buffer.write('\'$key\': '); - _generateMap(base, locale, buffer, queue, childClassName, value.entries, depth + 1); + _generateMap(base, locale, buffer, queue, childClassName, value.entries, + depth + 1); } else { // generate a class later on queue.add(ClassTask(childClassName, value.entries)); @@ -349,8 +377,15 @@ void _generateMap( } /// generates a list -void _generateList(bool base, String locale, StringBuffer buffer, - Queue queue, String className, List currList, int depth) { +void _generateList( + bool base, + String locale, + StringBuffer buffer, + Queue queue, + String className, + List currList, + int depth, +) { buffer.writeln('['); for (int i = 0; i < currList.length; i++) { @@ -360,10 +395,12 @@ void _generateList(bool base, String locale, StringBuffer buffer, if (value.params.isEmpty) { buffer.writeln('\'${value.content}\','); } else { - buffer.writeln('${_toParameterList(value.params)} => \'${value.content}\','); + buffer.writeln( + '${_toParameterList(value.params)} => \'${value.content}\','); } } else if (value is ListNode) { - _generateList(base, locale, buffer, queue, className, value.entries, depth + 1); + _generateList( + base, locale, buffer, queue, className, value.entries, depth + 1); } else if (value is ObjectNode) { String childClassName = className + depth.toString() + 'i' + i.toString(); queue.add(ClassTask(childClassName, value.entries)); @@ -421,8 +458,10 @@ extension on String { String toCase(String caseName) { switch (caseName) { - case 'snake': return snakeCase; - case 'camel': return camelCase; + case 'snake': + return snakeCase; + case 'camel': + return camelCase; default: return this; } diff --git a/lib/src/model.dart b/lib/src/model.dart index 95bcfcaa..06d2eeeb 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -8,7 +8,13 @@ class I18nConfig { final String keyCase; final String translateVariable; - I18nConfig({this.baseName, this.baseLocale, this.maps, this.keyCase, this.translateVariable}); + I18nConfig({ + this.baseName, + this.baseLocale, + this.maps, + this.keyCase, + this.translateVariable, + }); @override String toString() => '$baseLocale, maps: $maps'; @@ -80,8 +86,7 @@ class TextNode extends Value { /// 'my name is $name and I am $age years old' => ['name', 'age'] /// 'my name is ${name} and I am ${age} years old' => ['name', 'age'] List _findArguments(String content) { - return Utils - .argumentsRegex + return Utils.argumentsRegex .allMatches(content) .map((e) => e.group(2)) .toList(); diff --git a/lib/src/parser_json.dart b/lib/src/parser_json.dart index 5c97d455..b42a6192 100644 --- a/lib/src/parser_json.dart +++ b/lib/src/parser_json.dart @@ -4,7 +4,8 @@ import 'package:fast_i18n/src/model.dart'; /// parses a json of one locale /// returns an I18nData object -I18nData parseJSON(I18nConfig config, String baseName, String locale, String content) { +I18nData parseJSON( + I18nConfig config, String baseName, String locale, String content) { Map map = json.decode(content); Map destination = Map(); _parseJSONObject(config.maps, map, destination, []); @@ -12,11 +13,16 @@ I18nData parseJSON(I18nConfig config, String baseName, String locale, String con return I18nData( base: config.baseLocale == locale, locale: locale, - root: ObjectNode(destination, false) + root: ObjectNode(destination, false), ); } -void _parseJSONObject(List maps, Map curr, Map destination, List stack) { +void _parseJSONObject( + List maps, + Map curr, + Map destination, + List stack, +) { curr.forEach((key, value) { if (value is String) { // key: 'value' @@ -38,7 +44,12 @@ void _parseJSONObject(List maps, Map curr, Map maps, List curr, List destination, List stack) { +void _parseJSONArray( + List maps, + List curr, + List destination, + List stack, +) { for (dynamic value in curr) { if (value is String) { // key: 'value' diff --git a/lib/utils.dart b/lib/utils.dart index 2c6dfbb8..ef41da11 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -3,7 +3,8 @@ class Utils { /// finds the parts of the locale /// must start with an underscore - static RegExp localeRegex = RegExp(r'^((\w+)_)?([a-z]{2})([-_]([a-zA-Z]{2}))?$'); + static RegExp localeRegex = + RegExp(r'^((\w+)_)?([a-z]{2})([-_]([a-zA-Z]{2}))?$'); /// returns the locale with the following syntax: /// - all lowercase diff --git a/pubspec.yaml b/pubspec.yaml index 6436b1b0..49ce2fd7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fast_i18n description: Lightweight i18n solution. Use JSON files to create typesafe translations. -version: 2.2.1 +version: 2.3.0 homepage: https://github.com/Tienisto/flutter-fast-i18n environment: diff --git a/test/fast_i18n_test.dart b/test/fast_i18n_test.dart index 20a8bbe4..7a641af1 100644 --- a/test/fast_i18n_test.dart +++ b/test/fast_i18n_test.dart @@ -35,4 +35,69 @@ void testSelectLocale() { expect(FastI18n.selectLocale('fr', ['en', 'de'], 'cz'), 'cz'); }); }); + + group('convertToLocales', () { + test('puts the base locale first', () { + final localesAsStrings = ['en-us', 'ru-RU', 'de-de', 'zh-Hans-CN']; + final baseLocaleString = 'ru-RU'; + + final locales = + FastI18n.convertToLocales(localesAsStrings, baseLocaleString); + + expect(locales.length, 4); + expect(locales[0].toLanguageTag(), 'ru-RU'); + expect(locales[1].toLanguageTag(), 'en-us'); + expect(locales[2].toLanguageTag(), 'de-de'); + expect(locales[3].toLanguageTag(), 'zh-Hans-CN'); + }); + + test('when there is no country code present', () { + final localesAsStrings = ['en', 'ru-RU', 'de']; + final baseLocaleString = 'de'; + + final locales = + FastI18n.convertToLocales(localesAsStrings, baseLocaleString); + + expect(locales.length, 3); + expect(locales[0].toLanguageTag(), 'de'); + expect(locales[1].toLanguageTag(), 'en'); + expect(locales[2].toLanguageTag(), 'ru-RU'); + }); + + test( + "throws Exception if a locale with '-' delimiter doesn't have 2+ non-empty parts", + () { + final localesAsStrings = ['ru-', 'de']; + final baseLocaleString = 'de'; + + expect( + () => FastI18n.convertToLocales(localesAsStrings, baseLocaleString), + throwsA(isA()), + ); + }); + + test( + 'throws AssertionError if primary language subtag is not present in the provided base locale', + () { + final localesAsStrings = ['en-us']; + final baseLocaleString = ''; + + expect( + () => FastI18n.convertToLocales(localesAsStrings, baseLocaleString), + throwsA(isA()), + ); + }); + + test( + 'throws AssertionError if primary language subtag is not present in the provided locales', + () { + final localesAsStrings = ['en-us', '']; + final baseLocaleString = 'en-US'; + + expect( + () => FastI18n.convertToLocales(localesAsStrings, baseLocaleString), + throwsA(isA()), + ); + }); + }); }