diff --git a/.gitignore b/.gitignore index 7a597d9a6c..140aed39fc 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,5 @@ 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 06b2dd27e1..d95a9d3b42 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -62,3 +62,18 @@ 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 e4779fe862..96c7d95b92 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,6 +20,3 @@ 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 c6c055ac3b..bb2926d081 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,7 @@ "@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", @@ -173,8 +174,7 @@ "browserstacktunnel-wrapper": "^2.0.4", "check-side-effects": "0.0.23", "clang-format": "^1.4.0", - "cldr": "7.0.0", - "cldr-data-downloader": "^0.3.5", + "cldr": "5.7.0", "cldrjs": "0.5.5", "cli-progress": "^3.7.0", "conventional-changelog": "^3.1.24", @@ -218,6 +218,5 @@ "@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 new file mode 100644 index 0000000000..c795b94d5a --- /dev/null +++ b/packages/common/locales/generate-locales-tool/BUILD.bazel @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000000..03a7cb4b43 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/array-deduplication.ts @@ -0,0 +1,35 @@ +/** + * @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 new file mode 100644 index 0000000000..302190b3f1 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/bin/BUILD.bazel @@ -0,0 +1,33 @@ +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 new file mode 100644 index 0000000000..8398497afc --- /dev/null +++ b/packages/common/locales/generate-locales-tool/bin/base-locale.ts @@ -0,0 +1,17 @@ +/** + * @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 new file mode 100644 index 0000000000..94aa782ada --- /dev/null +++ b/packages/common/locales/generate-locales-tool/bin/get-base-currencies-file.ts @@ -0,0 +1,23 @@ +/** + * @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 new file mode 100644 index 0000000000..97089a509b --- /dev/null +++ b/packages/common/locales/generate-locales-tool/bin/get-base-locale-file.ts @@ -0,0 +1,25 @@ +/** + * @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 new file mode 100644 index 0000000000..0f597fd891 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/bin/get-closure-locale-file.ts @@ -0,0 +1,25 @@ +/** + * @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 new file mode 100644 index 0000000000..2d40023cf6 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/bin/write-locale-files-to-dist.ts @@ -0,0 +1,71 @@ +/** + * @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 new file mode 100644 index 0000000000..3346c118d8 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/cldr-data.bzl @@ -0,0 +1,48 @@ +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 new file mode 100644 index 0000000000..ea11843301 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/cldr-data.ts @@ -0,0 +1,140 @@ +/** + * @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'; + +/** + * Instance providing access to a locale's CLDR data. This type extends the `cldrjs` + * instance type with the missing `bundle` attribute property. + */ +export type CldrLocaleData = CldrStatic&{ + attributes: { + /** + * Resolved bundle name for the locale. + * More details: http://www.unicode.org/reports/tr35/#Bundle_vs_Item_Lookup + */ + bundle: string; + } +}; + +/** + * 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//`. */ + readonly cldrDataDir = runfiles.resolve('cldr_data'); + + /** List of all available locales CLDR provides data for. */ + readonly availableLocales: readonly CldrLocaleData[]; + + constructor() { + this._loadAndPopulateCldrData(); + this.availableLocales = this._getAvailableLocales(); + } + + /** Gets the CLDR data for the specified locale. */ + getLocaleData(localeName: string): CldrLocaleData|null { + // Cast to `CldrLocaleData` because the default `cldrjs` types from `DefinitelyTyped` + // are outdated and do not capture the `bundle` attribute. See: + // https://github.com/rxaviers/cldrjs#instantiate-a-locale-and-get-it-normalized. + const localeData = new cldrjs(localeName) as CldrLocaleData; + + // In case a locale has been requested for which no data is available, we return + // `null` immediately instead of returning an empty `CldrStatic` instance. + if (localeData.attributes.bundle === null) { + return null; + } + + return localeData; + } + + /** Gets a list of all locales CLDR provides data for. */ + private _getAvailableLocales(): CldrLocaleData[] { + const allLocales = + require(`${this.cldrDataDir}/${CLDR_AVAILABLE_LOCALES_PATH}`).availableLocales.full; + const localesWithData: CldrLocaleData[] = []; + + for (const localeName of allLocales) { + const localeData = this.getLocaleData(localeName); + + // 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 !== null) { + localesWithData.push(localeData); + } + } + + return localesWithData; + } + + /** 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 new file mode 100644 index 0000000000..810b960a6f --- /dev/null +++ b/packages/common/locales/generate-locales-tool/closure-locale-file.ts @@ -0,0 +1,153 @@ +/** + * @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 new file mode 100644 index 0000000000..f2c9a1df07 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/day-periods.ts @@ -0,0 +1,75 @@ +/** + * @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 {CldrLocaleData} from './cldr-data'; + +/** + * Returns data for the chosen day periods + */ +export function getDayPeriods(localeData: CldrLocaleData, 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: CldrLocaleData): {[key: string]: []} { + const dayPeriodRules = + localeData.get(`supplemental/dayPeriodRuleSet/${localeData.attributes.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]; + } + }); + } + + return rules; +} + + +/** + * Returns the basic day periods (am/pm) + */ +export function getDayPeriodsAmPm(localeData: CldrLocaleData) { + return getDayPeriods(localeData, ['am', 'pm']); +} + +/** + * Returns the extra day periods (without am/pm) + */ +export function getDayPeriodsNoAmPm(localeData: CldrLocaleData) { + 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 new file mode 100644 index 0000000000..a631cb1cf0 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/file-header.ts @@ -0,0 +1,17 @@ +/** + * @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 new file mode 100644 index 0000000000..fc3cc19dac --- /dev/null +++ b/packages/common/locales/generate-locales-tool/locale-base-currencies.ts @@ -0,0 +1,74 @@ +/** + * @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 {CldrLocaleData} from './cldr-data'; +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: CldrLocaleData) { + 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: CldrLocaleData) { + 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 new file mode 100644 index 0000000000..34fbae370b --- /dev/null +++ b/packages/common/locales/generate-locales-tool/locale-currencies.ts @@ -0,0 +1,87 @@ +/** + * @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 {CldrLocaleData} from './cldr-data'; +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: CldrLocaleData, 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: CldrLocaleData) { + 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 new file mode 100644 index 0000000000..48f2b20091 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/locale-extra-file.ts @@ -0,0 +1,57 @@ +/** + * @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 {removeDuplicates} from './array-deduplication'; +import {CldrLocaleData} from './cldr-data'; +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: CldrLocaleData) { + 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: CldrLocaleData) { + 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 new file mode 100644 index 0000000000..9fd8cf7e08 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/locale-file.ts @@ -0,0 +1,225 @@ +/** + * @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 {removeDuplicates} from './array-deduplication'; +import {CldrLocaleData} from './cldr-data'; +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: CldrLocaleData, 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: CldrLocaleData, 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: CldrLocaleData): '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: CldrLocaleData) { + 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: CldrLocaleData) { + 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: CldrLocaleData) { + 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: CldrLocaleData) { + 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: CldrLocaleData) { + 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: CldrLocaleData) { + // 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 new file mode 100644 index 0000000000..656c095f8a --- /dev/null +++ b/packages/common/locales/generate-locales-tool/locale-global-file.ts @@ -0,0 +1,42 @@ +/** + * @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 {CldrLocaleData} from './cldr-data'; +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: CldrLocaleData, 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 new file mode 100644 index 0000000000..2dcc9fc394 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/object-stringify.ts @@ -0,0 +1,30 @@ +/** + * @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 new file mode 100644 index 0000000000..9cda1d6294 --- /dev/null +++ b/packages/common/locales/generate-locales-tool/plural-function.ts @@ -0,0 +1,36 @@ +/** + * @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 deleted file mode 100644 index 5d1ce7802f..0000000000 --- a/tools/gulp-tasks/cldr.js +++ /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 - */ - -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 deleted file mode 100644 index dc68ab210d..0000000000 --- a/tools/gulp-tasks/cldr/cldr-data.js +++ /dev/null @@ -1,86 +0,0 @@ -// 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 deleted file mode 100644 index bec583e83b..0000000000 --- a/tools/gulp-tasks/cldr/cldr-urls.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "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 deleted file mode 100644 index 631d045a3d..0000000000 --- a/tools/gulp-tasks/cldr/closure.js +++ /dev/null @@ -1,163 +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 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 deleted file mode 100644 index aef0ac0001..0000000000 --- a/tools/gulp-tasks/cldr/extract.js +++ /dev/null @@ -1,632 +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 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 deleted file mode 100644 index 4b6c005f73..0000000000 --- a/tools/gulp-tasks/cldr/util.js +++ /dev/null @@ -1,182 +0,0 @@ -// 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 0130b2d196..2c21b76aa5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1620,6 +1620,11 @@ 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,11 +2137,6 @@ 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" @@ -2210,16 +2210,6 @@ 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" @@ -2724,7 +2714,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.6.0, aws4@^1.8.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== @@ -3511,33 +3501,20 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -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== +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== dependencies: - 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" + escodegen "^1.12.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.6.0" - xpath "^0.0.32" + xmldom "^0.3.0" + xpath "^0.0.27" cldrjs@0.5.5: version "0.5.5" @@ -3691,11 +3668,6 @@ 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" @@ -3796,7 +3768,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.5, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, 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== @@ -5263,7 +5235,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.8.1: +escodegen@^1.12.0, 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== @@ -5530,7 +5502,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.1, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, 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== @@ -5589,11 +5561,6 @@ 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" @@ -5972,7 +5939,7 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.3.1, form-data@~2.3.2: +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== @@ -6651,14 +6618,6 @@ 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" @@ -7902,11 +7861,6 @@ 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" @@ -9033,11 +8987,6 @@ 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" @@ -9138,13 +9087,6 @@ 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" @@ -9413,13 +9355,6 @@ 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" @@ -9580,11 +9515,6 @@ 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" @@ -10970,11 +10900,6 @@ 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" @@ -11141,7 +11066,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.4.1: +punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -11176,11 +11101,6 @@ 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" @@ -11208,7 +11128,7 @@ qs@^6.4.0, qs@^6.6.0: dependencies: side-channel "^1.0.4" -qs@~6.5.1, qs@~6.5.2: +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== @@ -11598,13 +11518,6 @@ 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" @@ -11631,32 +11544,6 @@ 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" @@ -11966,7 +11853,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.1, 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.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== @@ -12018,7 +11905,6 @@ 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: @@ -13128,11 +13014,6 @@ 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" @@ -13280,13 +13161,6 @@ 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" @@ -13859,7 +13733,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.1.0, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.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== @@ -14395,25 +14269,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.32: - version "0.0.32" - resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" - integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== +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== xregexp@2.0.0: version "2.0.0"