From 5da101ddc01dc2405c65585380c6b9ec4ec3ab35 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Sun, 6 Mar 2016 09:27:22 +0100 Subject: [PATCH] feat(Translate): adding TRANSLATE_PROVIDERS for DI Fixes #48 --- README.md | 42 ++++++++++++------- ng2-translate.ts | 10 ++++- src/translate.service.ts | 23 +++++++++-- tests/translate.pipe.spec.ts | 4 +- tests/translate.service.spec.ts | 73 ++++++++++++++++++++++++++++++--- tsconfig.json | 3 ++ 6 files changed, 128 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index a34e8980..1f15b875 100644 --- a/README.md +++ b/README.md @@ -21,28 +21,21 @@ System.config({ ``` Finally, you can use ng2-translate in your Angular 2 project (make sure that you've loaded the angular2/http bundle as well). -It is recommended to use `NG_TRANSLATE_PROVIDERS` in the bootstrap of your application and to never add `TranslateService` to the "providers" property of your components, this way you will keep it as a singleton. -`NG_TRANSLATE_PROVIDERS` provides a default configuration for the static translation file loader. +It is recommended to use `TRANSLATE_PROVIDERS` in the bootstrap of your application and to never add `TranslateService` to the "providers" property of your components, this way you will keep it as a singleton. +`TRANSLATE_PROVIDERS` provides a default configuration for the static translation file loader. If you add `TranslateService` to the "providers" property of a component it will instantiate a new instance of the service that won't be initialized with the language to use or the default language. ```js import {HTTP_PROVIDERS} from 'angular2/http'; import {Component, Injectable, provide} from 'angular2/core'; -import {NG_TRANSLATE_PROVIDERS, TranslateService, TranslatePipe, +import {TRANSLATE_PROVIDERS, TranslateService, TranslatePipe, TranslateLoader, TranslateStaticLoader} from 'ng2-translate/ng2-translate'; import {bootstrap} from 'angular2/platform/browser'; bootstrap(AppComponent, [ HTTP_PROVIDERS, - // not required if you use TranslateStaticLoader (default) - // use this if you want to use another loader - // or to configure TranslateStaticLoader - provide(TranslateLoader, { - useFactory: (http: Http) => new TranslateStaticLoader(http), - deps: [Http] - }), - // recommended to have 1 unique instance of your service - NG_TRANSLATE_PROVIDERS + // not required, but recommended to have 1 unique instance of your service + TRANSLATE_PROVIDERS ]); @Component({ @@ -110,10 +103,13 @@ translate.setTranslation('en', { ``` #### Methods: +- `useLoader(loader: TranslateLoader)`: Use a different loader - `setDefaultLang(lang: string)`: Sets the default language to use as a fallback - `use(lang: string): Observable`: Changes the lang currently used - `getTranslation(lang: string): Observable`: Gets an object of translations for a given language with the current loader - `setTranslation(lang: string, translations: Object)`: Manually sets an object of translations for a given language +- `setMissingTranslationHandler(handler: MissingTranslationHandler): void`: sets the Missing Translation Handler which will be +used when the requested translation is not available - `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) - `instant(key: string|Array, interpolateParams?: Object): string|Object`: Gets the instant translated value of a key (or an array of keys) @@ -141,8 +137,17 @@ bootstrap(AppComponent, [ ]); ``` +Or you can just use the `useLoader` method: +```js +export class AppComponent { + constructor(translate: TranslateService, myLoader: CustomLoader) { + translate.useLoader(myLoader); + } +} +``` + #### How to handle missing translations -You can setup a provider for `MissingTranslationHandler` to define a handler that will be called when the requested translation is not available. +You can setup a provider for `MissingTranslationHandler` in the bootstrap of your application (recommended), or you can use the method `setMissingTranslationHandler` later to define a handler that will be called when the requested translation is not available. The only required method is `handle` where you can do whatever you want. Just don't forget that it will be called synchronously from the `get` & `instant` methods. ##### Example: @@ -157,11 +162,20 @@ export class MyMissingTranslationHandler implements MissingTranslationHandler { } ``` -Setup the Missing Translation Handler in bootstrap +Setup the Missing Translation Handler in bootstrap (recommended) ```js provide(MissingTranslationHandler, { useClass: MyMissingTranslationHandler }) ``` +Set the Missing Translation Handler later +```js +constructor(translate: TranslateService) { + ... + translate.setMissingTranslationHandler(new MyMissingTranslationHandler()); + ... +} +``` + ### TranslatePipe You can call the TranslatePipe with some optional parameters that will be transpolated into the translation for the given key. diff --git a/ng2-translate.ts b/ng2-translate.ts index 0a220c68..9448bc26 100644 --- a/ng2-translate.ts +++ b/ng2-translate.ts @@ -7,10 +7,16 @@ export * from './src/translate.pipe'; export * from './src/translate.service'; export * from './src/translate.parser'; -export const NG_TRANSLATE_PROVIDERS: any = [ +export const TRANSLATE_PROVIDERS: any = [ provide(TranslateLoader, { useFactory: (http: Http) => new TranslateStaticLoader(http), deps: [Http] }), TranslateService -]; \ No newline at end of file +]; + +// for angular-cli +export default { + pipes: [TranslatePipe], + providers: [TranslateService] +} \ No newline at end of file diff --git a/src/translate.service.ts b/src/translate.service.ts index a964bf77..d4a23972 100644 --- a/src/translate.service.ts +++ b/src/translate.service.ts @@ -16,7 +16,6 @@ export abstract class TranslateLoader { } export class TranslateStaticLoader implements TranslateLoader { - constructor(private http: Http, private prefix: string = 'i18n', private suffix: string = '.json') { } @@ -53,9 +52,21 @@ export class TranslateService { private langs: Array; private parser: Parser = new Parser(); - constructor(private http: Http, - public currentLoader: TranslateLoader, - @Optional() private missingTranslationHandler: MissingTranslationHandler) {} + /** + * + * @param http The Angular 2 http provider + * @param currentLoader An instance of the loader currently used + * @param missingTranslationHandler A handler for missing translations + */ + constructor(private http: Http, public currentLoader: TranslateLoader, @Optional() private missingTranslationHandler: MissingTranslationHandler) {} + + /** + * Use a translations loader + * @param loader + */ + public useLoader(loader: TranslateLoader) { + this.currentLoader = loader; + } /** * Sets the default language to use as a fallback @@ -220,4 +231,8 @@ export class TranslateService { this.onLangChange.emit({lang: lang, translations: this.translations[lang]}); } + public setMissingTranslationHandler(handler: MissingTranslationHandler) { + this.missingTranslationHandler = handler; + } + } diff --git a/tests/translate.pipe.spec.ts b/tests/translate.pipe.spec.ts index 283465dd..d1b6f3d4 100644 --- a/tests/translate.pipe.spec.ts +++ b/tests/translate.pipe.spec.ts @@ -1,6 +1,6 @@ import {TranslatePipe} from '../src/translate.pipe'; import {MockConnection, MockBackend} from "angular2/src/http/backends/mock_backend"; -import {NG_TRANSLATE_PROVIDERS, TranslateService} from "./../ng2-translate"; +import {TRANSLATE_PROVIDERS, TranslateService} from "./../ng2-translate"; import {XHRBackend, HTTP_PROVIDERS} from "angular2/http"; import {provide, Injector} from "angular2/core"; @@ -17,7 +17,7 @@ export function main() { HTTP_PROVIDERS, // Provide a mocked (fake) backend for Http provide(XHRBackend, {useClass: MockBackend}), - NG_TRANSLATE_PROVIDERS, + TRANSLATE_PROVIDERS, TranslatePipe ]); backend = injector.get(XHRBackend); diff --git a/tests/translate.service.spec.ts b/tests/translate.service.spec.ts index 309bbb39..756d2dd8 100644 --- a/tests/translate.service.spec.ts +++ b/tests/translate.service.spec.ts @@ -6,7 +6,7 @@ import { } from "angular2/http"; import {MockBackend, MockConnection} from "angular2/http/testing"; import { - NG_TRANSLATE_PROVIDERS, + TRANSLATE_PROVIDERS, TranslateService, MissingTranslationHandler, TranslateLoader, TranslateStaticLoader } from './../ng2-translate'; @@ -30,7 +30,7 @@ export function main() { HTTP_PROVIDERS, // Provide a mocked (fake) backend for Http provide(XHRBackend, {useClass: MockBackend}), - NG_TRANSLATE_PROVIDERS + TRANSLATE_PROVIDERS ]); backend = injector.get(XHRBackend); translate = injector.get(TranslateService); @@ -184,6 +184,69 @@ export function main() { expect(translate.instant('TEST2')).toEqual('TEST2'); }); + + function prepareMissingTranslationHandler() { + class Missing implements MissingTranslationHandler { + handle(key: string) {} + } + let handler = new Missing(); + spyOn(handler, 'handle'); + + translate.setMissingTranslationHandler(handler); + + return handler; + } + + it('should use the MissingTranslationHandler when the key does not exist', () => { + translate.use('en'); + let handler = prepareMissingTranslationHandler(); + + translate.get('nonExistingKey').subscribe(() => { + expect(handler.handle).toHaveBeenCalledWith('nonExistingKey'); + }); + }); + + it('should not call the MissingTranslationHandler when the key exists', () => { + translate.use('en'); + let handler = prepareMissingTranslationHandler(); + + translate.get('TEST').subscribe(() => { + expect(handler.handle).not.toHaveBeenCalled(); + }); + }); + + it('should use the MissingTranslationHandler when the key does not exist & we use instant translation', () => { + translate.use('en'); + let handler = prepareMissingTranslationHandler(); + + translate.instant('nonExistingKey'); + expect(handler.handle).toHaveBeenCalledWith('nonExistingKey'); + }); + + it('should be able to change the loader', () => { + class CustomLoader implements TranslateLoader { + getTranslation(lang: string): Observable { + return Observable.of({"TEST": "This is a test"}); + } + } + translate.use('en'); + + expect(translate).toBeDefined(); + expect(translate.currentLoader).toBeDefined(); + expect(translate.currentLoader instanceof TranslateStaticLoader).toBeTruthy(); + + translate.useLoader(new CustomLoader()); + expect(translate.currentLoader).toBeDefined(); + expect(translate.currentLoader instanceof CustomLoader).toBeTruthy(); + + // the lang to use, if the lang isn't available, it will use the current loader to get them + translate.use('en'); + + // this will request the translation from the backend because we use a static files loader for TranslateService + translate.get('TEST').subscribe((res: string) => { + expect(res).toEqual('This is a test'); + }); + }); }); describe('MissingTranslationHandler', () => { @@ -202,7 +265,7 @@ export function main() { HTTP_PROVIDERS, // Provide a mocked (fake) backend for Http provide(XHRBackend, {useClass: MockBackend}), - NG_TRANSLATE_PROVIDERS, + TRANSLATE_PROVIDERS, provide(MissingTranslationHandler, { useClass: Missing }) ]); backend = injector.get(XHRBackend); @@ -271,7 +334,7 @@ export function main() { HTTP_PROVIDERS, // Provide a mocked (fake) backend for Http provide(XHRBackend, {useClass: MockBackend}), - NG_TRANSLATE_PROVIDERS + TRANSLATE_PROVIDERS ]); prepare(injector); @@ -301,7 +364,7 @@ export function main() { HTTP_PROVIDERS, // Provide a mocked (fake) backend for Http provide(XHRBackend, {useClass: MockBackend}), - NG_TRANSLATE_PROVIDERS, + TRANSLATE_PROVIDERS, provide(TranslateLoader, { useClass: CustomLoader }) ]); prepare(injector); diff --git a/tsconfig.json b/tsconfig.json index 2346a258..b0c4e091 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,9 @@ "files": [ "typings/main.d.ts", "ng2-translate.ts", + "./src/translate.pipe.ts", + "./src/translate.service.ts", + "./src/translate.parser.ts", "tests/translate.parser.spec.ts", "tests/translate.service.spec.ts", "tests/translate.pipe.spec.ts"