diff --git a/README.md b/README.md index f50fe03b..dbc9eeec 100644 --- a/README.md +++ b/README.md @@ -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'; } } diff --git a/src/translate.service.ts b/src/translate.service.ts index a9adc4ed..045d99d9 100644 --- a/src/translate.service.ts +++ b/src/translate.service.ts @@ -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; } @@ -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 { @@ -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; diff --git a/tests/translate.service.spec.ts b/tests/translate.service.spec.ts index a85b74f4..13f8b99f 100644 --- a/tests/translate.service.spec.ts +++ b/tests/translate.service.spec.ts @@ -4,6 +4,7 @@ import {MockBackend, MockConnection} from "@angular/http/testing"; import { TranslateService, MissingTranslationHandler, + MissingTranslationHandlerParams, TranslateLoader, TranslateStaticLoader, LangChangeEvent, @@ -310,14 +311,14 @@ 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 { - return Observable.of(`handled: ${key}`); + handle(params: MissingTranslationHandlerParams): Observable { + return Observable.of(`handled: ${params.key}`); } } @@ -325,8 +326,8 @@ describe('MissingTranslationHandler', () => { 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(); @@ -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'); }); @@ -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) { } } @@ -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'); }); @@ -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', () => { @@ -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'); });