mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
640 lines
26 KiB
Dart
640 lines
26 KiB
Dart
// 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 = Set<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 = Set<String>();
|
|
for (LocaleInfo locale in localeToResources.keys.toList()..sort()) {
|
|
if (locale.scriptCode != null) {
|
|
languageToScriptCodes[locale.languageCode] ??= Set<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] ??= Set<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, assume: 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, assume: 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());
|
|
}
|
|
}
|