Skip to content

Commit

Permalink
feat(MissingTranslationHandler): re-release of this feature from 3.2.…
Browse files Browse the repository at this point in the history
…0 as a new major

BREAKING CHANGE: the signature of the MissingTranslationHandler has changed, it uses a `MissingTranslationHandlerParams` instead of just a simple string, check the docs for more information on this
  • Loading branch information
ocombe committed Nov 3, 2016
1 parent c843d91 commit 96cfa78
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 16 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,10 @@ Just don't forget that it will be called synchronously from the `instant` method
##### Example:
Create a Missing Translation Handler
```ts
import {MissingTranslationHandler} from 'ng2-translate';
import {MissingTranslationHandler, MissingTranslationHandlerParams} from 'ng2-translate';
export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(key: string) {
handle(params: MissingTranslationHandlerParams) {
return 'some value';
}
}
Expand Down
37 changes: 33 additions & 4 deletions src/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,29 @@ export interface LangChangeEvent {
translations: any;
}

export interface MissingTranslationHandlerParams {
/**
* the key that's missing in translation files
*
* @type {string}
*/
key: string;

/**
* an instance of the service that was unable to translate the key.
*
* @type {TranslateService}
*/
translateService: TranslateService;

/**
* interpolation params that were passed along for translating the given key.
*
* @type {Object}
*/
interpolateParams?: Object;
}

declare interface Window {
navigator: any;
}
Expand All @@ -28,13 +51,15 @@ declare var window: Window;
export abstract class MissingTranslationHandler {
/**
* A function that handles missing translations.
* @param key the missing key
*
* @abstract
* @param {MissingTranslationHandlerParams} the context for resolving a missing translation
* @returns {any} a value or an observable
* If it returns a value, then this value is used.
* If it return an observable, the value returned by this observable will be used (except if the method was "instant").
* If it doesn't return then the key will be used as a value
*/
abstract handle(key: string): any;
abstract handle(params: MissingTranslationHandlerParams): any;
}

export abstract class TranslateLoader {
Expand Down Expand Up @@ -252,8 +277,12 @@ export class TranslateService {
res = this.parser.interpolate(this.parser.getValue(this.translations[this.defaultLang], key), interpolateParams);
}

if(!res && this.missingTranslationHandler) {
res = this.missingTranslationHandler.handle(key);
if (!res && this.missingTranslationHandler) {
let params: MissingTranslationHandlerParams = { key, translateService: this };
if (typeof interpolateParams !== 'undefined') {
params.interpolateParams = interpolateParams;
}
res = this.missingTranslationHandler.handle(params);
}

return res !== undefined ? res : key;
Expand Down
54 changes: 44 additions & 10 deletions tests/translate.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {MockBackend, MockConnection} from "@angular/http/testing";
import {
TranslateService,
MissingTranslationHandler,
MissingTranslationHandlerParams,
TranslateLoader,
TranslateStaticLoader,
LangChangeEvent,
Expand Down Expand Up @@ -310,23 +311,23 @@ describe('MissingTranslationHandler', () => {
let missingTranslationHandler: MissingTranslationHandler;

class Missing implements MissingTranslationHandler {
handle(key: string) {
handle(params: MissingTranslationHandlerParams) {
return "handled";
}
}

class MissingObs implements MissingTranslationHandler {
handle(key: string): Observable<any> {
return Observable.of(`handled: ${key}`);
handle(params: MissingTranslationHandlerParams): Observable<any> {
return Observable.of(`handled: ${params.key}`);
}
}

let prepare = ((handlerClass: Function) => {
TestBed.configureTestingModule({
imports: [HttpModule, TranslateModule.forRoot()],
providers: [
{provide: MissingTranslationHandler, useClass: handlerClass},
{provide: XHRBackend, useClass: MockBackend}
{ provide: MissingTranslationHandler, useClass: handlerClass },
{ provide: XHRBackend, useClass: MockBackend }
]
});
injector = getTestBed();
Expand All @@ -351,7 +352,40 @@ describe('MissingTranslationHandler', () => {
spyOn(missingTranslationHandler, 'handle').and.callThrough();

translate.get('nonExistingKey').subscribe((res: string) => {
expect(missingTranslationHandler.handle).toHaveBeenCalledWith('nonExistingKey');
expect(missingTranslationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({ key: 'nonExistingKey' }));
//test that the instance of the last called argument is string
expect(res).toEqual('handled');
});

// mock response after the xhr request, otherwise it will be undefined
mockBackendResponse(connection, '{"TEST": "This is a test"}');
});

it('should propagate interpolation params when the key does not exist', () => {
prepare(Missing);
translate.use('en');
spyOn(missingTranslationHandler, 'handle').and.callThrough();
let interpolateParams = { some: 'params' };

translate.get('nonExistingKey', interpolateParams).subscribe((res: string) => {
expect(missingTranslationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({ interpolateParams: interpolateParams }));
//test that the instance of the last called argument is string
expect(res).toEqual('handled');
});

// mock response after the xhr request, otherwise it will be undefined
mockBackendResponse(connection, '{"TEST": "This is a test"}');
});

it('should propagate TranslationService params when the key does not exist', () => {
prepare(Missing);
translate.use('en');
spyOn(missingTranslationHandler, 'handle').and.callThrough();
let interpolateParams = { some: 'params' };

translate.get('nonExistingKey', interpolateParams).subscribe((res: string) => {
expect(missingTranslationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({ translateService: translate }));
//test that the instance of the last called argument is string
expect(res).toEqual('handled');
});

Expand All @@ -361,7 +395,7 @@ describe('MissingTranslationHandler', () => {

it('should return the key when using MissingTranslationHandler & the handler returns nothing', () => {
class MissingUndef implements MissingTranslationHandler {
handle(key: string) {
handle(params: MissingTranslationHandlerParams) {
}
}

Expand All @@ -370,7 +404,7 @@ describe('MissingTranslationHandler', () => {
spyOn(missingTranslationHandler, 'handle').and.callThrough();

translate.get('nonExistingKey').subscribe((res: string) => {
expect(missingTranslationHandler.handle).toHaveBeenCalledWith('nonExistingKey');
expect(missingTranslationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({ key: 'nonExistingKey' }));
expect(res).toEqual('nonExistingKey');
});

Expand All @@ -397,7 +431,7 @@ describe('MissingTranslationHandler', () => {
spyOn(missingTranslationHandler, 'handle').and.callThrough();

expect(translate.instant('nonExistingKey')).toEqual('handled');
expect(missingTranslationHandler.handle).toHaveBeenCalledWith('nonExistingKey');
expect(missingTranslationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({ key: 'nonExistingKey' }));
});

it('should wait for the MissingTranslationHandler when it returns an observable & we use get', () => {
Expand All @@ -406,7 +440,7 @@ describe('MissingTranslationHandler', () => {
spyOn(missingTranslationHandler, 'handle').and.callThrough();

translate.get('nonExistingKey').subscribe((res: string) => {
expect(missingTranslationHandler.handle).toHaveBeenCalledWith('nonExistingKey');
expect(missingTranslationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({ key: 'nonExistingKey' }));
expect(res).toEqual('handled: nonExistingKey');
});

Expand Down

0 comments on commit 96cfa78

Please sign in to comment.