Unverified Commit 31418570 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Gen localization classes intead of a big map (#13653)

* Gen localization classes intead of a big map

* tighten up the newlines so that std output matches file output

* restore locale sorting
parent 30720bd1
...@@ -91,28 +91,139 @@ String generateString(String s) { ...@@ -91,28 +91,139 @@ String generateString(String s) {
return output.toString(); return output.toString();
} }
String generateLocalizationsMap() { String generateTranslationBundles() {
final StringBuffer output = new StringBuffer(); final StringBuffer output = new StringBuffer();
final Map<String, List<String>> languageToLocales = <String, List<String>>{};
final Set<String> allResourceIdentifiers = new Set<String>();
for(String locale in localeToResources.keys.toList()..sort()) {
final List<String> codes = locale.split('_'); // [language, country]
assert(codes.length == 1 || codes.length == 2);
languageToLocales[codes[0]] ??= <String>[];
languageToLocales[codes[0]].add(locale);
allResourceIdentifiers.addAll(localeToResources[locale].keys);
}
// Generate the TranslationsBundle base class. It contains one getter
// per resource identifier found in any of the .arb files.
//
// class TranslationsBundle {
// const TranslationsBundle(this.parent);
// final TranslationsBundle parent;
// String get scriptCategory => parent?.scriptCategory;
// ...
// }
output.writeln(''' output.writeln('''
/// Maps from [Locale.languageCode] to a map that contains the localized strings // The TranslationBundle subclasses defined here encode all of the translations
/// for that locale. // found in the flutter_localizations/lib/src/l10n/*.arb files.
/// //
/// This variable is used by [MaterialLocalizations]. // The [MaterialLocalizations] class uses the (generated)
const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {'''); // translationBundleForLocale() function to look up a const TranslationBundle
// instance for a locale.
import \'dart:ui\' show Locale;
for (String locale in localeToResources.keys.toList()..sort()) { class TranslationBundle {
output.writeln(" '$locale': const <String, String>{"); const TranslationBundle(this.parent);
final TranslationBundle parent;''');
for (String key in allResourceIdentifiers)
output.writeln(' String get $key => parent?.$key;');
output.writeln('''
}''');
// Generate one private TranslationBundle subclass per supported
// language. Each of these classes overrides every resource identifier
// getter. For example:
//
// class _Bundle_en extends TranslationBundle {
// const _Bundle_en() : super(null);
// @override String get scriptCategory => r'English-like';
// ...
// }
for(String language in languageToLocales.keys) {
final Map<String, String> resources = localeToResources[language];
output.writeln('''
final Map<String, String> resources = localeToResources[locale]; // ignore: camel_case_types
for (String name in resources.keys) { class _Bundle_$language extends TranslationBundle {
final String value = generateString(resources[name]); const _Bundle_$language() : super(null);''');
output.writeln(" '$name': $value,"); for (String key in resources.keys) {
final String value = generateString(resources[key]);
output.writeln('''
@override String get $key => $value;''');
} }
output.writeln(' },'); output.writeln('''
}''');
}
// Generate one private TranslationBundle subclass for each locale
// with a country code. The parent of these subclasses is a const
// instance of a translation bundle for the same locale, but without
// a country code. These subclasses only override getters that
// return different value than the parent class, or a resource identifier
// that's not defined in the parent class. For example:
//
// class _Bundle_en_CA extends TranslationBundle {
// const _Bundle_en_CA() : super(const _Bundle_en());
// @override String get licensesPageTitle => r'Licences';
// ...
// }
for(String language in languageToLocales.keys) {
final Map<String, String> languageResources = localeToResources[language];
for(String localeName in languageToLocales[language]) {
if (localeName == language)
continue;
final Map<String, String> localeResources = localeToResources[localeName];
output.writeln('''
// ignore: camel_case_types
class _Bundle_$localeName extends TranslationBundle {
const _Bundle_$localeName() : super(const _Bundle_$language());''');
for (String key in localeResources.keys) {
if (languageResources[key] == localeResources[key])
continue;
final String value = generateString(localeResources[key]);
output.writeln('''
@override String get $key => $value;''');
}
output.writeln('''
}''');
}
}
// Generate the translationBundleForLocale function. Given a Locale
// it returns the corresponding const TranslationBundle.
output.writeln('''
TranslationBundle translationBundleForLocale(Locale locale) {
switch(locale.languageCode) {''');
for(String language in languageToLocales.keys) {
if (languageToLocales[language].length == 1) {
output.writeln('''
case \'$language\':
return const _Bundle_${languageToLocales[language][0]}();''');
} else {
output.writeln('''
case \'$language\': {
switch(locale.toString()) {''');
for(String localeName in languageToLocales[language]) {
if (localeName == language)
continue;
output.writeln('''
case \'$localeName\':
return const _Bundle_$localeName();''');
}
output.writeln('''
}
return const _Bundle_$language();
}''');
}
}
output.writeln('''
} }
return const TranslationBundle(null);
}''');
output.writeln('};');
return output.toString(); return output.toString();
} }
...@@ -161,12 +272,12 @@ void main(List<String> rawArgs) { ...@@ -161,12 +272,12 @@ void main(List<String> rawArgs) {
final String regenerate = 'dart dev/tools/gen_localizations.dart --overwrite'; final String regenerate = 'dart dev/tools/gen_localizations.dart --overwrite';
final StringBuffer buffer = new StringBuffer(); final StringBuffer buffer = new StringBuffer();
buffer.writeln(outputHeader.replaceFirst('@(regenerate)', regenerate)); buffer.writeln(outputHeader.replaceFirst('@(regenerate)', regenerate));
buffer.writeln(generateLocalizationsMap()); buffer.write(generateTranslationBundles());
if (options.writeToFile) { if (options.writeToFile) {
final File localizationsFile = new File(pathlib.join(directory.path, 'localizations.dart')); final File localizationsFile = new File(pathlib.join(directory.path, 'localizations.dart'));
localizationsFile.writeAsStringSync('$buffer'); localizationsFile.writeAsStringSync(buffer.toString());
} else { } else {
print(buffer); stdout.write(buffer.toString());
} }
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -11,7 +11,7 @@ import 'package:intl/date_symbols.dart' as intl; ...@@ -11,7 +11,7 @@ import 'package:intl/date_symbols.dart' as intl;
import 'package:intl/date_symbol_data_custom.dart' as date_symbol_data_custom; import 'package:intl/date_symbol_data_custom.dart' as date_symbol_data_custom;
import 'l10n/date_localizations.dart' as date_localizations; import 'l10n/date_localizations.dart' as date_localizations;
import 'l10n/localizations.dart'; import 'l10n/localizations.dart' show TranslationBundle, translationBundleForLocale;
import 'widgets_localizations.dart'; import 'widgets_localizations.dart';
/// Localized strings for the material widgets. /// Localized strings for the material widgets.
...@@ -68,13 +68,11 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -68,13 +68,11 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
/// function, rather than constructing this class directly. /// function, rather than constructing this class directly.
GlobalMaterialLocalizations(this.locale) GlobalMaterialLocalizations(this.locale)
: assert(locale != null), : assert(locale != null),
this._localeName = _computeLocaleName(locale) { _localeName = _computeLocaleName(locale) {
_loadDateIntlDataIfNotLoaded(); _loadDateIntlDataIfNotLoaded();
if (localizations.containsKey(locale.languageCode)) _translationBundle = translationBundleForLocale(locale);
_nameToValue.addAll(localizations[locale.languageCode]); assert(_translationBundle != null);
if (localizations.containsKey(_localeName))
_nameToValue.addAll(localizations[_localeName]);
const String kMediumDatePattern = 'E, MMM\u00a0d'; const String kMediumDatePattern = 'E, MMM\u00a0d';
if (intl.DateFormat.localeExists(_localeName)) { if (intl.DateFormat.localeExists(_localeName)) {
...@@ -113,7 +111,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -113,7 +111,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
final String _localeName; final String _localeName;
final Map<String, String> _nameToValue = <String, String>{}; TranslationBundle _translationBundle;
intl.NumberFormat _decimalFormat; intl.NumberFormat _decimalFormat;
...@@ -132,24 +130,6 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -132,24 +130,6 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
return intl.Intl.canonicalizedLocale(localeName); return intl.Intl.canonicalizedLocale(localeName);
} }
// TODO(hmuller): the rules for mapping from an integer value to
// "one" or "two" etc. are locale specific and an additional "few" category
// is needed. See http://cldr.unicode.org/index/cldr-spec/plural-rules
String _nameToPluralValue(int count, String key) {
String text;
if (count == 0)
text = _nameToValue['${key}Zero'];
else if (count == 1)
text = _nameToValue['${key}One'];
else if (count == 2)
text = _nameToValue['${key}Two'];
else if (count > 2)
text = _nameToValue['${key}Many'];
text ??= _nameToValue['${key}Other'];
assert(text != null);
return text;
}
@override @override
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) { String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
switch (hourFormat(of: timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat))) { switch (hourFormat(of: timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat))) {
...@@ -242,45 +222,45 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -242,45 +222,45 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
} }
@override @override
String get openAppDrawerTooltip => _nameToValue['openAppDrawerTooltip']; String get openAppDrawerTooltip => _translationBundle.openAppDrawerTooltip;
@override @override
String get backButtonTooltip => _nameToValue['backButtonTooltip']; String get backButtonTooltip => _translationBundle.backButtonTooltip;
@override @override
String get closeButtonTooltip => _nameToValue['closeButtonTooltip']; String get closeButtonTooltip => _translationBundle.closeButtonTooltip;
@override @override
String get deleteButtonTooltip => _nameToValue['deleteButtonTooltip']; String get deleteButtonTooltip => _translationBundle.deleteButtonTooltip;
@override @override
String get nextMonthTooltip => _nameToValue['nextMonthTooltip']; String get nextMonthTooltip => _translationBundle.nextMonthTooltip;
@override @override
String get previousMonthTooltip => _nameToValue['previousMonthTooltip']; String get previousMonthTooltip => _translationBundle.previousMonthTooltip;
@override @override
String get nextPageTooltip => _nameToValue['nextPageTooltip']; String get nextPageTooltip => _translationBundle.nextPageTooltip;
@override @override
String get previousPageTooltip => _nameToValue['previousPageTooltip']; String get previousPageTooltip => _translationBundle.previousPageTooltip;
@override @override
String get showMenuTooltip => _nameToValue['showMenuTooltip']; String get showMenuTooltip => _translationBundle.showMenuTooltip;
@override @override
String aboutListTileTitle(String applicationName) { String aboutListTileTitle(String applicationName) {
final String text = _nameToValue['aboutListTileTitle']; final String text = _translationBundle.aboutListTileTitle;
return text.replaceFirst(r'$applicationName', applicationName); return text.replaceFirst(r'$applicationName', applicationName);
} }
@override @override
String get licensesPageTitle => _nameToValue['licensesPageTitle']; String get licensesPageTitle => _translationBundle.licensesPageTitle;
@override @override
String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) { String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
String text = rowCountIsApproximate ? _nameToValue['pageRowsInfoTitleApproximate'] : null; String text = rowCountIsApproximate ? _translationBundle.pageRowsInfoTitleApproximate : null;
text ??= _nameToValue['pageRowsInfoTitle']; text ??= _translationBundle.pageRowsInfoTitle;
assert(text != null, 'A $locale localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate'); assert(text != null, 'A $locale localization was not found for pageRowsInfoTitle or pageRowsInfoTitleApproximate');
// TODO(hansmuller): this could be more efficient. // TODO(hansmuller): this could be more efficient.
return text return text
...@@ -290,55 +270,69 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -290,55 +270,69 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
} }
@override @override
String get rowsPerPageTitle => _nameToValue['rowsPerPageTitle']; String get rowsPerPageTitle => _translationBundle.rowsPerPageTitle;
@override @override
String selectedRowCountTitle(int selectedRowCount) { String selectedRowCountTitle(int selectedRowCount) {
return _nameToPluralValue(selectedRowCount, 'selectedRowCountTitle') // asserts on no match // TODO(hmuller): the rules for mapping from an integer value to
.replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount)); // "one" or "two" etc. are locale specific and an additional "few" category
// is needed. See http://cldr.unicode.org/index/cldr-spec/plural-rules
String text;
if (selectedRowCount == 0)
text = _translationBundle.selectedRowCountTitleZero;
else if (selectedRowCount == 1)
text = _translationBundle.selectedRowCountTitleOne;
else if (selectedRowCount == 2)
text = _translationBundle.selectedRowCountTitleTwo;
else if (selectedRowCount > 2)
text = _translationBundle.selectedRowCountTitleMany;
text ??= _translationBundle.selectedRowCountTitleOther;
assert(text != null);
return text.replaceFirst(r'$selectedRowCount', formatDecimal(selectedRowCount));
} }
@override @override
String get cancelButtonLabel => _nameToValue['cancelButtonLabel']; String get cancelButtonLabel => _translationBundle.cancelButtonLabel;
@override @override
String get closeButtonLabel => _nameToValue['closeButtonLabel']; String get closeButtonLabel => _translationBundle.closeButtonLabel;
@override @override
String get continueButtonLabel => _nameToValue['continueButtonLabel']; String get continueButtonLabel => _translationBundle.continueButtonLabel;
@override @override
String get copyButtonLabel => _nameToValue['copyButtonLabel']; String get copyButtonLabel => _translationBundle.copyButtonLabel;
@override @override
String get cutButtonLabel => _nameToValue['cutButtonLabel']; String get cutButtonLabel => _translationBundle.cutButtonLabel;
@override @override
String get okButtonLabel => _nameToValue['okButtonLabel']; String get okButtonLabel => _translationBundle.okButtonLabel;
@override @override
String get pasteButtonLabel => _nameToValue['pasteButtonLabel']; String get pasteButtonLabel => _translationBundle.pasteButtonLabel;
@override @override
String get selectAllButtonLabel => _nameToValue['selectAllButtonLabel']; String get selectAllButtonLabel => _translationBundle.selectAllButtonLabel;
@override @override
String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel']; String get viewLicensesButtonLabel => _translationBundle.viewLicensesButtonLabel;
@override @override
String get anteMeridiemAbbreviation => _nameToValue['anteMeridiemAbbreviation']; String get anteMeridiemAbbreviation => _translationBundle.anteMeridiemAbbreviation;
@override @override
String get postMeridiemAbbreviation => _nameToValue['postMeridiemAbbreviation']; String get postMeridiemAbbreviation => _translationBundle.postMeridiemAbbreviation;
@override @override
String get timePickerHourModeAnnouncement => _nameToValue['timePickerHourModeAnnouncement']; String get timePickerHourModeAnnouncement => _translationBundle.timePickerHourModeAnnouncement;
@override @override
String get timePickerMinuteModeAnnouncement => _nameToValue['timePickerMinuteModeAnnouncement']; String get timePickerMinuteModeAnnouncement => _translationBundle.timePickerMinuteModeAnnouncement;
@override @override
String get modalBarrierDismissLabel => _nameToValue['modalBarrierDismissLabel']; String get modalBarrierDismissLabel => _translationBundle.modalBarrierDismissLabel;
/// The [TimeOfDayFormat] corresponding to one of the following supported /// The [TimeOfDayFormat] corresponding to one of the following supported
/// patterns: /// patterns:
...@@ -358,7 +352,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -358,7 +352,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
/// short time pattern used in locale en_US /// short time pattern used in locale en_US
@override @override
TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false }) { TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false }) {
final String icuShortTimePattern = _nameToValue['timeOfDayFormat']; final String icuShortTimePattern = _translationBundle.timeOfDayFormat;
assert(() { assert(() {
if (!_icuTimeOfDayToEnum.containsKey(icuShortTimePattern)) { if (!_icuTimeOfDayToEnum.containsKey(icuShortTimePattern)) {
...@@ -382,7 +376,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations { ...@@ -382,7 +376,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
/// Looks up text geometry defined in [MaterialTextGeometry]. /// Looks up text geometry defined in [MaterialTextGeometry].
@override @override
TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_nameToValue['scriptCategory']); TextTheme get localTextGeometry => MaterialTextGeometry.forScriptCategory(_translationBundle.scriptCategory);
/// Creates an object that provides localized resource values for the /// Creates an object that provides localized resource values for the
/// for the widgets of the material library. /// for the widgets of the material library.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment