build: generate closure locale files using hard-coded list of locales (#42230)

With the refactoring from a Gulp task to a Bazel too, we tried switching
away from the hard-coded list of locales and aliases for the Closure
Locale file generation. After multiple attempts of landing this, it
turned out that Closure Compiler/Closure Library relies on locale
identifiers CLDR does not capture within it's `availableLocales.json`
or `aliases.json` data.

Closure Library does not use any unknown locale identifiers here. The
locale identifiers can be resolved within CLDR using the bundle lookup
algorithm that is specified as part of CLDR; instead the problem is that
the locale identifiers do not follow any reasonable pattern and
therefore it's extremely difficult to generate them automatically (it's
almost like we'd need to build up _all_ possible combinations). Instead
of doing that, we just use the hard-coded locales and aliases from the
old Closure Locale generation script.

PR Close #42230
This commit is contained in:
Paul Gschwendtner 2021-06-18 16:03:29 +02:00 committed by Alex Rickabaugh
parent 9d1deb16fa
commit 3b2f607cda
2 changed files with 939 additions and 9900 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,40 +12,70 @@ import {BaseCurrencies} from './locale-base-currencies';
import {generateLocale} from './locale-file'; import {generateLocale} from './locale-file';
interface ClosureLocale { interface ClosureLocale {
/** Locale name to match with a Closure-supported locale. */ /** Closure-supported locale names that resolve to this locale. */
closureLocaleName: string; closureLocaleNames: string[];
/** Canonical locale name that is used to resolve the CLDR data. */
canonicalLocaleName: string;
/** Locale data. Can have a different locale name if this captures an aliased locale. */ /** Locale data. Can have a different locale name if this captures an aliased locale. */
data: CldrLocaleData; data: CldrLocaleData;
} }
/**
* Locales used by closure that need to be captured within the Closure Locale file. Extracted from:
* https://github.com/google/closure-library/blob/c7445058af72f679ef3273274e936d5d5f40b55a/closure/goog/i18n/datetimepatterns.js#L2450
*/
const closureLibraryLocales = [
'af', 'am', 'ar', 'ar-DZ', 'az', 'be', 'bg', 'bn', 'br', 'bs',
'ca', 'chr', 'cs', 'cy', 'da', 'de', 'de-AT', 'de-CH', 'el', 'en-AU',
'en-CA', 'en-GB', 'en-IE', 'en-IN', 'en-SG', 'en-ZA', 'es', 'es-419', 'es-MX', 'es-US',
'et', 'eu', 'fa', 'fi', 'fr', 'fr-CA', 'ga', 'gl', 'gsw', 'gu',
'haw', 'hi', 'hr', 'hu', 'hy', 'id', 'is', 'it', 'he', 'ja',
'ka', 'kk', 'km', 'kn', 'ko', 'ky', 'ln', 'lo', 'lt', 'lv',
'mk', 'ml', 'mn', 'ro-MD', 'mr', 'ms', 'mt', 'my', 'ne', 'nl',
'nb', 'or', 'pa', 'pl', 'pt', 'pt-PT', 'ro', 'ru', 'sr-Latn', 'si',
'sk', 'sl', 'sq', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'fil',
'tr', 'uk', 'ur', 'uz', 'vi', 'zh', 'zh-Hans', 'zh-Hant-HK', 'zh-Hant', 'zu'
] as const;
/** Union type matching possible Closure Library locales. */
type ClosureLibraryLocaleName = typeof closureLibraryLocales[number];
/**
* Locale ID aliases to support deprecated locale ids used by Closure. Maps locales supported
* by Closure library to a list of aliases that match the same locale data.
*/
const closureLibraryAliases: {[l in ClosureLibraryLocaleName]?: string[]} = {
'id': ['in'],
'he': ['iw'],
'ro-MD': ['mo'],
'nb': ['no', 'no-NO'],
'sr-Latn': ['sh'],
'fil': ['tl'],
'pt': ['pt-BR'],
'zh-Hans': ['zh-Hans-CN', 'zh-CN'],
'zh-Hant-HK': ['zh-HK'],
'zh-Hant': ['zh-Hant-TW', 'zh-TW'],
};
/** /**
* Generate a file that contains all locale to import for closure. * Generate a file that contains all locale to import for closure.
* Tree shaking will only keep the data for the `goog.LOCALE` locale. * Tree shaking will only keep the data for the `goog.LOCALE` locale.
*/ */
export function generateClosureLocaleFile(cldrData: CldrData, baseCurrencies: BaseCurrencies) { export function generateClosureLocaleFile(cldrData: CldrData, baseCurrencies: BaseCurrencies) {
const locales: ClosureLocale[] = const locales: ClosureLocale[] = closureLibraryLocales.map(localeName => {
[...cldrData.availableLocales.map(data => ({closureLocaleName: data.locale, data}))]; const data = cldrData.getLocaleData(localeName);
const aliases = cldrData.getLanguageAliases();
// We also generate locale data for aliases known within CLDR. Closure compiler does not if (data === null) {
// limit its locale identifiers to CLDR-canonical identifiers/or BCP47 identifiers. throw Error(`Missing locale data for Closure locale: ${localeName}`);
// To ensure deprecated/historical locale identifiers which are supported by Closure
// can work with closure-compiled Angular applications, we respect CLDR locale aliases.
for (const [aliasName, data] of Object.entries(aliases)) {
// We skip bibliographic aliases as those have never been supported by Closure compiler.
if (data._reason === 'bibliographic') {
continue;
} }
const localeData = cldrData.getLocaleData(data._replacement); return {
data,
// If CLDR does not provide data for the replacement locale, we skip this alias. canonicalLocaleName: localeName,
if (localeData === null) { closureLocaleNames: computeEquivalentLocaleNames(localeName),
continue; };
} });
locales.push({closureLocaleName: aliasName, data: localeData});
}
return `${fileHeader} return `${fileHeader}
@ -53,35 +83,81 @@ import {registerLocaleData} from '../src/i18n/locale_data';
const u = undefined; const u = undefined;
${locales.map(locale => `${generateLocaleConstant(locale)}`).join('\n')} ${locales.map(locale => generateLocaleConstants(locale)).join('\n')}
let l: any; let l: any;
let locales: string[] = [];
switch (goog.LOCALE) { switch (goog.LOCALE) {
${locales.map(locale => generateCase(locale)).join('')}} ${locales.map(locale => generateCase(locale)).join('')}}
if (l) { if (l) {
registerLocaleData(l); locales.forEach(locale => registerLocaleData(l, locale));
} }
`; `;
function generateLocaleConstant(locale: ClosureLocale): string { /**
const localeNameFormattedForJs = formatLocale(locale.closureLocaleName); * Generates locale data constants for all locale names within the specified
return generateLocale(locale.closureLocaleName, locale.data, baseCurrencies) * Closure Library locale.
*/
function generateLocaleConstants(locale: ClosureLocale): string {
// Closure Locale names contain both the dashed and underscore variant. We filter out
// the dashed variant as otherwise we would end up with the same constant twice. e.g.
// https://github.com/google/closure-library/blob/c7445058af72f679ef3273274e936d5d5f40b55a/closure/goog/i18n/datetimepatternsext.js#L11659-L11660.
const localeConstantNames = locale.closureLocaleNames.filter(d => !d.includes('-'))
.map(d => `locale_${formatLocale(d)}`);
const dataConstantName = localeConstantNames[0];
const otherAliasConstantNames = localeConstantNames.slice(1);
// We only generate the locale data once. All other constants just refer to the
// first constant with the actual locale data. This reduces the Closure Locale file
// size and potentially speeds up compilation with Closure Compiler.
return `
${generateLocaleConstant(locale, dataConstantName)}
${otherAliasConstantNames.map(d => `export const ${d} = ${dataConstantName};`).join('\n')}`;
}
/** Generates a locale data constant for the specified locale. */
function generateLocaleConstant(locale: ClosureLocale, constantName: string): string {
return generateLocale(locale.canonicalLocaleName, locale.data, baseCurrencies)
.replace(`${fileHeader}\n`, '') .replace(`${fileHeader}\n`, '')
.replace('export default ', `export const locale_${localeNameFormattedForJs} = `) .replace('export default ', `export const ${constantName} = `)
.replace('function plural', `function plural_${localeNameFormattedForJs}`) .replace('function plural', `function plural_${constantName}`)
.replace(/,\s+plural/, `, plural_${localeNameFormattedForJs}`) .replace(/,\s+plural/, `, plural_${constantName}`)
.replace(/\s*const u = undefined;\s*/, ''); .replace(/\s*const u = undefined;\s*/, '');
} }
function generateCase(locale: ClosureLocale) { /** Generates a TypeScript `switch` case for the specified locale. */
return `case '${locale.closureLocaleName}':\n` + function generateCase(locale: ClosureLocale): string {
`l = locale_${formatLocale(locale.closureLocaleName)};\n` + return `
`break;\n`; ${locale.closureLocaleNames.map(l => `case '${l}':`).join('\n')}
l = locale_${formatLocale(locale.canonicalLocaleName)};
locales = [${locale.closureLocaleNames.map(n => `"${n}"`).join(', ')}];
break;`;
} }
} }
function computeEquivalentLocaleNames(localeName: ClosureLibraryLocaleName): string[] {
const equivalents = new Set<string>([localeName]);
if (localeName.includes('-')) {
equivalents.add(localeName.replace(/-/g, '_'));
}
const aliases = closureLibraryAliases[localeName];
if (aliases !== undefined) {
aliases.forEach(aliasName => {
equivalents.add(aliasName);
if (aliasName.includes('-')) {
equivalents.add(aliasName.replace(/-/g, '_'));
}
});
}
return Array.from(equivalents);
}
function formatLocale(locale: string): string { function formatLocale(locale: string): string {
return locale.replace(/-/g, '_'); return locale.replace(/-/g, '_');
} }