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:
parent
8f24d71142
commit
044e0229bd
File diff suppressed because it is too large
Load Diff
|
@ -31,6 +31,9 @@ const CLDR_DATA_GLOBS = [
|
||||||
/** Path to the CLDR available locales file. */
|
/** Path to the CLDR available locales file. */
|
||||||
const CLDR_AVAILABLE_LOCALES_PATH = 'cldr-core-37.0.0/availableLocales.json';
|
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 providing access to a locale's CLDR data. This type extends the `cldrjs`
|
||||||
* instance type with the missing `bundle` attribute property.
|
* 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
|
* Class that provides access to the CLDR data downloaded as part of
|
||||||
* the `@cldr_data` Bazel repository.
|
* the `@cldr_data` Bazel repository.
|
||||||
|
@ -77,6 +87,16 @@ export class CldrData {
|
||||||
return localeData;
|
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. */
|
/** Gets a list of all locales CLDR provides data for. */
|
||||||
private _getAvailableLocales(): CldrLocaleData[] {
|
private _getAvailableLocales(): CldrLocaleData[] {
|
||||||
const allLocales =
|
const allLocales =
|
||||||
|
|
|
@ -6,35 +6,45 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CldrStatic} from 'cldrjs';
|
import {CldrData, CldrLocaleData} from './cldr-data';
|
||||||
|
|
||||||
import {CldrData} from './cldr-data';
|
|
||||||
import {fileHeader} from './file-header';
|
import {fileHeader} from './file-header';
|
||||||
import {BaseCurrencies} from './locale-base-currencies';
|
import {BaseCurrencies} from './locale-base-currencies';
|
||||||
import {generateLocale} from './locale-file';
|
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.
|
* 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 = cldrData.availableLocales;
|
const locales: ClosureLocale[] =
|
||||||
|
[...cldrData.availableLocales.map(data => ({closureLocaleName: data.locale, data}))];
|
||||||
|
const aliases = cldrData.getLanguageAliases();
|
||||||
|
|
||||||
function generateLocaleConstant(localeData: CldrStatic): string {
|
// We also generate locale data for aliases known within CLDR. Closure compiler does not
|
||||||
const locale = localeData.locale;
|
// limit its locale identifiers to CLDR-canonical identifiers/or BCP47 identifiers.
|
||||||
const localeNameFormattedForJs = formatLocale(locale);
|
// To ensure deprecated/historical locale identifiers which are supported by Closure
|
||||||
return generateLocale(locale, localeData, baseCurrencies)
|
// can work with closure-compiled Angular applications, we respect CLDR locale aliases.
|
||||||
.replace(`${fileHeader}\n`, '')
|
for (const [aliasName, data] of Object.entries(aliases)) {
|
||||||
.replace('export default ', `export const locale_${localeNameFormattedForJs} = `)
|
// We skip bibliographic aliases as those have never been supported by Closure compiler.
|
||||||
.replace('function plural', `function plural_${localeNameFormattedForJs}`)
|
if (data._reason === 'bibliographic') {
|
||||||
.replace(/,\s+plural/, `, plural_${localeNameFormattedForJs}`)
|
continue;
|
||||||
.replace(/\s*const u = undefined;\s*/, '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateCase(localeName: string) {
|
const localeData = cldrData.getLocaleData(data._replacement);
|
||||||
return `case '${localeName}':\n` +
|
|
||||||
`l = locale_${formatLocale(localeName)};\n` +
|
// If CLDR does not provide data for the replacement locale, we skip this alias.
|
||||||
`break;\n`;
|
if (localeData === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
locales.push({closureLocaleName: aliasName, data: localeData});
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${fileHeader}
|
return `${fileHeader}
|
||||||
|
@ -48,12 +58,28 @@ ${locales.map(locale => `${generateLocaleConstant(locale)}`).join('\n')}
|
||||||
let l: any;
|
let l: any;
|
||||||
|
|
||||||
switch (goog.LOCALE) {
|
switch (goog.LOCALE) {
|
||||||
${locales.map(localeData => generateCase(localeData.locale)).join('')}}
|
${locales.map(locale => generateCase(locale)).join('')}}
|
||||||
|
|
||||||
if (l) {
|
if (l) {
|
||||||
registerLocaleData(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 {
|
function formatLocale(locale: string): string {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function generateLocale(
|
||||||
return `${fileHeader}
|
return `${fileHeader}
|
||||||
const u = undefined;
|
const u = undefined;
|
||||||
|
|
||||||
${getPluralFunction(locale)}
|
${getPluralFunction(localeData)}
|
||||||
|
|
||||||
export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)};
|
export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function generateLocaleGlobalFile(
|
||||||
global.ng.common = global.ng.common || {};
|
global.ng.common = global.ng.common || {};
|
||||||
global.ng.common.locales = global.ng.common.locales || {};
|
global.ng.common.locales = global.ng.common.locales || {};
|
||||||
const u = undefined;
|
const u = undefined;
|
||||||
${getPluralFunction(locale, false)}
|
${getPluralFunction(localeData, false)}
|
||||||
global.ng.common.locales['${normalizeLocale(locale)}'] = ${data};
|
global.ng.common.locales['${normalizeLocale(locale)}'] = ${data};
|
||||||
})(typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window);
|
})(typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window);
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {CldrLocaleData} from './cldr-data';
|
||||||
|
|
||||||
// There are no types available for `cldr`.
|
// There are no types available for `cldr`.
|
||||||
const cldr = require('cldr');
|
const cldr = require('cldr');
|
||||||
|
@ -14,8 +15,13 @@ const cldr = require('cldr');
|
||||||
* TODO(ocombe): replace "cldr" extractPluralRuleFunction with our own extraction using "CldrJS"
|
* 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
|
* 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) {
|
export function getPluralFunction(localeData: CldrLocaleData, withTypes = true) {
|
||||||
let fn = cldr.extractPluralRuleFunction(locale).toString();
|
// 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' : '';
|
const numberType = withTypes ? ': number' : '';
|
||||||
fn =
|
fn =
|
||||||
|
|
Loading…
Reference in New Issue