From 23b5b33233e2b900882a8c2e5b3d2725ea57778a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20St=C3=B8ttrup=20Nielsen?= Date: Wed, 24 May 2017 18:37:42 +0200 Subject: [PATCH 1/3] chore: added tsconfig.json to .npmignore Fixes #521 --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index ff96c675..07957582 100644 --- a/.npmignore +++ b/.npmignore @@ -15,6 +15,7 @@ examples coverage !*.metadata.json !bundles/*.js +tsconfig.json ################# ## JetBrains From d5f44ec524a9dc75b460afdae2c33c08cd0be90c Mon Sep 17 00:00:00 2001 From: Gil Hanan Date: Wed, 24 May 2017 19:52:44 +0300 Subject: [PATCH 2/3] docs: single quotation marks in import statements for readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bea13456..58341b4b 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ import {BrowserModule} from '@angular/platform-browser'; import {HttpModule, Http} from '@angular/http'; import {TranslateModule, TranslateLoader} from '@ngx-translate/core'; import {TranslateHttpLoader} from '@ngx-translate/http-loader'; -import {AppComponent} from "./app"; +import {AppComponent} from './app'; // AoT requires an exported function for factories export function HttpLoaderFactory(http: Http) { From d84c69b772bfc9f8b5be012118843cf718e403e0 Mon Sep 17 00:00:00 2001 From: Gustorn Date: Wed, 24 May 2017 20:47:35 +0200 Subject: [PATCH 3/3] feat(TranslateService): new `stream` method to get an observable of translations that updates on lang change Fixes #330 --- README.md | 1 + src/translate.service.ts | 26 ++++++++++ tests/translate.service.spec.ts | 86 +++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/README.md b/README.md index 58341b4b..872a9f63 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,7 @@ To render them, simply use the `innerHTML` attribute with the pipe on any elemen - `addLangs(langs: Array)`: Add new langs to the list - `getLangs()`: Returns an array of currently available langs - `get(key: string|Array, interpolateParams?: Object): Observable`: Gets the translated value of a key (or an array of keys) or the key if the value was not found +- `stream(key: string|Array, interpolateParams?: Object): Observable`: Returns a stream of translated values of a key (or an array of keys) or the key if the value was not found. Without any `onLangChange` events this returns the same value as `get` but it will also emit new values whenever the used language changes. - `instant(key: string|Array, interpolateParams?: Object): string|Object`: Gets the instant translated value of a key (or an array of keys). /!\ This method is **synchronous** and the default file loader is asynchronous. You are responsible for knowing when your translations have been loaded and it is safe to use this method. If you are not sure then you should use the `get` method instead. - `set(key: string, value: string, lang?: string)`: Sets the translated value of a key - `reloadLang(lang: string): Observable`: Calls resetLang and retrieves the translations object for the current loader diff --git a/src/translate.service.ts b/src/translate.service.ts index e7815481..5bebd41d 100644 --- a/src/translate.service.ts +++ b/src/translate.service.ts @@ -2,9 +2,11 @@ import {Injectable, EventEmitter, Inject, OpaqueToken} from "@angular/core"; import {Observable} from "rxjs/Observable"; import {Observer} from "rxjs/Observer"; import "rxjs/add/observable/of"; +import "rxjs/add/operator/concat"; import "rxjs/add/operator/share"; import "rxjs/add/operator/map"; import "rxjs/add/operator/merge"; +import "rxjs/add/operator/switchMap"; import "rxjs/add/operator/toArray"; import "rxjs/add/operator/take"; @@ -400,6 +402,30 @@ export class TranslateService { } } + /** + * Returns a stream of translated values of a key (or an array of keys) which updates + * whenever the language changes. + * @param key + * @param interpolateParams + * @returns {any} A stream of the translated key, or an object of translated keys + */ + public stream(key: string | Array, interpolateParams?: Object): Observable { + if(!isDefined(key) || !key.length) { + throw new Error(`Parameter "key" required`); + } + + return this + .get(key, interpolateParams) + .concat(this.onLangChange.switchMap((event: LangChangeEvent) => { + const res = this.getParsedResult(event.translations, key, interpolateParams); + if(typeof res.subscribe === "function") { + return res; + } else { + return Observable.of(res); + } + })); + } + /** * Returns a translation instantly from the internal state of loaded translation. * All rules regarding the current language, the preferred language of even fallback languages will be used except any promise handling. diff --git a/tests/translate.service.spec.ts b/tests/translate.service.spec.ts index 504a94b5..9939e06a 100644 --- a/tests/translate.service.spec.ts +++ b/tests/translate.service.spec.ts @@ -4,7 +4,9 @@ import {Observable} from "rxjs/Observable"; import {getTestBed, TestBed, fakeAsync, tick} from "@angular/core/testing"; import 'rxjs/add/observable/timer'; +import 'rxjs/add/operator/take'; import 'rxjs/add/operator/mapTo'; +import 'rxjs/add/operator/zip'; let translations: any = {"TEST": "This is a test"}; class FakeLoader implements TranslateLoader { @@ -195,6 +197,90 @@ describe('TranslateService', () => { }); }); + it('should be able to stream a translation for the current language', (done: Function) => { + translations = {"TEST": "This is a test"}; + translate.use('en'); + + translate.stream('TEST').subscribe((res: string) => { + expect(res).toEqual('This is a test'); + done(); + }); + }); + + it('should be able to stream a translation of an array for the current language', (done: Function) => { + let tr = {"TEST": "This is a test", "TEST2": "This is a test2"}; + translate.setTranslation('en', tr); + translate.use('en'); + + translate.stream(['TEST', 'TEST2']).subscribe((res: any) => { + expect(res).toEqual(tr); + done(); + }); + }); + + it('should initially return the same value for streaming and non-streaming get', (done: Function) => { + translations = {"TEST": "This is a test"}; + translate.use('en'); + + translate.stream('TEST').zip(translate.get('TEST')).subscribe((value: [string, string]) => { + const [streamed, nonStreamed] = value; + expect(streamed).toEqual('This is a test'); + expect(streamed).toEqual(nonStreamed); + done(); + }); + }); + + it('should update streaming translations on language change', (done: Function) => { + translations = {"TEST": "This is a test"}; + translate.use('en'); + + translate.stream('TEST').take(3).toArray().subscribe((res: string[]) => { + const expected = ['This is a test', 'Dit is een test', 'This is a test']; + expect(res).toEqual(expected); + done(); + }); + + translate.setTranslation('nl', {"TEST": "Dit is een test"}); + translate.use('nl'); + translate.use('en'); + }); + + it('should update streaming translations of an array on language change', (done: Function) => { + const en = {"TEST": "This is a test", "TEST2": "This is a test2"}; + const nl = {"TEST": "Dit is een test", "TEST2": "Dit is een test2"}; + translate.setTranslation('en', en); + translate.use('en'); + + translate.stream(['TEST', 'TEST2']).take(3).toArray().subscribe((res: any[]) => { + const expected = [en, nl, en]; + expect(res).toEqual(expected); + done(); + }); + + translate.setTranslation('nl', nl); + translate.use('nl'); + translate.use('en'); + }); + + it('should interpolate the same param into each streamed value', (done: Function) => { + translations = {"TEST": "This is a test {{param}}"}; + translate.use('en'); + + translate.stream('TEST', { param: 'with param' }).take(3).toArray().subscribe((res: string[]) => { + const expected = [ + 'This is a test with param', + 'Dit is een test with param', + 'This is a test with param' + ]; + expect(res).toEqual(expected); + done(); + }); + + translate.setTranslation('nl', {"TEST": "Dit is een test {{param}}"}); + translate.use('nl'); + translate.use('en'); + }); + it('should be able to get instant translations', () => { translate.setTranslation('en', {"TEST": "This is a test"}); translate.use('en');