Skip to content

Commit 044e022

Browse files
devversionalxhub
authored andcommitted
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
1 parent 8f24d71 commit 044e022

File tree

6 files changed

+5042
-1194
lines changed

6 files changed

+5042
-1194
lines changed

packages/common/locales/closure-locale.ts

Lines changed: 4967 additions & 1171 deletions
Large diffs are not rendered by default.

packages/common/locales/generate-locales-tool/cldr-data.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const CLDR_DATA_GLOBS = [
3131
/** Path to the CLDR available locales file. */
3232
const CLDR_AVAILABLE_LOCALES_PATH = 'cldr-core-37.0.0/availableLocales.json';
3333

34+
/** Path to the CLDR locale aliases file. */
35+
const CLDR_LOCALE_ALIASES_PATH = 'cldr-core-37.0.0/supplemental/aliases.json';
36+
3437
/**
3538
* Instance providing access to a locale's CLDR data. This type extends the `cldrjs`
3639
* instance type with the missing `bundle` attribute property.
@@ -45,6 +48,13 @@ export type CldrLocaleData = CldrStatic&{
4548
}
4649
};
4750

51+
/**
52+
* Possible reasons for an alias in the CLDR supplemental data. See:
53+
* https://unicode.org/reports/tr35/tr35-info.html#Appendix_Supplemental_Metadata.
54+
*/
55+
export type CldrLocaleAliasReason =
56+
'deprecated'|'overlong'|'macrolanguage'|'legacy'|'bibliographic';
57+
4858
/**
4959
* Class that provides access to the CLDR data downloaded as part of
5060
* the `@cldr_data` Bazel repository.
@@ -77,6 +87,16 @@ export class CldrData {
7787
return localeData;
7888
}
7989

90+
/**
91+
* Gets the CLDR language aliases.
92+
* http://cldr.unicode.org/index/cldr-spec/language-tag-equivalences.
93+
*/
94+
getLanguageAliases():
95+
{[localeName: string]: {_reason: CldrLocaleAliasReason, _replacement: string}} {
96+
return require(`${this.cldrDataDir}/${CLDR_LOCALE_ALIASES_PATH}`)
97+
.supplemental.metadata.alias.languageAlias;
98+
}
99+
80100
/** Gets a list of all locales CLDR provides data for. */
81101
private _getAvailableLocales(): CldrLocaleData[] {
82102
const allLocales =

packages/common/locales/generate-locales-tool/closure-locale-file.ts

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,45 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {CldrStatic} from 'cldrjs';
10-
11-
import {CldrData} from './cldr-data';
9+
import {CldrData, CldrLocaleData} from './cldr-data';
1210
import {fileHeader} from './file-header';
1311
import {BaseCurrencies} from './locale-base-currencies';
1412
import {generateLocale} from './locale-file';
1513

14+
interface ClosureLocale {
15+
/** Locale name to match with a Closure-supported locale. */
16+
closureLocaleName: string;
17+
/** Locale data. Can have a different locale name if this captures an aliased locale. */
18+
data: CldrLocaleData;
19+
}
20+
1621
/**
1722
* Generate a file that contains all locale to import for closure.
1823
* Tree shaking will only keep the data for the `goog.LOCALE` locale.
1924
*/
2025
export function generateClosureLocaleFile(cldrData: CldrData, baseCurrencies: BaseCurrencies) {
21-
const locales = cldrData.availableLocales;
26+
const locales: ClosureLocale[] =
27+
[...cldrData.availableLocales.map(data => ({closureLocaleName: data.locale, data}))];
28+
const aliases = cldrData.getLanguageAliases();
2229

23-
function generateLocaleConstant(localeData: CldrStatic): string {
24-
const locale = localeData.locale;
25-
const localeNameFormattedForJs = formatLocale(locale);
26-
return generateLocale(locale, localeData, baseCurrencies)
27-
.replace(`${fileHeader}\n`, '')
28-
.replace('export default ', `export const locale_${localeNameFormattedForJs} = `)
29-
.replace('function plural', `function plural_${localeNameFormattedForJs}`)
30-
.replace(/,\s+plural/, `, plural_${localeNameFormattedForJs}`)
31-
.replace(/\s*const u = undefined;\s*/, '');
32-
}
30+
// We also generate locale data for aliases known within CLDR. Closure compiler does not
31+
// limit its locale identifiers to CLDR-canonical identifiers/or BCP47 identifiers.
32+
// To ensure deprecated/historical locale identifiers which are supported by Closure
33+
// can work with closure-compiled Angular applications, we respect CLDR locale aliases.
34+
for (const [aliasName, data] of Object.entries(aliases)) {
35+
// We skip bibliographic aliases as those have never been supported by Closure compiler.
36+
if (data._reason === 'bibliographic') {
37+
continue;
38+
}
3339

34-
function generateCase(localeName: string) {
35-
return `case '${localeName}':\n` +
36-
`l = locale_${formatLocale(localeName)};\n` +
37-
`break;\n`;
40+
const localeData = cldrData.getLocaleData(data._replacement);
41+
42+
// If CLDR does not provide data for the replacement locale, we skip this alias.
43+
if (localeData === null) {
44+
continue;
45+
}
46+
47+
locales.push({closureLocaleName: aliasName, data: localeData});
3848
}
3949

4050
return `${fileHeader}
@@ -48,12 +58,28 @@ ${locales.map(locale => `${generateLocaleConstant(locale)}`).join('\n')}
4858
let l: any;
4959
5060
switch (goog.LOCALE) {
51-
${locales.map(localeData => generateCase(localeData.locale)).join('')}}
61+
${locales.map(locale => generateCase(locale)).join('')}}
5262
5363
if (l) {
5464
registerLocaleData(l);
5565
}
5666
`;
67+
68+
function generateLocaleConstant(locale: ClosureLocale): string {
69+
const localeNameFormattedForJs = formatLocale(locale.closureLocaleName);
70+
return generateLocale(locale.closureLocaleName, locale.data, baseCurrencies)
71+
.replace(`${fileHeader}\n`, '')
72+
.replace('export default ', `export const locale_${localeNameFormattedForJs} = `)
73+
.replace('function plural', `function plural_${localeNameFormattedForJs}`)
74+
.replace(/,\s+plural/, `, plural_${localeNameFormattedForJs}`)
75+
.replace(/\s*const u = undefined;\s*/, '');
76+
}
77+
78+
function generateCase(locale: ClosureLocale) {
79+
return `case '${locale.closureLocaleName}':\n` +
80+
`l = locale_${formatLocale(locale.closureLocaleName)};\n` +
81+
`break;\n`;
82+
}
5783
}
5884

5985
function formatLocale(locale: string): string {

packages/common/locales/generate-locales-tool/locale-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function generateLocale(
2323
return `${fileHeader}
2424
const u = undefined;
2525
26-
${getPluralFunction(locale)}
26+
${getPluralFunction(localeData)}
2727
2828
export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)};
2929
`;

packages/common/locales/generate-locales-tool/locale-global-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function generateLocaleGlobalFile(
2727
global.ng.common = global.ng.common || {};
2828
global.ng.common.locales = global.ng.common.locales || {};
2929
const u = undefined;
30-
${getPluralFunction(locale, false)}
30+
${getPluralFunction(localeData, false)}
3131
global.ng.common.locales['${normalizeLocale(locale)}'] = ${data};
3232
})(typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window);
3333
`;

packages/common/locales/generate-locales-tool/plural-function.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import {CldrLocaleData} from './cldr-data';
89

910
// There are no types available for `cldr`.
1011
const cldr = require('cldr');
@@ -14,8 +15,13 @@ const cldr = require('cldr');
1415
* TODO(ocombe): replace "cldr" extractPluralRuleFunction with our own extraction using "CldrJS"
1516
* because the 2 libs can become out of sync if they use different versions of the cldr database
1617
*/
17-
export function getPluralFunction(locale: string, withTypes = true) {
18-
let fn = cldr.extractPluralRuleFunction(locale).toString();
18+
export function getPluralFunction(localeData: CldrLocaleData, withTypes = true) {
19+
// We use the resolved bundle for extracting the plural function. This matches with the
20+
// lookup logic used by other extractions in the tool (using `cldrjs`), and also ensures
21+
// we follow the CLDR-specified bundle lookup algorithm. A language does not necessarily
22+
// resolve directly to a bundle CLDR provides data for.
23+
const bundleName = localeData.attributes.bundle;
24+
let fn = cldr.extractPluralRuleFunction(bundleName).toString();
1925

2026
const numberType = withTypes ? ': number' : '';
2127
fn =

0 commit comments

Comments
 (0)