diff --git a/packages/core/src/application_module.ts b/packages/core/src/application_module.ts index fa8e44affb641..2030c6363173f 100644 --- a/packages/core/src/application_module.ts +++ b/packages/core/src/application_module.ts @@ -24,6 +24,8 @@ import {SCHEDULER} from './render3/component_ref'; import {setLocaleId} from './render3/i18n'; import {NgZone} from './zone'; +declare const $localize: {locale?: string}; + export function _iterableDiffersFactory() { return defaultIterableDiffers; } @@ -33,22 +35,38 @@ export function _keyValueDiffersFactory() { } export function _localeFactory(locale?: string): string { - if (locale) { - if (ivyEnabled) { - setLocaleId(locale); - } - return locale; + locale = locale || getGlobalLocale(); + if (ivyEnabled) { + setLocaleId(locale); } - // Use `goog.LOCALE` as default value for `LOCALE_ID` token for Closure Compiler. - // Note: default `goog.LOCALE` value is `en`, when Angular used `en-US`. In order to preserve - // backwards compatibility, we use Angular default value over Closure Compiler's one. + return locale; +} + +/** + * Work out the locale from the potential global properties. + * + * * Closure Compiler: use `goog.LOCALE`. + * * Ivy enabled: use `$localize.locale` + */ +export function getGlobalLocale(): string { if (ngI18nClosureMode && typeof goog !== 'undefined' && goog.LOCALE !== 'en') { - if (ivyEnabled) { - setLocaleId(goog.LOCALE); - } + // * The default `goog.LOCALE` value is `en`, while Angular used `en-US`. + // * In order to preserve backwards compatibility, we use Angular default value over + // Closure Compiler's one. return goog.LOCALE; + } else { + // KEEP `typeof $localize !== 'undefined' && $localize.locale` IN SYNC WITH THE LOCALIZE + // COMPILE-TIME INLINER. + // + // * During compile time inlining of translations the expression will be replaced + // with a string literal that is the current locale. Other forms of this expression are not + // guaranteed to be replaced. + // + // * During runtime translation evaluation, the developer is required to set `$localize.locale` + // if required, or just to provide their own `LOCALE_ID` provider. + return (ivyEnabled && typeof $localize !== 'undefined' && $localize.locale) || + DEFAULT_LOCALE_ID; } - return DEFAULT_LOCALE_ID; } /** diff --git a/packages/core/test/application_module_spec.ts b/packages/core/test/application_module_spec.ts index 640063465837f..ea97692f3f9c1 100644 --- a/packages/core/test/application_module_spec.ts +++ b/packages/core/test/application_module_spec.ts @@ -7,11 +7,74 @@ */ import {LOCALE_ID} from '@angular/core'; +import {ivyEnabled} from '@angular/private/testing'; + +import {getLocaleId} from '../src/render3'; +import {global} from '../src/util/global'; +import {TestBed} from '../testing'; import {describe, expect, inject, it} from '../testing/src/testing_internal'; { describe('Application module', () => { it('should set the default locale to "en-US"', inject([LOCALE_ID], (defaultLocale: string) => { expect(defaultLocale).toEqual('en-US'); })); + + if (ivyEnabled) { + it('should set the ivy locale with the configured LOCALE_ID', () => { + TestBed.configureTestingModule({providers: [{provide: LOCALE_ID, useValue: 'fr'}]}); + const before = getLocaleId(); + const locale = TestBed.inject(LOCALE_ID); + const after = getLocaleId(); + expect(before).toEqual('en-us'); + expect(locale).toEqual('fr'); + expect(after).toEqual('fr'); + }); + + describe('$localize.locale', () => { + beforeEach(() => initLocale('de')); + afterEach(() => restoreLocale()); + + it('should set the ivy locale to `$localize.locale` value if it is defined', () => { + // Injecting `LOCALE_ID` should also initialize the ivy locale + const locale = TestBed.inject(LOCALE_ID); + expect(locale).toEqual('de'); + expect(getLocaleId()).toEqual('de'); + }); + + it('should set the ivy locale to an application provided LOCALE_ID even if `$localize.locale` is defined', + () => { + TestBed.configureTestingModule({providers: [{provide: LOCALE_ID, useValue: 'fr'}]}); + const locale = TestBed.inject(LOCALE_ID); + expect(locale).toEqual('fr'); + expect(getLocaleId()).toEqual('fr'); + }); + }); + } }); } + +let hasGlobalLocalize: boolean; +let hasGlobalLocale: boolean; +let originalLocale: string; + +function initLocale(locale: string) { + hasGlobalLocalize = Object.getOwnPropertyNames(global).includes('$localize'); + if (!hasGlobalLocalize) { + global.$localize = {}; + } + hasGlobalLocale = Object.getOwnPropertyNames(global.$localize).includes('locale'); + originalLocale = global.$localize.locale; + global.$localize.locale = locale; +} + +function restoreLocale() { + if (hasGlobalLocale) { + global.$localize.locale = originalLocale; + } else { + delete global.$localize.locale; + } + + if (!hasGlobalLocalize) { + delete global.$localize; + } +} diff --git a/packages/localize/src/localize/src/localize.ts b/packages/localize/src/localize/src/localize.ts index ad0f31cd8e2fa..e94d9853a8cde 100644 --- a/packages/localize/src/localize/src/localize.ts +++ b/packages/localize/src/localize/src/localize.ts @@ -20,6 +20,22 @@ export interface LocalizeFn { * different translations. */ translate?: TranslateFn; + /** + * The current locale of the translated messages. + * + * The compile-time translation inliner is able to replace the following code: + * + * ``` + * $localize && $localize.locale + * ``` + * + * with a string literal of the current locale. E.g. + * + * ``` + * "fr" + * ``` + */ + locale?: string; } export interface TranslateFn {