From 4a66c148f7e3627f0d5b0642fe71c11bb88202b1 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 22 Oct 2019 09:29:35 +0100 Subject: [PATCH] feat(ivy): allow the locale to be set via a global property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ivy compile time inlining tools work on the code after it has been compiled and bundled (potentially minified and mangled). It is only at this point in the processing that the current locale can be specified. The cleanest way for the tooling to provide this locale value is by setting a well-known global property (e.g. `ɵNG_LOCALE_ID`) which is then read at runtime by Angular and provided as the `LOCALE_ID` token and also passed to the ivy machinery via `setLocaleId()`. // FW-1639 See https://github.com/angular/angular-cli/issues/15896 --- packages/core/src/application_module.ts | 30 ++++++++--- packages/core/test/application_module_spec.ts | 50 +++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/packages/core/src/application_module.ts b/packages/core/src/application_module.ts index fa8e44affb641..aa970d3605fae 100644 --- a/packages/core/src/application_module.ts +++ b/packages/core/src/application_module.ts @@ -22,6 +22,7 @@ import {Compiler} from './linker/compiler'; import {NgModule} from './metadata'; import {SCHEDULER} from './render3/component_ref'; import {setLocaleId} from './render3/i18n'; +import {global} from './util/global'; import {NgZone} from './zone'; export function _iterableDiffersFactory() { @@ -35,19 +36,34 @@ export function _keyValueDiffersFactory() { export function _localeFactory(locale?: string): string { if (locale) { if (ivyEnabled) { + // The developer has provided a value for the `LOCALE_ID` token, which overrides the one + // generated by this factory. So tell the ivy infrastructure about it. setLocaleId(locale); } return 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. - if (ngI18nClosureMode && typeof goog !== 'undefined' && goog.LOCALE !== 'en') { - if (ivyEnabled) { - setLocaleId(goog.LOCALE); + + if (ngI18nClosureMode && typeof goog !== 'undefined') { + // Use `goog.LOCALE` as default value for `LOCALE_ID` token for Closure Compiler. + + // Note: 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. + if (goog.LOCALE !== 'en') { + if (ivyEnabled) { + setLocaleId(goog.LOCALE); + } + return goog.LOCALE; } - return goog.LOCALE; + } else if (ivyEnabled) { + // The CLI tooling (e.g. translation inliner) may provide a locale by setting the ɵNG_LOCALE_ID + // global variable. + const localeId = global.ɵNG_LOCALE_ID || DEFAULT_LOCALE_ID; + setLocaleId(localeId); + return localeId; } + + // No Closure Compiler and no ivy - so just go with the default. return DEFAULT_LOCALE_ID; } diff --git a/packages/core/test/application_module_spec.ts b/packages/core/test/application_module_spec.ts index 640063465837f..7f809a4091d6d 100644 --- a/packages/core/test/application_module_spec.ts +++ b/packages/core/test/application_module_spec.ts @@ -7,11 +7,61 @@ */ 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('global.ɵNG_LOCALE_ID', () => { + let hasGlobalLocale: boolean; + let globalLocale: string; + beforeEach(() => { + hasGlobalLocale = Object.getOwnPropertyNames(global).includes('ɵNG_LOCALE_ID'); + if (hasGlobalLocale) { + globalLocale = global.ɵNG_LOCALE_ID; + } + global.ɵNG_LOCALE_ID = 'de'; + }); + afterEach(() => { + if (hasGlobalLocale) { + global.ɵNG_LOCALE_ID = globalLocale; + } else { + delete global.ɵNG_LOCALE_ID; + } + }); + + it('should set the locale to the global value', () => { + const locale = TestBed.inject(LOCALE_ID); + expect(locale).toEqual('de'); + expect(getLocaleId()).toEqual('de'); + }); + + it('should set the locale to the configured LOCALE_ID even if there is a global value', + () => { + TestBed.configureTestingModule({providers: [{provide: LOCALE_ID, useValue: 'fr'}]}); + const locale = TestBed.inject(LOCALE_ID); + expect(locale).toEqual('fr'); + expect(getLocaleId()).toEqual('fr'); + }); + }); + } }); }