Skip to content

Commit

Permalink
build: generate alias locale data for closure locale (#42230)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
devversion authored and alxhub committed Jun 14, 2021
1 parent 8f24d71 commit 044e022
Show file tree
Hide file tree
Showing 6 changed files with 5,042 additions and 1,194 deletions.
6,138 changes: 4,967 additions & 1,171 deletions packages/common/locales/closure-locale.ts

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions packages/common/locales/generate-locales-tool/cldr-data.ts
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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 =
Expand Down
Expand Up @@ -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}
Expand All @@ -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 {
Expand Down
Expand Up @@ -23,7 +23,7 @@ export function generateLocale(
return `${fileHeader}
const u = undefined;
${getPluralFunction(locale)}
${getPluralFunction(localeData)}
export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)};
`;
Expand Down
Expand Up @@ -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);
`;
Expand Down
10 changes: 8 additions & 2 deletions packages/common/locales/generate-locales-tool/plural-function.ts
Expand Up @@ -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');
Expand All @@ -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 =
Expand Down

0 comments on commit 044e022

Please sign in to comment.