| // Copyright 2017 The Chromium 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 a getTranslation() function that looks up the |
| // translations contained by the arb files. The returned value is an |
| // instance of GlobalMaterialLocalizations 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", 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/gen_localizations.dart |
| // ``` |
| // |
| // If the data looks good, use the `-w` or `--overwrite` option to overwrite the |
| // packages/flutter_localizations/lib/src/l10n/localizations.dart file: |
| // |
| // ``` |
| // dart dev/tools/gen_localizations.dart --overwrite |
| // ``` |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| import 'package:meta/meta.dart'; |
| |
| import 'localizations_utils.dart'; |
| import 'localizations_validator.dart'; |
| |
| const String outputHeader = ''' |
| // Copyright 2017 The Chromium 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 file has been automatically generated. Please do not edit it manually. |
| // To regenerate the file, use: |
| // @(regenerate) |
| |
| import 'dart:collection'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:intl/intl.dart' as intl; |
| |
| import '../material_localizations.dart'; |
| '''; |
| |
| /// Maps locales to resource key/value pairs. |
| final Map<LocaleInfo, Map<String, String>> localeToResources = <LocaleInfo, Map<String, String>>{}; |
| |
| /// Maps locales to resource key/attributes pairs. |
| /// |
| /// See also: <https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes> |
| final Map<LocaleInfo, Map<String, dynamic>> localeToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{}; |
| |
| /// Set that holds the locales that were assumed from the existing locales. |
| /// |
| /// For example, when the data lacks data for zh_Hant, we will use the data of |
| /// the first Hant Chinese locale as a default by repeating the data. If an |
| /// explicit match is later found, we can reference this set to see if we should |
| /// overwrite the existing assumed data. |
| final Set<LocaleInfo> assumedLocales = <LocaleInfo>{}; |
| |
| /// Return `s` as a Dart-parseable raw string in single or double quotes. |
| /// |
| /// Double quotes are expanded: |
| /// |
| /// ``` |
| /// foo => r'foo' |
| /// foo "bar" => r'foo "bar"' |
| /// foo 'bar' => r'foo ' "'" r'bar' "'" |
| /// ``` |
| String generateString(String s) { |
| if (!s.contains("'")) |
| return "r'$s'"; |
| |
| final StringBuffer output = StringBuffer(); |
| bool started = false; // Have we started writing a raw string. |
| for (int i = 0; i < s.length; i++) { |
| if (s[i] == "'") { |
| if (started) |
| output.write("'"); |
| output.write(' "\'" '); |
| started = false; |
| } else if (!started) { |
| output.write("r'${s[i]}"); |
| started = true; |
| } else { |
| output.write(s[i]); |
| } |
| } |
| if (started) |
| output.write("'"); |
| return output.toString(); |
| } |
| |
| /// This is the core of this script; it generates the code used for translations. |
| String generateTranslationBundles() { |
| final StringBuffer output = StringBuffer(); |
| 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 (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); |
| } |
| |
| output.writeln(''' |
| // The classes defined here encode all of the translations found in the |
| // `flutter_localizations/lib/src/l10n/*.arb` files. |
| // |
| // These classes are constructed by the [getTranslation] method at the bottom of |
| // this file, and used by the [_MaterialLocalizationsDelegate.load] method defined |
| // in `flutter_localizations/lib/src/material_localizations.dart`.'''); |
| |
| // We generate one class per supported language (e.g. |
| // `MaterialLocalizationEn`). These implement everything that is needed by |
| // 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 sublcass (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 (String languageName in languageCodes) { |
| final LocaleInfo languageLocale = LocaleInfo.fromString(languageName); |
| writeClassHeader(output, languageLocale, 'GlobalMaterialLocalizations'); |
| final Map<String, String> languageResources = localeToResources[languageLocale]; |
| for (String key in allKeys) { |
| final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key]; |
| output.writeln(generateGetter(key, languageResources[key], attributes)); |
| } |
| 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 (String scriptCode in languageToScriptCodes[languageName]) { |
| final LocaleInfo scriptBaseLocale = LocaleInfo.fromString(languageName + '_' + scriptCode); |
| writeClassHeader(output, scriptBaseLocale, 'MaterialLocalization${camelCase(languageLocale)}'); |
| final Map<String, String> scriptResources = localeToResources[scriptBaseLocale]; |
| for (String key in scriptResources.keys) { |
| if (languageResources[key] == scriptResources[key]) |
| continue; |
| final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key]; |
| output.writeln(generateGetter(key, scriptResources[key], attributes)); |
| } |
| output.writeln('}'); |
| |
| final List<LocaleInfo> localeCodes = languageToLocales[languageName]..sort(); |
| for (LocaleInfo locale in localeCodes) { |
| if (locale.originalString == languageName) |
| continue; |
| if (locale.originalString == languageName + '_' + scriptCode) |
| continue; |
| if (locale.scriptCode != scriptCode) |
| continue; |
| countryCodeCount += 1; |
| writeClassHeader(output, locale, 'MaterialLocalization${camelCase(scriptBaseLocale)}'); |
| final Map<String, String> localeResources = localeToResources[locale]; |
| for (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]; |
| output.writeln(generateGetter(key, localeResources[key], attributes)); |
| } |
| 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 (LocaleInfo locale in localeCodes) { |
| if (locale.originalString == languageName) |
| continue; |
| countryCodeCount += 1; |
| final Map<String, String> localeResources = localeToResources[locale]; |
| writeClassHeader(output, locale, 'MaterialLocalization${camelCase(languageLocale)}'); |
| for (String key in localeResources.keys) { |
| if (languageResources[key] == localeResources[key]) |
| continue; |
| final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key]; |
| output.writeln(generateGetter(key, localeResources[key], attributes)); |
| } |
| 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 getTranslation function. Given a Locale it returns the |
| // corresponding const GlobalMaterialLocalizations. |
| output.writeln(''' |
| |
| /// The set of supported languages, as language code strings. |
| /// |
| /// The [GlobalMaterialLocalizations.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: |
| /// |
| /// * [getTranslation], whose documentation describes these values. |
| final Set<String> kSupportedLanguages = HashSet<String>.from(const <String>[ |
| ${languageCodes.map<String>((String value) => " '$value', // ${describeLocale(value)}").toList().join('\n')} |
| ]); |
| |
| /// Creates a [GlobalMaterialLocalizations] instance for the given `locale`. |
| /// |
| /// All of the function's arguments except `locale` will be passed to the [new |
| /// GlobalMaterialLocalizations] 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 flutter.localizations.languages} |
| $supportedLocales/// {@endtemplate} |
| /// |
| /// Generally speaking, this method is only intended to be used by |
| /// [GlobalMaterialLocalizations.delegate]. |
| GlobalMaterialLocalizations getTranslation( |
| Locale locale, |
| intl.DateFormat fullYearFormat, |
| intl.DateFormat mediumDateFormat, |
| intl.DateFormat longDateFormat, |
| intl.DateFormat yearMonthFormat, |
| intl.NumberFormat decimalFormat, |
| intl.NumberFormat twoDigitZeroPaddedFormat, |
| ) { |
| switch (locale.languageCode) {'''); |
| const String arguments = 'fullYearFormat: fullYearFormat, mediumDateFormat: mediumDateFormat, longDateFormat: longDateFormat, yearMonthFormat: yearMonthFormat, decimalFormat: decimalFormat, twoDigitZeroPaddedFormat: twoDigitZeroPaddedFormat'; |
| for (String language in languageToLocales.keys) { |
| // Only one instance of the language. |
| if (languageToLocales[language].length == 1) { |
| output.writeln(''' |
| case '$language': |
| return MaterialLocalization${camelCase(languageToLocales[language][0])}($arguments);'''); |
| } else if (!languageToScriptCodes.containsKey(language)) { // Does not distinguish between scripts. Switch on countryCode directly. |
| output.writeln(''' |
| case '$language': { |
| switch (locale.countryCode) {'''); |
| for (LocaleInfo locale in languageToLocales[language]) { |
| if (locale.originalString == language) |
| continue; |
| assert(locale.length > 1); |
| final String countryCode = locale.countryCode; |
| output.writeln(''' |
| case '$countryCode': |
| return MaterialLocalization${camelCase(locale)}($arguments);'''); |
| } |
| output.writeln(''' |
| } |
| return MaterialLocalization${camelCase(LocaleInfo.fromString(language))}($arguments); |
| }'''); |
| } else { // Language has scriptCode, add additional switch logic. |
| bool hasCountryCode = false; |
| output.writeln(''' |
| case '$language': { |
| switch (locale.scriptCode) {'''); |
| for (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 (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 MaterialLocalization${camelCase(locale)}($arguments);'''); |
| } |
| } |
| // 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 MaterialLocalization${camelCase(scriptLocale)}($arguments); |
| }'''); |
| } else { |
| // Not Explicitly defined, fallback to first locale with the same language and |
| // script: |
| for (LocaleInfo locale in languageToLocales[language]) { |
| if (locale.scriptCode != scriptCode) |
| continue; |
| if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) { |
| output.writeln(''' |
| }'''); |
| } |
| output.writeln(''' |
| return MaterialLocalization${camelCase(scriptLocale)}($arguments); |
| }'''); |
| break; |
| } |
| } |
| } |
| output.writeln(''' |
| }'''); |
| if (hasCountryCode) { |
| output.writeln(''' |
| switch (locale.countryCode) {'''); |
| for (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 MaterialLocalization${camelCase(locale)}($arguments);'''); |
| } |
| output.writeln(''' |
| }'''); |
| } |
| output.writeln(''' |
| return MaterialLocalization${camelCase(LocaleInfo.fromString(language))}($arguments); |
| }'''); |
| } |
| } |
| output.writeln(''' |
| } |
| assert(false, 'getTranslation() called for unsupported locale "\$locale"'); |
| return null; |
| }'''); |
| |
| return output.toString(); |
| } |
| |
| /// Writes the header of each class which corresponds to a locale. |
| void writeClassHeader(StringBuffer output, LocaleInfo locale, String superClass) { |
| final String camelCaseName = camelCase(locale); |
| final String className = 'MaterialLocalization$camelCaseName'; |
| final String constructor = generateConstructor(className, locale); |
| output.writeln(''); |
| output.writeln('/// The translations for ${describeLocale(locale.originalString)} (`${locale.originalString}`).'); |
| output.writeln('class $className extends $superClass {'); |
| output.writeln(constructor); |
| } |
| |
| /// 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) { |
| if (attributes != null) { |
| switch (attributes['x-flutter-type']) { |
| case 'icuShortTimePattern': |
| return 'TimeOfDayFormat'; |
| case 'scriptCategory': |
| return 'ScriptCategory'; |
| } |
| } |
| return 'String'; |
| } |
| |
| /// 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']) { |
| case 'icuShortTimePattern': |
| return '${key}Raw'; |
| } |
| } |
| 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) { |
| if (value == null) |
| return null; |
| if (attributes != null) { |
| switch (attributes['x-flutter-type']) { |
| 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 generateString(value); |
| } |
| |
| /// Combines [generateType], [generateKey], and [generateValue] to return |
| /// the source of getters for the GlobalMaterialLocalizations subclass. |
| String generateGetter(String key, String value, Map<String, dynamic> attributes) { |
| final String type = generateType(attributes); |
| key = generateKey(key, attributes); |
| value = generateValue(value, attributes); |
| return ''' |
| |
| @override |
| $type get $key => $value;'''; |
| } |
| |
| /// Returns the source of the constructor for a GlobalMaterialLocalizations |
| /// subclass. |
| String generateConstructor(String className, LocaleInfo locale) { |
| final String localeName = locale.originalString; |
| return ''' |
| /// Create an instance of the translation bundle for ${describeLocale(localeName)}. |
| /// |
| /// For details on the meaning of the arguments, see [GlobalMaterialLocalizations]. |
| const $className({ |
| String localeName = '$localeName', |
| @required intl.DateFormat fullYearFormat, |
| @required intl.DateFormat mediumDateFormat, |
| @required intl.DateFormat longDateFormat, |
| @required intl.DateFormat yearMonthFormat, |
| @required intl.NumberFormat decimalFormat, |
| @required intl.NumberFormat twoDigitZeroPaddedFormat, |
| }) : super( |
| localeName: localeName, |
| fullYearFormat: fullYearFormat, |
| mediumDateFormat: mediumDateFormat, |
| longDateFormat: longDateFormat, |
| yearMonthFormat: yearMonthFormat, |
| decimalFormat: decimalFormat, |
| twoDigitZeroPaddedFormat: twoDigitZeroPaddedFormat, |
| );'''; |
| } |
| |
| /// Parse the data for a locale from a file, and store it in the [attributes] |
| /// and [resources] keys. |
| void processBundle(File file, { @required String localeString }) { |
| assert(localeString != null); |
| // Helper method to fill the maps with the correct data from file. |
| void populateResources(LocaleInfo locale) { |
| final Map<String, String> resources = localeToResources[locale]; |
| final Map<String, dynamic> attributes = localeToResourceAttributes[locale]; |
| final Map<String, dynamic> bundle = json.decode(file.readAsStringSync()); |
| for (String key in bundle.keys) { |
| // The ARB file resource "attributes" for foo are called @foo. |
| if (key.startsWith('@')) |
| attributes[key.substring(1)] = bundle[key]; |
| else |
| resources[key] = bundle[key]; |
| } |
| } |
| // Only pre-assume scriptCode if there is a country or script code to assume off of. |
| // When we assume scriptCode based on languageCode-only, we want this initial pass |
| // to use the un-assumed version as a base class. |
| LocaleInfo locale = LocaleInfo.fromString(localeString, deriveScriptCode: localeString.split('_').length > 1); |
| // Allow overwrite if the existing data is assumed. |
| if (assumedLocales.contains(locale)) { |
| localeToResources[locale] = <String, String>{}; |
| localeToResourceAttributes[locale] = <String, dynamic>{}; |
| assumedLocales.remove(locale); |
| } else { |
| localeToResources[locale] ??= <String, String>{}; |
| localeToResourceAttributes[locale] ??= <String, dynamic>{}; |
| } |
| populateResources(locale); |
| // Add an assumed locale to default to when there is no info on scriptOnly locales. |
| locale = LocaleInfo.fromString(localeString, deriveScriptCode: true); |
| if (locale.scriptCode != null) { |
| final LocaleInfo scriptLocale = LocaleInfo.fromString(locale.languageCode + '_' + locale.scriptCode); |
| if (!localeToResources.containsKey(scriptLocale)) { |
| assumedLocales.add(scriptLocale); |
| localeToResources[scriptLocale] ??= <String, String>{}; |
| localeToResourceAttributes[scriptLocale] ??= <String, dynamic>{}; |
| populateResources(scriptLocale); |
| } |
| } |
| } |
| |
| Future<void> main(List<String> rawArgs) async { |
| 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 filenameRE = RegExp(r'material_(\w+)\.arb$'); |
| |
| try { |
| validateEnglishLocalizations(File(path.join(directory.path, 'material_en.arb'))); |
| } on ValidationError catch (exception) { |
| exitWithError('$exception'); |
| } |
| |
| await precacheLanguageAndRegionTags(); |
| |
| for (FileSystemEntity entity in directory.listSync()) { |
| final String entityPath = entity.path; |
| if (FileSystemEntity.isFileSync(entityPath) && filenameRE.hasMatch(entityPath)) { |
| processBundle(File(entityPath), localeString: filenameRE.firstMatch(entityPath)[1]); |
| } |
| } |
| |
| try { |
| validateLocalizations(localeToResources, localeToResourceAttributes); |
| } on ValidationError catch (exception) { |
| exitWithError('$exception'); |
| } |
| |
| final StringBuffer buffer = StringBuffer(); |
| buffer.writeln(outputHeader.replaceFirst('@(regenerate)', 'dart dev/tools/gen_localizations.dart --overwrite')); |
| buffer.write(generateTranslationBundles()); |
| |
| if (options.writeToFile) { |
| final File localizationsFile = File(path.join(directory.path, 'localizations.dart')); |
| localizationsFile.writeAsStringSync(buffer.toString()); |
| } else { |
| stdout.write(buffer.toString()); |
| } |
| } |