Skip to content

Commit

Permalink
feat(Translate): adding TRANSLATE_PROVIDERS for DI
Browse files Browse the repository at this point in the history
Fixes #48
  • Loading branch information
ocombe committed Mar 6, 2016
1 parent e0ac54e commit 5da101d
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 27 deletions.
42 changes: 28 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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<any>`: Changes the lang currently used
- `getTranslation(lang: string): Observable<any>`: 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<string>, interpolateParams?: Object): Observable<string|Object>`: Gets the translated value of a key (or an array of keys)
- `instant(key: string|Array<string>, interpolateParams?: Object): string|Object`: Gets the instant translated value of a key (or an array of keys)
Expand Down Expand Up @@ -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:
Expand All @@ -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.

Expand Down
10 changes: 8 additions & 2 deletions ng2-translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
];
];

// for angular-cli
export default {
pipes: [TranslatePipe],
providers: [TranslateService]
}
23 changes: 19 additions & 4 deletions src/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
}

Expand Down Expand Up @@ -53,9 +52,21 @@ export class TranslateService {
private langs: Array<string>;
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
Expand Down Expand Up @@ -220,4 +231,8 @@ export class TranslateService {
this.onLangChange.emit({lang: lang, translations: this.translations[lang]});
}

public setMissingTranslationHandler(handler: MissingTranslationHandler) {
this.missingTranslationHandler = handler;
}

}
4 changes: 2 additions & 2 deletions tests/translate.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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);
Expand Down
73 changes: 68 additions & 5 deletions tests/translate.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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<any> {
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', () => {
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 5da101d

Please sign in to comment.