diff --git a/packages/common/src/i18n/locale_data.ts b/packages/common/src/i18n/locale_data.ts index 1d3301999a..975dcc0b69 100644 --- a/packages/common/src/i18n/locale_data.ts +++ b/packages/common/src/i18n/locale_data.ts @@ -6,10 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -/** - * @publicApi - */ -export const LOCALE_DATA: {[localeId: string]: any} = {}; +import {ɵLOCALE_DATA as LOCALE_DATA, ɵLocaleDataIndex as LocaleDataIndex} from '@angular/core'; /** * Register global data to be used internally by Angular. See the @@ -33,32 +30,6 @@ export function registerLocaleData(data: any, localeId?: string | any, extraData } } -/** - * Index of each type of locale data from the locale data array - */ -export const enum LocaleDataIndex { - LocaleId = 0, - DayPeriodsFormat, - DayPeriodsStandalone, - DaysFormat, - DaysStandalone, - MonthsFormat, - MonthsStandalone, - Eras, - FirstDayOfWeek, - WeekendRange, - DateFormat, - TimeFormat, - DateTimeFormat, - NumberSymbols, - NumberFormats, - CurrencySymbol, - CurrencyName, - Currencies, - PluralCase, - ExtraData -} - /** * Index of each type of locale data from the extra locale data array */ diff --git a/packages/common/src/i18n/locale_data_api.ts b/packages/common/src/i18n/locale_data_api.ts index 46d02421cc..072add5903 100644 --- a/packages/common/src/i18n/locale_data_api.ts +++ b/packages/common/src/i18n/locale_data_api.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import localeEn from './locale_en'; -import {LOCALE_DATA, LocaleDataIndex, ExtraLocaleDataIndex, CurrencyIndex} from './locale_data'; +import {ɵLocaleDataIndex as LocaleDataIndex, ɵfindLocaleData as 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. @@ -31,7 +31,8 @@ export enum NumberFormatStyle { * @see `NgPluralCase` * @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n) * - * @publicApi */ + * @publicApi + */ export enum Plural { Zero = 0, One = 1, @@ -485,19 +486,11 @@ function getLocaleCurrencies(locale: string): {[code: string]: CurrenciesSymbols } /** - * 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) - * + * @alias core/ɵgetLocalePluralCase * @publicApi */ -export function getLocalePluralCase(locale: string): (value: number) => Plural { - const data = findLocaleData(locale); - return data[LocaleDataIndex.PluralCase]; -} +export const getLocalePluralCase: (locale: string) => ((value: number) => Plural) = + ɵgetLocalePluralCase; function checkFullData(data: any) { if (!data[LocaleDataIndex.ExtraData]) { @@ -609,37 +602,7 @@ function extractTime(time: string): Time { return {hours: +h, minutes: +m}; } -/** - * 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) - * - * @publicApi - */ -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}".`); -} /** * Retrieves the currency symbol for a given currency code. diff --git a/packages/common/test/i18n/locale_data_api_spec.ts b/packages/common/test/i18n/locale_data_api_spec.ts index 9fee387070..6babf0bd20 100644 --- a/packages/common/test/i18n/locale_data_api_spec.ts +++ b/packages/common/test/i18n/locale_data_api_spec.ts @@ -6,6 +6,7 @@ * 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 localeEn from '@angular/common/locales/en'; import localeFr from '@angular/common/locales/fr'; @@ -13,7 +14,7 @@ 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 {findLocaleData, getCurrencySymbol, getLocaleDateFormat, FormatWidth, getNumberOfCurrencyDigits} from '../../src/i18n/locale_data_api'; +import {getCurrencySymbol, getLocaleDateFormat, FormatWidth, getNumberOfCurrencyDigits} from '../../src/i18n/locale_data_api'; { describe('locale data api', () => { diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 52040dfd1e..fcb414c366 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, ExternalExpr, InvokeFunctionExpr, LiteralArrayExpr, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, Statement, WrappedNodeExpr, compileInjector, compileNgModule} from '@angular/compiler'; +import {Expression, ExternalExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, Statement, WrappedNodeExpr, compileInjector, compileNgModule} from '@angular/compiler'; +import {STRING_TYPE} from '@angular/compiler/src/output/output_ast'; import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -18,7 +19,6 @@ import {NgModuleRouteAnalyzer} from '../../routing'; import {LocalModuleScopeRegistry, ScopeData} from '../../scope'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform'; import {getSourceFile} from '../../util/src/typescript'; - import {generateSetClassMetadataCall} from './metadata'; import {ReferencesRegistry} from './references_registry'; import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, isExpressionForwardReference, toR3Reference, unwrapExpression} from './util'; @@ -43,7 +43,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler remove(this._modules, moduleRef)); ngZone !.runOutsideAngular( () => ngZone !.onError.subscribe( diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index d4f139a16e..510f4326f1 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -34,3 +34,5 @@ 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'; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 6a9e57fb18..23b3cfac9c 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -162,6 +162,8 @@ export { ɵɵi18nPostprocess, i18nConfigureLocalize as ɵi18nConfigureLocalize, ɵɵi18nLocalize, + setLocaleId as ɵsetLocaleId, + DEFAULT_LOCALE_ID as ɵDEFAULT_LOCALE_ID, setClassMetadata as ɵsetClassMetadata, ɵɵresolveWindow, ɵɵresolveDocument, diff --git a/packages/core/src/i18n/locale_data.ts b/packages/core/src/i18n/locale_data.ts new file mode 100644 index 0000000000..734cc16680 --- /dev/null +++ b/packages/core/src/i18n/locale_data.ts @@ -0,0 +1,38 @@ +/** + * @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 + */ + +/** + * This const is used to store the locale data registered with `registerLocaleData` + */ +export const LOCALE_DATA: {[localeId: string]: any} = {}; + +/** + * Index of each type of locale data from the locale data array + */ +export enum LocaleDataIndex { + LocaleId = 0, + DayPeriodsFormat, + DayPeriodsStandalone, + DaysFormat, + DaysStandalone, + MonthsFormat, + MonthsStandalone, + Eras, + FirstDayOfWeek, + WeekendRange, + DateFormat, + TimeFormat, + DateTimeFormat, + NumberSymbols, + NumberFormats, + CurrencySymbol, + CurrencyName, + Currencies, + PluralCase, + ExtraData +} diff --git a/packages/core/src/i18n/locale_data_api.ts b/packages/core/src/i18n/locale_data_api.ts new file mode 100644 index 0000000000..cc30c20348 --- /dev/null +++ b/packages/core/src/i18n/locale_data_api.ts @@ -0,0 +1,53 @@ +/** + * @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, LocaleDataIndex} from './locale_data'; +import localeEn from './locale_en'; + +/** + * 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]; +} + +/** + * 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 = 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}".`); +} diff --git a/packages/common/src/i18n/locale_en.ts b/packages/core/src/i18n/locale_en.ts similarity index 100% rename from packages/common/src/i18n/locale_en.ts rename to packages/core/src/i18n/locale_en.ts diff --git a/packages/core/src/i18n/localization.ts b/packages/core/src/i18n/localization.ts new file mode 100644 index 0000000000..0cb492f18b --- /dev/null +++ b/packages/core/src/i18n/localization.ts @@ -0,0 +1,31 @@ +/** + * @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 {getLocalePluralCase} from './locale_data_api'; + +/** + * Returns the plural case based on the locale + */ +export function getPluralCase(value: any, locale: string): string { + const plural = getLocalePluralCase(locale)(value); + + switch (plural) { + case 0: + return 'zero'; + case 1: + return 'one'; + case 2: + return 'two'; + case 3: + return 'few'; + case 4: + return 'many'; + default: + return 'other'; + } +} diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 6f436b01f8..ed6cd447e7 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -61,7 +61,7 @@ export interface CreateComponentOptions { * Typically, the features in this list are features that cannot be added to the * other features list in the component definition because they rely on other factors. * - * Example: `RootLifecycleHooks` is a function that adds lifecycle hook capabilities + * Example: `LifecycleHooksFeature` is a function that adds lifecycle hook capabilities * to root components in a tree-shakable way. It cannot be added to the component * features list because there's no way of knowing when the component will be used as * a root component. diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index b2637ef8db..ed07f6a001 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -7,7 +7,6 @@ */ import '../util/ng_dev_mode'; - import {ChangeDetectionStrategy} from '../change_detection/constants'; import {NG_INJECTABLE_DEF, ɵɵdefineInjectable} from '../di/interface/defs'; import {Mutable, Type} from '../interface/type'; @@ -16,9 +15,8 @@ import {SchemaMetadata} from '../metadata/schema'; import {ViewEncapsulation} from '../metadata/view'; import {noSideEffects} from '../util/closure'; import {stringify} from '../util/stringify'; - import {EMPTY_ARRAY, EMPTY_OBJ} from './empty'; -import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields'; +import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_LOCALE_ID_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields'; import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction, ɵɵBaseDef} from './interfaces/definition'; // while SelectorFlags is unused here, it's required so that types don't get resolved lazily // see: https://github.com/Microsoft/web-build-tools/issues/1050 @@ -779,3 +777,7 @@ export function getNgModuleDef(type: any, throwNotFound?: boolean): NgModuleD } return ngModuleDef; } + +export function getNgLocaleIdDef(type: any): string|null { + return (type as any)[NG_LOCALE_ID_DEF] || null; +} diff --git a/packages/core/src/render3/fields.ts b/packages/core/src/render3/fields.ts index 26b29af9da..92642b417f 100644 --- a/packages/core/src/render3/fields.ts +++ b/packages/core/src/render3/fields.ts @@ -12,6 +12,7 @@ export const NG_COMPONENT_DEF = getClosureSafeProperty({ngComponentDef: getClosu export const NG_DIRECTIVE_DEF = getClosureSafeProperty({ngDirectiveDef: getClosureSafeProperty}); export const NG_PIPE_DEF = getClosureSafeProperty({ngPipeDef: getClosureSafeProperty}); export const NG_MODULE_DEF = getClosureSafeProperty({ngModuleDef: getClosureSafeProperty}); +export const NG_LOCALE_ID_DEF = getClosureSafeProperty({ngLocaleIdDef: getClosureSafeProperty}); export const NG_BASE_DEF = getClosureSafeProperty({ngBaseDef: getClosureSafeProperty}); /** diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 0d921754c9..182ad42026 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -7,13 +7,12 @@ */ import '../util/ng_i18n_closure_mode'; - +import {getPluralCase} from '../i18n/localization'; import {SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent} from '../sanitization/html_sanitizer'; import {InertBodyHelper} from '../sanitization/inert_body'; import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer'; import {addAllToArray} from '../util/array_utils'; import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert'; - import {attachPatchData} from './context_discovery'; import {attachI18nOpCodesDebug} from './debug'; import {ɵɵelementAttribute, ɵɵload, ɵɵtextBinding} from './instructions/all'; @@ -1020,351 +1019,6 @@ export function ɵɵi18nApply(index: number) { } } -enum Plural { - Zero = 0, - One = 1, - Two = 2, - Few = 3, - Many = 4, - Other = 5, -} - -/** - * Returns the plural case based on the locale. - * This is a copy of the deprecated function that we used in Angular v4. - * // TODO(ocombe): remove this once we can the real getPluralCase function - * - * @deprecated from v5 the plural case function is in locale data files common/locales/*.ts - */ -function getPluralCase(locale: string, nLike: number | string): Plural { - if (typeof nLike === 'string') { - nLike = parseInt(nLike, 10); - } - const n: number = nLike as number; - const nDecimal = n.toString().replace(/^[^.]*\.?/, ''); - const i = Math.floor(Math.abs(n)); - const v = nDecimal.length; - const f = parseInt(nDecimal, 10); - const t = parseInt(n.toString().replace(/^[^.]*\.?|0+$/g, ''), 10) || 0; - - const lang = locale.split('-')[0].toLowerCase(); - - switch (lang) { - case 'af': - case 'asa': - case 'az': - case 'bem': - case 'bez': - case 'bg': - case 'brx': - case 'ce': - case 'cgg': - case 'chr': - case 'ckb': - case 'ee': - case 'el': - case 'eo': - case 'es': - case 'eu': - case 'fo': - case 'fur': - case 'gsw': - case 'ha': - case 'haw': - case 'hu': - case 'jgo': - case 'jmc': - case 'ka': - case 'kk': - case 'kkj': - case 'kl': - case 'ks': - case 'ksb': - case 'ky': - case 'lb': - case 'lg': - case 'mas': - case 'mgo': - case 'ml': - case 'mn': - case 'nb': - case 'nd': - case 'ne': - case 'nn': - case 'nnh': - case 'nyn': - case 'om': - case 'or': - case 'os': - case 'ps': - case 'rm': - case 'rof': - case 'rwk': - case 'saq': - case 'seh': - case 'sn': - case 'so': - case 'sq': - case 'ta': - case 'te': - case 'teo': - case 'tk': - case 'tr': - case 'ug': - case 'uz': - case 'vo': - case 'vun': - case 'wae': - case 'xog': - if (n === 1) return Plural.One; - return Plural.Other; - case 'ak': - case 'ln': - case 'mg': - case 'pa': - case 'ti': - if (n === Math.floor(n) && n >= 0 && n <= 1) return Plural.One; - return Plural.Other; - case 'am': - case 'as': - case 'bn': - case 'fa': - case 'gu': - case 'hi': - case 'kn': - case 'mr': - case 'zu': - if (i === 0 || n === 1) return Plural.One; - return Plural.Other; - case 'ar': - if (n === 0) return Plural.Zero; - if (n === 1) return Plural.One; - if (n === 2) return Plural.Two; - if (n % 100 === Math.floor(n % 100) && n % 100 >= 3 && n % 100 <= 10) return Plural.Few; - if (n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 99) return Plural.Many; - return Plural.Other; - case 'ast': - case 'ca': - case 'de': - case 'en': - case 'et': - case 'fi': - case 'fy': - case 'gl': - case 'it': - case 'nl': - case 'sv': - case 'sw': - case 'ur': - case 'yi': - if (i === 1 && v === 0) return Plural.One; - return Plural.Other; - case 'be': - if (n % 10 === 1 && !(n % 100 === 11)) return Plural.One; - if (n % 10 === Math.floor(n % 10) && n % 10 >= 2 && n % 10 <= 4 && - !(n % 100 >= 12 && n % 100 <= 14)) - return Plural.Few; - if (n % 10 === 0 || n % 10 === Math.floor(n % 10) && n % 10 >= 5 && n % 10 <= 9 || - n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 14) - return Plural.Many; - return Plural.Other; - case 'br': - if (n % 10 === 1 && !(n % 100 === 11 || n % 100 === 71 || n % 100 === 91)) return Plural.One; - if (n % 10 === 2 && !(n % 100 === 12 || n % 100 === 72 || n % 100 === 92)) return Plural.Two; - if (n % 10 === Math.floor(n % 10) && (n % 10 >= 3 && n % 10 <= 4 || n % 10 === 9) && - !(n % 100 >= 10 && n % 100 <= 19 || n % 100 >= 70 && n % 100 <= 79 || - n % 100 >= 90 && n % 100 <= 99)) - return Plural.Few; - if (!(n === 0) && n % 1e6 === 0) return Plural.Many; - return Plural.Other; - case 'bs': - case 'hr': - case 'sr': - if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11)) - return Plural.One; - if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 && - !(i % 100 >= 12 && i % 100 <= 14) || - f % 10 === Math.floor(f % 10) && f % 10 >= 2 && f % 10 <= 4 && - !(f % 100 >= 12 && f % 100 <= 14)) - return Plural.Few; - return Plural.Other; - case 'cs': - case 'sk': - if (i === 1 && v === 0) return Plural.One; - if (i === Math.floor(i) && i >= 2 && i <= 4 && v === 0) return Plural.Few; - if (!(v === 0)) return Plural.Many; - return Plural.Other; - case 'cy': - if (n === 0) return Plural.Zero; - if (n === 1) return Plural.One; - if (n === 2) return Plural.Two; - if (n === 3) return Plural.Few; - if (n === 6) return Plural.Many; - return Plural.Other; - case 'da': - if (n === 1 || !(t === 0) && (i === 0 || i === 1)) return Plural.One; - return Plural.Other; - case 'dsb': - case 'hsb': - if (v === 0 && i % 100 === 1 || f % 100 === 1) return Plural.One; - if (v === 0 && i % 100 === 2 || f % 100 === 2) return Plural.Two; - if (v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 3 && i % 100 <= 4 || - f % 100 === Math.floor(f % 100) && f % 100 >= 3 && f % 100 <= 4) - return Plural.Few; - return Plural.Other; - case 'ff': - case 'fr': - case 'hy': - case 'kab': - if (i === 0 || i === 1) return Plural.One; - return Plural.Other; - case 'fil': - if (v === 0 && (i === 1 || i === 2 || i === 3) || - v === 0 && !(i % 10 === 4 || i % 10 === 6 || i % 10 === 9) || - !(v === 0) && !(f % 10 === 4 || f % 10 === 6 || f % 10 === 9)) - return Plural.One; - return Plural.Other; - case 'ga': - if (n === 1) return Plural.One; - if (n === 2) return Plural.Two; - if (n === Math.floor(n) && n >= 3 && n <= 6) return Plural.Few; - if (n === Math.floor(n) && n >= 7 && n <= 10) return Plural.Many; - return Plural.Other; - case 'gd': - if (n === 1 || n === 11) return Plural.One; - if (n === 2 || n === 12) return Plural.Two; - if (n === Math.floor(n) && (n >= 3 && n <= 10 || n >= 13 && n <= 19)) return Plural.Few; - return Plural.Other; - case 'gv': - if (v === 0 && i % 10 === 1) return Plural.One; - if (v === 0 && i % 10 === 2) return Plural.Two; - if (v === 0 && - (i % 100 === 0 || i % 100 === 20 || i % 100 === 40 || i % 100 === 60 || i % 100 === 80)) - return Plural.Few; - if (!(v === 0)) return Plural.Many; - return Plural.Other; - case 'he': - if (i === 1 && v === 0) return Plural.One; - if (i === 2 && v === 0) return Plural.Two; - if (v === 0 && !(n >= 0 && n <= 10) && n % 10 === 0) return Plural.Many; - return Plural.Other; - case 'is': - if (t === 0 && i % 10 === 1 && !(i % 100 === 11) || !(t === 0)) return Plural.One; - return Plural.Other; - case 'ksh': - if (n === 0) return Plural.Zero; - if (n === 1) return Plural.One; - return Plural.Other; - case 'kw': - case 'naq': - case 'se': - case 'smn': - if (n === 1) return Plural.One; - if (n === 2) return Plural.Two; - return Plural.Other; - case 'lag': - if (n === 0) return Plural.Zero; - if ((i === 0 || i === 1) && !(n === 0)) return Plural.One; - return Plural.Other; - case 'lt': - if (n % 10 === 1 && !(n % 100 >= 11 && n % 100 <= 19)) return Plural.One; - if (n % 10 === Math.floor(n % 10) && n % 10 >= 2 && n % 10 <= 9 && - !(n % 100 >= 11 && n % 100 <= 19)) - return Plural.Few; - if (!(f === 0)) return Plural.Many; - return Plural.Other; - case 'lv': - case 'prg': - if (n % 10 === 0 || n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 19 || - v === 2 && f % 100 === Math.floor(f % 100) && f % 100 >= 11 && f % 100 <= 19) - return Plural.Zero; - if (n % 10 === 1 && !(n % 100 === 11) || v === 2 && f % 10 === 1 && !(f % 100 === 11) || - !(v === 2) && f % 10 === 1) - return Plural.One; - return Plural.Other; - case 'mk': - if (v === 0 && i % 10 === 1 || f % 10 === 1) return Plural.One; - return Plural.Other; - case 'mt': - if (n === 1) return Plural.One; - if (n === 0 || n % 100 === Math.floor(n % 100) && n % 100 >= 2 && n % 100 <= 10) - return Plural.Few; - if (n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 19) return Plural.Many; - return Plural.Other; - case 'pl': - if (i === 1 && v === 0) return Plural.One; - if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 && - !(i % 100 >= 12 && i % 100 <= 14)) - return Plural.Few; - if (v === 0 && !(i === 1) && i % 10 === Math.floor(i % 10) && i % 10 >= 0 && i % 10 <= 1 || - v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 5 && i % 10 <= 9 || - v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 12 && i % 100 <= 14) - return Plural.Many; - return Plural.Other; - case 'pt': - if (n === Math.floor(n) && n >= 0 && n <= 2 && !(n === 2)) return Plural.One; - return Plural.Other; - case 'ro': - if (i === 1 && v === 0) return Plural.One; - if (!(v === 0) || n === 0 || - !(n === 1) && n % 100 === Math.floor(n % 100) && n % 100 >= 1 && n % 100 <= 19) - return Plural.Few; - return Plural.Other; - case 'ru': - case 'uk': - if (v === 0 && i % 10 === 1 && !(i % 100 === 11)) return Plural.One; - if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 && - !(i % 100 >= 12 && i % 100 <= 14)) - return Plural.Few; - if (v === 0 && i % 10 === 0 || - v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 5 && i % 10 <= 9 || - v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 11 && i % 100 <= 14) - return Plural.Many; - return Plural.Other; - case 'shi': - if (i === 0 || n === 1) return Plural.One; - if (n === Math.floor(n) && n >= 2 && n <= 10) return Plural.Few; - return Plural.Other; - case 'si': - if (n === 0 || n === 1 || i === 0 && f === 1) return Plural.One; - return Plural.Other; - case 'sl': - if (v === 0 && i % 100 === 1) return Plural.One; - if (v === 0 && i % 100 === 2) return Plural.Two; - if (v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 3 && i % 100 <= 4 || !(v === 0)) - return Plural.Few; - return Plural.Other; - case 'tzm': - if (n === Math.floor(n) && n >= 0 && n <= 1 || n === Math.floor(n) && n >= 11 && n <= 99) - return Plural.One; - return Plural.Other; - // When there is no specification, the default is always "other" - // Spec: http://cldr.unicode.org/index/cldr-spec/plural-rules - // > other (required—general plural form — also used if the language only has a single form) - default: - return Plural.Other; - } -} - -function getPluralCategory(value: any, locale: string): string { - const plural = getPluralCase(locale, value); - - switch (plural) { - case Plural.Zero: - return 'zero'; - case Plural.One: - return 'one'; - case Plural.Two: - return 'two'; - case Plural.Few: - return 'few'; - case Plural.Many: - return 'many'; - default: - return 'other'; - } -} - /** * Returns the index of the current case of an ICU expression depending on the main binding value * @@ -1376,9 +1030,7 @@ function getCaseIndex(icuExpression: TIcu, bindingValue: string): number { if (index === -1) { switch (icuExpression.type) { case IcuType.plural: { - // TODO(ocombe): replace this hard-coded value by the real LOCALE_ID value - const locale = 'en-US'; - const resolvedCase = getPluralCategory(bindingValue, locale); + const resolvedCase = getPluralCase(bindingValue, getLocaleId()); index = icuExpression.cases.indexOf(resolvedCase); if (index === -1 && resolvedCase !== 'other') { index = icuExpression.cases.indexOf('other'); @@ -1624,7 +1276,7 @@ const LOCALIZE_PH_REGEXP = /\{\$(.*?)\}/g; * running outside of Closure Compiler. This method will not be needed once runtime translation * service support is introduced. * - * @publicApi + * @codeGenApi * @deprecated this method is temporary & should not be used as it will be removed soon */ export function ɵɵi18nLocalize(input: string, placeholders: {[key: string]: string} = {}) { @@ -1635,3 +1287,31 @@ export function ɵɵi18nLocalize(input: string, placeholders: {[key: string]: st input.replace(LOCALIZE_PH_REGEXP, (match, key) => placeholders[key] || '') : input; } + +/** + * The locale id that the application is currently using (for translations and ICU expressions). + * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine + * but is now defined as a global value. + */ +export const DEFAULT_LOCALE_ID = 'en-US'; +let LOCALE_ID = DEFAULT_LOCALE_ID; + +/** + * Sets the locale id that will be used for translations and ICU expressions. + * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine + * but is now defined as a global value. + * + * @param localeId + */ +export function setLocaleId(localeId: string) { + LOCALE_ID = localeId.toLowerCase().replace(/_/g, '-'); +} + +/** + * Gets the locale id that will be used for translations and ICU expressions. + * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine + * but is now defined as a global value. + */ +export function getLocaleId(): string { + return LOCALE_ID; +} diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index a85c4255e2..22af60ac7a 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -131,6 +131,7 @@ export { } from './state'; export { + DEFAULT_LOCALE_ID, ɵɵi18n, ɵɵi18nAttributes, ɵɵi18nExp, @@ -140,6 +141,8 @@ export { ɵɵi18nPostprocess, i18nConfigureLocalize, ɵɵi18nLocalize, + getLocaleId, + setLocaleId, } from './i18n'; export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref'; diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index e639c9b25b..a0f6145254 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -104,7 +104,6 @@ export function compileNgModuleDefs( ngDevMode && assertDefined(moduleType, 'Required value moduleType'); ngDevMode && assertDefined(ngModule, 'Required value ngModule'); const declarations: Type[] = flatten(ngModule.declarations || EMPTY_ARRAY); - let ngModuleDef: any = null; Object.defineProperty(moduleType, NG_MODULE_DEF, { configurable: true, diff --git a/packages/core/src/render3/ng_module_ref.ts b/packages/core/src/render3/ng_module_ref.ts index 07518d77f2..96b88d10ae 100644 --- a/packages/core/src/render3/ng_module_ref.ts +++ b/packages/core/src/render3/ng_module_ref.ts @@ -20,7 +20,8 @@ import {assertDefined} from '../util/assert'; import {stringify} from '../util/stringify'; import {ComponentFactoryResolver} from './component_ref'; -import {getNgModuleDef} from './definition'; +import {getNgLocaleIdDef, getNgModuleDef} from './definition'; +import {setLocaleId} from './i18n'; import {maybeUnwrapFn} from './util/misc_utils'; export interface NgModuleType extends Type { ngModuleDef: NgModuleDef; } @@ -47,6 +48,11 @@ export class NgModuleRef extends viewEngine_NgModuleRef implements Interna ngModuleDef, `NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`); + const ngLocaleIdDef = getNgLocaleIdDef(ngModuleType); + if (ngLocaleIdDef) { + setLocaleId(ngLocaleIdDef); + } + this._bootstrapComponents = maybeUnwrapFn(ngModuleDef !.bootstrap); const additionalProviders: StaticProvider[] = [ { diff --git a/packages/core/test/BUILD.bazel b/packages/core/test/BUILD.bazel index 99fa8a82cc..bc1d670deb 100644 --- a/packages/core/test/BUILD.bazel +++ b/packages/core/test/BUILD.bazel @@ -18,6 +18,7 @@ ts_library( "//packages/animations/browser", "//packages/animations/browser/testing", "//packages/common", + "//packages/common/locales", "//packages/compiler", "//packages/compiler/testing", "//packages/core", diff --git a/packages/core/test/acceptance/BUILD.bazel b/packages/core/test/acceptance/BUILD.bazel index 19c700031d..1b7f6fd370 100644 --- a/packages/core/test/acceptance/BUILD.bazel +++ b/packages/core/test/acceptance/BUILD.bazel @@ -13,6 +13,7 @@ ts_library( "//packages/animations/browser", "//packages/animations/browser/testing", "//packages/common", + "//packages/common/locales", "//packages/compiler", "//packages/compiler/testing", "//packages/core", diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 51f615d830..d5431494f8 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -6,7 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ContentChild, ContentChildren, Directive, HostBinding, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core'; +import {registerLocaleData} from '@angular/common'; +import localeRo from '@angular/common/locales/ro'; +import {Component, ContentChild, ContentChildren, Directive, HostBinding, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {onlyInIvy} from '@angular/private/testing'; @@ -549,6 +551,46 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual(`
4 animaux!
`); }); + + it('should return the correct plural form for ICU expressions when using a specific locale', + () => { + registerLocaleData(localeRo); + TestBed.configureTestingModule({providers: [{provide: LOCALE_ID, useValue: 'ro'}]}); + // We could also use `TestBed.overrideProvider(LOCALE_ID, {useValue: 'ro'});` + const fixture = initWithTemplate(AppComp, ` + {count, plural, + =0 {no email} + =one {one email} + =few {a few emails} + =other {lots of emails} + }`); + + expect(fixture.nativeElement.innerHTML).toEqual('no email'); + + // Change detection cycle, no model changes + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); + + fixture.componentInstance.count = 3; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); + + fixture.componentInstance.count = 1; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('one email'); + + fixture.componentInstance.count = 10; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('a few emails'); + + fixture.componentInstance.count = 20; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('lots of emails'); + + fixture.componentInstance.count = 0; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('no email'); + }); }); describe('should support attributes', () => { diff --git a/packages/core/test/application_ref_spec.ts b/packages/core/test/application_ref_spec.ts index 571ad7b111..3974575acd 100644 --- a/packages/core/test/application_ref_spec.ts +++ b/packages/core/test/application_ref_spec.ts @@ -8,14 +8,16 @@ import {DOCUMENT} from '@angular/common'; import {ResourceLoader} from '@angular/compiler'; -import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, InjectionToken, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; +import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, InjectionToken, LOCALE_ID, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {ApplicationRef} from '@angular/core/src/application_ref'; import {ErrorHandler} from '@angular/core/src/error_handler'; import {ComponentRef} from '@angular/core/src/linker/component_factory'; +import {getLocaleId} from '@angular/core/src/render3'; import {BrowserModule} from '@angular/platform-browser'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; +import {onlyInIvy} from '@angular/private/testing'; import {NoopNgZone} from '../src/zone/ng_zone'; import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing'; @@ -325,6 +327,22 @@ class SomeComponent { expect(loadResourceSpy).toHaveBeenCalledTimes(1); expect(loadResourceSpy).toHaveBeenCalledWith('/test-template.html'); }); + + onlyInIvy('We only need to define `LOCALE_ID` for runtime i18n') + .it('should define `LOCALE_ID`', async() => { + @Component({ + selector: 'i18n-app', + templateUrl: '', + }) + class I18nComponent { + } + + const testModule = createModule( + {component: I18nComponent, providers: [{provide: LOCALE_ID, useValue: 'ro'}]}); + await defaultPlatform.bootstrapModule(testModule); + + expect(getLocaleId()).toEqual('ro'); + }); }); describe('bootstrapModuleFactory', () => { diff --git a/packages/core/testing/src/r3_test_bed_compiler.ts b/packages/core/testing/src/r3_test_bed_compiler.ts index 3c53f11ad5..5943e89717 100644 --- a/packages/core/testing/src/r3_test_bed_compiler.ts +++ b/packages/core/testing/src/r3_test_bed_compiler.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ApplicationInitStatus, COMPILER_OPTIONS, Compiler, Component, Directive, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgZone, Injector, Pipe, PlatformRef, Provider, Type, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵgetInjectableDef as getInjectableDef, ɵNG_COMPONENT_DEF as NG_COMPONENT_DEF, ɵNG_DIRECTIVE_DEF as NG_DIRECTIVE_DEF, ɵNG_INJECTOR_DEF as NG_INJECTOR_DEF, ɵNG_MODULE_DEF as NG_MODULE_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵɵInjectableDef as InjectableDef, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵDirectiveDef as DirectiveDef, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵtransitiveScopesFor as transitiveScopesFor,} from '@angular/core'; import {ResourceLoader} from '@angular/compiler'; +import {ApplicationInitStatus, COMPILER_OPTIONS, Compiler, Component, Directive, Injector, LOCALE_ID, ModuleWithComponentFactories, NgModule, NgModuleFactory, NgZone, Pipe, PlatformRef, Provider, Type, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵDirectiveDef as DirectiveDef, ɵNG_COMPONENT_DEF as NG_COMPONENT_DEF, ɵNG_DIRECTIVE_DEF as NG_DIRECTIVE_DEF, ɵNG_INJECTOR_DEF as NG_INJECTOR_DEF, ɵNG_MODULE_DEF as NG_MODULE_DEF, ɵNG_PIPE_DEF as NG_PIPE_DEF, ɵNgModuleFactory as R3NgModuleFactory, ɵNgModuleTransitiveScopes as NgModuleTransitiveScopes, ɵNgModuleType as NgModuleType, ɵRender3ComponentFactory as ComponentFactory, ɵRender3NgModuleRef as NgModuleRef, ɵcompileComponent as compileComponent, ɵcompileDirective as compileDirective, ɵcompileNgModuleDefs as compileNgModuleDefs, ɵcompilePipe as compilePipe, ɵgetInjectableDef as getInjectableDef, ɵpatchComponentDefWithScope as patchComponentDefWithScope, ɵsetLocaleId as setLocaleId, ɵtransitiveScopesFor as transitiveScopesFor, ɵɵInjectableDef as InjectableDef} from '@angular/core'; -import {clearResolutionOfComponentResourcesQueue, restoreComponentResolutionQueue, resolveComponentResources, isComponentDefPendingResolution} from '../../src/metadata/resource_loading'; - +import {clearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution, resolveComponentResources, restoreComponentResolutionQueue} from '../../src/metadata/resource_loading'; import {MetadataOverride} from './metadata_override'; import {ComponentResolver, DirectiveResolver, NgModuleResolver, PipeResolver, Resolver} from './resolvers'; import {TestModuleMetadata} from './test_bed_common'; @@ -238,6 +237,9 @@ export class R3TestBedCompiler { const parentInjector = this.platform.injector; this.testModuleRef = new NgModuleRef(this.testModuleType, parentInjector); + // Set the locale ID, it can be overridden for the tests + const localeId = this.testModuleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID); + setLocaleId(localeId); // ApplicationInitStatus.runInitializers() is marked @internal to core. // Cast it to any before accessing it. @@ -523,6 +525,8 @@ export class R3TestBedCompiler { this.initialNgDefs.clear(); this.moduleProvidersOverridden.clear(); this.restoreComponentResolutionQueue(); + // Restore the locale ID to the default value, this shouldn't be necessary but we never know + setLocaleId(DEFAULT_LOCALE_ID); } private compileTestModule(): void { diff --git a/tools/gulp-tasks/cldr/extract.js b/tools/gulp-tasks/cldr/extract.js index 3c0e0ce16c..1a92bc8fd2 100644 --- a/tools/gulp-tasks/cldr/extract.js +++ b/tools/gulp-tasks/cldr/extract.js @@ -14,11 +14,14 @@ const cldr = require('cldr'); // used to extract all other cldr data const cldrJs = require('cldrjs'); -const PACKAGE_FOLDER = 'packages/common'; -const I18N_FOLDER = `${PACKAGE_FOLDER}/src/i18n`; -const I18N_DATA_FOLDER = `${PACKAGE_FOLDER}/locales`; +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 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 DEFAULT_RULE = 'function anonymous(n\n/*``*/) {\nreturn"other"\n}'; @@ -60,9 +63,9 @@ module.exports = (gulp, done) => { const baseCurrencies = generateBaseCurrencies(new cldrJs('en')); // additional "en" file that will be included in common - console.log(`Writing file ${I18N_FOLDER}/locale_en.ts`); + console.log(`Writing file ${I18N_CORE_FOLDER}/locale_en.ts`); const localeEnFile = generateLocale('en', new cldrJs('en'), baseCurrencies); - fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/locale_en.ts`, localeEnFile); + fs.writeFileSync(`${RELATIVE_I18N_CORE_FOLDER}/locale_en.ts`, localeEnFile); LOCALES.forEach((locale, index) => { const localeData = new cldrJs(locale); @@ -82,7 +85,7 @@ module.exports = (gulp, done) => { .src([ `${I18N_DATA_FOLDER}/**/*.ts`, `${I18N_FOLDER}/currencies.ts`, - `${I18N_FOLDER}/locale_en.ts` + `${I18N_CORE_FOLDER}/locale_en.ts` ], {base: '.'}) .pipe(format.format('file', clangFormat)) .pipe(gulp.dest('.')); diff --git a/tools/public_api_guard/common/common.d.ts b/tools/public_api_guard/common/common.d.ts index 3cded495d8..2606601215 100644 --- a/tools/public_api_guard/common/common.d.ts +++ b/tools/public_api_guard/common/common.d.ts @@ -103,7 +103,7 @@ export declare function getLocaleNumberFormat(locale: string, type: NumberFormat export declare function getLocaleNumberSymbol(locale: string, symbol: NumberSymbol): string; -export declare function getLocalePluralCase(locale: string): (value: number) => Plural; +export declare const getLocalePluralCase: (locale: string) => ((value: number) => Plural); export declare function getLocaleTimeFormat(locale: string, width: FormatWidth): string;