build: generate alias locale data for closure locale (#42230)

Within Google, closure compiler is used for dealing with translations.
We generate a closure-compatible locale file that allows for
registration within Angular, so that Closure i18n works well together
with Angular applications. Closure compiler does not limit its
locales to BCP47-canonical locale identifiers. This commit updates
the generation logic so that we also support deprecated (but aliased)
locale identifiers, or other aliases which are likely used within
Closure. We use CLDR's alias supplemental data for this. It instructs
us to alias `iw` to `he` for example. `iw` is still supported in Closure.

Note that we do not manually extract all locales supported in Closure;
instead we only support the CLDR canonical locales (as done before) +
common aliases that CLDR provides data for. We are not aware of other
locale aliases within Closure that wouldn't be part of the CLDR aliases.
If there would be, then Angular/Closure would fail accordingly.

PR Close #42230
This commit is contained in:
Paul Gschwendtner 2021-06-10 20:56:25 +02:00 committed by Alex Rickabaugh
parent e640db198f
commit 9d1deb16fa
6 changed files with 3871 additions and 23 deletions

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,9 @@ const CLDR_DATA_GLOBS = [
/** Path to the CLDR available locales file. */
const CLDR_AVAILABLE_LOCALES_PATH = 'cldr-core-37.0.0/availableLocales.json';
/** Path to the CLDR locale aliases file. */
const CLDR_LOCALE_ALIASES_PATH = 'cldr-core-37.0.0/supplemental/aliases.json';
/**
* Instance providing access to a locale's CLDR data. This type extends the `cldrjs`
* instance type with the missing `bundle` attribute property.
@ -45,6 +48,13 @@ export type CldrLocaleData = CldrStatic&{
}
};
/**
* Possible reasons for an alias in the CLDR supplemental data. See:
* https://unicode.org/reports/tr35/tr35-info.html#Appendix_Supplemental_Metadata.
*/
export type CldrLocaleAliasReason =
'deprecated'|'overlong'|'macrolanguage'|'legacy'|'bibliographic';
/**
* Class that provides access to the CLDR data downloaded as part of
* the `@cldr_data` Bazel repository.
@ -77,6 +87,16 @@ export class CldrData {
return localeData;
}
/**
* Gets the CLDR language aliases.
* http://cldr.unicode.org/index/cldr-spec/language-tag-equivalences.
*/
getLanguageAliases():
{[localeName: string]: {_reason: CldrLocaleAliasReason, _replacement: string}} {
return require(`${this.cldrDataDir}/${CLDR_LOCALE_ALIASES_PATH}`)
.supplemental.metadata.alias.languageAlias;
}
/** Gets a list of all locales CLDR provides data for. */
private _getAvailableLocales(): CldrLocaleData[] {
const allLocales =

View File

@ -6,35 +6,45 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CldrStatic} from 'cldrjs';
import {CldrData} from './cldr-data';
import {CldrData, CldrLocaleData} from './cldr-data';
import {fileHeader} from './file-header';
import {BaseCurrencies} from './locale-base-currencies';
import {generateLocale} from './locale-file';
interface ClosureLocale {
/** Locale name to match with a Closure-supported locale. */
closureLocaleName: string;
/** Locale data. Can have a different locale name if this captures an aliased locale. */
data: CldrLocaleData;
}
/**
* 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 = cldrData.availableLocales;
const locales: ClosureLocale[] =
[...cldrData.availableLocales.map(data => ({closureLocaleName: data.locale, data}))];
const aliases = cldrData.getLanguageAliases();
function generateLocaleConstant(localeData: CldrStatic): string {
const locale = localeData.locale;
const localeNameFormattedForJs = formatLocale(locale);
return generateLocale(locale, localeData, baseCurrencies)
.replace(`${fileHeader}\n`, '')
.replace('export default ', `export const locale_${localeNameFormattedForJs} = `)
.replace('function plural', `function plural_${localeNameFormattedForJs}`)
.replace(/,\s+plural/, `, plural_${localeNameFormattedForJs}`)
.replace(/\s*const u = undefined;\s*/, '');
// We also generate locale data for aliases known within CLDR. Closure compiler does not
// limit its locale identifiers to CLDR-canonical identifiers/or BCP47 identifiers.
// 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;
}
function generateCase(localeName: string) {
return `case '${localeName}':\n` +
`l = locale_${formatLocale(localeName)};\n` +
`break;\n`;
const localeData = cldrData.getLocaleData(data._replacement);
// If CLDR does not provide data for the replacement locale, we skip this alias.
if (localeData === null) {
continue;
}
locales.push({closureLocaleName: aliasName, data: localeData});
}
return `${fileHeader}
@ -48,12 +58,28 @@ ${locales.map(locale => `${generateLocaleConstant(locale)}`).join('\n')}
let l: any;
switch (goog.LOCALE) {
${locales.map(localeData => generateCase(localeData.locale)).join('')}}
${locales.map(locale => generateCase(locale)).join('')}}
if (l) {
registerLocaleData(l);
}
`;
function generateLocaleConstant(locale: ClosureLocale): string {
const localeNameFormattedForJs = formatLocale(locale.closureLocaleName);
return generateLocale(locale.closureLocaleName, locale.data, baseCurrencies)
.replace(`${fileHeader}\n`, '')
.replace('export default ', `export const locale_${localeNameFormattedForJs} = `)
.replace('function plural', `function plural_${localeNameFormattedForJs}`)
.replace(/,\s+plural/, `, plural_${localeNameFormattedForJs}`)
.replace(/\s*const u = undefined;\s*/, '');
}
function generateCase(locale: ClosureLocale) {
return `case '${locale.closureLocaleName}':\n` +
`l = locale_${formatLocale(locale.closureLocaleName)};\n` +
`break;\n`;
}
}
function formatLocale(locale: string): string {

View File

@ -23,7 +23,7 @@ export function generateLocale(
return `${fileHeader}
const u = undefined;
${getPluralFunction(locale)}
${getPluralFunction(localeData)}
export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)};
`;

View File

@ -27,7 +27,7 @@ export function generateLocaleGlobalFile(
global.ng.common = global.ng.common || {};
global.ng.common.locales = global.ng.common.locales || {};
const u = undefined;
${getPluralFunction(locale, false)}
${getPluralFunction(localeData, false)}
global.ng.common.locales['${normalizeLocale(locale)}'] = ${data};
})(typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window);
`;

View File

@ -5,6 +5,7 @@
* 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 {CldrLocaleData} from './cldr-data';
// There are no types available for `cldr`.
const cldr = require('cldr');
@ -14,8 +15,13 @@ const cldr = require('cldr');
* TODO(ocombe): replace "cldr" extractPluralRuleFunction with our own extraction using "CldrJS"
* because the 2 libs can become out of sync if they use different versions of the cldr database
*/
export function getPluralFunction(locale: string, withTypes = true) {
let fn = cldr.extractPluralRuleFunction(locale).toString();
export function getPluralFunction(localeData: CldrLocaleData, withTypes = true) {
// We use the resolved bundle for extracting the plural function. This matches with the
// lookup logic used by other extractions in the tool (using `cldrjs`), and also ensures
// we follow the CLDR-specified bundle lookup algorithm. A language does not necessarily
// resolve directly to a bundle CLDR provides data for.
const bundleName = localeData.attributes.bundle;
let fn = cldr.extractPluralRuleFunction(bundleName).toString();
const numberType = withTypes ? ': number' : '';
fn =