build: support generating global locale files from CLDR data (#33523)
In order to support adding locales during compile-time inlining of translations (i.e. after the TS build has completed), we need to be able to attach the locale to the global scope. This commit modifies CLDR extraction to emit additional "global" locale files that appear in the `@angular/common/locales/global` folder. These files are of the form: ``` (function() { const root = typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window; root.ng = root.ng || {}; root.ng.common = root.ng.common || {}; root.ng.common.locale = root.ng.common.locale || {}; const u = undefined; function plural(n) { if (n === 1) return 1; return 5; } root.ng.common.locale['xx-yy'] = [...]; })(); ``` The IIFE will ensure that `ng.common.locale` exists and attach the given locale (and its "extras") to it using it "normalized" locale name. * "extras": in the UMD module locale files the "extra" locale data, currently the day period rules, and extended day period data, are stored in separate files under the "common/locales/extra" folder. * "normalized": Angular references locales using a normalized form, which is lower case with `_` replaced by `-`. For example: `en_UK` => `en-uk`. PR Close #33523
This commit is contained in:
parent
502dd89290
commit
a3c44124ab
|
@ -12,7 +12,7 @@ ts_library(
|
||||||
|
|
||||||
npm_package(
|
npm_package(
|
||||||
name = "package",
|
name = "package",
|
||||||
srcs = ["package.json"],
|
srcs = glob(["global/*.js"]) + ["package.json"],
|
||||||
replacements = {
|
replacements = {
|
||||||
|
|
||||||
# Workaround for `.d.ts`` containing `/// <amd-module .../>`
|
# Workaround for `.d.ts`` containing `/// <amd-module .../>`
|
||||||
|
|
|
@ -25,5 +25,8 @@
|
||||||
"ng-update": {
|
"ng-update": {
|
||||||
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
|
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
|
||||||
},
|
},
|
||||||
"sideEffects": false
|
"sideEffects": [
|
||||||
|
"**/global/*.js",
|
||||||
|
"**/closure-locale.*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,15 @@ const I18N_FOLDER = `${COMMON_PACKAGE}/src/i18n`;
|
||||||
const I18N_CORE_FOLDER = `${CORE_PACKAGE}/src/i18n`;
|
const I18N_CORE_FOLDER = `${CORE_PACKAGE}/src/i18n`;
|
||||||
const I18N_DATA_FOLDER = `${COMMON_PACKAGE}/locales`;
|
const I18N_DATA_FOLDER = `${COMMON_PACKAGE}/locales`;
|
||||||
const I18N_DATA_EXTRA_FOLDER = `${I18N_DATA_FOLDER}/extra`;
|
const I18N_DATA_EXTRA_FOLDER = `${I18N_DATA_FOLDER}/extra`;
|
||||||
|
const I18N_GLOBAL_FOLDER = `${I18N_DATA_FOLDER}/global`;
|
||||||
const RELATIVE_I18N_FOLDER = path.resolve(__dirname, `../../../${I18N_FOLDER}`);
|
const RELATIVE_I18N_FOLDER = path.resolve(__dirname, `../../../${I18N_FOLDER}`);
|
||||||
const RELATIVE_I18N_CORE_FOLDER = path.resolve(__dirname, `../../../${I18N_CORE_FOLDER}`);
|
const RELATIVE_I18N_CORE_FOLDER = path.resolve(__dirname, `../../../${I18N_CORE_FOLDER}`);
|
||||||
const RELATIVE_I18N_DATA_FOLDER = path.resolve(__dirname, `../../../${I18N_DATA_FOLDER}`);
|
const RELATIVE_I18N_DATA_FOLDER = path.resolve(__dirname, `../../../${I18N_DATA_FOLDER}`);
|
||||||
const RELATIVE_I18N_DATA_EXTRA_FOLDER =
|
const RELATIVE_I18N_DATA_EXTRA_FOLDER =
|
||||||
path.resolve(__dirname, `../../../${I18N_DATA_EXTRA_FOLDER}`);
|
path.resolve(__dirname, `../../../${I18N_DATA_EXTRA_FOLDER}`);
|
||||||
const DEFAULT_RULE = 'function anonymous(n\n/*``*/) {\nreturn"other"\n}';
|
const RELATIVE_I18N_GLOBAL_FOLDER = path.resolve(__dirname, `../../../${I18N_GLOBAL_FOLDER}`);
|
||||||
const EMPTY_RULE = 'function anonymous(n\n/*``*/) {\n\n}';
|
const DEFAULT_RULE = 'function anonymous(n) {\nreturn"other"\n}';
|
||||||
|
const EMPTY_RULE = 'function anonymous(n) {\n\n}';
|
||||||
const WEEK_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
const WEEK_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||||
const HEADER = `/**
|
const HEADER = `/**
|
||||||
* @license
|
* @license
|
||||||
|
@ -58,6 +60,9 @@ module.exports = (gulp, done) => {
|
||||||
if (!fs.existsSync(RELATIVE_I18N_DATA_EXTRA_FOLDER)) {
|
if (!fs.existsSync(RELATIVE_I18N_DATA_EXTRA_FOLDER)) {
|
||||||
fs.mkdirSync(RELATIVE_I18N_DATA_EXTRA_FOLDER);
|
fs.mkdirSync(RELATIVE_I18N_DATA_EXTRA_FOLDER);
|
||||||
}
|
}
|
||||||
|
if (!fs.existsSync(RELATIVE_I18N_GLOBAL_FOLDER)) {
|
||||||
|
fs.mkdirSync(RELATIVE_I18N_GLOBAL_FOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Writing file ${I18N_FOLDER}/currencies.ts`);
|
console.log(`Writing file ${I18N_FOLDER}/currencies.ts`);
|
||||||
fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/currencies.ts`, generateCurrenciesFile());
|
fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/currencies.ts`, generateCurrenciesFile());
|
||||||
|
@ -79,6 +84,12 @@ module.exports = (gulp, done) => {
|
||||||
console.log(`\t${I18N_DATA_EXTRA_FOLDER}/${locale}.ts`);
|
console.log(`\t${I18N_DATA_EXTRA_FOLDER}/${locale}.ts`);
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`${RELATIVE_I18N_DATA_EXTRA_FOLDER}/${locale}.ts`, generateLocaleExtra(locale, localeData));
|
`${RELATIVE_I18N_DATA_EXTRA_FOLDER}/${locale}.ts`, generateLocaleExtra(locale, localeData));
|
||||||
|
console.log(`\t${I18N_GLOBAL_FOLDER}/${locale}.ts`);
|
||||||
|
fs.writeFileSync(
|
||||||
|
`${RELATIVE_I18N_GLOBAL_FOLDER}/${locale}.js`,
|
||||||
|
generateGlobalLocale(
|
||||||
|
locale, locale === 'en' ? new cldrJs('en') : localeData, baseCurrencies));
|
||||||
|
|
||||||
});
|
});
|
||||||
console.log(`${LOCALES.length} locale files generated.`);
|
console.log(`${LOCALES.length} locale files generated.`);
|
||||||
|
|
||||||
|
@ -88,8 +99,10 @@ module.exports = (gulp, done) => {
|
||||||
return gulp
|
return gulp
|
||||||
.src(
|
.src(
|
||||||
[
|
[
|
||||||
`${I18N_DATA_FOLDER}/**/*.ts`, `${I18N_FOLDER}/currencies.ts`,
|
`${I18N_DATA_FOLDER}/**/*.ts`,
|
||||||
`${I18N_CORE_FOLDER}/locale_en.ts`
|
`${I18N_FOLDER}/currencies.ts`,
|
||||||
|
`${I18N_CORE_FOLDER}/locale_en.ts`,
|
||||||
|
`${I18N_GLOBAL_FOLDER}/*.js`,
|
||||||
],
|
],
|
||||||
{base: '.'})
|
{base: '.'})
|
||||||
.pipe(format.format('file', clangFormat))
|
.pipe(format.format('file', clangFormat))
|
||||||
|
@ -97,10 +110,52 @@ module.exports = (gulp, done) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate file that contains basic locale data
|
* Generate contents for the basic locale data file
|
||||||
*/
|
*/
|
||||||
function generateLocale(locale, localeData, baseCurrencies) {
|
function generateLocale(locale, localeData, baseCurrencies) {
|
||||||
// [ localeId, dateTime, number, currency, pluralCase ]
|
return `${HEADER}
|
||||||
|
const u = undefined;
|
||||||
|
|
||||||
|
${getPluralFunction(locale)}
|
||||||
|
|
||||||
|
export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)};
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the contents for the extra data file
|
||||||
|
*/
|
||||||
|
function generateLocaleExtra(locale, localeData) {
|
||||||
|
return `${HEADER}
|
||||||
|
const u = undefined;
|
||||||
|
|
||||||
|
export default ${generateDayPeriodsSupplementalString(locale, localeData)};
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated the contents for the global locale file
|
||||||
|
*/
|
||||||
|
function generateGlobalLocale(locale, localeData, baseCurrencies) {
|
||||||
|
const basicLocaleData = generateBasicLocaleString(locale, localeData, baseCurrencies);
|
||||||
|
const extraLocaleData = generateDayPeriodsSupplementalString(locale, localeData);
|
||||||
|
const data = basicLocaleData.replace(/\]$/, `, ${extraLocaleData}]`);
|
||||||
|
return `${HEADER}
|
||||||
|
(function(global) {
|
||||||
|
global.ng = global.ng || {};
|
||||||
|
global.ng.common = global.ng.common || {};
|
||||||
|
global.ng.common.locales = global.ng.common.locales || {};
|
||||||
|
const u = undefined;
|
||||||
|
${getPluralFunction(locale, false)}
|
||||||
|
root.ng.common.locales['${normalizeLocale(locale)}'] = ${data};
|
||||||
|
})(typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window);
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect up the basic locale data [ localeId, dateTime, number, currency, pluralCase ].
|
||||||
|
*/
|
||||||
|
function generateBasicLocaleString(locale, localeData, baseCurrencies) {
|
||||||
let data =
|
let data =
|
||||||
stringify(
|
stringify(
|
||||||
[
|
[
|
||||||
|
@ -113,27 +168,18 @@ function generateLocale(locale, localeData, baseCurrencies) {
|
||||||
.replace(/undefined/g, 'u');
|
.replace(/undefined/g, 'u');
|
||||||
|
|
||||||
// adding plural function after, because we don't want it as a string
|
// adding plural function after, because we don't want it as a string
|
||||||
data = data.substring(0, data.lastIndexOf(']')) + `, plural]`;
|
data = data.replace(/\]$/, ', plural]');
|
||||||
|
return data;
|
||||||
return `${HEADER}
|
|
||||||
const u = undefined;
|
|
||||||
|
|
||||||
${getPluralFunction(locale)}
|
|
||||||
|
|
||||||
export default ${data};
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a file that contains extra data
|
* Collect up the day period rules, and extended day period data.
|
||||||
* (for now: day period rules, and extended day period data)
|
|
||||||
*/
|
*/
|
||||||
function generateLocaleExtra(locale, localeData) {
|
function generateDayPeriodsSupplementalString(locale, localeData) {
|
||||||
const dayPeriods = getDayPeriodsNoAmPm(localeData);
|
const dayPeriods = getDayPeriodsNoAmPm(localeData);
|
||||||
const dayPeriodRules = getDayPeriodRules(localeData);
|
const dayPeriodRules = getDayPeriodRules(localeData);
|
||||||
|
|
||||||
let dayPeriodsSupplemental = [];
|
let dayPeriodsSupplemental = [];
|
||||||
|
|
||||||
if (Object.keys(dayPeriods.format.narrow).length) {
|
if (Object.keys(dayPeriods.format.narrow).length) {
|
||||||
const keys = Object.keys(dayPeriods.format.narrow);
|
const keys = Object.keys(dayPeriods.format.narrow);
|
||||||
|
|
||||||
|
@ -153,15 +199,9 @@ function generateLocaleExtra(locale, localeData) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const rules = keys.map(key => dayPeriodRules[key]);
|
const rules = keys.map(key => dayPeriodRules[key]);
|
||||||
|
|
||||||
dayPeriodsSupplemental = [...removeDuplicates([dayPeriodsFormat, dayPeriodsStandalone]), rules];
|
dayPeriodsSupplemental = [...removeDuplicates([dayPeriodsFormat, dayPeriodsStandalone]), rules];
|
||||||
}
|
}
|
||||||
|
return stringify(dayPeriodsSupplemental).replace(/undefined/g, 'u');
|
||||||
return `${HEADER}
|
|
||||||
const u = undefined;
|
|
||||||
|
|
||||||
export default ${stringify(dayPeriodsSupplemental).replace(/undefined/g, 'u')};
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -512,16 +552,15 @@ function toRegExp(s) {
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
function getPluralFunction(locale) {
|
function getPluralFunction(locale, withTypes = true) {
|
||||||
let fn = cldr.extractPluralRuleFunction(locale).toString();
|
let fn = cldr.extractPluralRuleFunction(locale).toString();
|
||||||
|
|
||||||
if (fn === EMPTY_RULE) {
|
if (fn === EMPTY_RULE) {
|
||||||
fn = DEFAULT_RULE;
|
fn = DEFAULT_RULE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn = fn.replace(
|
const numberType = withTypes ? ': number' : '';
|
||||||
toRegExp('function anonymous(n\n/*``*/) {\n'),
|
fn = fn.replace(/function anonymous\(n[^}]+{/g, `function plural(n${numberType})${numberType} {`)
|
||||||
'function plural(n: number): number {\n ')
|
|
||||||
.replace(toRegExp('var'), 'let')
|
.replace(toRegExp('var'), 'let')
|
||||||
.replace(toRegExp('if(typeof n==="string")n=parseInt(n,10);'), '')
|
.replace(toRegExp('if(typeof n==="string")n=parseInt(n,10);'), '')
|
||||||
.replace(toRegExp('\n}'), ';\n}');
|
.replace(toRegExp('\n}'), ';\n}');
|
||||||
|
@ -572,6 +611,13 @@ function removeDuplicates(data) {
|
||||||
return dedup;
|
return dedup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In Angular the locale is referenced by a "normalized" form.
|
||||||
|
*/
|
||||||
|
function normalizeLocale(locale) {
|
||||||
|
return locale.toLowerCase().replace(/_/g, '-');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.I18N_FOLDER = I18N_FOLDER;
|
module.exports.I18N_FOLDER = I18N_FOLDER;
|
||||||
module.exports.I18N_DATA_FOLDER = I18N_DATA_FOLDER;
|
module.exports.I18N_DATA_FOLDER = I18N_DATA_FOLDER;
|
||||||
module.exports.RELATIVE_I18N_DATA_FOLDER = RELATIVE_I18N_DATA_FOLDER;
|
module.exports.RELATIVE_I18N_DATA_FOLDER = RELATIVE_I18N_DATA_FOLDER;
|
||||||
|
|
Loading…
Reference in New Issue