Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build(common): add global UMD export to locale files #33504

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/common/locales/BUILD.bazel
Expand Up @@ -18,13 +18,20 @@ npm_package(
# Workaround for `.d.ts`` containing `/// <amd-module .../>`
# which are generated in TypeScript v2.9, but not before.
"/// <amd-module name=.*/>": "",

# 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
# dependencies in case require was called.
# 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"],
)
9 changes: 8 additions & 1 deletion packages/common/test/i18n/format_date_spec.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
9 changes: 8 additions & 1 deletion packages/common/test/i18n/format_number_spec.ts
Expand Up @@ -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(() => {
Expand All @@ -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', () => {
Expand Down
36 changes: 35 additions & 1 deletion packages/common/test/i18n/locale_data_api_spec.ts
Expand Up @@ -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);
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
9 changes: 8 additions & 1 deletion packages/common/test/i18n/localization_spec.ts
Expand Up @@ -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';
Expand All @@ -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',
Expand Down
8 changes: 8 additions & 0 deletions packages/common/test/pipes/date_pipe_spec.ts
Expand Up @@ -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;
Expand All @@ -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');
Expand Down
8 changes: 8 additions & 0 deletions packages/common/test/pipes/number_pipe_spec.ts
Expand Up @@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than exporting ɵLOCALE_DATA from the @angular/core could we:

  1. export clearLocaleData() method and use it in the afterAll functions
  2. export getLocaleData as ɵgetLocaleData in case it is needed. (optional)
  3. move registerLocaleData from @angular/common to @angular/core and than export it as ɵregisterLocaleData.
  4. Have @angular/common re-export ɵregisterLocaleData as registerLocaleData so that it maintains backwards compatibility with public API.


{
describe('Number pipes', () => {
Expand All @@ -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;
Expand Down
37 changes: 34 additions & 3 deletions packages/core/src/i18n/locale_data_api.ts
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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 = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these checks just bail if the locale object is not present?

How strict should these checks be? If someone decides to set ng.common.locale to null or 5, for instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right - we should just bail. And we don't need to worry about using typeof to prevent crashes because the global is guaranteed to exist.


// 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (const l in global.ng.common.locale) {
for (const l of Object.keys(global.ng.common.locale)) {

Probably won't be a problem but for..in walks the prototype chain. Object.entries would probably be cleaner overall but it would require a polyfill.

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, '-');
}