Unverified Commit aab92447 authored by Gary Qian's avatar Gary Qian Committed by GitHub

scriptCode support in gen_localizations and strip scriptCodes for date l10n. (#23853)

parent b21fe0aa
This diff is collapsed.
...@@ -9,6 +9,123 @@ import 'dart:io'; ...@@ -9,6 +9,123 @@ import 'dart:io';
import 'package:args/args.dart' as argslib; import 'package:args/args.dart' as argslib;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
/// Simple data class to hold parsed locale. Does not promise validity of any data.
class LocaleInfo implements Comparable<LocaleInfo> {
LocaleInfo({
this.languageCode,
this.scriptCode,
this.countryCode,
this.length,
this.originalString,
});
/// Simple parser. Expects the locale string to be in the form of 'language_script_COUNTRY'
/// where the langauge is 2 characters, script is 4 characters with the first uppercase,
/// and country is 2-3 characters and all uppercase.
///
/// 'language_COUNTRY' or 'language_script' are also valid. Missing fields will be null.
factory LocaleInfo.fromString(String locale, {bool assume = false}) {
final List<String> codes = locale.split('_'); // [language, script, country]
assert(codes.isNotEmpty && codes.length < 4);
final String languageCode = codes[0];
String scriptCode;
String countryCode;
int length = codes.length;
String originalString = locale;
if (codes.length == 2) {
scriptCode = codes[1].length >= 4 ? codes[1] : null;
countryCode = codes[1].length < 4 ? codes[1] : null;
} else if (codes.length == 3) {
scriptCode = codes[1].length > codes[2].length ? codes[1] : codes[2];
countryCode = codes[1].length < codes[2].length ? codes[1] : codes[2];
}
assert(codes[0] != null && codes[0].isNotEmpty);
assert(countryCode == null || countryCode.isNotEmpty);
assert(scriptCode == null || scriptCode.isNotEmpty);
/// Adds scriptCodes to locales where we are able to assume it to provide
/// finer granularity when resolving locales.
///
/// The basis of the assumptions here are based off of known usage of scripts
/// across various countries. For example, we know Taiwan uses traditional (Hant)
/// script, so it is safe to apply (Hant) to Taiwanese languages.
if (assume && scriptCode == null) {
switch (languageCode) {
case 'zh': {
if (countryCode == null) {
scriptCode = 'Hans';
}
switch (countryCode) {
case 'CN':
case 'SG':
scriptCode = 'Hans';
break;
case 'TW':
case 'HK':
case 'MO':
scriptCode = 'Hant';
break;
}
break;
}
case 'sr': {
if (countryCode == null) {
scriptCode = 'Cyrl';
}
break;
}
}
// Increment length if we were able to assume a scriptCode.
if (scriptCode != null) {
length += 1;
}
// Update the base string to reflect assumed scriptCodes.
originalString = languageCode;
if (scriptCode != null)
originalString += '_' + scriptCode;
if (countryCode != null)
originalString += '_' + countryCode;
}
return LocaleInfo(
languageCode: languageCode,
scriptCode: scriptCode,
countryCode: countryCode,
length: length,
originalString: originalString,
);
}
final String languageCode;
final String scriptCode;
final String countryCode;
final int length; // The number of fields. Ranges from 1-3.
final String originalString; // Original un-parsed locale string.
@override
bool operator ==(Object other) {
if (!(other is LocaleInfo))
return false;
final LocaleInfo otherLocale = other;
return originalString == otherLocale.originalString;
}
@override
int get hashCode {
return originalString.hashCode;
}
@override
String toString() {
return originalString;
}
@override
int compareTo(LocaleInfo other) {
return originalString.compareTo(other.originalString);
}
}
void exitWithError(String errorMessage) { void exitWithError(String errorMessage) {
assert(errorMessage != null); assert(errorMessage != null);
stderr.writeln('fatal: $errorMessage'); stderr.writeln('fatal: $errorMessage');
...@@ -26,8 +143,8 @@ void checkCwdIsRepoRoot(String commandName) { ...@@ -26,8 +143,8 @@ void checkCwdIsRepoRoot(String commandName) {
} }
} }
String camelCase(String locale) { String camelCase(LocaleInfo locale) {
return locale return locale.originalString
.split('_') .split('_')
.map<String>((String part) => part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase()) .map<String>((String part) => part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase())
.join(''); .join('');
...@@ -129,14 +246,21 @@ String describeLocale(String tag) { ...@@ -129,14 +246,21 @@ String describeLocale(String tag) {
assert(subtags.isNotEmpty); assert(subtags.isNotEmpty);
assert(_languages.containsKey(subtags[0])); assert(_languages.containsKey(subtags[0]));
final String language = _languages[subtags[0]]; final String language = _languages[subtags[0]];
if (subtags.length >= 2) { String output = '$language';
final String region = _regions[subtags[1]]; String region;
final String script = _scripts[subtags[1]]; String script;
if (subtags.length == 2) {
region = _regions[subtags[1]];
script = _scripts[subtags[1]];
assert(region != null || script != null); assert(region != null || script != null);
if (region != null) } else if (subtags.length >= 3) {
return '$language, as used in $region'; region = _regions[subtags[2]];
if (script != null) script = _scripts[subtags[1]];
return '$language, using the $script script'; assert(region != null && script != null);
} }
return '$language'; if (region != null)
output += ', as used in $region';
if (script != null)
output += ', using the $script script';
return output;
} }
\ No newline at end of file
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'dart:convert' show json; import 'dart:convert' show json;
import 'dart:io'; import 'dart:io';
import 'localizations_utils.dart';
// The first suffix in kPluralSuffixes must be "Other". "Other" is special // The first suffix in kPluralSuffixes must be "Other". "Other" is special
// because it's the only one that is required. // because it's the only one that is required.
const List<String> kPluralSuffixes = <String>['Other', 'Zero', 'One', 'Two', 'Few', 'Many']; const List<String> kPluralSuffixes = <String>['Other', 'Zero', 'One', 'Two', 'Few', 'Many'];
...@@ -96,14 +98,14 @@ void validateEnglishLocalizations(File file) { ...@@ -96,14 +98,14 @@ void validateEnglishLocalizations(File file) {
/// ///
/// If validation fails, throws an exception. /// If validation fails, throws an exception.
void validateLocalizations( void validateLocalizations(
Map<String, Map<String, String>> localeToResources, Map<LocaleInfo, Map<String, String>> localeToResources,
Map<String, Map<String, dynamic>> localeToAttributes, Map<LocaleInfo, Map<String, dynamic>> localeToAttributes,
) { ) {
final Map<String, String> canonicalLocalizations = localeToResources['en']; final Map<String, String> canonicalLocalizations = localeToResources[LocaleInfo.fromString('en')];
final Set<String> canonicalKeys = Set<String>.from(canonicalLocalizations.keys); final Set<String> canonicalKeys = Set<String>.from(canonicalLocalizations.keys);
final StringBuffer errorMessages = StringBuffer(); final StringBuffer errorMessages = StringBuffer();
bool explainMissingKeys = false; bool explainMissingKeys = false;
for (final String locale in localeToResources.keys) { for (final LocaleInfo locale in localeToResources.keys) {
final Map<String, String> resources = localeToResources[locale]; final Map<String, String> resources = localeToResources[locale];
// Whether `key` corresponds to one of the plural variations of a key with // Whether `key` corresponds to one of the plural variations of a key with
...@@ -128,14 +130,12 @@ void validateLocalizations( ...@@ -128,14 +130,12 @@ void validateLocalizations(
final Set<String> invalidKeys = keys.difference(canonicalKeys); final Set<String> invalidKeys = keys.difference(canonicalKeys);
if (invalidKeys.isNotEmpty) if (invalidKeys.isNotEmpty)
errorMessages.writeln('Locale "$locale" contains invalid resource keys: ${invalidKeys.join(', ')}'); errorMessages.writeln('Locale "$locale" contains invalid resource keys: ${invalidKeys.join(', ')}');
// For language-level locales only, check that they have a complete list of // For language-level locales only, check that they have a complete list of
// keys, or opted out of using certain ones. // keys, or opted out of using certain ones.
if (locale.length == 2) { if (locale.length == 1) {
final Map<String, dynamic> attributes = localeToAttributes[locale]; final Map<String, dynamic> attributes = localeToAttributes[locale];
final List<String> missingKeys = <String>[]; final List<String> missingKeys = <String>[];
for (final String missingKey in canonicalKeys.difference(keys)) {
for (final String missingKey in canonicalKeys.difference(keys)) {
final dynamic attribute = attributes[missingKey]; final dynamic attribute = attributes[missingKey];
final bool intentionallyOmitted = attribute is Map && attribute.containsKey('notUsed'); final bool intentionallyOmitted = attribute is Map && attribute.containsKey('notUsed');
if (!intentionallyOmitted && !isPluralVariation(missingKey)) if (!intentionallyOmitted && !isPluralVariation(missingKey))
......
...@@ -6779,10 +6779,10 @@ const Map<String, dynamic> dateSymbols = <String, dynamic>{ ...@@ -6779,10 +6779,10 @@ const Map<String, dynamic> dateSymbols = <String, dynamic>{
r'''S''' r'''S'''
], ],
'SHORTQUARTERS': <dynamic>[ 'SHORTQUARTERS': <dynamic>[
r'''1. cet.''', r'''1. cet.''',
r'''2. cet.''', r'''2. cet.''',
r'''3. cet.''', r'''3. cet.''',
r'''4. cet.''' r'''4. cet.'''
], ],
'QUARTERS': <dynamic>[ 'QUARTERS': <dynamic>[
r'''1. ceturksnis''', r'''1. ceturksnis''',
...@@ -13904,4 +13904,4 @@ const Map<String, Map<String, String>> datePatterns = ...@@ -13904,4 +13904,4 @@ const Map<String, Map<String, String>> datePatterns =
'zzzz': r'''zzzz''', 'zzzz': r'''zzzz''',
'ZZZZ': r'''ZZZZ''', 'ZZZZ': r'''ZZZZ''',
}, },
}; };
\ No newline at end of file
...@@ -568,7 +568,27 @@ class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocal ...@@ -568,7 +568,27 @@ class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocal
/// data. Subsequent invocations have no effect. /// data. Subsequent invocations have no effect.
static void _loadDateIntlDataIfNotLoaded() { static void _loadDateIntlDataIfNotLoaded() {
if (!_dateIntlDataInitialized) { if (!_dateIntlDataInitialized) {
// TODO(garyq): Add support for scriptCodes. Do not strip scriptCode from string.
// Keep track of initialzed locales, or will fail on attempted double init.
// This can only happen if a locale with a stripped scriptCode has already
// been initialzed. This should be removed when scriptCode stripping is removed.
final Set<String> initializedLocales = Set<String>();
date_localizations.dateSymbols.forEach((String locale, dynamic data) { date_localizations.dateSymbols.forEach((String locale, dynamic data) {
// Strip scriptCode from the locale, as we do not distinguish between scripts
// for dates.
final List<String> codes = locale.split('_');
String countryCode;
if (codes.length == 2) {
countryCode = codes[1].length < 4 ? codes[1] : null;
} else if (codes.length == 3) {
countryCode = codes[1].length < codes[2].length ? codes[1] : codes[2];
}
locale = codes[0] + (countryCode != null ? '_' + countryCode : '');
if (initializedLocales.contains(locale))
return;
initializedLocales.add(locale);
// Perform initialization.
assert(date_localizations.datePatterns.containsKey(locale)); assert(date_localizations.datePatterns.containsKey(locale));
final intl.DateSymbols symbols = intl.DateSymbols.deserializeFromMap(data); final intl.DateSymbols symbols = intl.DateSymbols.deserializeFromMap(data);
date_symbol_data_custom.initializeDateFormattingCustom( date_symbol_data_custom.initializeDateFormattingCustom(
......
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