feat(ivy): use i18n locale data to determine the plural form of ICU expressions (#29249)
Plural ICU expressions depend on the locale (different languages have different plural forms). Until now the locale was hard coded as `en-US`. For compatibility reasons, if you use ivy with AOT and bootstrap your app with `bootstrapModule` then the `LOCALE_ID` token will be set automatically for ivy, which is then used to get the correct plural form. If you use JIT, you need to define the `LOCALE_ID` provider on the module that you bootstrap. For `TestBed` you can use either `configureTestingModule` or `overrideProvider` to define that provider. If you don't use the compat mode and start your app with `renderComponent` you need to call `ɵsetLocaleId` manually to define the `LOCALE_ID` before bootstrap. We expect this to change once we start adding the new i18n APIs, so don't rely on this function (there's a reason why it's a private export). PR Close #29249
This commit is contained in:
parent
6a1441f727
commit
6a8cca7975
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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';
|
||||
|
@ -17,7 +18,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';
|
||||
|
@ -41,7 +41,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private referencesRegistry: ReferencesRegistry, private isCore: boolean,
|
||||
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
|
||||
private defaultImportRecorder: DefaultImportRecorder) {}
|
||||
private defaultImportRecorder: DefaultImportRecorder, private localeId?: string) {}
|
||||
|
||||
readonly precedence = HandlerPrecedence.PRIMARY;
|
||||
|
||||
|
@ -247,7 +247,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
ngModuleStatements.push(callExpr.toStmt());
|
||||
}
|
||||
}
|
||||
return [
|
||||
const res: CompileResult[] = [
|
||||
{
|
||||
name: 'ngModuleDef',
|
||||
initializer: ngModuleDef.expression,
|
||||
|
@ -259,8 +259,19 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
initializer: ngInjectorDef.expression,
|
||||
statements: ngInjectorDef.statements,
|
||||
type: ngInjectorDef.type,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
if (this.localeId) {
|
||||
res.push({
|
||||
name: 'ngLocaleIdDef',
|
||||
initializer: new LiteralExpr(this.localeId),
|
||||
statements: [],
|
||||
type: STRING_TYPE
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private _toR3Reference(
|
||||
|
|
|
@ -447,7 +447,8 @@ export class NgtscProgram implements api.Program {
|
|||
this.options.strictInjectionParameters || false),
|
||||
new NgModuleDecoratorHandler(
|
||||
this.reflector, evaluator, scopeRegistry, referencesRegistry, this.isCore,
|
||||
this.routeAnalyzer, this.refEmitter, this.defaultImportTracker),
|
||||
this.routeAnalyzer, this.refEmitter, this.defaultImportTracker,
|
||||
this.options.i18nInLocale),
|
||||
new PipeDecoratorHandler(
|
||||
this.reflector, evaluator, scopeRegistry, this.defaultImportTracker, this.isCore),
|
||||
];
|
||||
|
|
|
@ -15,6 +15,7 @@ import {getCompilerFacade} from './compiler/compiler_facade';
|
|||
import {Console} from './console';
|
||||
import {Injectable, InjectionToken, Injector, StaticProvider} from './di';
|
||||
import {ErrorHandler} from './error_handler';
|
||||
import {LOCALE_ID} from './i18n/tokens';
|
||||
import {Type} from './interface/type';
|
||||
import {COMPILER_OPTIONS, CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||
import {ComponentFactory, ComponentRef} from './linker/component_factory';
|
||||
|
@ -25,6 +26,7 @@ import {isComponentResourceResolutionQueueEmpty, resolveComponentResources} from
|
|||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
||||
import {assertNgModuleType} from './render3/assert';
|
||||
import {ComponentFactory as R3ComponentFactory} from './render3/component_ref';
|
||||
import {DEFAULT_LOCALE_ID, setLocaleId} from './render3/i18n';
|
||||
import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
|
||||
import {Testability, TestabilityRegistry} from './testability/testability';
|
||||
import {isDevMode} from './util/is_dev_mode';
|
||||
|
@ -261,6 +263,9 @@ export class PlatformRef {
|
|||
if (!exceptionHandler) {
|
||||
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
|
||||
}
|
||||
// If the `LOCALE_ID` provider is defined at bootstrap we set the value for runtime i18n (ivy)
|
||||
const localeId = moduleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
|
||||
setLocaleId(localeId);
|
||||
moduleRef.onDestroy(() => remove(this._modules, moduleRef));
|
||||
ngZone !.runOutsideAngular(
|
||||
() => ngZone !.onError.subscribe(
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -136,6 +136,8 @@ export {
|
|||
ɵɵi18nPostprocess,
|
||||
i18nConfigureLocalize as ɵi18nConfigureLocalize,
|
||||
ɵɵi18nLocalize,
|
||||
setLocaleId as ɵsetLocaleId,
|
||||
DEFAULT_LOCALE_ID as ɵDEFAULT_LOCALE_ID,
|
||||
setClassMetadata as ɵsetClassMetadata,
|
||||
ɵɵresolveWindow,
|
||||
ɵɵresolveDocument,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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}".`);
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -60,7 +60,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.
|
||||
|
|
|
@ -17,7 +17,7 @@ import {noSideEffects} from '../util/closure';
|
|||
import {stringify} from '../util/stringify';
|
||||
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
|
||||
import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields';
|
||||
import {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
|
||||
|
@ -738,3 +738,7 @@ export function getNgModuleDef<T>(type: any, throwNotFound?: boolean): NgModuleD
|
|||
}
|
||||
return ngModuleDef;
|
||||
}
|
||||
|
||||
export function getNgLocaleIdDef(type: any): string|null {
|
||||
return (type as any)[NG_LOCALE_ID_DEF] || null;
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
|
||||
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {attachI18nOpCodesDebug} from './debug';
|
||||
import {ɵɵelementAttribute, ɵɵload, ɵɵtextBinding} from './instructions/all';
|
||||
|
@ -1026,351 +1025,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(<string>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
|
||||
*
|
||||
|
@ -1382,9 +1036,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');
|
||||
|
@ -1630,7 +1282,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} = {}) {
|
||||
|
@ -1641,3 +1293,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;
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ export {
|
|||
} from './state';
|
||||
|
||||
export {
|
||||
DEFAULT_LOCALE_ID,
|
||||
ɵɵi18n,
|
||||
ɵɵi18nAttributes,
|
||||
ɵɵi18nExp,
|
||||
|
@ -109,6 +110,8 @@ export {
|
|||
ɵɵi18nPostprocess,
|
||||
i18nConfigureLocalize,
|
||||
ɵɵi18nLocalize,
|
||||
getLocaleId,
|
||||
setLocaleId,
|
||||
} from './i18n';
|
||||
|
||||
export {NgModuleFactory, NgModuleRef, NgModuleType} from './ng_module_ref';
|
||||
|
|
|
@ -99,7 +99,6 @@ export function compileNgModuleDefs(moduleType: NgModuleType, ngModule: NgModule
|
|||
ngDevMode && assertDefined(moduleType, 'Required value moduleType');
|
||||
ngDevMode && assertDefined(ngModule, 'Required value ngModule');
|
||||
const declarations: Type<any>[] = flatten(ngModule.declarations || EMPTY_ARRAY);
|
||||
|
||||
let ngModuleDef: any = null;
|
||||
Object.defineProperty(moduleType, NG_MODULE_DEF, {
|
||||
configurable: true,
|
||||
|
|
|
@ -17,7 +17,8 @@ import {NgModuleDef} from '../metadata/ng_module';
|
|||
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<T = any> extends Type<T> { ngModuleDef: NgModuleDef<T>; }
|
||||
|
@ -44,6 +45,11 @@ export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> 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[] = [
|
||||
{
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -8,15 +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 {ivyEnabled} from '@angular/private/testing';
|
||||
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
import {NoopNgZone} from '../src/zone/ng_zone';
|
||||
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
||||
|
@ -326,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', () => {
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ContentChild, ContentChildren, Directive, QueryList, TemplateRef, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
|
||||
import {registerLocaleData} from '@angular/common';
|
||||
import {Component, ContentChild, ContentChildren, Directive, LOCALE_ID, QueryList, TemplateRef, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core';
|
||||
import localeRo from '@angular/common/locales/ro';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
|
@ -637,4 +639,45 @@ onlyInIvy('Ivy i18n logic').describe('i18n', function() {
|
|||
expect(child.innerHTML).toBe(`<li>Section 1</li><li>Section 2</li><li>Section 3</li>`);
|
||||
}
|
||||
});
|
||||
|
||||
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 template = `
|
||||
{count, plural,
|
||||
=0 {no email}
|
||||
=one {one email}
|
||||
=few {a few emails}
|
||||
=other {lots of emails}
|
||||
}`;
|
||||
const fixture = getFixtureWithOverrides({template});
|
||||
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 2-->');
|
||||
|
||||
// Change detection cycle, no model changes
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 2-->');
|
||||
|
||||
fixture.componentInstance.count = 0;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
|
||||
|
||||
fixture.componentInstance.count = 1;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('one email<!--ICU 2-->');
|
||||
|
||||
fixture.componentInstance.count = 10;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('a few emails<!--ICU 2-->');
|
||||
|
||||
fixture.componentInstance.count = 20;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('lots of emails<!--ICU 2-->');
|
||||
|
||||
fixture.componentInstance.count = 0;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('no email<!--ICU 2-->');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ ts_library(
|
|||
"//packages/animations/browser",
|
||||
"//packages/animations/browser/testing",
|
||||
"//packages/common",
|
||||
"//packages/common/locales",
|
||||
"//packages/compiler",
|
||||
"//packages/core",
|
||||
"//packages/core/src/di/interface",
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgForOfContext} from '@angular/common';
|
||||
import {NgForOfContext, registerLocaleData} from '@angular/common';
|
||||
import localeRo from '@angular/common/locales/ro';
|
||||
|
||||
import {noop} from '../../../compiler/src/render3/view/util';
|
||||
import {Component as _Component} from '../../src/core';
|
||||
import {Component as _Component, ɵNgModuleDef as NgModuleDef, ɵɵdefineInjector} from '../../src/core';
|
||||
import {ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/definition';
|
||||
import {getTranslationForTemplate, ɵɵi18n, ɵɵi18nApply, ɵɵi18nAttributes, ɵɵi18nEnd, ɵɵi18nExp, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
|
||||
import {ɵɵallocHostVars, ɵɵbind, ɵɵelement, ɵɵelementContainerEnd, ɵɵelementContainerStart, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵnextContext, ɵɵprojection, ɵɵprojectionDef, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all';
|
||||
|
@ -17,8 +18,8 @@ import {RenderFlags} from '../../src/render3/interfaces/definition';
|
|||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n} from '../../src/render3/interfaces/i18n';
|
||||
import {AttributeMarker} from '../../src/render3/interfaces/node';
|
||||
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
||||
import {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
||||
import {getNativeByIndex, getTNode} from '../../src/render3/util/view_utils';
|
||||
|
||||
import {NgForOf, NgIf} from './common_with_def';
|
||||
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||
|
||||
|
@ -2259,4 +2260,57 @@ describe('Runtime i18n', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the correct plural form for ICU expressions when using a specific locale',
|
||||
() => {
|
||||
registerLocaleData(localeRo);
|
||||
const MSG_DIV = `{<7B>0<EFBFBD>, plural,
|
||||
=0 {no email}
|
||||
=one {one email}
|
||||
=few {a few emails}
|
||||
=other {lots of emails}
|
||||
}`;
|
||||
const ctx = {value: 0};
|
||||
|
||||
class MyAppModule {
|
||||
static ngLocaleIdDef = 'ro';
|
||||
static ngInjectorDef = ɵɵdefineInjector({factory: () => new MyAppModule()});
|
||||
static ngModuleDef: NgModuleDef<any> = { bootstrap: [] } as any;
|
||||
}
|
||||
const myAppModuleFactory = new NgModuleFactory(MyAppModule);
|
||||
const ngModuleRef = myAppModuleFactory.create(null);
|
||||
|
||||
const fixture = prepareFixture(
|
||||
() => {
|
||||
ɵɵelementStart(0, 'div');
|
||||
ɵɵi18n(1, MSG_DIV);
|
||||
ɵɵelementEnd();
|
||||
},
|
||||
() => {
|
||||
ɵɵi18nExp(ɵɵbind(ctx.value));
|
||||
ɵɵi18nApply(1);
|
||||
},
|
||||
2, 1);
|
||||
expect(fixture.html).toEqual('<div>no email<!--ICU 3--></div>');
|
||||
|
||||
// Change detection cycle, no model changes
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>no email<!--ICU 3--></div>');
|
||||
|
||||
ctx.value = 1;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>one email<!--ICU 3--></div>');
|
||||
|
||||
ctx.value = 10;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>a few emails<!--ICU 3--></div>');
|
||||
|
||||
ctx.value = 20;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>lots of emails<!--ICU 3--></div>');
|
||||
|
||||
ctx.value = 0;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>no email<!--ICU 3--></div>');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +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';
|
||||
|
@ -227,6 +227,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.
|
||||
|
@ -501,6 +504,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 {
|
||||
|
|
|
@ -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('.'));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue