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
164 lines
6.2 KiB
TypeScript
164 lines
6.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {CldrData, CldrLocaleData} from './cldr-data';
|
|
import {fileHeader} from './file-header';
|
|
import {BaseCurrencies} from './locale-base-currencies';
|
|
import {generateLocale} from './locale-file';
|
|
|
|
interface ClosureLocale {
|
|
/** Closure-supported locale names that resolve to this locale. */
|
|
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. */
|
|
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.
|
|
* Tree shaking will only keep the data for the `goog.LOCALE` locale.
|
|
*/
|
|
export function generateClosureLocaleFile(cldrData: CldrData, baseCurrencies: BaseCurrencies) {
|
|
const locales: ClosureLocale[] = closureLibraryLocales.map(localeName => {
|
|
const data = cldrData.getLocaleData(localeName);
|
|
|
|
if (data === null) {
|
|
throw Error(`Missing locale data for Closure locale: ${localeName}`);
|
|
}
|
|
|
|
return {
|
|
data,
|
|
canonicalLocaleName: localeName,
|
|
closureLocaleNames: computeEquivalentLocaleNames(localeName),
|
|
};
|
|
});
|
|
|
|
return `${fileHeader}
|
|
|
|
import {registerLocaleData} from '../src/i18n/locale_data';
|
|
|
|
const u = undefined;
|
|
|
|
${locales.map(locale => generateLocaleConstants(locale)).join('\n')}
|
|
|
|
let l: any;
|
|
let locales: string[] = [];
|
|
|
|
switch (goog.LOCALE) {
|
|
${locales.map(locale => generateCase(locale)).join('')}}
|
|
|
|
if (l) {
|
|
locales.forEach(locale => registerLocaleData(l, locale));
|
|
}
|
|
`;
|
|
|
|
/**
|
|
* Generates locale data constants for all locale names within the specified
|
|
* 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('export default ', `export const ${constantName} = `)
|
|
.replace('function plural', `function plural_${constantName}`)
|
|
.replace(/,\s+plural/, `, plural_${constantName}`)
|
|
.replace(/\s*const u = undefined;\s*/, '');
|
|
}
|
|
|
|
/** Generates a TypeScript `switch` case for the specified locale. */
|
|
function generateCase(locale: ClosureLocale): string {
|
|
return `
|
|
${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 {
|
|
return locale.replace(/-/g, '_');
|
|
}
|