From 219e4cb725f5805c8a0175a07cd85679e82031e2 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 31 Oct 2019 12:55:54 +0000 Subject: [PATCH] feat(common): support loading locales from a global To support compile time localization, we need to be able to provide the locales via a well known global property `ng.common.locale`. This commit changes `findLocaleData()` so that it will attempt to read the local from the global if the locale has not already been registered. --- packages/common/public_api.ts | 1 - packages/common/src/common.ts | 2 +- packages/common/src/i18n/locale_data.ts | 45 --------- packages/common/src/i18n/locale_data_api.ts | 85 ++++++++-------- packages/common/test/i18n/format_date_spec.ts | 41 ++++---- .../common/test/i18n/format_number_spec.ts | 99 ++++++++++--------- .../common/test/i18n/locale_data_api_spec.ts | 45 ++------- .../common/test/i18n/localization_spec.ts | 13 +-- packages/common/test/pipes/date_pipe_spec.ts | 11 +-- .../common/test/pipes/number_pipe_spec.ts | 15 +-- packages/core/src/core_private_export.ts | 4 +- packages/core/src/i18n/locale_data.ts | 14 +++ packages/core/src/i18n/locale_data_api.ts | 75 +++++++++++--- .../core/test/i18n/locale_data_api_spec.ts | 80 +++++++++++++++ 14 files changed, 299 insertions(+), 231 deletions(-) delete mode 100644 packages/common/src/i18n/locale_data.ts create mode 100644 packages/core/test/i18n/locale_data_api_spec.ts diff --git a/packages/common/public_api.ts b/packages/common/public_api.ts index 308ff0cf653b8..7dfd92837baaf 100644 --- a/packages/common/public_api.ts +++ b/packages/common/public_api.ts @@ -12,6 +12,5 @@ * Entry point for all public APIs of this package. */ export * from './src/common'; -export {registerLocaleData as ɵregisterLocaleData} from './src/i18n/locale_data'; // This file only reexports content of the `src` folder. Keep it that way. diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts index fa37614273d6a..e3274df565a3a 100644 --- a/packages/common/src/common.ts +++ b/packages/common/src/common.ts @@ -16,7 +16,7 @@ export * from './location/index'; export {formatDate} from './i18n/format_date'; export {formatCurrency, formatNumber, formatPercent} from './i18n/format_number'; export {NgLocaleLocalization, NgLocalization} from './i18n/localization'; -export {registerLocaleData} from './i18n/locale_data'; +export {ɵregisterLocaleData as registerLocaleData} from '@angular/core'; export {Plural, NumberFormatStyle, FormStyle, Time, TranslationWidth, FormatWidth, NumberSymbol, WeekDay, getNumberOfCurrencyDigits, getCurrencySymbol, getLocaleDayPeriods, getLocaleDayNames, getLocaleMonthNames, getLocaleId, getLocaleEraNames, getLocaleWeekEndRange, getLocaleFirstDayOfWeek, getLocaleDateFormat, getLocaleDateTimeFormat, getLocaleExtraDayPeriodRules, getLocaleExtraDayPeriods, getLocalePluralCase, getLocaleTimeFormat, getLocaleNumberSymbol, getLocaleNumberFormat, getLocaleCurrencyName, getLocaleCurrencySymbol} from './i18n/locale_data_api'; export {parseCookieValue as ɵparseCookieValue} from './cookie'; export {CommonModule} from './common_module'; diff --git a/packages/common/src/i18n/locale_data.ts b/packages/common/src/i18n/locale_data.ts deleted file mode 100644 index 975dcc0b69e80..0000000000000 --- a/packages/common/src/i18n/locale_data.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @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 {ɵLOCALE_DATA as LOCALE_DATA, ɵLocaleDataIndex as LocaleDataIndex} from '@angular/core'; - -/** - * Register global data to be used internally by Angular. See the - * ["I18n guide"](guide/i18n#i18n-pipes) to know how to import additional locale data. - * - * @publicApi - */ -// 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; - } -} - -/** - * 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} diff --git a/packages/common/src/i18n/locale_data_api.ts b/packages/common/src/i18n/locale_data_api.ts index 072add5903c93..d804c02e4500b 100644 --- a/packages/common/src/i18n/locale_data_api.ts +++ b/packages/common/src/i18n/locale_data_api.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵLocaleDataIndex as LocaleDataIndex, ɵfindLocaleData as findLocaleData, ɵgetLocalePluralCase} from '@angular/core'; +import {ɵCurrencyIndex, ɵExtraLocaleDataIndex, ɵLocaleDataIndex, ɵfindLocaleData, ɵgetLocalePluralCase} from '@angular/core'; import {CURRENCIES_EN, CurrenciesSymbols} from './currencies'; -import {CurrencyIndex, ExtraLocaleDataIndex} from './locale_data'; /** * Format styles that can be used to represent numbers. @@ -217,7 +216,7 @@ export enum WeekDay { * @publicApi */ export function getLocaleId(locale: string): string { - return findLocaleData(locale)[LocaleDataIndex.LocaleId]; + return ɵfindLocaleData(locale)[ɵLocaleDataIndex.LocaleId]; } /** @@ -233,10 +232,10 @@ export function getLocaleId(locale: string): string { */ export function getLocaleDayPeriods( locale: string, formStyle: FormStyle, width: TranslationWidth): [string, string] { - const data = findLocaleData(locale); + const data = ɵfindLocaleData(locale); const amPmData = <[ string, string - ][][]>[data[LocaleDataIndex.DayPeriodsFormat], data[LocaleDataIndex.DayPeriodsStandalone]]; + ][][]>[data[ɵLocaleDataIndex.DayPeriodsFormat], data[ɵLocaleDataIndex.DayPeriodsStandalone]]; const amPm = getLastDefinedValue(amPmData, formStyle); return getLastDefinedValue(amPm, width); } @@ -255,9 +254,9 @@ export function getLocaleDayPeriods( */ export function getLocaleDayNames( locale: string, formStyle: FormStyle, width: TranslationWidth): string[] { - const data = findLocaleData(locale); + const data = ɵfindLocaleData(locale); const daysData = - [data[LocaleDataIndex.DaysFormat], data[LocaleDataIndex.DaysStandalone]]; + [data[ɵLocaleDataIndex.DaysFormat], data[ɵLocaleDataIndex.DaysStandalone]]; const days = getLastDefinedValue(daysData, formStyle); return getLastDefinedValue(days, width); } @@ -276,9 +275,9 @@ export function getLocaleDayNames( */ export function getLocaleMonthNames( locale: string, formStyle: FormStyle, width: TranslationWidth): string[] { - const data = findLocaleData(locale); + const data = ɵfindLocaleData(locale); const monthsData = - [data[LocaleDataIndex.MonthsFormat], data[LocaleDataIndex.MonthsStandalone]]; + [data[ɵLocaleDataIndex.MonthsFormat], data[ɵLocaleDataIndex.MonthsStandalone]]; const months = getLastDefinedValue(monthsData, formStyle); return getLastDefinedValue(months, width); } @@ -296,8 +295,8 @@ export function getLocaleMonthNames( * @publicApi */ export function getLocaleEraNames(locale: string, width: TranslationWidth): [string, string] { - const data = findLocaleData(locale); - const erasData = <[string, string][]>data[LocaleDataIndex.Eras]; + const data = ɵfindLocaleData(locale); + const erasData = <[string, string][]>data[ɵLocaleDataIndex.Eras]; return getLastDefinedValue(erasData, width); } @@ -313,8 +312,8 @@ export function getLocaleEraNames(locale: string, width: TranslationWidth): [str * @publicApi */ export function getLocaleFirstDayOfWeek(locale: string): WeekDay { - const data = findLocaleData(locale); - return data[LocaleDataIndex.FirstDayOfWeek]; + const data = ɵfindLocaleData(locale); + return data[ɵLocaleDataIndex.FirstDayOfWeek]; } /** @@ -327,8 +326,8 @@ export function getLocaleFirstDayOfWeek(locale: string): WeekDay { * @publicApi */ export function getLocaleWeekEndRange(locale: string): [WeekDay, WeekDay] { - const data = findLocaleData(locale); - return data[LocaleDataIndex.WeekendRange]; + const data = ɵfindLocaleData(locale); + return data[ɵLocaleDataIndex.WeekendRange]; } /** @@ -343,8 +342,8 @@ export function getLocaleWeekEndRange(locale: string): [WeekDay, WeekDay] { * @publicApi */ export function getLocaleDateFormat(locale: string, width: FormatWidth): string { - const data = findLocaleData(locale); - return getLastDefinedValue(data[LocaleDataIndex.DateFormat], width); + const data = ɵfindLocaleData(locale); + return getLastDefinedValue(data[ɵLocaleDataIndex.DateFormat], width); } /** @@ -359,8 +358,8 @@ export function getLocaleDateFormat(locale: string, width: FormatWidth): string * @publicApi */ export function getLocaleTimeFormat(locale: string, width: FormatWidth): string { - const data = findLocaleData(locale); - return getLastDefinedValue(data[LocaleDataIndex.TimeFormat], width); + const data = ɵfindLocaleData(locale); + return getLastDefinedValue(data[ɵLocaleDataIndex.TimeFormat], width); } /** @@ -375,8 +374,8 @@ export function getLocaleTimeFormat(locale: string, width: FormatWidth): string * @publicApi */ export function getLocaleDateTimeFormat(locale: string, width: FormatWidth): string { - const data = findLocaleData(locale); - const dateTimeFormatData = data[LocaleDataIndex.DateTimeFormat]; + const data = ɵfindLocaleData(locale); + const dateTimeFormatData = data[ɵLocaleDataIndex.DateTimeFormat]; return getLastDefinedValue(dateTimeFormatData, width); } @@ -391,13 +390,13 @@ export function getLocaleDateTimeFormat(locale: string, width: FormatWidth): str * @publicApi */ export function getLocaleNumberSymbol(locale: string, symbol: NumberSymbol): string { - const data = findLocaleData(locale); - const res = data[LocaleDataIndex.NumberSymbols][symbol]; + const data = ɵfindLocaleData(locale); + const res = data[ɵLocaleDataIndex.NumberSymbols][symbol]; if (typeof res === 'undefined') { if (symbol === NumberSymbol.CurrencyDecimal) { - return data[LocaleDataIndex.NumberSymbols][NumberSymbol.Decimal]; + return data[ɵLocaleDataIndex.NumberSymbols][NumberSymbol.Decimal]; } else if (symbol === NumberSymbol.CurrencyGroup) { - return data[LocaleDataIndex.NumberSymbols][NumberSymbol.Group]; + return data[ɵLocaleDataIndex.NumberSymbols][NumberSymbol.Group]; } } return res; @@ -439,8 +438,8 @@ export function getLocaleNumberSymbol(locale: string, symbol: NumberSymbol): str * @publicApi */ export function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): string { - const data = findLocaleData(locale); - return data[LocaleDataIndex.NumberFormats][type]; + const data = ɵfindLocaleData(locale); + return data[ɵLocaleDataIndex.NumberFormats][type]; } /** @@ -455,8 +454,8 @@ export function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): * @publicApi */ export function getLocaleCurrencySymbol(locale: string): string|null { - const data = findLocaleData(locale); - return data[LocaleDataIndex.CurrencySymbol] || null; + const data = ɵfindLocaleData(locale); + return data[ɵLocaleDataIndex.CurrencySymbol] || null; } /** @@ -470,8 +469,8 @@ export function getLocaleCurrencySymbol(locale: string): string|null { * @publicApi */ export function getLocaleCurrencyName(locale: string): string|null { - const data = findLocaleData(locale); - return data[LocaleDataIndex.CurrencyName] || null; + const data = ɵfindLocaleData(locale); + return data[ɵLocaleDataIndex.CurrencyName] || null; } /** @@ -481,8 +480,8 @@ export function getLocaleCurrencyName(locale: string): string|null { * @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n) */ function getLocaleCurrencies(locale: string): {[code: string]: CurrenciesSymbols} { - const data = findLocaleData(locale); - return data[LocaleDataIndex.Currencies]; + const data = ɵfindLocaleData(locale); + return data[ɵLocaleDataIndex.Currencies]; } /** @@ -493,9 +492,9 @@ export const getLocalePluralCase: (locale: string) => ((value: number) => Plural ɵgetLocalePluralCase; function checkFullData(data: any) { - if (!data[LocaleDataIndex.ExtraData]) { + if (!data[ɵLocaleDataIndex.ExtraData]) { throw new Error( - `Missing extra locale data for the locale "${data[LocaleDataIndex.LocaleId]}". Use "registerLocaleData" to load new data. See the "I18n guide" on angular.io to know more.`); + `Missing extra locale data for the locale "${data[ɵLocaleDataIndex.LocaleId]}". Use "registerLocaleData" to load new data. See the "I18n guide" on angular.io to know more.`); } } @@ -522,9 +521,9 @@ function checkFullData(data: any) { * @publicApi */ export function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Time])[] { - const data = findLocaleData(locale); + const data = ɵfindLocaleData(locale); checkFullData(data); - const rules = data[LocaleDataIndex.ExtraData][ExtraLocaleDataIndex.ExtraDayPeriodsRules] || []; + const rules = data[ɵLocaleDataIndex.ExtraData][ɵExtraLocaleDataIndex.ExtraDayPeriodsRules] || []; return rules.map((rule: string | [string, string]) => { if (typeof rule === 'string') { return extractTime(rule); @@ -552,11 +551,11 @@ export function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Tim */ export function getLocaleExtraDayPeriods( locale: string, formStyle: FormStyle, width: TranslationWidth): string[] { - const data = findLocaleData(locale); + const data = ɵfindLocaleData(locale); checkFullData(data); const dayPeriodsData = [ - data[LocaleDataIndex.ExtraData][ExtraLocaleDataIndex.ExtraDayPeriodFormats], - data[LocaleDataIndex.ExtraData][ExtraLocaleDataIndex.ExtraDayPeriodStandalone] + data[ɵLocaleDataIndex.ExtraData][ɵExtraLocaleDataIndex.ExtraDayPeriodFormats], + data[ɵLocaleDataIndex.ExtraData][ɵExtraLocaleDataIndex.ExtraDayPeriodStandalone] ]; const dayPeriods = getLastDefinedValue(dayPeriodsData, formStyle) || []; return getLastDefinedValue(dayPeriods, width) || []; @@ -621,13 +620,13 @@ function extractTime(time: string): Time { */ export function getCurrencySymbol(code: string, format: 'wide' | 'narrow', locale = 'en'): string { const currency = getLocaleCurrencies(locale)[code] || CURRENCIES_EN[code] || []; - const symbolNarrow = currency[CurrencyIndex.SymbolNarrow]; + const symbolNarrow = currency[ɵCurrencyIndex.SymbolNarrow]; if (format === 'narrow' && typeof symbolNarrow === 'string') { return symbolNarrow; } - return currency[CurrencyIndex.Symbol] || code; + return currency[ɵCurrencyIndex.Symbol] || code; } // Most currencies have cents, that's why the default is 2 @@ -647,7 +646,7 @@ export function getNumberOfCurrencyDigits(code: string): number { let digits; const currency = CURRENCIES_EN[code]; if (currency) { - digits = currency[CurrencyIndex.NbOfDigits]; + digits = currency[ɵCurrencyIndex.NbOfDigits]; } return typeof digits === 'number' ? digits : DEFAULT_NB_OF_CURRENCY_DIGITS; } diff --git a/packages/common/test/i18n/format_date_spec.ts b/packages/common/test/i18n/format_date_spec.ts index 085f81375ad7e..f9f6e1e2381f3 100644 --- a/packages/common/test/i18n/format_date_spec.ts +++ b/packages/common/test/i18n/format_date_spec.ts @@ -5,8 +5,6 @@ * 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 {registerLocaleData} from '@angular/common'; import localeAr from '@angular/common/locales/ar'; import localeDe from '@angular/common/locales/de'; import localeEn from '@angular/common/locales/en'; @@ -15,7 +13,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, ɵclearLocaleData, ɵregisterLocaleData} from '@angular/core'; describe('Format date', () => { describe('toDate', () => { @@ -52,18 +50,21 @@ describe('Format date', () => { // Check the transformation of a date into a pattern function expectDateFormatAs(date: Date | string, pattern: any, output: string): void { - expect(formatDate(date, pattern, DEFAULT_LOCALE_ID)).toEqual(output, `pattern: "${pattern}"`); + expect(formatDate(date, pattern, ɵDEFAULT_LOCALE_ID)) + .toEqual(output, `pattern: "${pattern}"`); } beforeAll(() => { - registerLocaleData(localeEn, localeEnExtra); - registerLocaleData(localeDe); - registerLocaleData(localeHu); - registerLocaleData(localeSr); - registerLocaleData(localeTh); - registerLocaleData(localeAr); + ɵregisterLocaleData(localeEn, localeEnExtra); + ɵregisterLocaleData(localeDe); + ɵregisterLocaleData(localeHu); + ɵregisterLocaleData(localeSr); + ɵregisterLocaleData(localeTh); + ɵregisterLocaleData(localeAr); }); + afterAll(() => ɵclearLocaleData()); + beforeEach(() => { date = new Date(2015, 5, 15, 9, 3, 1, 550); }); it('should format each component correctly', () => { @@ -209,7 +210,7 @@ describe('Format date', () => { }; Object.keys(dateFixtures).forEach((pattern: string) => { - expect(formatDate(date, pattern, DEFAULT_LOCALE_ID, '+0430')) + expect(formatDate(date, pattern, ɵDEFAULT_LOCALE_ID, '+0430')) .toMatch(dateFixtures[pattern]); }); }); @@ -254,22 +255,22 @@ describe('Format date', () => { }; Object.keys(dateFixtures).forEach((pattern: string) => { - expect(formatDate(date, pattern, DEFAULT_LOCALE_ID)).toMatch(dateFixtures[pattern]); + expect(formatDate(date, pattern, ɵDEFAULT_LOCALE_ID)).toMatch(dateFixtures[pattern]); }); }); it('should format invalid in IE ISO date', - () => expect(formatDate('2017-01-11T12:00:00.014-0500', defaultFormat, DEFAULT_LOCALE_ID)) + () => expect(formatDate('2017-01-11T12:00:00.014-0500', defaultFormat, ɵDEFAULT_LOCALE_ID)) .toEqual('Jan 11, 2017')); it('should format invalid in Safari ISO date', - () => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, DEFAULT_LOCALE_ID)) + () => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, ɵDEFAULT_LOCALE_ID)) .toEqual('Jan 20, 2017')); // https://github.com/angular/angular/issues/9524 // https://github.com/angular/angular/issues/9524 it('should format correctly with iso strings that contain time', - () => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', DEFAULT_LOCALE_ID)) + () => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', ɵDEFAULT_LOCALE_ID)) .toMatch(/07-05-2017 \d{2}:\d{2}/)); // https://github.com/angular/angular/issues/21491 @@ -277,22 +278,22 @@ describe('Format date', () => { // this test only works if the timezone is not in UTC // which is the case for BrowserStack when we test Safari if (new Date().getTimezoneOffset() !== 0) { - expect(formatDate('2018-01-11T13:00:00', 'HH', DEFAULT_LOCALE_ID)) - .not.toEqual(formatDate('2018-01-11T13:00:00Z', 'HH', DEFAULT_LOCALE_ID)); + expect(formatDate('2018-01-11T13:00:00', 'HH', ɵDEFAULT_LOCALE_ID)) + .not.toEqual(formatDate('2018-01-11T13:00:00Z', 'HH', ɵDEFAULT_LOCALE_ID)); } }); // https://github.com/angular/angular/issues/16624 // https://github.com/angular/angular/issues/17478 it('should show the correct time when the timezone is fixed', () => { - expect(formatDate('2017-06-13T10:14:39+0000', 'shortTime', DEFAULT_LOCALE_ID, '+0000')) + expect(formatDate('2017-06-13T10:14:39+0000', 'shortTime', ɵDEFAULT_LOCALE_ID, '+0000')) .toEqual('10:14 AM'); - expect(formatDate('2017-06-13T10:14:39+0000', 'h:mm a', DEFAULT_LOCALE_ID, '+0000')) + expect(formatDate('2017-06-13T10:14:39+0000', 'h:mm a', ɵDEFAULT_LOCALE_ID, '+0000')) .toEqual('10:14 AM'); }); it('should remove bidi control characters', - () => expect(formatDate(date, 'MM/dd/yyyy', DEFAULT_LOCALE_ID) !.length).toEqual(10)); + () => expect(formatDate(date, 'MM/dd/yyyy', ɵDEFAULT_LOCALE_ID) !.length).toEqual(10)); it(`should format the date correctly in various locales`, () => { expect(formatDate(date, 'short', 'de')).toEqual('15.06.15, 09:03'); diff --git a/packages/common/test/i18n/format_number_spec.ts b/packages/common/test/i18n/format_number_spec.ts index 9a8bd07b99432..a7ccf75a0ac60 100644 --- a/packages/common/test/i18n/format_number_spec.ts +++ b/packages/common/test/i18n/format_number_spec.ts @@ -10,33 +10,35 @@ import localeEn from '@angular/common/locales/en'; import localeEsUS from '@angular/common/locales/es-US'; import localeFr from '@angular/common/locales/fr'; import localeAr from '@angular/common/locales/ar'; -import {formatCurrency, formatNumber, formatPercent, registerLocaleData} from '@angular/common'; +import {formatCurrency, formatNumber, formatPercent} 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, ɵclearLocaleData, ɵregisterLocaleData} from '@angular/core'; describe('Format number', () => { beforeAll(() => { - registerLocaleData(localeEn); - registerLocaleData(localeEsUS); - registerLocaleData(localeFr); - registerLocaleData(localeAr); + ɵregisterLocaleData(localeEn); + ɵregisterLocaleData(localeEsUS); + ɵregisterLocaleData(localeFr); + ɵregisterLocaleData(localeAr); }); + afterAll(() => ɵclearLocaleData()); + describe('Number', () => { describe('transform', () => { it('should return correct value for numbers', () => { - expect(formatNumber(12345, DEFAULT_LOCALE_ID)).toEqual('12,345'); - expect(formatNumber(123, DEFAULT_LOCALE_ID, '.2')).toEqual('123.00'); - expect(formatNumber(1, DEFAULT_LOCALE_ID, '3.')).toEqual('001'); - expect(formatNumber(1.1, DEFAULT_LOCALE_ID, '3.4-5')).toEqual('001.1000'); - expect(formatNumber(1.123456, DEFAULT_LOCALE_ID, '3.4-5')).toEqual('001.12346'); - expect(formatNumber(1.1234, DEFAULT_LOCALE_ID)).toEqual('1.123'); - expect(formatNumber(1.123456, DEFAULT_LOCALE_ID, '.2')).toEqual('1.123'); - expect(formatNumber(1.123456, DEFAULT_LOCALE_ID, '.4')).toEqual('1.1235'); + expect(formatNumber(12345, ɵDEFAULT_LOCALE_ID)).toEqual('12,345'); + expect(formatNumber(123, ɵDEFAULT_LOCALE_ID, '.2')).toEqual('123.00'); + expect(formatNumber(1, ɵDEFAULT_LOCALE_ID, '3.')).toEqual('001'); + expect(formatNumber(1.1, ɵDEFAULT_LOCALE_ID, '3.4-5')).toEqual('001.1000'); + expect(formatNumber(1.123456, ɵDEFAULT_LOCALE_ID, '3.4-5')).toEqual('001.12346'); + expect(formatNumber(1.1234, ɵDEFAULT_LOCALE_ID)).toEqual('1.123'); + expect(formatNumber(1.123456, ɵDEFAULT_LOCALE_ID, '.2')).toEqual('1.123'); + expect(formatNumber(1.123456, ɵDEFAULT_LOCALE_ID, '.4')).toEqual('1.1235'); }); it('should throw if minFractionDigits is explicitly higher than maxFractionDigits', () => { - expect(() => formatNumber(1.1, DEFAULT_LOCALE_ID, '3.4-2')) + expect(() => formatNumber(1.1, ɵDEFAULT_LOCALE_ID, '3.4-2')) .toThrowError(/is higher than the maximum/); }); }); @@ -50,27 +52,27 @@ describe('Format number', () => { describe('Percent', () => { describe('transform', () => { it('should return correct value for numbers', () => { - expect(formatPercent(1.23, DEFAULT_LOCALE_ID)).toEqual('123%'); - expect(formatPercent(1.2, DEFAULT_LOCALE_ID, '.2')).toEqual('120.00%'); - expect(formatPercent(1.2, DEFAULT_LOCALE_ID, '4.2')).toEqual('0,120.00%'); + expect(formatPercent(1.23, ɵDEFAULT_LOCALE_ID)).toEqual('123%'); + expect(formatPercent(1.2, ɵDEFAULT_LOCALE_ID, '.2')).toEqual('120.00%'); + expect(formatPercent(1.2, ɵDEFAULT_LOCALE_ID, '4.2')).toEqual('0,120.00%'); expect(formatPercent(1.2, 'fr', '4.2')).toEqual('0 120,00 %'); expect(formatPercent(1.2, 'ar', '4.2')).toEqual('0,120.00‎%‎'); // see issue #20136 - expect(formatPercent(0.12345674, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('12.345674%'); - expect(formatPercent(0, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('0%'); - expect(formatPercent(0.00, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('0%'); - expect(formatPercent(1, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('100%'); - expect(formatPercent(0.1, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('10%'); - expect(formatPercent(0.12, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('12%'); - expect(formatPercent(0.123, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('12.3%'); - expect(formatPercent(12.3456, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('1,234.56%'); - expect(formatPercent(12.345600, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('1,234.56%'); - expect(formatPercent(12.345699999, DEFAULT_LOCALE_ID, '0.0-6')).toEqual('1,234.57%'); - expect(formatPercent(12.345699999, DEFAULT_LOCALE_ID, '0.4-6')).toEqual('1,234.5700%'); - expect(formatPercent(100, DEFAULT_LOCALE_ID, '0.4-6')).toEqual('10,000.0000%'); - expect(formatPercent(100, DEFAULT_LOCALE_ID, '0.0-10')).toEqual('10,000%'); - expect(formatPercent(1.5e2, DEFAULT_LOCALE_ID)).toEqual('15,000%'); - expect(formatPercent(1e100, DEFAULT_LOCALE_ID)).toEqual('1E+102%'); + expect(formatPercent(0.12345674, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('12.345674%'); + expect(formatPercent(0, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('0%'); + expect(formatPercent(0.00, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('0%'); + expect(formatPercent(1, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('100%'); + expect(formatPercent(0.1, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('10%'); + expect(formatPercent(0.12, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('12%'); + expect(formatPercent(0.123, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('12.3%'); + expect(formatPercent(12.3456, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('1,234.56%'); + expect(formatPercent(12.345600, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('1,234.56%'); + expect(formatPercent(12.345699999, ɵDEFAULT_LOCALE_ID, '0.0-6')).toEqual('1,234.57%'); + expect(formatPercent(12.345699999, ɵDEFAULT_LOCALE_ID, '0.4-6')).toEqual('1,234.5700%'); + expect(formatPercent(100, ɵDEFAULT_LOCALE_ID, '0.4-6')).toEqual('10,000.0000%'); + expect(formatPercent(100, ɵDEFAULT_LOCALE_ID, '0.0-10')).toEqual('10,000%'); + expect(formatPercent(1.5e2, ɵDEFAULT_LOCALE_ID)).toEqual('15,000%'); + expect(formatPercent(1e100, ɵDEFAULT_LOCALE_ID)).toEqual('1E+102%'); }); }); }); @@ -79,16 +81,16 @@ describe('Format number', () => { const defaultCurrencyCode = 'USD'; describe('transform', () => { it('should return correct value for numbers', () => { - expect(formatCurrency(123, DEFAULT_LOCALE_ID, '$')).toEqual('$123.00'); - expect(formatCurrency(12, DEFAULT_LOCALE_ID, 'EUR', 'EUR', '.1')).toEqual('EUR12.0'); + expect(formatCurrency(123, ɵDEFAULT_LOCALE_ID, '$')).toEqual('$123.00'); + expect(formatCurrency(12, ɵDEFAULT_LOCALE_ID, 'EUR', 'EUR', '.1')).toEqual('EUR12.0'); expect(formatCurrency( - 5.1234, DEFAULT_LOCALE_ID, defaultCurrencyCode, defaultCurrencyCode, '.0-3')) + 5.1234, ɵDEFAULT_LOCALE_ID, defaultCurrencyCode, defaultCurrencyCode, '.0-3')) .toEqual('USD5.123'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, defaultCurrencyCode)).toEqual('USD5.12'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, '$')).toEqual('$5.12'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'CA$')).toEqual('CA$5.12'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, '$')).toEqual('$5.12'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, '$', defaultCurrencyCode, '5.2-2')) + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, defaultCurrencyCode)).toEqual('USD5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, '$')).toEqual('$5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'CA$')).toEqual('CA$5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, '$')).toEqual('$5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, '$', defaultCurrencyCode, '5.2-2')) .toEqual('$00,005.12'); expect(formatCurrency(5.1234, 'fr', '$', defaultCurrencyCode, '5.2-2')) .toEqual('00 005,12 $'); @@ -97,21 +99,22 @@ describe('Format number', () => { it('should support any currency code name', () => { // currency code is unknown, default formatting options will be used - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'unexisting_ISO_code')) + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'unexisting_ISO_code')) .toEqual('unexisting_ISO_code5.12'); // currency code is USD, the pipe will format based on USD but will display "Custom name" - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'Custom name')).toEqual('Custom name5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'Custom name')) + .toEqual('Custom name5.12'); }); it('should round to the default number of digits if no digitsInfo', () => { // IDR has a default number of digits of 0 - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'IDR', 'IDR')).toEqual('IDR5'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'IDR', 'IDR', '.2')).toEqual('IDR5.12'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'Custom name', 'IDR')) + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'IDR', 'IDR')).toEqual('IDR5'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'IDR', 'IDR', '.2')).toEqual('IDR5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'Custom name', 'IDR')) .toEqual('Custom name5'); // BHD has a default number of digits of 3 - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'BHD', 'BHD')).toEqual('BHD5.123'); - expect(formatCurrency(5.1234, DEFAULT_LOCALE_ID, 'BHD', 'BHD', '.1-2')).toEqual('BHD5.12'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'BHD', 'BHD')).toEqual('BHD5.123'); + expect(formatCurrency(5.1234, ɵDEFAULT_LOCALE_ID, 'BHD', 'BHD', '.1-2')).toEqual('BHD5.12'); }); }); }); diff --git a/packages/common/test/i18n/locale_data_api_spec.ts b/packages/common/test/i18n/locale_data_api_spec.ts index 6babf0bd2080b..c0c0fbd32c11e 100644 --- a/packages/common/test/i18n/locale_data_api_spec.ts +++ b/packages/common/test/i18n/locale_data_api_spec.ts @@ -6,56 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵfindLocaleData as findLocaleData} from '@angular/core'; -import localeCaESVALENCIA from '@angular/common/locales/ca-ES-VALENCIA'; +import {ɵclearLocaleData, ɵregisterLocaleData} from '@angular/core'; 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 {registerLocaleData} from '../../src/i18n/locale_data'; import {getCurrencySymbol, getLocaleDateFormat, FormatWidth, getNumberOfCurrencyDigits} from '../../src/i18n/locale_data_api'; { describe('locale data api', () => { beforeAll(() => { - registerLocaleData(localeCaESVALENCIA); - registerLocaleData(localeEn); - registerLocaleData(localeFr); - registerLocaleData(localeFrCA); - registerLocaleData(localeFr, 'fake-id'); - registerLocaleData(localeFrCA, 'fake_Id2'); - registerLocaleData(localeZh); - registerLocaleData(localeEnAU); + ɵregisterLocaleData(localeEn); + ɵregisterLocaleData(localeFr); + ɵregisterLocaleData(localeZh); + ɵregisterLocaleData(localeEnAU); }); - 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); - }); - }); + afterAll(() => { ɵclearLocaleData(); }); describe('getting currency symbol', () => { it('should return the correct symbol', () => { diff --git a/packages/common/test/i18n/localization_spec.ts b/packages/common/test/i18n/localization_spec.ts index 66d8f7d6b6c4d..eba3ceaf63ea7 100644 --- a/packages/common/test/i18n/localization_spec.ts +++ b/packages/common/test/i18n/localization_spec.ts @@ -10,20 +10,21 @@ 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, ɵclearLocaleData, ɵregisterLocaleData} 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'; { describe('l10n', () => { beforeAll(() => { - registerLocaleData(localeRo); - registerLocaleData(localeSr); - registerLocaleData(localeZgh); - registerLocaleData(localeFr); + ɵregisterLocaleData(localeRo); + ɵregisterLocaleData(localeSr); + ɵregisterLocaleData(localeZgh); + ɵregisterLocaleData(localeFr); }); + afterAll(() => ɵclearLocaleData()); + 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..ed12392a17b35 100644 --- a/packages/common/test/pipes/date_pipe_spec.ts +++ b/packages/common/test/pipes/date_pipe_spec.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {DatePipe, registerLocaleData} from '@angular/common'; +import {DatePipe} from '@angular/common'; 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 {ɵclearLocaleData, ɵregisterLocaleData} from '@angular/core'; { let date: Date; @@ -18,12 +19,8 @@ import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_refle const isoStringWithoutTime = '2015-01-01'; let pipe: DatePipe; - // Check the transformation of a date into a pattern - function expectDateFormatAs(date: Date | string, pattern: any, output: string): void { - expect(pipe.transform(date, pattern)).toEqual(output); - } - - beforeAll(() => { registerLocaleData(localeEn, localeEnExtra); }); + beforeAll(() => ɵregisterLocaleData(localeEn, localeEnExtra)); + afterAll(() => ɵclearLocaleData()); beforeEach(() => { date = new Date(2015, 5, 15, 9, 3, 1, 550); diff --git a/packages/common/test/pipes/number_pipe_spec.ts b/packages/common/test/pipes/number_pipe_spec.ts index 3dee0a97f146f..dfe8e0466e333 100644 --- a/packages/common/test/pipes/number_pipe_spec.ts +++ b/packages/common/test/pipes/number_pipe_spec.ts @@ -11,19 +11,22 @@ import localeEsUS from '@angular/common/locales/es-US'; import localeFr from '@angular/common/locales/fr'; 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 {ɵclearLocaleData, ɵregisterLocaleData} from '@angular/core'; +import {CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common'; import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; { describe('Number pipes', () => { beforeAll(() => { - registerLocaleData(localeEn); - registerLocaleData(localeEsUS); - registerLocaleData(localeFr); - registerLocaleData(localeAr); - registerLocaleData(localeDeAt); + ɵregisterLocaleData(localeEn); + ɵregisterLocaleData(localeEsUS); + ɵregisterLocaleData(localeFr); + ɵregisterLocaleData(localeAr); + ɵregisterLocaleData(localeDeAt); }); + afterAll(() => ɵclearLocaleData()); + describe('DecimalPipe', () => { describe('transform', () => { let pipe: DecimalPipe; diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 07f37b8684176..7712a9030dd88 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -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 {clearLocaleData as ɵclearLocaleData, 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'; diff --git a/packages/core/src/i18n/locale_data.ts b/packages/core/src/i18n/locale_data.ts index 734cc166804be..875b6f53d107b 100644 --- a/packages/core/src/i18n/locale_data.ts +++ b/packages/core/src/i18n/locale_data.ts @@ -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} diff --git a/packages/core/src/i18n/locale_data_api.ts b/packages/core/src/i18n/locale_data_api.ts index cc30c203484c2..9dc6a58d38946 100644 --- a/packages/core/src/i18n/locale_data_api.ts +++ b/packages/core/src/i18n/locale_data_api.ts @@ -8,18 +8,27 @@ 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 - * for a given locale. - * @param locale A locale code for the locale format rules to use. - * @returns The plural function for the locale. - * @see `NgPlural` - * @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n) + * 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 getLocalePluralCase(locale: string): (value: number) => number { - const data = findLocaleData(locale); - return data[LocaleDataIndex.PluralCase]; +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; + } } /** @@ -30,17 +39,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 +59,44 @@ export function findLocaleData(locale: string): any { 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. + * @param locale A locale code for the locale format rules to use. + * @returns The plural function for the locale. + * @see `NgPlural` + * @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n) + */ +export function getLocalePluralCase(locale: string): (value: number) => number { + const data = findLocaleData(locale); + return data[LocaleDataIndex.PluralCase]; +} + + + +/** + * Helper function get the given `normalizedLocale` from `LOCALE_DATA` or + * from the global `ng.common.locale`. + */ +export function getLocaleData(normalizedLocale: string): any { + if (!(normalizedLocale in LOCALE_DATA)) { + LOCALE_DATA[normalizedLocale] = global.ng && global.ng.common && global.ng.common.locale && + global.ng.common.locale[normalizedLocale]; + } + return LOCALE_DATA[normalizedLocale]; +} + +/** + * Helper function to remove all the locale data from `LOCALE_DATA`. + */ +export function clearLocaleData() { + 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, '-'); +} \ No newline at end of file diff --git a/packages/core/test/i18n/locale_data_api_spec.ts b/packages/core/test/i18n/locale_data_api_spec.ts new file mode 100644 index 0000000000000..3176d13206532 --- /dev/null +++ b/packages/core/test/i18n/locale_data_api_spec.ts @@ -0,0 +1,80 @@ +/** + * @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 {clearLocaleData, findLocaleData, registerLocaleData} from '../../src/i18n/locale_data_api'; +import {global} from '../../src/util/global'; + +{ + describe('locale data api', () => { + const localeCaESVALENCIA: any[] = ['ca-ES-VALENCIA']; + const localeDe: any[] = ['de']; + const localeDeCH: any[] = ['de-CH']; + const localeEn: any[] = ['en']; + const localeFr: any[] = ['fr']; + const localeFrCA: any[] = ['fr-CA']; + const localeZh: any[] = ['zh']; + const localeEnAU: any[] = ['en-AU']; + const fakeGlobalFr: any[] = ['fr']; + + beforeAll(() => { + registerLocaleData(localeCaESVALENCIA); + registerLocaleData(localeEn); + registerLocaleData(localeFr); + registerLocaleData(localeFrCA); + registerLocaleData(localeFr, 'fake-id'); + 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; + }); + + afterAll(() => { + clearLocaleData(); + delete global.ng.common.locale; + }); + + 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); + }); + + 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 find the registered LOCALE_DATA even if the same locale is on the global object', + () => { expect(findLocaleData('fr')).not.toBe(fakeGlobalFr); }); + }); + }); +}