From 6b675d65fae6e46b326c900b75ee3c6c94e4d037 Mon Sep 17 00:00:00 2001 From: stemda Date: Wed, 5 Feb 2020 16:27:28 +0100 Subject: [PATCH] feat(core): add option for lazy loaded modules to extend translations Fixes #425 --- README.md | 4 +++ .../core/src/lib/translate.service.ts | 12 ++++---- projects/ngx-translate/core/src/public_api.ts | 5 ++++ .../core/tests/translate.store.spec.ts | 30 +++++++++++++++++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 363d01fc..3ff9c17a 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,10 @@ export class SharedModule { } When you lazy load a module, you should use the `forChild` static method to import the `TranslateModule`. Since lazy loaded modules use a different injector from the rest of your application, you can configure them separately with a different loader/compiler/parser/missing translations handler. + +To make a child module extend translations from parent modules use `extend: true`. This will cause the service to also +use translations from its parent module. + You can also isolate the service by using `isolate: true`. In which case the service is a completely isolated instance (for translations, current lang, events, ...). Otherwise, by default, it will share its data with other instances of the service (but you can still use a different loader/compiler/parser/handler even if you don't isolate the service). diff --git a/projects/ngx-translate/core/src/lib/translate.service.ts b/projects/ngx-translate/core/src/lib/translate.service.ts index 1d12c64a..3c08ef00 100644 --- a/projects/ngx-translate/core/src/lib/translate.service.ts +++ b/projects/ngx-translate/core/src/lib/translate.service.ts @@ -11,6 +11,7 @@ import {isDefined, mergeDeep} from "./util"; export const USE_STORE = new InjectionToken('USE_STORE'); export const USE_DEFAULT_LANG = new InjectionToken('USE_DEFAULT_LANG'); +export const USE_EXTEND = new InjectionToken('USE_EXTEND'); export interface TranslationChangeEvent { translations: any; @@ -152,7 +153,8 @@ export class TranslateService { public parser: TranslateParser, public missingTranslationHandler: MissingTranslationHandler, @Inject(USE_DEFAULT_LANG) private useDefaultLang: boolean = true, - @Inject(USE_STORE) private isolate: boolean = false) { + @Inject(USE_STORE) private isolate: boolean = false, + @Inject(USE_EXTEND) private extend: boolean = false) { } /** @@ -223,8 +225,8 @@ export class TranslateService { private retrieveTranslations(lang: string): Observable { let pending: Observable; - // if this language is unavailable, ask for it - if (typeof this.translations[lang] === "undefined") { + // if this language is unavailable or extend is true, ask for it + if (typeof this.translations[lang] === "undefined" || this.extend) { this._translationRequests[lang] = this._translationRequests[lang] || this.getTranslation(lang); pending = this._translationRequests[lang]; } @@ -252,7 +254,7 @@ export class TranslateService { this.loadingTranslations .subscribe({ next: (res: Object) => { - this.translations[lang] = res; + this.translations[lang] = this.extend && this.translations[lang] ? { ...res, ...this.translations[lang] } : res; this.updateLangs(); this.pending = false; }, @@ -270,7 +272,7 @@ export class TranslateService { */ public setTranslation(lang: string, translations: Object, shouldMerge: boolean = false): void { translations = this.compiler.compileTranslations(translations, lang); - if (shouldMerge && this.translations[lang]) { + if ((shouldMerge || this.extend) && this.translations[lang]) { this.translations[lang] = mergeDeep(this.translations[lang], translations); } else { this.translations[lang] = translations; diff --git a/projects/ngx-translate/core/src/public_api.ts b/projects/ngx-translate/core/src/public_api.ts index cb4c6008..a0ba03f9 100644 --- a/projects/ngx-translate/core/src/public_api.ts +++ b/projects/ngx-translate/core/src/public_api.ts @@ -8,6 +8,7 @@ import {TranslateDirective} from "./lib/translate.directive"; import {TranslatePipe} from "./lib/translate.pipe"; import {TranslateStore} from "./lib/translate.store"; import {USE_STORE} from "./lib/translate.service"; +import {USE_EXTEND} from "./lib/translate.service"; import {USE_DEFAULT_LANG} from "./lib/translate.service"; export * from "./lib/translate.loader"; @@ -26,6 +27,8 @@ export interface TranslateModuleConfig { missingTranslationHandler?: Provider; // isolate the service instance, only works for lazy loaded modules or components with the "providers" property isolate?: boolean; + // extends translations for a given language instead of ignoring them if present + extend?: boolean; useDefaultLang?: boolean; } @@ -54,6 +57,7 @@ export class TranslateModule { TranslateStore, {provide: USE_STORE, useValue: config.isolate}, {provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang}, + {provide: USE_EXTEND, useValue: config.extend}, TranslateService ] }; @@ -72,6 +76,7 @@ export class TranslateModule { config.missingTranslationHandler || {provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler}, {provide: USE_STORE, useValue: config.isolate}, {provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang}, + {provide: USE_EXTEND, useValue: config.extend}, TranslateService ] }; diff --git a/projects/ngx-translate/core/tests/translate.store.spec.ts b/projects/ngx-translate/core/tests/translate.store.spec.ts index 8a45253b..b5ffdbff 100644 --- a/projects/ngx-translate/core/tests/translate.store.spec.ts +++ b/projects/ngx-translate/core/tests/translate.store.spec.ts @@ -12,7 +12,10 @@ import {TranslateModule, TranslateService} from "../src/public_api"; }) class RootCmp { constructor(public translate: TranslateService) { - translate.setTranslation('en', {"TEST": "Root"}); + translate.setTranslation('en', { + "TEST": "Root", + 'ROOT': 'Root' + }); translate.use('en'); } } @@ -28,7 +31,10 @@ function getLazyLoadedModule(importedModule: ModuleWithProviders) { @Component({selector: 'lazy', template: 'lazy-loaded-child'}) class ChildLazyLoadedComponent { constructor(public translate: TranslateService) { - translate.setTranslation('en', {"TEST": "Lazy"}); + translate.setTranslation('en', { + "TEST": "Lazy", + 'CHILD': 'Child' + }); translate.use('en'); expect(translate.instant('TEST')).toEqual('Lazy'); } @@ -197,4 +203,24 @@ describe("module", () => { sub.unsubscribe(); })) ); + + it('should extend translations with extend true', fakeAsync(inject( + [Router, Location, NgModuleFactoryLoader], + (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { + let loadedModule = getLazyLoadedModule(TranslateModule.forChild({ extend: true })); + loader.stubbedModules = { expected: loadedModule }; + + const fixture = createRoot(router, RootCmp); + const translate: TranslateService = TestBed.get(TranslateService); + + router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); + + router.navigateByUrl('/lazy/loaded/child'); + advance(fixture); + + expect(translate.instant('TEST')).toEqual('Lazy'); + expect(translate.instant('ROOT')).toEqual('Root'); + expect(translate.instant('CHILD')).toEqual('Child'); + })) + ); });