diff --git a/.gitignore b/.gitignore index 140aed39fc..7a597d9a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,8 @@ baseline.json # Ignore .history for the xyz.local-history VSCode extension .history +# CLDR data +tools/gulp-tasks/cldr/cldr-data/ + # Husky .husky/_ diff --git a/WORKSPACE b/WORKSPACE index d95a9d3b42..06b2dd27e1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -62,18 +62,3 @@ sass_repositories() load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories") skydoc_repositories() - -load("//packages/common/locales/generate-locales-tool:cldr-data.bzl", "cldr_data_repository") - -cldr_data_repository( - name = "cldr_data", - # Since we use the Github archives for CLDR 37, we need to specify a path - # to the available locales. This wouldn't be needed with CLDR 39 as that - # comes with an official JSON archive not containing a version suffix. - available_locales_path = "cldr-core-37.0.0/availableLocales.json", - urls = { - "https://github.com/unicode-cldr/cldr-core/archive/37.0.0.zip": "32b5c49c3874aa342b90412c207b42e7aefb2435295891fb714c34ce58b3c706", - "https://github.com/unicode-cldr/cldr-dates-full/archive/37.0.0.zip": "e1c410dd8ad7d75df4a5393efaf5d28f0d56c0fa126c5d66e171a3f21a988a1e", - "https://github.com/unicode-cldr/cldr-numbers-full/archive/37.0.0.zip": "a921b90cf7f436e63fbdd55880f96e39a203acd9e174b0ceafa20a02c242a12e", - }, -) diff --git a/gulpfile.js b/gulpfile.js index 96c7d95b92..e4779fe862 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,3 +20,6 @@ function loadTask(fileName, taskName) { gulp.task('source-map-test', loadTask('source-map-test')); gulp.task('changelog:zonejs', loadTask('changelog-zonejs')); +gulp.task('cldr:extract', loadTask('cldr', 'extract')); +gulp.task('cldr:download', loadTask('cldr', 'download')); +gulp.task('cldr:gen-closure-locale', loadTask('cldr', 'closure')); diff --git a/package.json b/package.json index 75b5652773..e0a7d32912 100644 --- a/package.json +++ b/package.json @@ -164,7 +164,6 @@ "@bazel/buildifier": "^4.0.1", "@bazel/ibazel": "^0.15.8", "@octokit/graphql": "^4.6.1", - "@types/cldrjs": "^0.4.22", "@types/cli-progress": "^3.4.2", "@types/conventional-commits-parser": "^3.0.1", "@types/ejs": "^3.0.6", @@ -174,7 +173,8 @@ "browserstacktunnel-wrapper": "^2.0.4", "check-side-effects": "0.0.23", "clang-format": "^1.4.0", - "cldr": "5.7.0", + "cldr": "7.0.0", + "cldr-data-downloader": "^0.3.5", "cldrjs": "0.5.5", "cli-progress": "^3.7.0", "conventional-changelog": "^3.1.24", @@ -218,5 +218,6 @@ "@babel/template": "7.8.6", "@babel/traverse": "7.8.6", "@babel/types": "7.8.6" - } + }, + "cldr-data-coverage": "full" } diff --git a/packages/common/locales/generate-locales-tool/BUILD.bazel b/packages/common/locales/generate-locales-tool/BUILD.bazel deleted file mode 100644 index c795b94d5a..0000000000 --- a/packages/common/locales/generate-locales-tool/BUILD.bazel +++ /dev/null @@ -1,17 +0,0 @@ -load("//tools:defaults.bzl", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -ts_library( - name = "generate-locales-tool", - srcs = glob(["*.ts"]), - deps = [ - "@npm//@bazel/runfiles", - "@npm//@types/cldrjs", - "@npm//@types/glob", - "@npm//@types/node", - "@npm//cldr", - "@npm//cldrjs", - "@npm//glob", - ], -) diff --git a/packages/common/locales/generate-locales-tool/array-deduplication.ts b/packages/common/locales/generate-locales-tool/array-deduplication.ts deleted file mode 100644 index 03a7cb4b43..0000000000 --- a/packages/common/locales/generate-locales-tool/array-deduplication.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 - */ - -/** - * To create smaller locale files, we remove duplicated data. - * To make this work we store the data in arrays, where `undefined` indicates that the - * value is a duplicate of the previous value in the array. - * e.g. consider an array like: [x, y, undefined, z, undefined, undefined] - * The first `undefined` is equivalent to y, the second and third are equivalent to z - * Note that the first value in an array is always defined. - * - * Also since we need to know which data is assumed similar, it is important that we store those - * similar data in arrays to mark the delimitation between values that have different meanings - * (e.g. months and days). - * - * For further size improvements, "undefined" values will be replaced by a constant in the arrays - * as the last step of the file generation (in generateLocale and generateLocaleExtra). - * e.g.: [x, y, undefined, z, undefined, undefined] will be [x, y, u, z, u, u] - */ -export function removeDuplicates(data: unknown[]) { - const dedup = [data[0]]; - for (let i = 1; i < data.length; i++) { - if (JSON.stringify(data[i]) !== JSON.stringify(data[i - 1])) { - dedup.push(data[i]); - } else { - dedup.push(undefined); - } - } - return dedup; -} diff --git a/packages/common/locales/generate-locales-tool/bin/BUILD.bazel b/packages/common/locales/generate-locales-tool/bin/BUILD.bazel deleted file mode 100644 index 302190b3f1..0000000000 --- a/packages/common/locales/generate-locales-tool/bin/BUILD.bazel +++ /dev/null @@ -1,33 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") -load("//tools:defaults.bzl", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -BIN_ENTRYPOINTS = [ - "get-base-currencies-file", - "get-base-locale-file", - "get-closure-locale-file", - "write-locale-files-to-dist", -] - -ts_library( - name = "bin", - srcs = glob(["*.ts"]), - deps = [ - "//packages/common/locales/generate-locales-tool", - "@npm//@types/node", - ], -) - -[nodejs_binary( - name = entrypoint, - data = [ - ":bin", - "@cldr_data//:all_json", - ], - entry_point = ":%s.ts" % entrypoint, - # We need to patch the NodeJS module resolution as this binary runs as - # part of a genrule where the linker does not work as expected. - # See: https://github.com/bazelbuild/rules_nodejs/issues/2600. - templated_args = ["--bazel_patch_module_resolver"], -) for entrypoint in BIN_ENTRYPOINTS] diff --git a/packages/common/locales/generate-locales-tool/bin/base-locale.ts b/packages/common/locales/generate-locales-tool/bin/base-locale.ts deleted file mode 100644 index 8398497afc..0000000000 --- a/packages/common/locales/generate-locales-tool/bin/base-locale.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 - */ - - -/** - * Base locale used as foundation for other locales. For example: A base locale allows - * generation of a file containing all currencies with their corresponding symbols. If we - * generate other locales, they can override currency symbols which are different in the base - * locale. This means that we do not need re-generate all currencies w/ symbols multiple times, - * and allows us to reduce the locale data payload as the base locale is always included. - * */ -export const BASE_LOCALE = 'en'; diff --git a/packages/common/locales/generate-locales-tool/bin/get-base-currencies-file.ts b/packages/common/locales/generate-locales-tool/bin/get-base-currencies-file.ts deleted file mode 100644 index eb11e294ed..0000000000 --- a/packages/common/locales/generate-locales-tool/bin/get-base-currencies-file.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrData} from '../cldr-data'; -import {generateBaseCurrenciesFile} from '../locale-base-currencies'; - -import {BASE_LOCALE} from './base-locale'; - -/** Generates the base currencies file and prints it to the stdout. */ -function main() { - const cldrData = new CldrData(); - const baseLocaleData = cldrData.getLocaleData(BASE_LOCALE); - - process.stdout.write(generateBaseCurrenciesFile(baseLocaleData)); -} - -if (require.main === module) { - main(); -} diff --git a/packages/common/locales/generate-locales-tool/bin/get-base-locale-file.ts b/packages/common/locales/generate-locales-tool/bin/get-base-locale-file.ts deleted file mode 100644 index 7c6b3f5d2e..0000000000 --- a/packages/common/locales/generate-locales-tool/bin/get-base-locale-file.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrData} from '../cldr-data'; -import {generateBaseCurrencies} from '../locale-base-currencies'; -import {generateLocale} from '../locale-file'; - -import {BASE_LOCALE} from './base-locale'; - -/** Generates the base locale file and prints it to the stdout. */ -function main() { - const cldrData = new CldrData(); - const baseLocaleData = cldrData.getLocaleData(BASE_LOCALE); - const baseCurrencies = generateBaseCurrencies(baseLocaleData); - - process.stdout.write(generateLocale(BASE_LOCALE, baseLocaleData, baseCurrencies)); -} - -if (require.main === module) { - main(); -} diff --git a/packages/common/locales/generate-locales-tool/bin/get-closure-locale-file.ts b/packages/common/locales/generate-locales-tool/bin/get-closure-locale-file.ts deleted file mode 100644 index 40be70e5da..0000000000 --- a/packages/common/locales/generate-locales-tool/bin/get-closure-locale-file.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrData} from '../cldr-data'; -import {generateClosureLocaleFile} from '../closure-locale-file'; -import {generateBaseCurrencies} from '../locale-base-currencies'; - -import {BASE_LOCALE} from './base-locale'; - -/** Generates the Google3 closure-locale file and prints it to the stdout. */ -function main() { - const cldrData = new CldrData(); - const baseLocaleData = cldrData.getLocaleData(BASE_LOCALE); - const baseCurrencies = generateBaseCurrencies(baseLocaleData); - - process.stdout.write(generateClosureLocaleFile(cldrData, baseCurrencies)); -} - -if (require.main === module) { - main(); -} diff --git a/packages/common/locales/generate-locales-tool/bin/write-locale-files-to-dist.ts b/packages/common/locales/generate-locales-tool/bin/write-locale-files-to-dist.ts deleted file mode 100644 index eab8a7a1fc..0000000000 --- a/packages/common/locales/generate-locales-tool/bin/write-locale-files-to-dist.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {writeFileSync} from 'fs'; -import {join} from 'path'; - -import {CldrData} from '../cldr-data'; -import {generateBaseCurrencies} from '../locale-base-currencies'; -import {generateLocaleExtra} from '../locale-extra-file'; -import {generateLocale} from '../locale-file'; -import {generateLocaleGlobalFile} from '../locale-global-file'; - -import {BASE_LOCALE} from './base-locale'; - -/** - * Generates locale files for each available CLDR locale and writes it to the - * specified directory. - */ -function main(outputDir: string) { - const cldrData = new CldrData(); - const baseLocaleData = cldrData.getLocaleData(BASE_LOCALE); - const baseCurrencies = generateBaseCurrencies(baseLocaleData); - const extraLocaleDir = join(outputDir, 'extra'); - const globalLocaleDir = join(outputDir, 'global'); - - console.info(`Writing locales to: ${outputDir}`); - - // Generate locale files for all locales we have data for. - cldrData.availableLocales.forEach((locale: string) => { - const localeData = cldrData.getLocaleData(locale); - - // If `cldrjs` is unable to resolve a `bundle` for the current locale, then there is no data - // for this locale, and it should not be generated. This can happen as with older versions of - // CLDR where `availableLocales.json` specifies locales for which no data is available - // (even within the `full` tier packages). See: - // http://cldr.unicode.org/development/development-process/design-proposals/json-packaging. - // TODO(devversion): Remove if we update to CLDR v39 where this seems fixed. Note that this - // worked before in the Gulp tooling without such a check because the `cldr-data-downloader` - // overwrote the `availableLocales` to only capture locales with data. - if (localeData && !(localeData.attributes as any).bundle) { - console.info(`Skipping generation of ${locale} as there is no data.`); - return; - } - - const localeFile = generateLocale(locale, localeData, baseCurrencies); - const localeExtraFile = generateLocaleExtra(locale, localeData); - const localeGlobalFile = generateLocaleGlobalFile(locale, localeData, baseCurrencies); - - writeFileSync(join(outputDir, `${locale}.ts`), localeFile); - writeFileSync(join(extraLocaleDir, `${locale}.ts`), localeExtraFile); - writeFileSync(join(globalLocaleDir, `${locale}.js`), localeGlobalFile); - }); -} - - -if (require.main === module) { - // The first argument is expected to be a path resolving to a directory - // where all locales should be generated into. - const outputDir = process.argv[2]; - - if (outputDir === undefined) { - throw Error('No output directory specified.'); - } - - main(outputDir); -} diff --git a/packages/common/locales/generate-locales-tool/cldr-data.bzl b/packages/common/locales/generate-locales-tool/cldr-data.bzl deleted file mode 100644 index 3346c118d8..0000000000 --- a/packages/common/locales/generate-locales-tool/cldr-data.bzl +++ /dev/null @@ -1,48 +0,0 @@ -def _cldr_data_repository_impl(ctx): - for url, sha256 in ctx.attr.urls.items(): - ctx.report_progress("Downloading CLDR data from: %s" % url) - ctx.download_and_extract( - url = url, - sha256 = sha256, - ) - - ctx.report_progress("Extracting available locales from: %s" % ctx.attr.available_locales_path) - locales_json = ctx.read(ctx.attr.available_locales_path) - locales = json.decode(locales_json)["availableLocales"]["full"] - ctx.report_progress("Extracted %s locales from CLDR" % len(locales)) - - ctx.file("index.bzl", content = """ -LOCALES=%s - """ % locales) - - ctx.file("BUILD.bazel", content = """ -filegroup( - name = "all_json", - srcs = glob(["**/*.json"]), - visibility = ["//visibility:public"], -) - """) - -""" - Repository rule that downloads CLDR data from the specified repository and generates a - `BUILD.bazel` file that exposes all data files. Additionally, an `index.bzl` file is generated - that exposes a constant for all locales the repository contains data for. This can be used to - generate pre-declared outputs. -""" -cldr_data_repository = repository_rule( - implementation = _cldr_data_repository_impl, - attrs = { - "urls": attr.string_dict(doc = """ - Dictionary of URLs that resolve to archives containing CLDR JSON data. These archives - will be downloaded and extracted at root of the repository. Each key can specify - a SHA256 checksum for hermetic builds. - """, mandatory = True), - "available_locales_path": attr.string( - doc = """ - Relative path to the JSON data file describing all available locales. - This file usually resides within the `cldr-core` package - """, - default = "cldr-core/availableLocales.json", - ), - }, -) diff --git a/packages/common/locales/generate-locales-tool/cldr-data.ts b/packages/common/locales/generate-locales-tool/cldr-data.ts deleted file mode 100644 index 9da8b77c5b..0000000000 --- a/packages/common/locales/generate-locales-tool/cldr-data.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {runfiles} from '@bazel/runfiles'; -import {CldrStatic} from 'cldrjs'; -import {sync as globSync} from 'glob'; - -// TypeScript doesn't allow us to import the default export without the `esModuleInterop`. We use -// the NodeJS require function instead as specifying a custom tsconfig complicates the setup -// unnecessarily. -// TODO: See if we can improve this by having better types for `cldrjs`. -const cldrjs: typeof import('cldrjs') = require('cldrjs'); - -/** - * Globs that match CLDR JSON data files that should be fetched. We limit these intentionally - * as loading unused data results in significant slow-down of the generation - * (noticeable in local development if locale data is re-generated). - */ -const CLDR_DATA_GLOBS = [ - 'cldr-core-37.0.0/scriptMetadata.json', - 'cldr-core-37.0.0/supplemental/**/*.json', - 'cldr-dates-full-37.0.0/main/**/*.json', - 'cldr-numbers-full-37.0.0/main/**/*.json', -]; - -/** Path to the CLDR available locales file. */ -const CLDR_AVAILABLE_LOCALES_PATH = 'cldr-core-37.0.0/availableLocales.json'; - -/** - * Class that provides access to the CLDR data downloaded as part of - * the `@cldr_data` Bazel repository. - */ -export class CldrData { - /** Path to the CLDR data Bazel repository. i.e. `@cldr_data//`. */ - cldrDataDir = runfiles.resolve('cldr_data'); - - /** List of all available locales CLDR provides data for. */ - availableLocales: string[]; - - constructor() { - this._loadAndPopulateCldrData(); - this.availableLocales = this._getAvailableLocales(); - } - - /** Gets the CLDR data for the specified locale. */ - getLocaleData(localeName: string): CldrStatic { - return new cldrjs(localeName); - } - - /** Gets a list of all locales CLDR provides data for. */ - private _getAvailableLocales(): string[] { - return require(`${this.cldrDataDir}/${CLDR_AVAILABLE_LOCALES_PATH}`).availableLocales.full; - } - - /** Loads the CLDR data and populates the `cldrjs` library with it. */ - private _loadAndPopulateCldrData() { - const localeData = this._readCldrDataFromRepository(); - - if (localeData.length === 0) { - throw Error('No CLDR data could be found.'); - } - - // Populate the `cldrjs` library with the locale data. Note that we need this type cast - // to satisfy the first `cldrjs.load` parameter which cannot be undefined. - cldrjs.load(...localeData as [object, ...object[]]); - } - - /** - * Reads the CLDR JSON data from the Bazel repository. - * @returns a list of read JSON objects representing the CLDR data. - */ - private _readCldrDataFromRepository(): object[] { - const jsonFiles = - CLDR_DATA_GLOBS.map(pattern => globSync(pattern, {cwd: this.cldrDataDir, absolute: true})) - .reduce((acc, dataFiles) => [...acc, ...dataFiles], []); - - // Read the JSON for all determined CLDR json files. - return jsonFiles.map(filePath => { - const parsed = require(filePath); - - // Guards against cases where non-CLDR data files are accidentally picked up - // by the glob above and would throw-off the bundle lookup in `cldrjs`. - if (parsed.main !== undefined && typeof parsed.main !== 'object') { - throw Error('Unexpected CLDR json file with "main" field which is not an object.'); - } - - return parsed; - }); - } -} diff --git a/packages/common/locales/generate-locales-tool/closure-locale-file.ts b/packages/common/locales/generate-locales-tool/closure-locale-file.ts deleted file mode 100644 index 810b960a6f..0000000000 --- a/packages/common/locales/generate-locales-tool/closure-locale-file.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrData} from './cldr-data'; -import {fileHeader} from './file-header'; -import {BaseCurrencies} from './locale-base-currencies'; -import {generateLocale} from './locale-file'; - -/** - * List of locales used by Closure. These locales will be incorporated in the generated - * closure locale file. See: - * https://github.com/google/closure-library/blob/master/closure/goog/i18n/datetimepatterns.js#L2450 - */ -const GOOG_LOCALES = [ - 'af', 'am', 'ar', 'ar-DZ', 'az', 'be', 'bg', 'bn', 'br', 'bs', - 'ca', 'chr', 'cs', 'cy', 'da', 'de', 'de-AT', 'de-CH', 'el', 'en-AU', - 'en-CA', 'en-GB', 'en-IE', 'en-IN', 'en-SG', 'en-ZA', 'es', 'es-419', 'es-MX', 'es-US', - 'et', 'eu', 'fa', 'fi', 'fr', 'fr-CA', 'ga', 'gl', 'gsw', 'gu', - 'haw', 'hi', 'hr', 'hu', 'hy', 'in', 'is', 'it', 'iw', 'ja', - 'ka', 'kk', 'km', 'kn', 'ko', 'ky', 'ln', 'lo', 'lt', 'lv', - 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'ne', 'nl', - 'no', 'or', 'pa', 'pl', 'pt', 'pt-PT', 'ro', 'ru', 'sh', 'si', - 'sk', 'sl', 'sq', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tl', - 'tr', 'uk', 'ur', 'uz', 'vi', 'zh', 'zh-CN', 'zh-HK', 'zh-TW', 'zu' -]; - -export function generateClosureLocaleFile( - cldrData: CldrData, baseCurrencies: BaseCurrencies): string { - // locale id aliases to support deprecated locale ids used by closure - // it maps deprecated ids --> new ids - // manually extracted from ./cldr-data/supplemental/aliases.json - // TODO: Consider extracting directly from the CLDR data instead. - const aliases = { - 'in': 'id', - 'iw': 'he', - 'mo': 'ro-MD', - 'no': 'nb', - 'nb': 'no-NO', - 'sh': 'sr-Latn', - 'tl': 'fil', - 'pt': 'pt-BR', - 'zh-CN': 'zh-Hans-CN', - 'zh-Hans-CN': 'zh-Hans', - 'zh-HK': 'zh-Hant-HK', - 'zh-TW': 'zh-Hant-TW', - 'zh-Hant-TW': 'zh-Hant', - }; - - return generateAllLocalesFile(cldrData, GOOG_LOCALES, aliases, baseCurrencies); -} - -/** - * Generate a file that contains all locale to import for closure. - * Tree shaking will only keep the data for the `goog.LOCALE` locale. - */ -function generateAllLocalesFile( - cldrData: CldrData, locales: string[], aliases: {[name: string]: string}, - baseCurrencies: BaseCurrencies) { - const existingLocalesAliases: {[locale: string]: Set} = {}; - const existingLocalesData: {[locale: string]: string} = {}; - - // for each locale, get the data and the list of equivalent locales - locales.forEach(locale => { - const eqLocales = new Set(); - eqLocales.add(locale); - if (locale.match(/-/)) { - eqLocales.add(locale.replace(/-/g, '_')); - } - - // check for aliases - const alias = aliases[locale]; - if (alias) { - eqLocales.add(alias); - - if (alias.match(/-/)) { - eqLocales.add(alias.replace(/-/g, '_')); - } - - // to avoid duplicated "case" we regroup all locales in the same "case" - // the simplest way to do that is to have alias aliases - // e.g. 'no' --> 'nb', 'nb' --> 'no-NO' - // which means that we'll have 'no', 'nb' and 'no-NO' in the same "case" - const aliasKeys = Object.keys(aliases); - for (let i = 0; i < aliasKeys.length; i++) { - const aliasValue = aliases[alias]; - if (aliasKeys.indexOf(alias) !== -1 && !eqLocales.has(aliasValue)) { - eqLocales.add(aliasValue); - - if (aliasValue.match(/-/)) { - eqLocales.add(aliasValue.replace(/-/g, '_')); - } - } - } - } - - const localeNameForData = aliases[locale] ?? locale; - const localeData = cldrData.getLocaleData(localeNameForData); - const localeName = formatLocale(locale); - existingLocalesData[locale] = - generateLocale(localeNameForData, localeData, baseCurrencies) - .replace(`${fileHeader}\n`, '') - .replace('export default ', `export const locale_${localeName} = `) - .replace('function plural', `function plural_${localeName}`) - .replace(/,\s+plural/, `, plural_${localeName}`) - .replace(/\s*const u = undefined;\s*/, ''); - - existingLocalesAliases[locale] = eqLocales; - }); - - function generateCases(locale: string) { - let str = ''; - let locales: string[] = []; - const eqLocales = existingLocalesAliases[locale]; - eqLocales.forEach(l => { - str += `case '${l}':\n`; - locales.push(`'${l}'`); - }); - let localesStr = '[' + locales.join(',') + ']'; - - str += ` l = locale_${formatLocale(locale)}; - locales = ${localesStr}; - break;\n`; - return str; - } - - return `${fileHeader} - -import {registerLocaleData} from '../src/i18n/locale_data'; - -const u = undefined; - -${locales.map(locale => `${existingLocalesData[locale]}`).join('\n')} - -let l: any; -let locales: string[] = []; - -switch (goog.LOCALE) { -${locales.map(locale => generateCases(locale)).join('')}} - -if(l) { - locales.forEach(locale => registerLocaleData(l, locale)); -} -`; -} - -function formatLocale(locale: string): string { - return locale.replace(/-/g, '_'); -} diff --git a/packages/common/locales/generate-locales-tool/day-periods.ts b/packages/common/locales/generate-locales-tool/day-periods.ts deleted file mode 100644 index 0294cadd61..0000000000 --- a/packages/common/locales/generate-locales-tool/day-periods.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrStatic} from 'cldrjs'; - -/** - * Returns data for the chosen day periods - */ -export function getDayPeriods(localeData: CldrStatic, dayPeriodsList: string[]): { - format: {narrow: string[], abbreviated: string[], wide: string[]}, - 'stand-alone': {narrow: string[], abbreviated: string[], wide: string[]} -} { - const dayPeriods = localeData.main(`dates/calendars/gregorian/dayPeriods`); - const result: any = {}; - // cleaning up unused keys - Object.keys(dayPeriods).forEach(key1 => { // format / stand-alone - result[key1] = {}; - Object.keys(dayPeriods[key1]).forEach(key2 => { // narrow / abbreviated / wide - result[key1][key2] = {}; - Object.keys(dayPeriods[key1][key2]).forEach(key3 => { - if (dayPeriodsList.indexOf(key3) !== -1) { - result[key1][key2][key3] = dayPeriods[key1][key2][key3]; - } - }); - }); - }); - - return result as any; -} - - -/** - * Returns day period rules for a locale - * @returns string[] - */ -export function getDayPeriodRules( - localeData: CldrStatic, language = localeData.attributes.language): {[key: string]: []} { - const dayPeriodRules = localeData.get(`supplemental/dayPeriodRuleSet/${language}`); - const rules: any = {}; - if (dayPeriodRules) { - Object.keys(dayPeriodRules).forEach(key => { - if (dayPeriodRules[key]._at) { - rules[key] = dayPeriodRules[key]._at; - } else { - rules[key] = [dayPeriodRules[key]._from, dayPeriodRules[key]._before]; - } - }); - } - - // Implements inheritance for the day periods. As of CLDR v39, the `nn` locale - // for example fully inherits from `no`. - // http://cldr.unicode.org/index/downloads/cldr-39?pli=1#TOC-Migration. - if (dayPeriodRules === undefined) { - const parentLocale = localeData.get(`supplemental/parentLocales/parentLocale/${language}`); - - if (parentLocale !== undefined) { - return getDayPeriodRules(localeData, parentLocale); - } - } - - return rules; -} - - -/** - * Returns the basic day periods (am/pm) - */ -export function getDayPeriodsAmPm(localeData: CldrStatic) { - return getDayPeriods(localeData, ['am', 'pm']); -} - -/** - * Returns the extra day periods (without am/pm) - */ -export function getDayPeriodsNoAmPm(localeData: CldrStatic) { - return getDayPeriods(localeData, [ - 'noon', 'midnight', 'morning1', 'morning2', 'afternoon1', 'afternoon2', 'evening1', 'evening2', - 'night1', 'night2' - ]); -} diff --git a/packages/common/locales/generate-locales-tool/file-header.ts b/packages/common/locales/generate-locales-tool/file-header.ts deleted file mode 100644 index a631cb1cf0..0000000000 --- a/packages/common/locales/generate-locales-tool/file-header.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 - */ - -export const fileHeader = `/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 - */ - -// THIS CODE IS GENERATED - DO NOT MODIFY.`; diff --git a/packages/common/locales/generate-locales-tool/locale-base-currencies.ts b/packages/common/locales/generate-locales-tool/locale-base-currencies.ts deleted file mode 100644 index 91edc9aa12..0000000000 --- a/packages/common/locales/generate-locales-tool/locale-base-currencies.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrStatic} from 'cldrjs'; - -import {fileHeader} from './file-header'; -import {stringify} from './object-stringify'; - -export type BaseCurrencySymbols = [ - string -]|[string | undefined, string]|[string, undefined, number]|[string | undefined, string, number]; - -export type BaseCurrencies = { - [code: string]: BaseCurrencySymbols|undefined; -}; - -/** - * Generate a file that contains the list of currencies, their symbols and digits. - */ -export function generateBaseCurrenciesFile(baseLocaleData: CldrStatic) { - const baseCurrencies = generateBaseCurrencies(baseLocaleData); - - return `${fileHeader} -export type CurrenciesSymbols = [string] | [string | undefined, string]; - -/** @internal */ -export const CURRENCIES_EN: {[code: string]: CurrenciesSymbols | [string | undefined, string | undefined, number]} = ${ - stringify(baseCurrencies)}; -`; -} - -/** - * Generate a list of currencies to be used as a base for other currencies - * e.g.: {'ARS': [, '$'], 'AUD': ['A$', '$'], ...} - */ -export function generateBaseCurrencies(localeData: CldrStatic) { - const currenciesData = localeData.main('numbers/currencies'); - const fractions = localeData.get(`supplemental/currencyData/fractions`); - const currencies: BaseCurrencies = {}; - - Object.keys(currenciesData).forEach(key => { - let symbolsArray = []; - const symbol = currenciesData[key].symbol; - const symbolNarrow = currenciesData[key]['symbol-alt-narrow']; - if (symbol && symbol !== key) { - symbolsArray.push(symbol); - } - if (symbolNarrow && symbolNarrow !== symbol) { - if (symbolsArray.length > 0) { - symbolsArray.push(symbolNarrow); - } else { - symbolsArray = [undefined, symbolNarrow]; - } - } - if (fractions[key] && fractions[key]['_digits']) { - const digits = parseInt(fractions[key]['_digits'], 10); - if (symbolsArray.length === 2) { - symbolsArray.push(digits); - } else if (symbolsArray.length === 1) { - symbolsArray = [...symbolsArray, undefined, digits]; - } else { - symbolsArray = [undefined, undefined, digits]; - } - } - if (symbolsArray.length > 0) { - currencies[key] = symbolsArray as BaseCurrencySymbols; - } - }); - return currencies; -} diff --git a/packages/common/locales/generate-locales-tool/locale-currencies.ts b/packages/common/locales/generate-locales-tool/locale-currencies.ts deleted file mode 100644 index ede37535a2..0000000000 --- a/packages/common/locales/generate-locales-tool/locale-currencies.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrStatic} from 'cldrjs'; -import {BaseCurrencies} from './locale-base-currencies'; - -/** - * To minimize the file even more, we only output the differences compared to the base currency - */ -export function generateLocaleCurrencies(localeData: CldrStatic, baseCurrencies: BaseCurrencies) { - const currenciesData = localeData.main('numbers/currencies'); - const currencies: any = {}; - - Object.keys(currenciesData).forEach(code => { - let symbolsArray = []; - const symbol = currenciesData[code].symbol; - const symbolNarrow = currenciesData[code]['symbol-alt-narrow']; - if (symbol && symbol !== code) { - symbolsArray.push(symbol); - } - if (symbolNarrow && symbolNarrow !== symbol) { - if (symbolsArray.length > 0) { - symbolsArray.push(symbolNarrow); - } else { - symbolsArray = [undefined, symbolNarrow]; - } - } - - const baseCurrencySymbols = baseCurrencies[code] || []; - - // Jf locale data is equal to the one in the base currencies, skip this currency to - // avoid unnecessary locale data that could be inferred from the base currency. - if (baseCurrencySymbols && baseCurrencySymbols[0] === symbolsArray[0] && - baseCurrencySymbols[1] === symbolsArray[1]) { - return; - } - - currencies[code] = symbolsArray; - }); - return currencies; -} - -/** - * Returns the currency code, symbol and name for a locale - */ -export function getCurrencySettings(localeName: string, localeData: CldrStatic) { - const currencyInfo = localeData.main(`numbers/currencies`); - let currentCurrency = ''; - - // find the currency currently used in this country - const currencies: any[] = - localeData.get(`supplemental/currencyData/region/${localeData.attributes.territory}`) || - localeData.get( - `supplemental/currencyData/region/${localeData.attributes.language.toUpperCase()}`); - - if (currencies) { - currencies.some(currency => { - const keys = Object.keys(currency); - return keys.some(key => { - if (currency[key]._from && !currency[key]._to) { - return currentCurrency = key; - } - }); - }); - - if (!currentCurrency) { - throw new Error(`Unable to find currency for locale "${localeName}"`); - } - } - - let currencySettings = [undefined, undefined, undefined]; - - if (currentCurrency) { - currencySettings = [ - currentCurrency, currencyInfo[currentCurrency].symbol, - currencyInfo[currentCurrency].displayName - ]; - } - - return currencySettings; -} diff --git a/packages/common/locales/generate-locales-tool/locale-extra-file.ts b/packages/common/locales/generate-locales-tool/locale-extra-file.ts deleted file mode 100644 index b2b9fafe5d..0000000000 --- a/packages/common/locales/generate-locales-tool/locale-extra-file.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrStatic} from 'cldrjs'; - -import {removeDuplicates} from './array-deduplication'; -import {getDayPeriodRules, getDayPeriodsNoAmPm} from './day-periods'; -import {fileHeader} from './file-header'; -import {stringify} from './object-stringify'; - -/** - * Generate the contents for the extra data file - */ -export function generateLocaleExtra(locale: string, localeData: CldrStatic) { - return `${fileHeader} -const u = undefined; - -export default ${generateDayPeriodsSupplementalString(locale, localeData)}; -`; -} - - -/** - * Collect up the day period rules, and extended day period data. - */ -export function generateDayPeriodsSupplementalString(locale: string, localeData: CldrStatic) { - const dayPeriods = getDayPeriodsNoAmPm(localeData); - const dayPeriodRules = getDayPeriodRules(localeData); - - let dayPeriodsSupplemental: any[] = []; - if (Object.keys(dayPeriods.format.narrow).length) { - const keys = Object.keys(dayPeriods.format.narrow); - - if (keys.length !== Object.keys(dayPeriodRules).length) { - throw new Error(`Error: locale ${locale} has not the correct number of day period rules`); - } - - const dayPeriodsFormat = removeDuplicates([ - Object.values(dayPeriods.format.narrow), Object.values(dayPeriods.format.abbreviated), - Object.values(dayPeriods.format.wide) - ]); - - const dayPeriodsStandalone = removeDuplicates([ - Object.values(dayPeriods['stand-alone'].narrow), - Object.values(dayPeriods['stand-alone'].abbreviated), - Object.values(dayPeriods['stand-alone'].wide) - ]); - - const rules = keys.map(key => dayPeriodRules[key]); - dayPeriodsSupplemental = [...removeDuplicates([dayPeriodsFormat, dayPeriodsStandalone]), rules]; - } - return stringify(dayPeriodsSupplemental).replace(/undefined/g, 'u'); -} diff --git a/packages/common/locales/generate-locales-tool/locale-file.ts b/packages/common/locales/generate-locales-tool/locale-file.ts deleted file mode 100644 index 20d71df416..0000000000 --- a/packages/common/locales/generate-locales-tool/locale-file.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrStatic} from 'cldrjs'; - -import {removeDuplicates} from './array-deduplication'; -import {getDayPeriodsAmPm} from './day-periods'; -import {fileHeader} from './file-header'; -import {BaseCurrencies} from './locale-base-currencies'; -import {generateLocaleCurrencies, getCurrencySettings} from './locale-currencies'; -import {stringify} from './object-stringify'; -import {getPluralFunction} from './plural-function'; - -const WEEK_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; - -/** Generate contents for the basic locale data file */ -export function generateLocale( - locale: string, localeData: CldrStatic, baseCurrencies: BaseCurrencies) { - return `${fileHeader} -const u = undefined; - -${getPluralFunction(locale)} - -export default ${generateBasicLocaleString(locale, localeData, baseCurrencies)}; -`; -} - - -/** - * Collect up the basic locale data [ localeId, dateTime, number, currency, directionality, - * pluralCase ]. - */ -export function generateBasicLocaleString( - locale: string, localeData: CldrStatic, baseCurrencies: BaseCurrencies) { - let data = stringify([ - locale, - ...getDateTimeTranslations(localeData), - ...getDateTimeSettings(localeData), - ...getNumberSettings(localeData), - ...getCurrencySettings(locale, localeData), - generateLocaleCurrencies(localeData, baseCurrencies), - getDirectionality(localeData), - ]) - // We remove "undefined" added by spreading arrays when there is no value - .replace(/undefined/g, 'u'); - - // adding plural function after, because we don't want it as a string. The function named `plural` - // is expected to be available in the file. See `generateLocale` above. - data = data.replace(/\]$/, ', plural]'); - return data; -} - -/** - * Returns the writing direction for a locale - * @returns 'rtl' | 'ltr' - */ -function getDirectionality(localeData: CldrStatic): 'rtl'|'ltr' { - const rtl = localeData.get('scriptMetadata/{script}/rtl'); - return rtl === 'YES' ? 'rtl' : 'ltr'; -} - - -/** - * Returns dateTime data for a locale - * @returns [ firstDayOfWeek, weekendRange, formats ] - */ -function getDateTimeSettings(localeData: CldrStatic) { - return [ - getFirstDayOfWeek(localeData), getWeekendRange(localeData), ...getDateTimeFormats(localeData) - ]; -} - - - -/** - * Returns the number symbols and formats for a locale - * @returns [ symbols, formats ] - * symbols: [ decimal, group, list, percentSign, plusSign, minusSign, exponential, - * superscriptingExponent, perMille, infinity, nan, timeSeparator, currencyDecimal?, currencyGroup? - * ] - * formats: [ currency, decimal, percent, scientific ] - */ -function getNumberSettings(localeData: CldrStatic) { - const decimalFormat = localeData.main('numbers/decimalFormats-numberSystem-latn/standard'); - const percentFormat = localeData.main('numbers/percentFormats-numberSystem-latn/standard'); - const scientificFormat = localeData.main('numbers/scientificFormats-numberSystem-latn/standard'); - const currencyFormat = localeData.main('numbers/currencyFormats-numberSystem-latn/standard'); - const symbols = localeData.main('numbers/symbols-numberSystem-latn'); - const symbolValues = [ - symbols.decimal, - symbols.group, - symbols.list, - symbols.percentSign, - symbols.plusSign, - symbols.minusSign, - symbols.exponential, - symbols.superscriptingExponent, - symbols.perMille, - symbols.infinity, - symbols.nan, - symbols.timeSeparator, - ]; - - if (symbols.currencyDecimal || symbols.currencyGroup) { - symbolValues.push(symbols.currencyDecimal); - } - - if (symbols.currencyGroup) { - symbolValues.push(symbols.currencyGroup); - } - - return [symbolValues, [decimalFormat, percentFormat, currencyFormat, scientificFormat]]; -} - -/** - * Returns week-end range for a locale, based on US week days - * @returns [number, number] - */ -function getWeekendRange(localeData: CldrStatic) { - const startDay = - localeData.get(`supplemental/weekData/weekendStart/${localeData.attributes.territory}`) || - localeData.get('supplemental/weekData/weekendStart/001'); - const endDay = - localeData.get(`supplemental/weekData/weekendEnd/${localeData.attributes.territory}`) || - localeData.get('supplemental/weekData/weekendEnd/001'); - return [WEEK_DAYS.indexOf(startDay), WEEK_DAYS.indexOf(endDay)]; -} - - - -/** - * Returns date-related translations for a locale - * @returns [ dayPeriodsFormat, dayPeriodsStandalone, daysFormat, dayStandalone, monthsFormat, - * monthsStandalone, eras ] - * each value: [ narrow, abbreviated, wide, short? ] - */ -function getDateTimeTranslations(localeData: CldrStatic) { - const dayNames = localeData.main(`dates/calendars/gregorian/days`); - const monthNames = localeData.main(`dates/calendars/gregorian/months`); - const erasNames = localeData.main(`dates/calendars/gregorian/eras`); - const dayPeriods = getDayPeriodsAmPm(localeData); - - const dayPeriodsFormat = removeDuplicates([ - Object.values(dayPeriods.format.narrow), Object.values(dayPeriods.format.abbreviated), - Object.values(dayPeriods.format.wide) - ]); - - const dayPeriodsStandalone = removeDuplicates([ - Object.values(dayPeriods['stand-alone'].narrow), - Object.values(dayPeriods['stand-alone'].abbreviated), - Object.values(dayPeriods['stand-alone'].wide) - ]); - - const daysFormat = removeDuplicates([ - Object.values(dayNames.format.narrow), Object.values(dayNames.format.abbreviated), - Object.values(dayNames.format.wide), Object.values(dayNames.format.short) - ]); - - const daysStandalone = removeDuplicates([ - Object.values(dayNames['stand-alone'].narrow), - Object.values(dayNames['stand-alone'].abbreviated), Object.values(dayNames['stand-alone'].wide), - Object.values(dayNames['stand-alone'].short) - ]); - - const monthsFormat = removeDuplicates([ - Object.values(monthNames.format.narrow), Object.values(monthNames.format.abbreviated), - Object.values(monthNames.format.wide) - ]); - - const monthsStandalone = removeDuplicates([ - Object.values(monthNames['stand-alone'].narrow), - Object.values(monthNames['stand-alone'].abbreviated), - Object.values(monthNames['stand-alone'].wide) - ]); - - const eras = removeDuplicates([ - [erasNames.eraNarrow['0'], erasNames.eraNarrow['1']], - [erasNames.eraAbbr['0'], erasNames.eraAbbr['1']], - [erasNames.eraNames['0'], erasNames.eraNames['1']] - ]); - - const dateTimeTranslations = [ - ...removeDuplicates([dayPeriodsFormat, dayPeriodsStandalone]), - ...removeDuplicates([daysFormat, daysStandalone]), - ...removeDuplicates([monthsFormat, monthsStandalone]), eras - ]; - - return dateTimeTranslations; -} - - -/** - * Returns date, time and dateTime formats for a locale - * @returns [dateFormats, timeFormats, dateTimeFormats] - * each format: [ short, medium, long, full ] - */ -function getDateTimeFormats(localeData: CldrStatic) { - function getFormats(data: any) { - return removeDuplicates([ - data.short._value || data.short, data.medium._value || data.medium, - data.long._value || data.long, data.full._value || data.full - ]); - } - - const dateFormats = localeData.main('dates/calendars/gregorian/dateFormats'); - const timeFormats = localeData.main('dates/calendars/gregorian/timeFormats'); - const dateTimeFormats = localeData.main('dates/calendars/gregorian/dateTimeFormats'); - - return [getFormats(dateFormats), getFormats(timeFormats), getFormats(dateTimeFormats)]; -} - - -/** - * Returns the first day of the week, based on US week days - * @returns number - */ -function getFirstDayOfWeek(localeData: CldrStatic) { - // The `cldrjs` package does not provide proper types for `supplemental`. The - // types are part of the package but embedded incorrectly and not usable. - return WEEK_DAYS.indexOf((localeData as any).supplemental.weekData.firstDay()); -} diff --git a/packages/common/locales/generate-locales-tool/locale-global-file.ts b/packages/common/locales/generate-locales-tool/locale-global-file.ts deleted file mode 100644 index 841cbba308..0000000000 --- a/packages/common/locales/generate-locales-tool/locale-global-file.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 {CldrStatic} from 'cldrjs'; - -import {fileHeader} from './file-header'; -import {BaseCurrencies} from './locale-base-currencies'; -import {generateDayPeriodsSupplementalString} from './locale-extra-file'; -import {generateBasicLocaleString} from './locale-file'; -import {getPluralFunction} from './plural-function'; - -/** - * Generated the contents for the global locale file - */ -export function generateLocaleGlobalFile( - locale: string, localeData: CldrStatic, baseCurrencies: BaseCurrencies) { - const basicLocaleData = generateBasicLocaleString(locale, localeData, baseCurrencies); - const extraLocaleData = generateDayPeriodsSupplementalString(locale, localeData); - const data = basicLocaleData.replace(/\]$/, `, ${extraLocaleData}]`); - return `${fileHeader} - (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)} - global.ng.common.locales['${normalizeLocale(locale)}'] = ${data}; - })(typeof globalThis !== 'undefined' && globalThis || typeof global !== 'undefined' && global || typeof window !== 'undefined' && window); - `; -} - - -/** - * In Angular the locale is referenced by a "normalized" form. - */ -function normalizeLocale(locale: string): string { - return locale.toLowerCase().replace(/_/g, '-'); -} diff --git a/packages/common/locales/generate-locales-tool/object-stringify.ts b/packages/common/locales/generate-locales-tool/object-stringify.ts deleted file mode 100644 index 2dcc9fc394..0000000000 --- a/packages/common/locales/generate-locales-tool/object-stringify.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 - */ - -const UNDEFINED_PLACEHOLDER = `ɵɵUNDEFINED_PLACEHOLDER_JSON`; -const UNDEFINED_PLACEHOLDER_REGEX = new RegExp(`["']${UNDEFINED_PLACEHOLDER}["']`, 'g'); - -/** - * Stringifies the given object while preserving `undefined` values which would usually - * be transformed into `null` with JSON5. We want to preserve `undefined` because in generated - * JavaScript, the `undefined` values are separate from `null`, and `undefined` can be minified - * more efficiently. For example in arrays: `[, , someValue]`. - * - * Note that we do not use `JSON5` or similar formats where properties are not explicitly - * wrapped in quotes. Quotes are necessary so that Closure compiler does not accidentally - * rename properties. See an example where the currency symbols will be incorrect: - * https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250Aconst%2520base_currencies%2520%253D%2520%257B%250A%2520%2520ABC%253A%2520'd'%252C%250A%2509USD%253A%2520'x'%252C%250A%257D%253B%250A%250Aconst%2520current_locale_currencies%2520%253D%2520%257B%257D%250A%250Afunction%2520getCurrencySymbol(l)%2520%257B%250A%2520%2520return%2520current_locale_currencies%255Bl%255D%2520%257C%257C%2520base_currencies%255Bl%255D%2520%257C%257C%2520l%250A%257D%250A%250Aconsole.log(getCurrencySymbol('de'))%253B - */ -export function stringify(value: any) { - const result = - JSON.stringify(value, ((_, value) => value === undefined ? UNDEFINED_PLACEHOLDER : value)); - - UNDEFINED_PLACEHOLDER_REGEX.lastIndex = 0; - - return result.replace(UNDEFINED_PLACEHOLDER_REGEX, 'undefined'); -} diff --git a/packages/common/locales/generate-locales-tool/plural-function.ts b/packages/common/locales/generate-locales-tool/plural-function.ts deleted file mode 100644 index 9cda1d6294..0000000000 --- a/packages/common/locales/generate-locales-tool/plural-function.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * 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 - */ - -// There are no types available for `cldr`. -const cldr = require('cldr'); - -/** - * Returns the plural function for a locale - * 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(); - - const numberType = withTypes ? ': number' : ''; - fn = - fn.replace(/function anonymous\(n[^}]+{/g, `function plural(n${numberType})${numberType} {`) - // Since our generated plural functions only take numbers, we can eliminate some of - // the logic generated by the `cldr` package (to reduce payload size). - .replace(/var/g, /let/) - .replace(/if\s+\(typeof\s+n\s+===\s+["']string["']\)\s+n\s+=\s+parseInt\(n,\s+10\);/, ''); - - // The replacement values must match the `Plural` enum from common. - // We do not use the enum directly to avoid depending on that package. - return fn.replace(/["']zero["']/, '0') - .replace(/["']one["']/, '1') - .replace(/["']two["']/, '2') - .replace(/["']few["']/, '3') - .replace(/["']many["']/, '4') - .replace(/["']other["']/, '5'); -} diff --git a/tools/gulp-tasks/cldr.js b/tools/gulp-tasks/cldr.js new file mode 100644 index 0000000000..5d1ce7802f --- /dev/null +++ b/tools/gulp-tasks/cldr.js @@ -0,0 +1,43 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +const path = require('path'); +const fs = require('fs'); + +module.exports = { + extract: gulp => done => { + if (!fs.existsSync(path.join(__dirname, 'cldr/cldr-data'))) { + throw new Error(`You must run "gulp cldr:download" before you can extract the data`); + } + const extract = require('./cldr/extract'); + return extract(gulp, done); + }, + + download: gulp => done => { + const cldrDownloader = require('cldr-data-downloader'); + const cldrDataFolder = path.join(__dirname, 'cldr/cldr-data'); + if (fs.existsSync(cldrDataFolder)) { + fs.rmdirSync(cldrDataFolder, {recursive: true}); + } else { + fs.mkdirSync(cldrDataFolder); + } + cldrDownloader(path.join(__dirname, 'cldr/cldr-urls.json'), cldrDataFolder, {}, done); + }, + + closure: gulp => done => { + const {RELATIVE_I18N_DATA_FOLDER} = require('./cldr/extract'); + // tslint:disable-next-line:no-console + console.log(RELATIVE_I18N_DATA_FOLDER, fs.existsSync(RELATIVE_I18N_DATA_FOLDER)); + if (!fs.existsSync(RELATIVE_I18N_DATA_FOLDER)) { + throw new Error( + `You must run "gulp cldr:extract" before you can create the closure-locale.ts file`); + } + const localeAll = require('./cldr/closure'); + return localeAll(gulp, done); + }, +}; diff --git a/tools/gulp-tasks/cldr/cldr-data.js b/tools/gulp-tasks/cldr/cldr-data.js new file mode 100644 index 0000000000..dc68ab210d --- /dev/null +++ b/tools/gulp-tasks/cldr/cldr-data.js @@ -0,0 +1,86 @@ +// tslint:disable:file-header + +/** + * Npm module for Unicode CLDR JSON data + * + * @license + * Copyright 2013 Rafael Xavier de Souza + * Released under the MIT license + * https://github.com/rxaviers/cldr-data-npm/blob/master/LICENSE-MIT + */ + +'use strict'; + +const JSON_EXTENSION = /^(.*)\.json$/; + +const assert = require('assert'); +const _fs = require('fs'); +const _path = require('path'); + +function argsToArray(arg) { + return [].slice.call(arg, 0); +} + +function jsonFiles(dirName) { + const fileList = _fs.readdirSync(_path.join(__dirname, 'cldr-data', dirName)); + + return fileList.reduce(function(sum, file) { + if (JSON_EXTENSION.test(file)) { + return sum.concat(file.match(JSON_EXTENSION)[1]); + } + }, []); +} + +function cldrData(path /*, ...*/) { + assert( + typeof path === 'string', + 'must include path (e.g., "main/en/numbers" or "supplemental/likelySubtags")'); + + if (arguments.length > 1) { + return argsToArray(arguments).reduce(function(sum, path) { + sum.push(cldrData(path)); + return sum; + }, []); + } + return require('./cldr-data/' + path); +} + +function mainPathsFor(locales) { + return locales.reduce(function(sum, locale) { + const mainFiles = jsonFiles(_path.join('main', locale)); + mainFiles.forEach(function(mainFile) { + sum.push(_path.join('main', locale, mainFile)); + }); + return sum; + }, []); +} + +function supplementalPaths() { + const supplementalFiles = jsonFiles('supplemental'); + + return supplementalFiles.map(function(supplementalFile) { + return _path.join('supplemental', supplementalFile); + }); +} + +Object.defineProperty(cldrData, 'availableLocales', { + get: function() { + return cldrData('availableLocales').availableLocales; + } +}); + +cldrData.all = function() { + const paths = supplementalPaths().concat(mainPathsFor(this.availableLocales)); + return cldrData.apply({}, paths); +}; + +cldrData.entireMainFor = function(locale /*, ...*/) { + assert(typeof locale === 'string', 'must include locale (e.g., "en")'); + return cldrData.apply({}, mainPathsFor(argsToArray(arguments))); +}; + +cldrData.entireSupplemental = function() { + return cldrData.apply({}, supplementalPaths()); +}; + +module.exports = cldrData; diff --git a/tools/gulp-tasks/cldr/cldr-urls.json b/tools/gulp-tasks/cldr/cldr-urls.json new file mode 100644 index 0000000000..bec583e83b --- /dev/null +++ b/tools/gulp-tasks/cldr/cldr-urls.json @@ -0,0 +1,22 @@ +{ + "core": [ + "https://github.com/unicode-cldr/cldr-core/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-segments-modern/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-dates-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-buddhist-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-chinese-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-coptic-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-dangi-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-ethiopic-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-hebrew-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-indian-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-islamic-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-japanese-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-persian-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-cal-roc-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-localenames-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-misc-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-numbers-full/archive/37.0.0.zip", + "https://github.com/unicode-cldr/cldr-units-full/archive/37.0.0.zip" + ] +} diff --git a/tools/gulp-tasks/cldr/closure.js b/tools/gulp-tasks/cldr/closure.js new file mode 100644 index 0000000000..631d045a3d --- /dev/null +++ b/tools/gulp-tasks/cldr/closure.js @@ -0,0 +1,163 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +const fs = require('fs'); +const yargs = require('yargs').argv; +const shelljs = require('shelljs'); +const {I18N_DATA_FOLDER, RELATIVE_I18N_DATA_FOLDER, HEADER} = require('./extract'); +const OUTPUT_NAME = `closure-locale.ts`; + +// tslint:disable:no-console +module.exports = (gulp, done) => { + // the locales used by closure that will be used to generate the closure-locale file + // extracted from: + // https://github.com/google/closure-library/blob/master/closure/goog/i18n/datetimepatterns.js#L2136 + let GOOG_LOCALES = [ + 'af', 'am', 'ar', 'ar-DZ', 'az', 'be', 'bg', 'bn', 'br', 'bs', + 'ca', 'chr', 'cs', 'cy', 'da', 'de', 'de-AT', 'de-CH', 'el', 'en-AU', + 'en-CA', 'en-GB', 'en-IE', 'en-IN', 'en-SG', 'en-ZA', 'es', 'es-419', 'es-MX', 'es-US', + 'et', 'eu', 'fa', 'fi', 'fr', 'fr-CA', 'ga', 'gl', 'gsw', 'gu', + 'haw', 'hi', 'hr', 'hu', 'hy', 'in', 'is', 'it', 'iw', 'ja', + 'ka', 'kk', 'km', 'kn', 'ko', 'ky', 'ln', 'lo', 'lt', 'lv', + 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'ne', 'nl', + 'no', 'or', 'pa', 'pl', 'pt', 'pt-PT', 'ro', 'ru', 'sh', 'si', + 'sk', 'sl', 'sq', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tl', + 'tr', 'uk', 'ur', 'uz', 'vi', 'zh', 'zh-CN', 'zh-HK', 'zh-TW', 'zu' + ]; + + // locale id aliases to support deprecated locale ids used by closure + // it maps deprecated ids --> new ids + // manually extracted from ./cldr-data/supplemental/aliases.json + const ALIASES = { + 'in': 'id', + 'iw': 'he', + 'mo': 'ro-MD', + 'no': 'nb', + 'nb': 'no-NO', + 'sh': 'sr-Latn', + 'tl': 'fil', + 'pt': 'pt-BR', + 'zh-CN': 'zh-Hans-CN', + 'zh-Hans-CN': 'zh-Hans', + 'zh-HK': 'zh-Hant-HK', + 'zh-TW': 'zh-Hant-TW', + 'zh-Hant-TW': 'zh-Hant', + }; + + if (yargs.locales) { + GOOG_LOCALES = yargs.locales.split(','); + } + + console.log(`Writing file ${I18N_DATA_FOLDER}/${OUTPUT_NAME}`); + fs.writeFileSync( + `${RELATIVE_I18N_DATA_FOLDER}/${OUTPUT_NAME}`, generateAllLocalesFile(GOOG_LOCALES, ALIASES)); + + console.log(`Formatting ${I18N_DATA_FOLDER}/${OUTPUT_NAME}..."`); + shelljs.exec(`yarn clang-format -i ${I18N_DATA_FOLDER}/${OUTPUT_NAME}`, {silent: true}); + done(); +}; + +/** + * Generate a file that contains all locale to import for closure. + * Tree shaking will only keep the data for the `goog.LOCALE` locale. + */ +function generateAllLocalesFile(LOCALES, ALIASES) { + const existingLocalesAliases = {}; + const existingLocalesData = {}; + + // for each locale, get the data and the list of equivalent locales + LOCALES.forEach(locale => { + const eqLocales = new Set(); + eqLocales.add(locale); + if (locale.match(/-/)) { + eqLocales.add(locale.replace(/-/g, '_')); + } + + // check for aliases + const alias = ALIASES[locale]; + if (alias) { + eqLocales.add(alias); + + if (alias.match(/-/)) { + eqLocales.add(alias.replace(/-/g, '_')); + } + + // to avoid duplicated "case" we regroup all locales in the same "case" + // the simplest way to do that is to have alias aliases + // e.g. 'no' --> 'nb', 'nb' --> 'no-NO' + // which means that we'll have 'no', 'nb' and 'no-NO' in the same "case" + const aliasKeys = Object.keys(ALIASES); + for (let i = 0; i < aliasKeys.length; i++) { + const aliasValue = ALIASES[alias]; + if (aliasKeys.indexOf(alias) !== -1 && !eqLocales.has(aliasValue)) { + eqLocales.add(aliasValue); + + if (aliasValue.match(/-/)) { + eqLocales.add(aliasValue.replace(/-/g, '_')); + } + } + } + } + + for (let l of eqLocales) { + // find the existing content file + const path = `${RELATIVE_I18N_DATA_FOLDER}/${l}.ts`; + if (fs.existsSync(`${RELATIVE_I18N_DATA_FOLDER}/${l}.ts`)) { + const localeName = formatLocale(locale); + existingLocalesData[locale] = + fs.readFileSync(path, 'utf8') + .replace(`${HEADER}\n`, '') + .replace('export default ', `export const locale_${localeName} = `) + .replace('function plural', `function plural_${localeName}`) + .replace(/,(\n | )plural/, `, plural_${localeName}`) + .replace('const u = undefined;\n\n', ''); + } + } + + existingLocalesAliases[locale] = eqLocales; + }); + + function generateCases(locale) { + let str = ''; + let locales = []; + const eqLocales = existingLocalesAliases[locale]; + for (let l of eqLocales) { + str += `case '${l}':\n`; + locales.push(`'${l}'`); + } + let localesStr = '[' + locales.join(',') + ']'; + + str += ` l = locale_${formatLocale(locale)}; + locales = ${localesStr}; + break;\n`; + return str; + } + + function formatLocale(locale) { + return locale.replace(/-/g, '_'); + } + // clang-format off + return `${HEADER} +import {registerLocaleData} from '../src/i18n/locale_data'; + +const u = undefined; + +${LOCALES.map(locale => `${existingLocalesData[locale]}`).join('\n')} + +let l: any; +let locales: string[] = []; + +switch (goog.LOCALE) { +${LOCALES.map(locale => generateCases(locale)).join('')}} + +if(l) { + locales.forEach(locale => registerLocaleData(l, locale)); +} +`; + // clang-format on +} diff --git a/tools/gulp-tasks/cldr/extract.js b/tools/gulp-tasks/cldr/extract.js new file mode 100644 index 0000000000..aef0ac0001 --- /dev/null +++ b/tools/gulp-tasks/cldr/extract.js @@ -0,0 +1,632 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +const fs = require('fs'); +const path = require('path'); +const stringify = require('./util').stringify; +// used to extract plural rules +const cldr = require('cldr'); +// used to extract all other cldr data +const cldrJs = require('cldrjs'); +// used to call to clang-format +const shelljs = require('shelljs'); + +const COMMON_PACKAGE = 'packages/common'; +const CORE_PACKAGE = 'packages/core'; +const I18N_FOLDER = `${COMMON_PACKAGE}/src/i18n`; +const I18N_CORE_FOLDER = `${CORE_PACKAGE}/src/i18n`; +const I18N_DATA_FOLDER = `${COMMON_PACKAGE}/locales`; +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_CORE_FOLDER = path.resolve(__dirname, `../../../${I18N_CORE_FOLDER}`); +const RELATIVE_I18N_DATA_FOLDER = path.resolve(__dirname, `../../../${I18N_DATA_FOLDER}`); +const RELATIVE_I18N_DATA_EXTRA_FOLDER = + path.resolve(__dirname, `../../../${I18N_DATA_EXTRA_FOLDER}`); +const RELATIVE_I18N_GLOBAL_FOLDER = path.resolve(__dirname, `../../../${I18N_GLOBAL_FOLDER}`); +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 HEADER = `/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * 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 + */ + +// THIS CODE IS GENERATED - DO NOT MODIFY +// See angular/tools/gulp-tasks/cldr/extract.js +`; + +// tslint:disable:no-console +module.exports = (gulp, done) => { + const cldrData = require('./cldr-data'); + const LOCALES = cldrData.availableLocales; + + console.log(`Loading CLDR data...`); + cldrJs.load(cldrData.all().concat(cldrData('scriptMetadata'))); + + console.log(`Writing locale files`); + if (!fs.existsSync(RELATIVE_I18N_FOLDER)) { + fs.mkdirSync(RELATIVE_I18N_FOLDER); + } + if (!fs.existsSync(RELATIVE_I18N_DATA_FOLDER)) { + fs.mkdirSync(RELATIVE_I18N_DATA_FOLDER); + } + if (!fs.existsSync(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`); + fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/currencies.ts`, generateCurrenciesFile()); + + const baseCurrencies = generateBaseCurrencies(new cldrJs('en')); + // additional "en" file that will be included in common + console.log(`Writing file ${I18N_CORE_FOLDER}/locale_en.ts`); + const localeEnFile = generateLocale('en', new cldrJs('en'), baseCurrencies); + fs.writeFileSync(`${RELATIVE_I18N_CORE_FOLDER}/locale_en.ts`, localeEnFile); + + LOCALES.forEach((locale, index) => { + const localeData = new cldrJs(locale); + + console.log(`${index + 1}/${LOCALES.length}`); + console.log(`\t${I18N_DATA_FOLDER}/${locale}.ts`); + fs.writeFileSync( + `${RELATIVE_I18N_DATA_FOLDER}/${locale}.ts`, + locale === 'en' ? localeEnFile : generateLocale(locale, localeData, baseCurrencies)); + console.log(`\t${I18N_DATA_EXTRA_FOLDER}/${locale}.ts`); + fs.writeFileSync( + `${RELATIVE_I18N_DATA_EXTRA_FOLDER}/${locale}.ts`, generateLocaleExtra(locale, localeData)); + console.log(`\t${I18N_GLOBAL_FOLDER}/${locale}.js`); + 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(`All i18n cldr files have been generated, formatting files..."`); + shelljs.exec( + `yarn clang-format -i ${I18N_DATA_FOLDER}/**/*.ts ${I18N_DATA_FOLDER}/*.ts ${ + I18N_FOLDER}/currencies.ts ${I18N_CORE_FOLDER}/locale_en.ts ${I18N_GLOBAL_FOLDER}/*.js`, + {silent: true}); + done(); +}; + +/** + * Generate contents for the basic locale data file + */ +function generateLocale(locale, localeData, baseCurrencies) { + 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)} + global.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 = stringify( + [ + locale, + ...getDateTimeTranslations(localeData), + ...getDateTimeSettings(localeData), + ...getNumberSettings(localeData), + ...getCurrencySettings(locale, localeData), + generateLocaleCurrencies(localeData, baseCurrencies), + getDirectionality(localeData), + ], + true) + // We remove "undefined" added by spreading arrays when there is no value + .replace(/undefined/g, 'u'); + + // adding plural function after, because we don't want it as a string + data = data.replace(/\]$/, ', plural]'); + return data; +} + +/** + * Collect up the day period rules, and extended day period data. + */ +function generateDayPeriodsSupplementalString(locale, localeData) { + const dayPeriods = getDayPeriodsNoAmPm(localeData); + const dayPeriodRules = getDayPeriodRules(localeData); + + let dayPeriodsSupplemental = []; + if (Object.keys(dayPeriods.format.narrow).length) { + const keys = Object.keys(dayPeriods.format.narrow); + + if (keys.length !== Object.keys(dayPeriodRules).length) { + throw new Error(`Error: locale ${locale} has not the correct number of day period rules`); + } + + const dayPeriodsFormat = removeDuplicates([ + objectValues(dayPeriods.format.narrow), objectValues(dayPeriods.format.abbreviated), + objectValues(dayPeriods.format.wide) + ]); + + const dayPeriodsStandalone = removeDuplicates([ + objectValues(dayPeriods['stand-alone'].narrow), + objectValues(dayPeriods['stand-alone'].abbreviated), + objectValues(dayPeriods['stand-alone'].wide) + ]); + + const rules = keys.map(key => dayPeriodRules[key]); + dayPeriodsSupplemental = [...removeDuplicates([dayPeriodsFormat, dayPeriodsStandalone]), rules]; + } + return stringify(dayPeriodsSupplemental).replace(/undefined/g, 'u'); +} + +/** + * Generate a list of currencies to be used as a based for other currencies + * e.g.: {'ARS': [, '$'], 'AUD': ['A$', '$'], ...} + */ +function generateBaseCurrencies(localeData, addDigits) { + const currenciesData = localeData.main('numbers/currencies'); + const fractions = new cldrJs('en').get(`supplemental/currencyData/fractions`); + const currencies = {}; + Object.keys(currenciesData).forEach(key => { + let symbolsArray = []; + const symbol = currenciesData[key].symbol; + const symbolNarrow = currenciesData[key]['symbol-alt-narrow']; + if (symbol && symbol !== key) { + symbolsArray.push(symbol); + } + if (symbolNarrow && symbolNarrow !== symbol) { + if (symbolsArray.length > 0) { + symbolsArray.push(symbolNarrow); + } else { + symbolsArray = [undefined, symbolNarrow]; + } + } + if (addDigits && fractions[key] && fractions[key]['_digits']) { + const digits = parseInt(fractions[key]['_digits'], 10); + if (symbolsArray.length === 2) { + symbolsArray.push(digits); + } else if (symbolsArray.length === 1) { + symbolsArray = [...symbolsArray, undefined, digits]; + } else { + symbolsArray = [undefined, undefined, digits]; + } + } + if (symbolsArray.length > 0) { + currencies[key] = symbolsArray; + } + }); + return currencies; +} + +/** + * To minimize the file even more, we only output the differences compared to the base currency + */ +function generateLocaleCurrencies(localeData, baseCurrencies) { + const currenciesData = localeData.main('numbers/currencies'); + const currencies = {}; + Object.keys(currenciesData).forEach(code => { + let symbolsArray = []; + const symbol = currenciesData[code].symbol; + const symbolNarrow = currenciesData[code]['symbol-alt-narrow']; + if (symbol && symbol !== code) { + symbolsArray.push(symbol); + } + if (symbolNarrow && symbolNarrow !== symbol) { + if (symbolsArray.length > 0) { + symbolsArray.push(symbolNarrow); + } else { + symbolsArray = [undefined, symbolNarrow]; + } + } + + // if locale data are different, set the value + if ((baseCurrencies[code] || []).toString() !== symbolsArray.toString()) { + currencies[code] = symbolsArray; + } + }); + return currencies; +} + +/** + * Generate a file that contains the list of currencies and their symbols + */ +function generateCurrenciesFile() { + const baseCurrencies = generateBaseCurrencies(new cldrJs('en'), true); + + return `${HEADER} +export type CurrenciesSymbols = [string] | [string | undefined, string]; + +/** @internal */ +export const CURRENCIES_EN: {[code: string]: CurrenciesSymbols | [string | undefined, string | undefined, number]} = ${ + stringify(baseCurrencies, true)}; +`; +} + +/** + * Returns data for the chosen day periods + * @returns { + * format: {narrow / abbreviated / wide: [...]}, + * stand-alone: {narrow / abbreviated / wide: [...]} + * } + */ +function getDayPeriods(localeData, dayPeriodsList) { + const dayPeriods = localeData.main(`dates/calendars/gregorian/dayPeriods`); + const result = {}; + // cleaning up unused keys + Object.keys(dayPeriods).forEach(key1 => { // format / stand-alone + result[key1] = {}; + Object.keys(dayPeriods[key1]).forEach(key2 => { // narrow / abbreviated / wide + result[key1][key2] = {}; + Object.keys(dayPeriods[key1][key2]).forEach(key3 => { + if (dayPeriodsList.indexOf(key3) !== -1) { + result[key1][key2][key3] = dayPeriods[key1][key2][key3]; + } + }); + }); + }); + + return result; +} + +/** + * Returns the basic day periods (am/pm) + */ +function getDayPeriodsAmPm(localeData) { + return getDayPeriods(localeData, ['am', 'pm']); +} + +/** + * Returns the extra day periods (without am/pm) + */ +function getDayPeriodsNoAmPm(localeData) { + return getDayPeriods(localeData, [ + 'noon', 'midnight', 'morning1', 'morning2', 'afternoon1', 'afternoon2', 'evening1', 'evening2', + 'night1', 'night2' + ]); +} + +/** + * Returns date-related translations for a locale + * @returns [ dayPeriodsFormat, dayPeriodsStandalone, daysFormat, dayStandalone, monthsFormat, + * monthsStandalone, eras ] + * each value: [ narrow, abbreviated, wide, short? ] + */ +function getDateTimeTranslations(localeData) { + const dayNames = localeData.main(`dates/calendars/gregorian/days`); + const monthNames = localeData.main(`dates/calendars/gregorian/months`); + const erasNames = localeData.main(`dates/calendars/gregorian/eras`); + const dayPeriods = getDayPeriodsAmPm(localeData); + + const dayPeriodsFormat = removeDuplicates([ + objectValues(dayPeriods.format.narrow), objectValues(dayPeriods.format.abbreviated), + objectValues(dayPeriods.format.wide) + ]); + + const dayPeriodsStandalone = removeDuplicates([ + objectValues(dayPeriods['stand-alone'].narrow), + objectValues(dayPeriods['stand-alone'].abbreviated), + objectValues(dayPeriods['stand-alone'].wide) + ]); + + const daysFormat = removeDuplicates([ + objectValues(dayNames.format.narrow), objectValues(dayNames.format.abbreviated), + objectValues(dayNames.format.wide), objectValues(dayNames.format.short) + ]); + + const daysStandalone = removeDuplicates([ + objectValues(dayNames['stand-alone'].narrow), objectValues(dayNames['stand-alone'].abbreviated), + objectValues(dayNames['stand-alone'].wide), objectValues(dayNames['stand-alone'].short) + ]); + + const monthsFormat = removeDuplicates([ + objectValues(monthNames.format.narrow), objectValues(monthNames.format.abbreviated), + objectValues(monthNames.format.wide) + ]); + + const monthsStandalone = removeDuplicates([ + objectValues(monthNames['stand-alone'].narrow), + objectValues(monthNames['stand-alone'].abbreviated), + objectValues(monthNames['stand-alone'].wide) + ]); + + const eras = removeDuplicates([ + [erasNames.eraNarrow['0'], erasNames.eraNarrow['1']], + [erasNames.eraAbbr['0'], erasNames.eraAbbr['1']], + [erasNames.eraNames['0'], erasNames.eraNames['1']] + ]); + + const dateTimeTranslations = [ + ...removeDuplicates([dayPeriodsFormat, dayPeriodsStandalone]), + ...removeDuplicates([daysFormat, daysStandalone]), + ...removeDuplicates([monthsFormat, monthsStandalone]), eras + ]; + + return dateTimeTranslations; +} + +/** + * Returns date, time and dateTime formats for a locale + * @returns [dateFormats, timeFormats, dateTimeFormats] + * each format: [ short, medium, long, full ] + */ +function getDateTimeFormats(localeData) { + function getFormats(data) { + return removeDuplicates([ + data.short._value || data.short, data.medium._value || data.medium, + data.long._value || data.long, data.full._value || data.full + ]); + } + + const dateFormats = localeData.main('dates/calendars/gregorian/dateFormats'); + const timeFormats = localeData.main('dates/calendars/gregorian/timeFormats'); + const dateTimeFormats = localeData.main('dates/calendars/gregorian/dateTimeFormats'); + + return [getFormats(dateFormats), getFormats(timeFormats), getFormats(dateTimeFormats)]; +} + +/** + * Returns day period rules for a locale + * @returns string[] + */ +function getDayPeriodRules(localeData) { + const dayPeriodRules = + localeData.get(`supplemental/dayPeriodRuleSet/${localeData.attributes.language}`); + const rules = {}; + if (dayPeriodRules) { + Object.keys(dayPeriodRules).forEach(key => { + if (dayPeriodRules[key]._at) { + rules[key] = dayPeriodRules[key]._at; + } else { + rules[key] = [dayPeriodRules[key]._from, dayPeriodRules[key]._before]; + } + }); + } + + return rules; +} + +/** + * Returns the first day of the week, based on US week days + * @returns number + */ +function getFirstDayOfWeek(localeData) { + return WEEK_DAYS.indexOf(localeData.supplemental.weekData.firstDay()); +} + +/** + * Returns week-end range for a locale, based on US week days + * @returns [number, number] + */ +function getWeekendRange(localeData) { + const startDay = + localeData.get(`supplemental/weekData/weekendStart/${localeData.attributes.territory}`) || + localeData.get('supplemental/weekData/weekendStart/001'); + const endDay = + localeData.get(`supplemental/weekData/weekendEnd/${localeData.attributes.territory}`) || + localeData.get('supplemental/weekData/weekendEnd/001'); + return [WEEK_DAYS.indexOf(startDay), WEEK_DAYS.indexOf(endDay)]; +} + +/** + * Returns dateTime data for a locale + * @returns [ firstDayOfWeek, weekendRange, formats ] + */ +function getDateTimeSettings(localeData) { + return [ + getFirstDayOfWeek(localeData), getWeekendRange(localeData), ...getDateTimeFormats(localeData) + ]; +} + +/** + * Returns the number symbols and formats for a locale + * @returns [ symbols, formats ] + * symbols: [ decimal, group, list, percentSign, plusSign, minusSign, exponential, + * superscriptingExponent, perMille, infinity, nan, timeSeparator, currencyDecimal?, currencyGroup? + * ] + * formats: [ currency, decimal, percent, scientific ] + */ +function getNumberSettings(localeData) { + const decimalFormat = localeData.main('numbers/decimalFormats-numberSystem-latn/standard'); + const percentFormat = localeData.main('numbers/percentFormats-numberSystem-latn/standard'); + const scientificFormat = localeData.main('numbers/scientificFormats-numberSystem-latn/standard'); + const currencyFormat = localeData.main('numbers/currencyFormats-numberSystem-latn/standard'); + const symbols = localeData.main('numbers/symbols-numberSystem-latn'); + const symbolValues = [ + symbols.decimal, + symbols.group, + symbols.list, + symbols.percentSign, + symbols.plusSign, + symbols.minusSign, + symbols.exponential, + symbols.superscriptingExponent, + symbols.perMille, + symbols.infinity, + symbols.nan, + symbols.timeSeparator, + ]; + + if (symbols.currencyDecimal || symbols.currencyGroup) { + symbolValues.push(symbols.currencyDecimal); + } + + if (symbols.currencyGroup) { + symbolValues.push(symbols.currencyGroup); + } + + return [symbolValues, [decimalFormat, percentFormat, currencyFormat, scientificFormat]]; +} + +/** + * Returns the currency code, symbol and name for a locale + * @returns [ code, symbol, name ] + */ +function getCurrencySettings(locale, localeData) { + const currencyInfo = localeData.main(`numbers/currencies`); + let currentCurrency = ''; + + // find the currency currently used in this country + const currencies = + localeData.get(`supplemental/currencyData/region/${localeData.attributes.territory}`) || + localeData.get( + `supplemental/currencyData/region/${localeData.attributes.language.toUpperCase()}`); + + if (currencies) { + currencies.some(currency => { + const keys = Object.keys(currency); + return keys.some(key => { + if (currency[key]._from && !currency[key]._to) { + return currentCurrency = key; + } + }); + }); + + if (!currentCurrency) { + throw new Error(`Unable to find currency for locale "${locale}"`); + } + } + + let currencySettings = [undefined, undefined, undefined]; + + if (currentCurrency) { + currencySettings = [ + currentCurrency, currencyInfo[currentCurrency].symbol, + currencyInfo[currentCurrency].displayName + ]; + } + + return currencySettings; +} + +/** + * Returns the writing direction for a locale + * @returns 'rtl' | 'ltr' + */ +function getDirectionality(localeData) { + const rtl = localeData.get('scriptMetadata/{script}/rtl'); + return rtl === 'YES' ? 'rtl' : 'ltr'; +} + +/** + * Transforms a string into a regexp + */ +function toRegExp(s) { + return new RegExp(s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'), 'g'); +} + +/** + * Returns the plural function for a locale + * 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 + */ +function getPluralFunction(locale, withTypes = true) { + let fn = cldr.extractPluralRuleFunction(locale).toString(); + + if (fn === EMPTY_RULE) { + fn = DEFAULT_RULE; + } + + const numberType = withTypes ? ': number' : ''; + fn = fn.replace(/function anonymous\(n[^}]+{/g, `function plural(n${numberType})${numberType} {`) + .replace(toRegExp('var'), 'let') + .replace(toRegExp('if(typeof n==="string")n=parseInt(n,10);'), '') + .replace(toRegExp('\n}'), ';\n}'); + + // The replacement values must match the `Plural` enum from common. + // We do not use the enum directly to avoid depending on that package. + return fn.replace(toRegExp('"zero"'), ' 0') + .replace(toRegExp('"one"'), ' 1') + .replace(toRegExp('"two"'), ' 2') + .replace(toRegExp('"few"'), ' 3') + .replace(toRegExp('"many"'), ' 4') + .replace(toRegExp('"other"'), ' 5'); +} + +/** + * Return an array of values from an object + */ +function objectValues(obj) { + return Object.keys(obj).map(key => obj[key]); +} + +/** + * To create smaller locale files, we remove duplicated data. + * To be make this work we need to store similar data in arrays, if some value in an array + * is undefined, we can take the previous defined value instead, because it means that it has + * been deduplicated. + * e.g.: [x, y, undefined, z, undefined, undefined] + * The first undefined is equivalent to y, the second and third are equivalent to z + * Note that the first value in an array is always defined. + * + * Also since we need to know which data is assumed similar, it is important that we store those + * similar data in arrays to mark the delimitation between values that have different meanings + * (e.g. months and days). + * + * For further size improvements, "undefined" values will be replaced by a constant in the arrays + * as the last step of the file generation (in generateLocale and generateLocaleExtra). + * e.g.: [x, y, undefined, z, undefined, undefined] will be [x, y, u, z, u, u] + */ +function removeDuplicates(data) { + const dedup = [data[0]]; + for (let i = 1; i < data.length; i++) { + if (stringify(data[i]) !== stringify(data[i - 1])) { + dedup.push(data[i]); + } else { + dedup.push(undefined); + } + } + 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_DATA_FOLDER = I18N_DATA_FOLDER; +module.exports.RELATIVE_I18N_DATA_FOLDER = RELATIVE_I18N_DATA_FOLDER; +module.exports.HEADER = HEADER; diff --git a/tools/gulp-tasks/cldr/util.js b/tools/gulp-tasks/cldr/util.js new file mode 100644 index 0000000000..4b6c005f73 --- /dev/null +++ b/tools/gulp-tasks/cldr/util.js @@ -0,0 +1,182 @@ +// tslint:disable:file-header + +/** + * Like JSON.stringify, but without double quotes around keys, and without null instead of undefined + * values + * Based on https://github.com/json5/json5/blob/master/lib/json5.js + * Use option "quoteKeys" to preserve quotes for keys + */ +module.exports.stringify = function(obj, quoteKeys) { + var getReplacedValueOrUndefined = function(holder, key) { + var value = holder[key]; + + // Replace the value with its toJSON value first, if possible + if (value && value.toJSON && typeof value.toJSON === 'function') { + value = value.toJSON(); + } + + return value; + }; + + function isWordChar(c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || + c === '_' || c === '$'; + } + + function isWordStart(c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_' || c === '$'; + } + + function isWord(key) { + if (typeof key !== 'string') { + return false; + } + if (!isWordStart(key[0])) { + return false; + } + var i = 1, length = key.length; + while (i < length) { + if (!isWordChar(key[i])) { + return false; + } + i++; + } + return true; + } + + // polyfills + function isArray(obj) { + if (Array.isArray) { + return Array.isArray(obj); + } else { + return Object.prototype.toString.call(obj) === '[object Array]'; + } + } + + function isDate(obj) { + return Object.prototype.toString.call(obj) === '[object Date]'; + } + + var objStack = []; + function checkForCircular(obj) { + for (var i = 0; i < objStack.length; i++) { + if (objStack[i] === obj) { + throw new TypeError('Converting circular structure to JSON'); + } + } + } + + // Copied from Crokford's implementation of JSON + // See + // https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195 + // Begin + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + function escapeString(str, keepQuotes) { + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + escapable.lastIndex = 0; + return escapable.test(str) && !keepQuotes ? '"' + str.replace(escapable, function(a) { + var c = meta[a]; + return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + str + '"'; + } + // End + + function internalStringify(holder, key) { + var buffer, res; + + // Replace the value, if necessary + var obj_part = getReplacedValueOrUndefined(holder, key); + + if (obj_part && !isDate(obj_part)) { + // unbox objects + // don't unbox dates, since will turn it into number + obj_part = obj_part.valueOf(); + } + switch (typeof obj_part) { + case 'boolean': + return obj_part.toString(); + + case 'number': + if (isNaN(obj_part) || !isFinite(obj_part)) { + return 'null'; + } + return obj_part.toString(); + + case 'string': + return escapeString(obj_part.toString()); + + case 'object': + if (obj_part === null) { + return 'null'; + } else if (isArray(obj_part)) { + checkForCircular(obj_part); + buffer = '['; + objStack.push(obj_part); + + for (var i = 0; i < obj_part.length; i++) { + res = internalStringify(obj_part, i); + if (res === null) { + buffer += 'null'; + } /* else if (typeof res === 'undefined') { // modified to support empty array values + buffer += ''; + }*/ + else { + buffer += res; + } + if (i < obj_part.length - 1) { + buffer += ','; + } + } + objStack.pop(); + buffer += ']'; + } else { + checkForCircular(obj_part); + buffer = '{'; + var nonEmpty = false; + objStack.push(obj_part); + for (var prop in obj_part) { + if (obj_part.hasOwnProperty(prop)) { + var value = internalStringify(obj_part, prop); + if (typeof value !== 'undefined' && value !== null) { + nonEmpty = true; + key = isWord(prop) && !quoteKeys ? prop : escapeString(prop, quoteKeys); + buffer += key + ':' + value + ','; + } + } + } + objStack.pop(); + if (nonEmpty) { + buffer = buffer.substring(0, buffer.length - 1) + '}'; + } else { + buffer = '{}'; + } + } + return buffer; + default: + // functions and undefined should be ignored + return undefined; + } + } + + // special case...when undefined is used inside of + // a compound object/array, return null. + // but when top-level, return undefined + var topLevelHolder = {'': obj}; + if (obj === undefined) { + return getReplacedValueOrUndefined(topLevelHolder, ''); + } + return internalStringify(topLevelHolder, ''); +}; diff --git a/yarn.lock b/yarn.lock index ef8597b526..f149d27174 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1620,11 +1620,6 @@ resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.35.tgz#3964c48372bf62d60616d8673dd77a9719ebac9b" integrity sha512-2WeeXK7BuQo7yPI4WGOBum90SzF/f8rqlvpaXx4rjeTmNssGRDHWf7fgDUH90xMB3sUOu716fUK5d+OVx0+ncQ== -"@types/cldrjs@^0.4.22": - version "0.4.22" - resolved "https://registry.yarnpkg.com/@types/cldrjs/-/cldrjs-0.4.22.tgz#24e31cdf15a4ea806ca0a774b024150d1066fea1" - integrity sha512-YyzxXZ5s9xwPWznXnI3++X14JGnomDdDAlin7kWZvxX/MzirC9BNFcDSQ0yR8YG2M/xNMn0nXsCGkgbFVyXjGw== - "@types/cli-progress@^3.4.2": version "3.9.1" resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.9.1.tgz#285e7fbdad6e7baf072d163ae1c3b23b7b219130" @@ -2132,6 +2127,11 @@ adjust-sourcemap-loader@^4.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" +adm-zip@0.4.11: + version "0.4.11" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" + integrity sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA== + adm-zip@^0.4.9, adm-zip@~0.4.3: version "0.4.16" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" @@ -2205,6 +2205,16 @@ ajv@8.2.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^5.1.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + ajv@^6.1.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -2709,7 +2719,7 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.8.0: +aws4@^1.6.0, aws4@^1.8.0: version "1.11.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== @@ -3496,20 +3506,33 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -cldr@5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/cldr/-/cldr-5.7.0.tgz#8c466bdc2500fd293462029631011adcd55bc5ae" - integrity sha512-Pyoh0kwXJIUvbAvQoQqKIr0pKWwWfkcYCIDKWmVIxJ9HftSsg57AqyfW1EzWRcP4nJj40WX4vB/lXQ+Uw4NbNA== +cldr-data-downloader@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/cldr-data-downloader/-/cldr-data-downloader-0.3.5.tgz#f5445cb9d222bf7fa8426c62e0ae9d7d4897b243" + integrity sha512-uyIMa1K98DAp/PE7dYpq2COIrkWn681Atjng1GgEzeJzYb1jANtugtp9wre6+voE+qzVC8jtWv6E/xZ1GTJdlw== dependencies: - escodegen "^1.12.0" + adm-zip "0.4.11" + mkdirp "0.5.0" + nopt "3.0.x" + progress "1.1.8" + q "1.0.1" + request "~2.87.0" + request-progress "0.3.1" + +cldr@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cldr/-/cldr-7.0.0.tgz#8e0f42acc21b5762842cf23a747120be898447fe" + integrity sha512-BmD93+RhHGkdVRO9LYL6kd7IA9ANxnpH1A+OM6FdhKVYXqRgBaDmt9P83VaQB6gMBaFvOl4IozW/g3ViLn9LeQ== + dependencies: + escodegen "^2.0.0" esprima "^4.0.1" memoizeasync "^1.1.0" passerror "^1.1.1" pegjs "^0.10.0" seq "^0.3.5" unicoderegexp "^0.4.1" - xmldom "^0.3.0" - xpath "^0.0.27" + xmldom "^0.6.0" + xpath "^0.0.32" cldrjs@0.5.5: version "0.5.5" @@ -3663,6 +3686,11 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -3763,7 +3791,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.5, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -5230,7 +5258,7 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.12.0, escodegen@^1.8.1: +escodegen@^1.8.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -5497,7 +5525,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, extend@~3.0.1, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -5556,6 +5584,11 @@ fancy-log@^1.3.2: parse-node-version "^1.0.0" time-stamp "^1.0.0" +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -5934,7 +5967,7 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.3.2: +form-data@~2.3.1, form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== @@ -6613,6 +6646,14 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + har-validator@~5.1.3: version "5.1.5" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" @@ -7856,6 +7897,11 @@ json-ptr@^2.2.0: dependencies: tslib "^2.2.0" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -8982,6 +9028,11 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + minimist@1.x, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -9082,6 +9133,13 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mkdirp@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + integrity sha1-HXMHam35hs2TROFecfzAWkyavxI= + dependencies: + minimist "0.0.8" + "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -9350,6 +9408,13 @@ nodejs-websocket@^1.7.2: resolved "https://registry.yarnpkg.com/nodejs-websocket/-/nodejs-websocket-1.7.2.tgz#94abd1e248f57d4d1c663dec3831015c6dad98a6" integrity sha512-PFX6ypJcCNDs7obRellR0DGTebfUhw1SXGKe2zpB+Ng1DQJhdzbzx1ob+AvJCLzy2TJF4r8cCDqMQqei1CZdPQ== +nopt@3.0.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -9501,6 +9566,11 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -10867,6 +10937,11 @@ process-nextick-args@~1.0.6: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= +progress@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= + progress@^2.0.1, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -11033,7 +11108,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.3.2: +punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -11068,6 +11143,11 @@ puppeteer@5.4.1: unbzip2-stream "^1.3.3" ws "^7.2.3" +q@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.0.1.tgz#11872aeedee89268110b10a718448ffb10112a14" + integrity sha1-EYcq7t7okmgRCxCnGESP+xARKhQ= + q@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" @@ -11095,7 +11175,7 @@ qs@^6.4.0, qs@^6.6.0: dependencies: side-channel "^1.0.4" -qs@~6.5.2: +qs@~6.5.1, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== @@ -11485,6 +11565,13 @@ replace-homedir@^1.0.0: is-absolute "^1.0.0" remove-trailing-separator "^1.1.0" +request-progress@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-0.3.1.tgz#0721c105d8a96ac6b2ce8b2c89ae2d5ecfcf6b3a" + integrity sha1-ByHBBdipasayzossia4tXs/Pazo= + dependencies: + throttleit "~0.0.2" + request@^2.87.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -11511,6 +11598,32 @@ request@^2.87.0, request@^2.88.2: tunnel-agent "^0.6.0" uuid "^3.3.2" +request@~2.87.0: + version "2.87.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -11820,7 +11933,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -11872,6 +11985,7 @@ sauce-connect-launcher@^1.2.4: "sauce-connect@https://saucelabs.com/downloads/sc-4.6.2-linux.tar.gz": version "0.0.0" + uid "7b7f35433af9c3380758e048894d7b9aecf3754e" resolved "https://saucelabs.com/downloads/sc-4.6.2-linux.tar.gz#7b7f35433af9c3380758e048894d7b9aecf3754e" saucelabs@^1.5.0: @@ -12981,6 +13095,11 @@ text-table@0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +throttleit@~0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" + integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= + through2-filter@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" @@ -13128,6 +13247,13 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== + dependencies: + punycode "^1.4.1" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -13700,7 +13826,7 @@ uuid@8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^3.0.0, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.0, uuid@^3.1.0, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -14236,25 +14362,25 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmldom@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a" - integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g== - xmldom@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e" integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA== +xmldom@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== + xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= -xpath@^0.0.27: - version "0.0.27" - resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" - integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== +xpath@^0.0.32: + version "0.0.32" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" + integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== xregexp@2.0.0: version "2.0.0"