refactor(common): move the low level locale registering to core (#33523)

To limit the exposure of the private `LOCALE_DATA` from outside
`@angular/core` this commit exposes private functions in the core
to hide the internal structures better.

* The `registerLocaleData()` implementation has moved from
`@angular/common` to `@angular/core`. A stub that delegates to
core has been left in common for backward compatibility.
* A new `ɵunregisterLocaleData()` function has been provided,
which is particularly useful in tests to clear out registered locales
to prevent subsequent tests from being affected.
* A private export of `ɵregisterLocaleData()` has been removed
from `@angular/common`. This was not being used and is accessible
via `@angular/core` anyway.

PR Close #33523
This commit is contained in:
Pete Bacon Darwin
2019-11-01 09:43:43 +00:00
committed by atscott
parent 5b21b71c9a
commit 7e8eec57f0
13 changed files with 200 additions and 138 deletions

View File

@ -33,6 +33,6 @@ export {makeDecorator as ɵmakeDecorator} from './util/decorators';
export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang';
export {clearOverrides as ɵclearOverrides, initServicesIfNeeded as ɵinitServicesIfNeeded, overrideComponentView as ɵoverrideComponentView, overrideProvider as ɵoverrideProvider} from './view/index';
export {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from './view/provider';
export {getLocalePluralCase as ɵgetLocalePluralCase, findLocaleData as ɵfindLocaleData} from './i18n/locale_data_api';
export {LOCALE_DATA as ɵLOCALE_DATA, LocaleDataIndex as ɵLocaleDataIndex} from './i18n/locale_data';
export {unregisterLocaleData as ɵunregisterLocaleData, getLocalePluralCase as ɵgetLocalePluralCase, findLocaleData as ɵfindLocaleData, registerLocaleData as ɵregisterLocaleData} from './i18n/locale_data_api';
export {LocaleDataIndex as ɵLocaleDataIndex, CurrencyIndex as ɵCurrencyIndex, ExtraLocaleDataIndex as ɵExtraLocaleDataIndex} from './i18n/locale_data';
export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, getSanitizationBypassType as ɵgetSanitizationBypassType, BypassType as ɵBypassType, unwrapSafeValue as ɵunwrapSafeValue, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue} from './sanitization/bypass';

View File

@ -36,3 +36,17 @@ export enum LocaleDataIndex {
PluralCase,
ExtraData
}
/**
* Index of each type of locale data from the extra locale data array
*/
export const enum ExtraLocaleDataIndex {
ExtraDayPeriodFormats = 0,
ExtraDayPeriodStandalone,
ExtraDayPeriodsRules
}
/**
* Index of each value in currency data (used to describe CURRENCIES_EN in currencies.ts)
*/
export const enum CurrencyIndex {Symbol = 0, SymbolNarrow, NbOfDigits}

View File

@ -9,6 +9,56 @@
import {LOCALE_DATA, LocaleDataIndex} from './locale_data';
import localeEn from './locale_en';
/**
* Register locale data to be used internally by Angular. See the
* ["I18n guide"](guide/i18n#i18n-pipes) to know how to import additional locale data.
*
* The signature `registerLocaleData(data: any, extraData?: any)` is deprecated since v5.1
*/
export function registerLocaleData(data: any, localeId?: string | any, extraData?: any): void {
if (typeof localeId !== 'string') {
extraData = localeId;
localeId = data[LocaleDataIndex.LocaleId];
}
localeId = localeId.toLowerCase().replace(/_/g, '-');
LOCALE_DATA[localeId] = data;
if (extraData) {
LOCALE_DATA[localeId][LocaleDataIndex.ExtraData] = extraData;
}
}
/**
* Finds the locale data for a given locale.
*
* @param locale The locale code.
* @returns The locale data.
* @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n)
*/
export function findLocaleData(locale: string): any {
const normalizedLocale = normalizeLocale(locale);
let match = getLocaleData(normalizedLocale);
if (match) {
return match;
}
// let's try to find a parent locale
const parentLocale = normalizedLocale.split('-')[0];
match = getLocaleData(parentLocale);
if (match) {
return match;
}
if (parentLocale === 'en') {
return localeEn;
}
throw new Error(`Missing locale data for the locale "${locale}".`);
}
/**
* Retrieves the plural function used by ICU expressions to determine the plural case to use
* for a given locale.
@ -22,32 +72,25 @@ export function getLocalePluralCase(locale: string): (value: number) => number {
return data[LocaleDataIndex.PluralCase];
}
/**
* Finds the locale data for a given locale.
*
* @param locale The locale code.
* @returns The locale data.
* @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n)
* Helper function to get the given `normalizedLocale` from `LOCALE_DATA`.
*/
export function findLocaleData(locale: string): any {
const normalizedLocale = locale.toLowerCase().replace(/_/g, '-');
let match = LOCALE_DATA[normalizedLocale];
if (match) {
return match;
}
// let's try to find a parent locale
const parentLocale = normalizedLocale.split('-')[0];
match = LOCALE_DATA[parentLocale];
if (match) {
return match;
}
if (parentLocale === 'en') {
return localeEn;
}
throw new Error(`Missing locale data for the locale "${locale}".`);
export function getLocaleData(normalizedLocale: string): any {
return LOCALE_DATA[normalizedLocale];
}
/**
* Helper function to remove all the locale data from `LOCALE_DATA`.
*/
export function unregisterLocaleData() {
Object.keys(LOCALE_DATA).forEach(key => delete LOCALE_DATA[key]);
}
/**
* Returns the canonical form of a locale name - lowercase with `_` replaced with `-`.
*/
function normalizeLocale(locale: string): string {
return locale.toLowerCase().replace(/_/g, '-');
}

View File

@ -0,0 +1,60 @@
/**
* @license
* Copyright Google Inc. 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 {findLocaleData, registerLocaleData, unregisterLocaleData} from '../../src/i18n/locale_data_api';
{
describe('locale data api', () => {
const localeCaESVALENCIA: any[] = ['ca-ES-VALENCIA'];
const localeEn: any[] = ['en'];
const localeFr: any[] = ['fr'];
const localeFrCA: any[] = ['fr-CA'];
const localeZh: any[] = ['zh'];
const localeEnAU: any[] = ['en-AU'];
beforeAll(() => {
registerLocaleData(localeCaESVALENCIA);
registerLocaleData(localeEn);
registerLocaleData(localeFr);
registerLocaleData(localeFrCA);
registerLocaleData(localeFr, 'fake-id');
registerLocaleData(localeFrCA, 'fake_Id2');
registerLocaleData(localeZh);
registerLocaleData(localeEnAU);
});
afterAll(() => unregisterLocaleData());
describe('findLocaleData', () => {
it('should throw if the LOCALE_DATA for the chosen locale or its parent locale is not available',
() => {
expect(() => findLocaleData('pt-AO'))
.toThrowError(/Missing locale data for the locale "pt-AO"/);
});
it('should return english data if the locale is en-US',
() => { expect(findLocaleData('en-US')).toEqual(localeEn); });
it('should return the exact LOCALE_DATA if it is available',
() => { expect(findLocaleData('fr-CA')).toEqual(localeFrCA); });
it('should return the parent LOCALE_DATA if it exists and exact locale is not available',
() => { expect(findLocaleData('fr-BE')).toEqual(localeFr); });
it(`should find the LOCALE_DATA even if the locale id is badly formatted`, () => {
expect(findLocaleData('ca-ES-VALENCIA')).toEqual(localeCaESVALENCIA);
expect(findLocaleData('CA_es_Valencia')).toEqual(localeCaESVALENCIA);
});
it(`should find the LOCALE_DATA if the locale id was registered`, () => {
expect(findLocaleData('fake-id')).toEqual(localeFr);
expect(findLocaleData('fake_iD')).toEqual(localeFr);
expect(findLocaleData('fake-id2')).toEqual(localeFrCA);
});
});
});
}