| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This program generates getMaterialTranslation(), getCupertinoTranslation(), |
| // and getWidgetsTranslation() functions that look up the translations provided by |
| // the arb files. The returned value is a generated instance of a |
| // GlobalMaterialLocalizations, GlobalCupertinoLocalizations, or |
| // GlobalWidgetsLocalizations object that corresponds to a single locale. |
| // |
| // The *.arb files are in packages/flutter_localizations/lib/src/l10n. |
| // |
| // The arb (JSON) format files must contain a single map indexed by locale. |
| // Each map value is itself a map with resource identifier keys and localized |
| // resource string values. |
| // |
| // The arb filenames are expected to have the form "material_(\w+)\.arb" or |
| // "cupertino_(\w+)\.arb" where the group following "_" identifies the language |
| // code and the country code, e.g. "material_en.arb" or "material_en_GB.arb". |
| // In most cases both codes are just two characters. |
| // |
| // This app is typically run by hand when a module's .arb files have been |
| // updated. |
| // |
| // ## Usage |
| // |
| // Run this program from the root of the git repository. |
| // |
| // The following outputs the generated Dart code to the console as a dry run: |
| // |
| // ``` |
| // dart dev/tools/localization/bin/gen_localizations.dart |
| // ``` |
| // |
| // If you have removed localizations from the canonical localizations, then |
| // add the '--remove-undefined' flag to also remove them from the other files. |
| // |
| // ``` |
| // dart dev/tools/localization/bin/gen_localizations.dart --remove-undefined |
| // ``` |
| // |
| // If the data looks good, use the `-w` or `--overwrite` option to overwrite the |
| // generated_material_localizations.dart, generated_cupertino_localizations.dart, |
| // and generated_widgets_localizations.dart files in packages/flutter_localizations/lib/src/l10n/: |
| // |
| // ``` |
| // dart dev/tools/localization/bin/gen_localizations.dart --overwrite |
| // ``` |
| |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| |
| import '../gen_cupertino_localizations.dart'; |
| import '../gen_material_localizations.dart'; |
| import '../gen_widgets_localizations.dart'; |
| import '../localizations_utils.dart'; |
| import '../localizations_validator.dart'; |
| import 'encode_kn_arb_files.dart'; |
| |
| /// This is the core of this script; it generates the code used for translations. |
| String generateArbBasedLocalizationSubclasses({ |
| required Map<LocaleInfo, Map<String, String>> localeToResources, |
| required Map<LocaleInfo, Map<String, dynamic>> localeToResourceAttributes, |
| required String generatedClassPrefix, |
| required String baseClass, |
| required HeaderGenerator generateHeader, |
| required ConstructorGenerator generateConstructor, |
| ConstructorGenerator? generateConstructorForCountrySubClass, |
| required String factoryName, |
| required String factoryDeclaration, |
| required bool callsFactoryWithConst, |
| required String factoryArguments, |
| required String supportedLanguagesConstant, |
| required String supportedLanguagesDocMacro, |
| }) { |
| assert(generatedClassPrefix.isNotEmpty); |
| assert(baseClass.isNotEmpty); |
| assert(factoryName.isNotEmpty); |
| assert(factoryDeclaration.isNotEmpty); |
| assert(factoryArguments.isNotEmpty); |
| assert(supportedLanguagesConstant.isNotEmpty); |
| assert(supportedLanguagesDocMacro.isNotEmpty); |
| generateConstructorForCountrySubClass ??= generateConstructor; |
| final StringBuffer output = StringBuffer(); |
| output.writeln(generateHeader('dart dev/tools/localization/bin/gen_localizations.dart --overwrite')); |
| |
| final StringBuffer supportedLocales = StringBuffer(); |
| |
| final Map<String, List<LocaleInfo>> languageToLocales = <String, List<LocaleInfo>>{}; |
| final Map<String, Set<String>> languageToScriptCodes = <String, Set<String>>{}; |
| // Used to calculate if there are any corresponding countries for a given language and script. |
| final Map<LocaleInfo, Set<String>> languageAndScriptToCountryCodes = <LocaleInfo, Set<String>>{}; |
| final Set<String> allResourceIdentifiers = <String>{}; |
| for (final LocaleInfo locale in localeToResources.keys.toList()..sort()) { |
| if (locale.scriptCode != null) { |
| languageToScriptCodes[locale.languageCode] ??= <String>{}; |
| languageToScriptCodes[locale.languageCode]!.add(locale.scriptCode!); |
| } |
| if (locale.countryCode != null && locale.scriptCode != null) { |
| final LocaleInfo key = LocaleInfo.fromString('${locale.languageCode}_${locale.scriptCode}'); |
| languageAndScriptToCountryCodes[key] ??= <String>{}; |
| languageAndScriptToCountryCodes[key]!.add(locale.countryCode!); |
| } |
| languageToLocales[locale.languageCode] ??= <LocaleInfo>[]; |
| languageToLocales[locale.languageCode]!.add(locale); |
| allResourceIdentifiers.addAll(localeToResources[locale]!.keys.toList()..sort()); |
| } |
| |
| // We generate one class per supported language (e.g. |
| // `MaterialLocalizationEn`). These implement everything that is needed by the |
| // superclass (e.g. GlobalMaterialLocalizations). |
| |
| // We also generate one subclass for each locale with a script code (e.g. |
| // `MaterialLocalizationZhHant`). Their superclasses are the aforementioned |
| // language classes for the same locale but without a script code (e.g. |
| // `MaterialLocalizationZh`). |
| |
| // We also generate one subclass for each locale with a country code (e.g. |
| // `MaterialLocalizationEnGb`). Their superclasses are the aforementioned |
| // language classes for the same locale but without a country code (e.g. |
| // `MaterialLocalizationEn`). |
| |
| // If scriptCodes for a language are defined, we expect a scriptCode to be |
| // defined for locales that contain a countryCode. The superclass becomes |
| // the script subclass (e.g. `MaterialLocalizationZhHant`) and the generated |
| // subclass will also contain the script code (e.g. `MaterialLocalizationZhHantTW`). |
| |
| // When scriptCodes are not defined for languages that use scriptCodes to distinguish |
| // between significantly differing scripts, we assume the scriptCodes in the |
| // [LocaleInfo.fromString] factory and add it to the [LocaleInfo]. We then generate |
| // the script classes based on the first locale that we assume to use the script. |
| |
| final List<String> allKeys = allResourceIdentifiers.toList()..sort(); |
| final List<String> languageCodes = languageToLocales.keys.toList()..sort(); |
| final LocaleInfo canonicalLocale = LocaleInfo.fromString('en'); |
| for (final String languageName in languageCodes) { |
| final LocaleInfo languageLocale = LocaleInfo.fromString(languageName); |
| output.writeln(generateClassDeclaration(languageLocale, generatedClassPrefix, baseClass)); |
| output.writeln(generateConstructor(languageLocale)); |
| |
| final Map<String, String> languageResources = localeToResources[languageLocale]!; |
| for (final String key in allKeys) { |
| final Map<String, dynamic>? attributes = localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?; |
| output.writeln(generateGetter(key, languageResources[key], attributes, languageLocale)); |
| } |
| output.writeln('}'); |
| int countryCodeCount = 0; |
| int scriptCodeCount = 0; |
| if (languageToScriptCodes.containsKey(languageName)) { |
| scriptCodeCount = languageToScriptCodes[languageName]!.length; |
| // Language has scriptCodes, so we need to properly fallback countries to corresponding |
| // script default values before language default values. |
| for (final String scriptCode in languageToScriptCodes[languageName]!) { |
| final LocaleInfo scriptBaseLocale = LocaleInfo.fromString('${languageName}_$scriptCode'); |
| output.writeln(generateClassDeclaration( |
| scriptBaseLocale, |
| generatedClassPrefix, |
| '$generatedClassPrefix${languageLocale.camelCase()}', |
| )); |
| output.writeln(generateConstructorForCountrySubClass(scriptBaseLocale)); |
| final Map<String, String> scriptResources = localeToResources[scriptBaseLocale]!; |
| for (final String key in scriptResources.keys.toList()..sort()) { |
| if (languageResources[key] == scriptResources[key]) { |
| continue; |
| } |
| final Map<String, dynamic>? attributes = localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?; |
| output.writeln(generateGetter(key, scriptResources[key], attributes, languageLocale)); |
| } |
| output.writeln('}'); |
| |
| final List<LocaleInfo> localeCodes = languageToLocales[languageName]!..sort(); |
| for (final LocaleInfo locale in localeCodes) { |
| if (locale.originalString == languageName) { |
| continue; |
| } |
| if (locale.originalString == '${languageName}_$scriptCode') { |
| continue; |
| } |
| if (locale.scriptCode != scriptCode) { |
| continue; |
| } |
| countryCodeCount += 1; |
| output.writeln(generateClassDeclaration( |
| locale, |
| generatedClassPrefix, |
| '$generatedClassPrefix${scriptBaseLocale.camelCase()}', |
| )); |
| output.writeln(generateConstructorForCountrySubClass(locale)); |
| final Map<String, String> localeResources = localeToResources[locale]!; |
| for (final String key in localeResources.keys) { |
| // When script fallback contains the key, we compare to it instead of language fallback. |
| if (scriptResources.containsKey(key) ? scriptResources[key] == localeResources[key] : languageResources[key] == localeResources[key]) { |
| continue; |
| } |
| final Map<String, dynamic>? attributes = localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?; |
| output.writeln(generateGetter(key, localeResources[key], attributes, languageLocale)); |
| } |
| output.writeln('}'); |
| } |
| } |
| } else { |
| // No scriptCode. Here, we do not compare against script default (because it |
| // doesn't exist). |
| final List<LocaleInfo> localeCodes = languageToLocales[languageName]!..sort(); |
| for (final LocaleInfo locale in localeCodes) { |
| if (locale.originalString == languageName) { |
| continue; |
| } |
| countryCodeCount += 1; |
| final Map<String, String> localeResources = localeToResources[locale]!; |
| output.writeln(generateClassDeclaration( |
| locale, |
| generatedClassPrefix, |
| '$generatedClassPrefix${languageLocale.camelCase()}', |
| )); |
| output.writeln(generateConstructorForCountrySubClass(locale)); |
| for (final String key in localeResources.keys) { |
| if (languageResources[key] == localeResources[key]) { |
| continue; |
| } |
| final Map<String, dynamic>? attributes = localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?; |
| output.writeln(generateGetter(key, localeResources[key], attributes, languageLocale)); |
| } |
| output.writeln('}'); |
| } |
| } |
| |
| final String scriptCodeMessage = scriptCodeCount == 0 ? '' : ' and $scriptCodeCount script${scriptCodeCount == 1 ? '' : 's'}'; |
| if (countryCodeCount == 0) { |
| if (scriptCodeCount == 0) { |
| supportedLocales.writeln('/// * `$languageName` - ${describeLocale(languageName)}'); |
| } else { |
| supportedLocales.writeln('/// * `$languageName` - ${describeLocale(languageName)} (plus $scriptCodeCount script${scriptCodeCount == 1 ? '' : 's'})'); |
| } |
| |
| } else if (countryCodeCount == 1) { |
| supportedLocales.writeln('/// * `$languageName` - ${describeLocale(languageName)} (plus one country variation$scriptCodeMessage)'); |
| } else { |
| supportedLocales.writeln('/// * `$languageName` - ${describeLocale(languageName)} (plus $countryCodeCount country variations$scriptCodeMessage)'); |
| } |
| } |
| |
| // Generate the factory function. Given a Locale it returns the corresponding |
| // base class implementation. |
| output.writeln(''' |
| |
| /// The set of supported languages, as language code strings. |
| /// |
| /// The [$baseClass.delegate] can generate localizations for |
| /// any [Locale] with a language code from this set, regardless of the region. |
| /// Some regions have specific support (e.g. `de` covers all forms of German, |
| /// but there is support for `de-CH` specifically to override some of the |
| /// translations for Switzerland). |
| /// |
| /// See also: |
| /// |
| /// * [$factoryName], whose documentation describes these values. |
| final Set<String> $supportedLanguagesConstant = HashSet<String>.from(const <String>[ |
| ${languageCodes.map<String>((String value) => " '$value', // ${describeLocale(value)}").toList().join('\n')} |
| ]); |
| |
| /// Creates a [$baseClass] instance for the given `locale`. |
| /// |
| /// All of the function's arguments except `locale` will be passed to the [ |
| /// $baseClass] constructor. (The `localeName` argument of that |
| /// constructor is specified by the actual subclass constructor by this |
| /// function.) |
| /// |
| /// The following locales are supported by this package: |
| /// |
| /// {@template $supportedLanguagesDocMacro} |
| $supportedLocales/// {@endtemplate} |
| /// |
| /// Generally speaking, this method is only intended to be used by |
| /// [$baseClass.delegate]. |
| $factoryDeclaration |
| switch (locale.languageCode) {'''); |
| for (final String language in languageToLocales.keys) { |
| // Only one instance of the language. |
| if (languageToLocales[language]!.length == 1) { |
| output.writeln(''' |
| case '$language': |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${languageToLocales[language]![0].camelCase()}($factoryArguments);'''); |
| } else if (!languageToScriptCodes.containsKey(language)) { // Does not distinguish between scripts. Switch on countryCode directly. |
| output.writeln(''' |
| case '$language': { |
| switch (locale.countryCode) {'''); |
| for (final LocaleInfo locale in languageToLocales[language]!) { |
| if (locale.originalString == language) { |
| continue; |
| } |
| assert(locale.length > 1); |
| final String countryCode = locale.countryCode!; |
| output.writeln(''' |
| case '$countryCode': |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);'''); |
| } |
| output.writeln(''' |
| } |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments); |
| }'''); |
| } else { // Language has scriptCode, add additional switch logic. |
| bool hasCountryCode = false; |
| output.writeln(''' |
| case '$language': { |
| switch (locale.scriptCode) {'''); |
| for (final String scriptCode in languageToScriptCodes[language]!) { |
| final LocaleInfo scriptLocale = LocaleInfo.fromString('${language}_$scriptCode'); |
| output.writeln(''' |
| case '$scriptCode': {'''); |
| if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) { |
| output.writeln(''' |
| switch (locale.countryCode) {'''); |
| for (final LocaleInfo locale in languageToLocales[language]!) { |
| if (locale.countryCode == null) { |
| continue; |
| } else { |
| hasCountryCode = true; |
| } |
| if (locale.originalString == language) { |
| continue; |
| } |
| if (locale.scriptCode != scriptCode && locale.scriptCode != null) { |
| continue; |
| } |
| final String countryCode = locale.countryCode!; |
| output.writeln(''' |
| case '$countryCode': |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);'''); |
| } |
| } |
| // Return a fallback locale that matches scriptCode, but not countryCode. |
| // |
| // Explicitly defined scriptCode fallback: |
| if (languageToLocales[language]!.contains(scriptLocale)) { |
| if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) { |
| output.writeln(''' |
| }'''); |
| } |
| output.writeln(''' |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments); |
| }'''); |
| } else { |
| // Not Explicitly defined, fallback to first locale with the same language and |
| // script: |
| for (final LocaleInfo locale in languageToLocales[language]!) { |
| if (locale.scriptCode != scriptCode) { |
| continue; |
| } |
| if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) { |
| output.writeln(''' |
| }'''); |
| } |
| output.writeln(''' |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments); |
| }'''); |
| break; |
| } |
| } |
| } |
| output.writeln(''' |
| }'''); |
| if (hasCountryCode) { |
| output.writeln(''' |
| switch (locale.countryCode) {'''); |
| for (final LocaleInfo locale in languageToLocales[language]!) { |
| if (locale.originalString == language) { |
| continue; |
| } |
| assert(locale.length > 1); |
| if (locale.countryCode == null) { |
| continue; |
| } |
| final String countryCode = locale.countryCode!; |
| output.writeln(''' |
| case '$countryCode': |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);'''); |
| } |
| output.writeln(''' |
| }'''); |
| } |
| output.writeln(''' |
| return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments); |
| }'''); |
| } |
| } |
| output.writeln(''' |
| } |
| assert(false, '$factoryName() called for unsupported locale "\$locale"'); |
| return null; |
| }'''); |
| |
| return output.toString(); |
| } |
| |
| /// Returns the appropriate type for getters with the given attributes. |
| /// |
| /// Typically "String", but some (e.g. "timeOfDayFormat") return enums. |
| /// |
| /// Used by [generateGetter] below. |
| String generateType(Map<String, dynamic>? attributes) { |
| bool optional = false; |
| String type = 'String'; |
| if (attributes != null) { |
| optional = attributes.containsKey('optional'); |
| switch (attributes['x-flutter-type'] as String?) { |
| case 'icuShortTimePattern': |
| type = 'TimeOfDayFormat'; |
| case 'scriptCategory': |
| type = 'ScriptCategory'; |
| } |
| } |
| return type + (optional ? '?' : ''); |
| } |
| |
| /// Returns the appropriate name for getters with the given attributes. |
| /// |
| /// Typically this is the key unmodified, but some have parameters, and |
| /// the GlobalMaterialLocalizations class does the substitution, and for |
| /// those we have to therefore provide an alternate name. |
| /// |
| /// Used by [generateGetter] below. |
| String generateKey(String key, Map<String, dynamic>? attributes) { |
| if (attributes != null) { |
| if (attributes.containsKey('parameters')) { |
| return '${key}Raw'; |
| } |
| switch (attributes['x-flutter-type'] as String?) { |
| case 'icuShortTimePattern': |
| return '${key}Raw'; |
| } |
| } |
| if (key == 'datePickerDateOrder') { |
| return 'datePickerDateOrderString'; |
| } |
| if (key == 'datePickerDateTimeOrder') { |
| return 'datePickerDateTimeOrderString'; |
| } |
| return key; |
| } |
| |
| const Map<String, String> _icuTimeOfDayToEnum = <String, String>{ |
| 'HH:mm': 'TimeOfDayFormat.HH_colon_mm', |
| 'HH.mm': 'TimeOfDayFormat.HH_dot_mm', |
| "HH 'h' mm": 'TimeOfDayFormat.frenchCanadian', |
| 'HH:mm น.': 'TimeOfDayFormat.HH_colon_mm', |
| 'H:mm': 'TimeOfDayFormat.H_colon_mm', |
| 'h:mm a': 'TimeOfDayFormat.h_colon_mm_space_a', |
| 'a h:mm': 'TimeOfDayFormat.a_space_h_colon_mm', |
| 'ah:mm': 'TimeOfDayFormat.a_space_h_colon_mm', |
| }; |
| |
| const Map<String, String> _scriptCategoryToEnum = <String, String>{ |
| 'English-like': 'ScriptCategory.englishLike', |
| 'dense': 'ScriptCategory.dense', |
| 'tall': 'ScriptCategory.tall', |
| }; |
| |
| /// Returns the literal that describes the value returned by getters |
| /// with the given attributes. |
| /// |
| /// This handles cases like the value being a literal `null`, an enum, and so |
| /// on. The default is to treat the value as a string and escape it and quote |
| /// it. |
| /// |
| /// Used by [generateGetter] below. |
| String? generateValue(String? value, Map<String, dynamic>? attributes, LocaleInfo locale) { |
| if (value == null) { |
| return null; |
| } |
| // cupertino_en.arb doesn't use x-flutter-type. |
| if (attributes != null) { |
| switch (attributes['x-flutter-type'] as String?) { |
| case 'icuShortTimePattern': |
| if (!_icuTimeOfDayToEnum.containsKey(value)) { |
| throw Exception( |
| '"$value" is not one of the ICU short time patterns supported ' |
| 'by the material library. Here is the list of supported ' |
| 'patterns:\n ${_icuTimeOfDayToEnum.keys.join('\n ')}' |
| ); |
| } |
| return _icuTimeOfDayToEnum[value]; |
| case 'scriptCategory': |
| if (!_scriptCategoryToEnum.containsKey(value)) { |
| throw Exception( |
| '"$value" is not one of the scriptCategory values supported ' |
| 'by the material library. Here is the list of supported ' |
| 'values:\n ${_scriptCategoryToEnum.keys.join('\n ')}' |
| ); |
| } |
| return _scriptCategoryToEnum[value]; |
| } |
| } |
| return generateEncodedString(locale.languageCode, value); |
| } |
| |
| /// Combines [generateType], [generateKey], and [generateValue] to return |
| /// the source of getters for the GlobalMaterialLocalizations subclass. |
| /// The locale is the locale for which the getter is being generated. |
| String generateGetter(String key, String? value, Map<String, dynamic>? attributes, LocaleInfo locale) { |
| final String type = generateType(attributes); |
| key = generateKey(key, attributes); |
| final String? generatedValue = generateValue(value, attributes, locale); |
| return ''' |
| |
| @override |
| $type get $key => $generatedValue;'''; |
| } |
| |
| void main(List<String> rawArgs) { |
| checkCwdIsRepoRoot('gen_localizations'); |
| final GeneratorOptions options = parseArgs(rawArgs); |
| |
| // filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb", where prefix |
| // is the 2nd command line argument, lc is a language code and cc is the country |
| // code. In most cases both codes are just two characters. |
| |
| final Directory directory = Directory(path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n')); |
| final RegExp widgetsFilenameRE = RegExp(r'widgets_(\w+)\.arb$'); |
| final RegExp materialFilenameRE = RegExp(r'material_(\w+)\.arb$'); |
| final RegExp cupertinoFilenameRE = RegExp(r'cupertino_(\w+)\.arb$'); |
| |
| try { |
| validateEnglishLocalizations(File(path.join(directory.path, 'widgets_en.arb'))); |
| validateEnglishLocalizations(File(path.join(directory.path, 'material_en.arb'))); |
| validateEnglishLocalizations(File(path.join(directory.path, 'cupertino_en.arb'))); |
| } on ValidationError catch (exception) { |
| exitWithError('$exception'); |
| } |
| |
| // Only rewrite material_kn.arb and cupertino_en.arb if overwriting the |
| // Material and Cupertino localizations files. |
| if (options.writeToFile) { |
| // Encodes the material_kn.arb file and the cupertino_en.arb files before |
| // generating localizations. This prevents a subset of Emacs users from |
| // crashing when opening up the Flutter source code. |
| // See https://github.com/flutter/flutter/issues/36704 for more context. |
| encodeKnArbFiles(directory); |
| } |
| |
| precacheLanguageAndRegionTags(); |
| |
| // Maps of locales to resource key/value pairs for Widgets ARBs. |
| final Map<LocaleInfo, Map<String, String>> widgetsLocaleToResources = <LocaleInfo, Map<String, String>>{}; |
| // Maps of locales to resource key/attributes pairs for Widgets ARBs. |
| // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes |
| final Map<LocaleInfo, Map<String, dynamic>> widgetsLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{}; |
| |
| // Maps of locales to resource key/value pairs for Material ARBs. |
| final Map<LocaleInfo, Map<String, String>> materialLocaleToResources = <LocaleInfo, Map<String, String>>{}; |
| // Maps of locales to resource key/attributes pairs for Material ARBs. |
| // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes |
| final Map<LocaleInfo, Map<String, dynamic>> materialLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{}; |
| |
| // Maps of locales to resource key/value pairs for Cupertino ARBs. |
| final Map<LocaleInfo, Map<String, String>> cupertinoLocaleToResources = <LocaleInfo, Map<String, String>>{}; |
| // Maps of locales to resource key/attributes pairs for Cupertino ARBs. |
| // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes |
| final Map<LocaleInfo, Map<String, dynamic>> cupertinoLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{}; |
| |
| loadMatchingArbsIntoBundleMaps( |
| directory: directory, |
| filenamePattern: widgetsFilenameRE, |
| localeToResources: widgetsLocaleToResources, |
| localeToResourceAttributes: widgetsLocaleToResourceAttributes, |
| ); |
| loadMatchingArbsIntoBundleMaps( |
| directory: directory, |
| filenamePattern: materialFilenameRE, |
| localeToResources: materialLocaleToResources, |
| localeToResourceAttributes: materialLocaleToResourceAttributes, |
| ); |
| loadMatchingArbsIntoBundleMaps( |
| directory: directory, |
| filenamePattern: cupertinoFilenameRE, |
| localeToResources: cupertinoLocaleToResources, |
| localeToResourceAttributes: cupertinoLocaleToResourceAttributes, |
| ); |
| |
| try { |
| validateLocalizations(widgetsLocaleToResources, widgetsLocaleToResourceAttributes, removeUndefined: options.removeUndefined); |
| validateLocalizations(materialLocaleToResources, materialLocaleToResourceAttributes, removeUndefined: options.removeUndefined); |
| validateLocalizations(cupertinoLocaleToResources, cupertinoLocaleToResourceAttributes, removeUndefined: options.removeUndefined); |
| } on ValidationError catch (exception) { |
| exitWithError('$exception'); |
| } |
| if (options.removeUndefined) { |
| removeUndefinedLocalizations(widgetsLocaleToResources); |
| removeUndefinedLocalizations(materialLocaleToResources); |
| removeUndefinedLocalizations(cupertinoLocaleToResources); |
| } |
| |
| final String? widgetsLocalizations = options.writeToFile || !options.cupertinoOnly |
| ? generateArbBasedLocalizationSubclasses( |
| localeToResources: widgetsLocaleToResources, |
| localeToResourceAttributes: widgetsLocaleToResourceAttributes, |
| generatedClassPrefix: 'WidgetsLocalization', |
| baseClass: 'GlobalWidgetsLocalizations', |
| generateHeader: generateWidgetsHeader, |
| generateConstructor: generateWidgetsConstructor, |
| generateConstructorForCountrySubClass: generateWidgetsConstructorForCountrySubclass, |
| factoryName: widgetsFactoryName, |
| factoryDeclaration: widgetsFactoryDeclaration, |
| callsFactoryWithConst: true, |
| factoryArguments: widgetsFactoryArguments, |
| supportedLanguagesConstant: widgetsSupportedLanguagesConstant, |
| supportedLanguagesDocMacro: widgetsSupportedLanguagesDocMacro, |
| ) |
| : null; |
| final String? materialLocalizations = options.writeToFile || !options.cupertinoOnly |
| ? generateArbBasedLocalizationSubclasses( |
| localeToResources: materialLocaleToResources, |
| localeToResourceAttributes: materialLocaleToResourceAttributes, |
| generatedClassPrefix: 'MaterialLocalization', |
| baseClass: 'GlobalMaterialLocalizations', |
| generateHeader: generateMaterialHeader, |
| generateConstructor: generateMaterialConstructor, |
| factoryName: materialFactoryName, |
| factoryDeclaration: materialFactoryDeclaration, |
| callsFactoryWithConst: false, |
| factoryArguments: materialFactoryArguments, |
| supportedLanguagesConstant: materialSupportedLanguagesConstant, |
| supportedLanguagesDocMacro: materialSupportedLanguagesDocMacro, |
| ) |
| : null; |
| final String? cupertinoLocalizations = options.writeToFile || !options.materialOnly |
| ? generateArbBasedLocalizationSubclasses( |
| localeToResources: cupertinoLocaleToResources, |
| localeToResourceAttributes: cupertinoLocaleToResourceAttributes, |
| generatedClassPrefix: 'CupertinoLocalization', |
| baseClass: 'GlobalCupertinoLocalizations', |
| generateHeader: generateCupertinoHeader, |
| generateConstructor: generateCupertinoConstructor, |
| factoryName: cupertinoFactoryName, |
| factoryDeclaration: cupertinoFactoryDeclaration, |
| callsFactoryWithConst: false, |
| factoryArguments: cupertinoFactoryArguments, |
| supportedLanguagesConstant: cupertinoSupportedLanguagesConstant, |
| supportedLanguagesDocMacro: cupertinoSupportedLanguagesDocMacro, |
| ) |
| : null; |
| |
| if (options.writeToFile) { |
| final File widgetsLocalizationsFile = File(path.join(directory.path, 'generated_widgets_localizations.dart')); |
| widgetsLocalizationsFile.writeAsStringSync(widgetsLocalizations!, flush: true); |
| final File materialLocalizationsFile = File(path.join(directory.path, 'generated_material_localizations.dart')); |
| materialLocalizationsFile.writeAsStringSync(materialLocalizations!, flush: true); |
| final File cupertinoLocalizationsFile = File(path.join(directory.path, 'generated_cupertino_localizations.dart')); |
| cupertinoLocalizationsFile.writeAsStringSync(cupertinoLocalizations!, flush: true); |
| } else { |
| if (options.cupertinoOnly) { |
| stdout.write(cupertinoLocalizations); |
| } else if (options.materialOnly) { |
| stdout.write(materialLocalizations); |
| } else if (options.widgetsOnly) { |
| stdout.write(widgetsLocalizations); |
| } else { |
| stdout.write(widgetsLocalizations); |
| stdout.write(materialLocalizations); |
| stdout.write(cupertinoLocalizations); |
| } |
| } |
| } |