diff --git a/packages/common/locales/BUILD.bazel b/packages/common/locales/BUILD.bazel
index 3a36a04ad91c4..a1f0bd258fd70 100644
--- a/packages/common/locales/BUILD.bazel
+++ b/packages/common/locales/BUILD.bazel
@@ -18,6 +18,7 @@ npm_package(
# Workaround for `.d.ts`` containing `/// `
# which are generated in TypeScript v2.9, but not before.
"/// ": "",
+
# Workaround for https://github.com/angular/angular/issues/23217
# Webpack will detect that the UMD outputs from TypeScript pass the
# `require` function into the module, and cannot accurately track
@@ -25,6 +26,12 @@ npm_package(
# We don't actually import anything in the locale code so we can
# null out the require reference passed into the module.
"factory\(require, exports\)": "factory(null, exports)",
+
+ # Attach the locale to the global scope at `ng.common.locale...` if not UMD or CommonJS
+ "\(function \(factory\) {": "(function (root, factory) {",
+ "}\)\(function \(require, exports\) {": "})(typeof globalThis !== \"undefined\" && globalThis || typeof global !== \"undefined\" && global || typeof window !== \"undefined\" && window || this, function (require, exports) {",
+ #11111111111111111111111111111111111111111111111111111111222221111111111111111111111111111111111333333331111111111111111111111111111111111111111111114444411
+ "(if \(typeof define === \"function\" && define.amd\) {\n(\s*)define\(\"@angular/common/locales/([^\"]+)\", \[\"require\", \"exports\"], factory\);\n(\s*)})": "$1 else {\n$2if (typeof root.ng === \"undefined\") root.ng = {};if (typeof root.ng.common === \"undefined\") root.ng.common = {};if (typeof root.ng.common.locale === \"undefined\") root.ng.common.locale = {};var container = {};factory(null, container);root.ng.common.locale[\"$3\"] = container.default;\n$4}",
},
deps = [":locales"],
)
diff --git a/packages/common/test/i18n/format_date_spec.ts b/packages/common/test/i18n/format_date_spec.ts
index 085f81375ad7e..a0e78586c2998 100644
--- a/packages/common/test/i18n/format_date_spec.ts
+++ b/packages/common/test/i18n/format_date_spec.ts
@@ -15,7 +15,7 @@ import localeHu from '@angular/common/locales/hu';
import localeSr from '@angular/common/locales/sr';
import localeTh from '@angular/common/locales/th';
import {isDate, toDate, formatDate} from '@angular/common/src/i18n/format_date';
-import {ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
+import {ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵLOCALE_DATA} from '@angular/core';
describe('Format date', () => {
describe('toDate', () => {
@@ -64,6 +64,13 @@ describe('Format date', () => {
registerLocaleData(localeAr);
});
+ afterAll(() => {
+ // Clear out the loaded locales
+ for (const key in ɵLOCALE_DATA) {
+ delete ɵLOCALE_DATA[key];
+ }
+ });
+
beforeEach(() => { date = new Date(2015, 5, 15, 9, 3, 1, 550); });
it('should format each component correctly', () => {
diff --git a/packages/common/test/i18n/format_number_spec.ts b/packages/common/test/i18n/format_number_spec.ts
index 9a8bd07b99432..8b9bf3a860b32 100644
--- a/packages/common/test/i18n/format_number_spec.ts
+++ b/packages/common/test/i18n/format_number_spec.ts
@@ -12,7 +12,7 @@ import localeFr from '@angular/common/locales/fr';
import localeAr from '@angular/common/locales/ar';
import {formatCurrency, formatNumber, formatPercent, registerLocaleData} from '@angular/common';
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
-import {ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
+import {ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵLOCALE_DATA} from '@angular/core';
describe('Format number', () => {
beforeAll(() => {
@@ -22,6 +22,13 @@ describe('Format number', () => {
registerLocaleData(localeAr);
});
+ afterAll(() => {
+ // Clear out the loaded locales
+ for (const key in ɵLOCALE_DATA) {
+ delete ɵLOCALE_DATA[key];
+ }
+ });
+
describe('Number', () => {
describe('transform', () => {
it('should return correct value for numbers', () => {
diff --git a/packages/common/test/i18n/locale_data_api_spec.ts b/packages/common/test/i18n/locale_data_api_spec.ts
index 6babf0bd2080b..178574657d91d 100644
--- a/packages/common/test/i18n/locale_data_api_spec.ts
+++ b/packages/common/test/i18n/locale_data_api_spec.ts
@@ -6,18 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ɵfindLocaleData as findLocaleData} from '@angular/core';
+import {ɵLOCALE_DATA, ɵLocaleDataIndex, ɵfindLocaleData as findLocaleData, ɵglobal} from '@angular/core';
import localeCaESVALENCIA from '@angular/common/locales/ca-ES-VALENCIA';
import localeEn from '@angular/common/locales/en';
import localeFr from '@angular/common/locales/fr';
import localeZh from '@angular/common/locales/zh';
import localeFrCA from '@angular/common/locales/fr-CA';
import localeEnAU from '@angular/common/locales/en-AU';
+import localeDe from '@angular/common/locales/de';
+import localeDeCH from '@angular/common/locales/de-CH';
+import localeDeExtra from '@angular/common/locales/extra/de';
import {registerLocaleData} from '../../src/i18n/locale_data';
import {getCurrencySymbol, getLocaleDateFormat, FormatWidth, getNumberOfCurrencyDigits} from '../../src/i18n/locale_data_api';
{
describe('locale data api', () => {
+ const fakeGlobalFr: any[] = [];
beforeAll(() => {
registerLocaleData(localeCaESVALENCIA);
registerLocaleData(localeEn);
@@ -27,6 +31,19 @@ import {getCurrencySymbol, getLocaleDateFormat, FormatWidth, getNumberOfCurrency
registerLocaleData(localeFrCA, 'fake_Id2');
registerLocaleData(localeZh);
registerLocaleData(localeEnAU);
+ ɵglobal.ng = {common: {locale: {}}};
+ ɵglobal.ng.common.locale['fr'] = fakeGlobalFr;
+ ɵglobal.ng.common.locale['de'] = localeDe;
+ ɵglobal.ng.common.locale['de-CH'] = localeDeCH;
+ ɵglobal.ng.common.locale['extra/de'] = localeDeExtra;
+ });
+
+ afterAll(() => {
+ // Clear out the loaded locales
+ delete ɵglobal.ng.common.locale;
+ for (const key in ɵLOCALE_DATA) {
+ delete ɵLOCALE_DATA[key];
+ }
});
describe('findLocaleData', () => {
@@ -55,6 +72,23 @@ import {getCurrencySymbol, getLocaleDateFormat, FormatWidth, getNumberOfCurrency
expect(findLocaleData('fake_iD')).toEqual(localeFr);
expect(findLocaleData('fake-id2')).toEqual(localeFrCA);
});
+
+ it('should find the exact LOCALE_DATA if the locale is on the global object',
+ () => { expect(findLocaleData('de-CH')).toEqual(localeDeCH); });
+
+ it('should find the parent LOCALE_DATA if the exact locale is not available and the parent locale is on the global object',
+ () => { expect(findLocaleData('de-BE')).toEqual(localeDe); });
+
+ it('should add the extra LOCALE_DATA if the locale and the extra locale are on the global object',
+ () => {
+ expect(findLocaleData('de')[ɵLocaleDataIndex.ExtraData]).toEqual(localeDeExtra);
+ });
+
+ it('should not add the parent extra LOCALE_DATA if the exact extra locale is not the global object',
+ () => { expect(findLocaleData('de-CH')[ɵLocaleDataIndex.ExtraData]).toBeUndefined(); });
+
+ it('should find the registered LOCALE_DATA even if the same locale is on the global object',
+ () => { expect(findLocaleData('fr')).not.toBe(fakeGlobalFr); });
});
describe('getting currency symbol', () => {
diff --git a/packages/common/test/i18n/localization_spec.ts b/packages/common/test/i18n/localization_spec.ts
index 66d8f7d6b6c4d..1ba0d10ece209 100644
--- a/packages/common/test/i18n/localization_spec.ts
+++ b/packages/common/test/i18n/localization_spec.ts
@@ -10,7 +10,7 @@ import localeRo from '@angular/common/locales/ro';
import localeSr from '@angular/common/locales/sr';
import localeZgh from '@angular/common/locales/zgh';
import localeFr from '@angular/common/locales/fr';
-import {LOCALE_ID} from '@angular/core';
+import {LOCALE_ID, ɵLOCALE_DATA} from '@angular/core';
import {TestBed, inject} from '@angular/core/testing';
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '@angular/common/src/i18n/localization';
import {registerLocaleData} from '../../src/i18n/locale_data';
@@ -24,6 +24,13 @@ import {registerLocaleData} from '../../src/i18n/locale_data';
registerLocaleData(localeFr);
});
+ afterAll(() => {
+ // Clear out the loaded locales
+ for (const key in ɵLOCALE_DATA) {
+ delete ɵLOCALE_DATA[key];
+ }
+ });
+
describe('NgLocalization', () => {
function roTests() {
it('should return plural cases for the provided locale',
diff --git a/packages/common/test/pipes/date_pipe_spec.ts b/packages/common/test/pipes/date_pipe_spec.ts
index 5b84bb45c67b8..a862ce90984e5 100644
--- a/packages/common/test/pipes/date_pipe_spec.ts
+++ b/packages/common/test/pipes/date_pipe_spec.ts
@@ -11,6 +11,7 @@ import localeEn from '@angular/common/locales/en';
import localeEnExtra from '@angular/common/locales/extra/en';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector';
+import {ɵLOCALE_DATA} from '@angular/core';
{
let date: Date;
@@ -25,6 +26,13 @@ import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_refle
beforeAll(() => { registerLocaleData(localeEn, localeEnExtra); });
+ afterAll(() => {
+ // Clear out the loaded locales
+ for (const key in ɵLOCALE_DATA) {
+ delete ɵLOCALE_DATA[key];
+ }
+ });
+
beforeEach(() => {
date = new Date(2015, 5, 15, 9, 3, 1, 550);
pipe = new DatePipe('en-US');
diff --git a/packages/common/test/pipes/number_pipe_spec.ts b/packages/common/test/pipes/number_pipe_spec.ts
index 3dee0a97f146f..2448f86d3f7db 100644
--- a/packages/common/test/pipes/number_pipe_spec.ts
+++ b/packages/common/test/pipes/number_pipe_spec.ts
@@ -13,6 +13,7 @@ import localeAr from '@angular/common/locales/ar';
import localeDeAt from '@angular/common/locales/de-AT';
import {registerLocaleData, CurrencyPipe, DecimalPipe, PercentPipe, formatNumber} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
+import {ɵLOCALE_DATA} from '@angular/core';
{
describe('Number pipes', () => {
@@ -24,6 +25,13 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
registerLocaleData(localeDeAt);
});
+ afterAll(() => {
+ // Clear out the loaded locales
+ for (const key in ɵLOCALE_DATA) {
+ delete ɵLOCALE_DATA[key];
+ }
+ });
+
describe('DecimalPipe', () => {
describe('transform', () => {
let pipe: DecimalPipe;
diff --git a/packages/core/src/i18n/locale_data_api.ts b/packages/core/src/i18n/locale_data_api.ts
index cc30c203484c2..9f82507681c5b 100644
--- a/packages/core/src/i18n/locale_data_api.ts
+++ b/packages/core/src/i18n/locale_data_api.ts
@@ -8,6 +8,7 @@
import {LOCALE_DATA, LocaleDataIndex} from './locale_data';
import localeEn from './locale_en';
+import {global} from '../util/global';
/**
* Retrieves the plural function used by ICU expressions to determine the plural case to use
@@ -30,16 +31,16 @@ export function getLocalePluralCase(locale: string): (value: number) => number {
* @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n)
*/
export function findLocaleData(locale: string): any {
- const normalizedLocale = locale.toLowerCase().replace(/_/g, '-');
+ const normalizedLocale = normalizeLocale(locale);
- let match = LOCALE_DATA[normalizedLocale];
+ let match = getLocaleData(normalizedLocale);
if (match) {
return match;
}
// let's try to find a parent locale
const parentLocale = normalizedLocale.split('-')[0];
- match = LOCALE_DATA[parentLocale];
+ match = getLocaleData(parentLocale);
if (match) {
return match;
@@ -51,3 +52,33 @@ export function findLocaleData(locale: string): any {
throw new Error(`Missing locale data for the locale "${locale}".`);
}
+
+function getLocaleData(normalizedLocale: string): any {
+ if (normalizedLocale in LOCALE_DATA) {
+ return LOCALE_DATA[normalizedLocale];
+ }
+
+ if (typeof global.ng === 'undefined') global.ng = {};
+ if (typeof global.ng.common === 'undefined') global.ng.common = {};
+ if (typeof global.ng.common.locale === 'undefined') global.ng.common.locale = {};
+
+ // The locale names on the global object are not normalized, so we have to do a search.
+ // This is only once per requested locale; after that it is cached on LOCALE_DATA.
+ // Also generally only one or very few locales should be loaded onto the global.
+ for (const l in global.ng.common.locale) {
+ if (normalizeLocale(l) === normalizedLocale) {
+ const localeData = LOCALE_DATA[normalizedLocale] = global.ng.common.locale[l];
+ if (localeData !== undefined) {
+ localeData[LocaleDataIndex.ExtraData] = global.ng.common.locale[`extra/${l}`];
+ return localeData;
+ }
+ }
+ }
+
+ return undefined;
+}
+
+
+function normalizeLocale(locale: string): string {
+ return locale.toLowerCase().replace(/_/g, '-');
+}
\ No newline at end of file