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';
import 'package:args/args.dart' as argslib;
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) {
assert(errorMessage != null);
stderr.writeln('fatal: $errorMessage');
......@@ -26,8 +143,8 @@ void checkCwdIsRepoRoot(String commandName) {
}
}
String camelCase(String locale) {
return locale
String camelCase(LocaleInfo locale) {
return locale.originalString
.split('_')
.map<String>((String part) => part.substring(0, 1).toUpperCase() + part.substring(1).toLowerCase())
.join('');
......@@ -129,14 +246,21 @@ String describeLocale(String tag) {
assert(subtags.isNotEmpty);
assert(_languages.containsKey(subtags[0]));
final String language = _languages[subtags[0]];
if (subtags.length >= 2) {
final String region = _regions[subtags[1]];
final String script = _scripts[subtags[1]];
String output = '$language';
String region;
String script;
if (subtags.length == 2) {
region = _regions[subtags[1]];
script = _scripts[subtags[1]];
assert(region != null || script != null);
} else if (subtags.length >= 3) {
region = _regions[subtags[2]];
script = _scripts[subtags[1]];
assert(region != null && script != null);
}
if (region != null)
return '$language, as used in $region';
output += ', as used in $region';
if (script != null)
return '$language, using the $script script';
}
return '$language';
output += ', using the $script script';
return output;
}
\ No newline at end of file
......@@ -5,6 +5,8 @@
import 'dart:convert' show json;
import 'dart:io';
import 'localizations_utils.dart';
// The first suffix in kPluralSuffixes must be "Other". "Other" is special
// because it's the only one that is required.
const List<String> kPluralSuffixes = <String>['Other', 'Zero', 'One', 'Two', 'Few', 'Many'];
......@@ -96,14 +98,14 @@ void validateEnglishLocalizations(File file) {
///
/// If validation fails, throws an exception.
void validateLocalizations(
Map<String, Map<String, String>> localeToResources,
Map<String, Map<String, dynamic>> localeToAttributes,
Map<LocaleInfo, Map<String, String>> localeToResources,
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 StringBuffer errorMessages = StringBuffer();
bool explainMissingKeys = false;
for (final String locale in localeToResources.keys) {
for (final LocaleInfo locale in localeToResources.keys) {
final Map<String, String> resources = localeToResources[locale];
// Whether `key` corresponds to one of the plural variations of a key with
......@@ -128,13 +130,11 @@ void validateLocalizations(
final Set<String> invalidKeys = keys.difference(canonicalKeys);
if (invalidKeys.isNotEmpty)
errorMessages.writeln('Locale "$locale" contains invalid resource keys: ${invalidKeys.join(', ')}');
// For language-level locales only, check that they have a complete list of
// keys, or opted out of using certain ones.
if (locale.length == 2) {
if (locale.length == 1) {
final Map<String, dynamic> attributes = localeToAttributes[locale];
final List<String> missingKeys = <String>[];
for (final String missingKey in canonicalKeys.difference(keys)) {
final dynamic attribute = attributes[missingKey];
final bool intentionallyOmitted = attribute is Map && attribute.containsKey('notUsed');
......
......@@ -6779,10 +6779,10 @@ const Map<String, dynamic> dateSymbols = <String, dynamic>{
r'''S'''
],
'SHORTQUARTERS': <dynamic>[
r'''1. cet.''',
r'''2. cet.''',
r'''3. cet.''',
r'''4. cet.'''
r'''1. cet.''',
r'''2. cet.''',
r'''3. cet.''',
r'''4. cet.'''
],
'QUARTERS': <dynamic>[
r'''1. ceturksnis''',
......
......@@ -568,7 +568,27 @@ class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocal
/// data. Subsequent invocations have no effect.
static void _loadDateIntlDataIfNotLoaded() {
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) {
// 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));
final intl.DateSymbols symbols = intl.DateSymbols.deserializeFromMap(data);
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