Skip to content

Commit

Permalink
feat: Added default fallback language to English
Browse files Browse the repository at this point in the history
Default fallback language was never set and setting a default language did not request for the

translations file if the language was not recognized.
  • Loading branch information
Patrick de Wit committed Nov 24, 2016
1 parent 5b778c5 commit 15a75c2
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 22 deletions.
17 changes: 16 additions & 1 deletion src/translate.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {PipeTransform, Pipe, Injectable, EventEmitter, OnDestroy, ChangeDetectorRef} from '@angular/core';
import {TranslateService, LangChangeEvent, TranslationChangeEvent} from './translate.service';
import {TranslateService, LangChangeEvent, TranslationChangeEvent, DefaultLangChangeEvent} from './translate.service';

@Injectable()
@Pipe({
Expand All @@ -12,6 +12,7 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
lastParams: any[];
onTranslationChange: EventEmitter<TranslationChangeEvent>;
onLangChange: EventEmitter<LangChangeEvent>;
onDefaultLangChange: EventEmitter<DefaultLangChangeEvent>;

constructor(private translate: TranslateService, private _ref: ChangeDetectorRef) {
}
Expand Down Expand Up @@ -146,6 +147,16 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
});
}

// subscribe to onDefaultLangChange event, in case the default language changes
if(!this.onDefaultLangChange) {
this.onDefaultLangChange = this.translate.onDefaultLangChange.subscribe((event: DefaultLangChangeEvent) => {
if(this.lastKey) {
this.lastKey = null; // we want to make sure it doesn't return the same value until it's been updated
this.updateValue(query, interpolateParams, event.translations);
}
});
}

return this.value;
}

Expand All @@ -162,6 +173,10 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
this.onLangChange.unsubscribe();
this.onLangChange = undefined;
}
if(typeof this.onDefaultLangChange !== 'undefined') {
this.onDefaultLangChange.unsubscribe();
this.onDefaultLangChange = undefined;
}
}

ngOnDestroy(): void {
Expand Down
70 changes: 61 additions & 9 deletions src/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export interface LangChangeEvent {
translations: any;
}

export interface DefaultLangChangeEvent {
lang: string;
translations: any;
}

export interface MissingTranslationHandlerParams {
/**
* the key that's missing in translation files
Expand Down Expand Up @@ -106,6 +111,15 @@ export class TranslateService {
*/
public onLangChange: EventEmitter<LangChangeEvent> = new EventEmitter<LangChangeEvent>();

/**
* An EventEmitter to listen to default lang change events
* onDefaultLangChange.subscribe((params: DefaultLangChangeEvent) => {
* // do something
* });
* @type {EventEmitter<DefaultLangChangeEvent>}
*/
public onDefaultLangChange: EventEmitter<DefaultLangChangeEvent> = new EventEmitter<DefaultLangChangeEvent>();

private pending: any;
private translations: any = {};
private defaultLang: string;
Expand All @@ -125,7 +139,21 @@ export class TranslateService {
* @param lang
*/
public setDefaultLang(lang: string): void {
this.defaultLang = lang;
let pending: Observable<any> = this.retrieveTranslations(lang);

if(typeof pending !== "undefined") {
// on init set the defaultLang immediately
if(!this.defaultLang) {
this.defaultLang = lang;
}

pending.subscribe((res: any) => {
this.changeDefaultLang(lang);
}, (err: any) => {
});
} else { // we already have this language
this.changeDefaultLang(lang);
}
}

/**
Expand All @@ -142,12 +170,7 @@ export class TranslateService {
* @returns {Observable<*>}
*/
public use(lang: string): Observable<any> {
let pending: Observable<any>;
// check if this language is available
if(typeof this.translations[lang] === "undefined") {
// not available, ask for it
pending = this.getTranslation(lang);
}
let pending: Observable<any> = this.retrieveTranslations(lang);

if(typeof pending !== "undefined") {
// on init set the currentLang immediately
Expand All @@ -167,6 +190,21 @@ export class TranslateService {
}
}

/**
* Retrieves the given translations
* @param lang
* @returns {Observable<*>}
*/
private retrieveTranslations(lang: string): Observable<any> {
let pending: Observable<any>;
// check if this language is available
if(typeof this.translations[lang] === "undefined") {
// not available, ask for it
pending = this.getTranslation(lang);
}
return pending;
}

/**
* Gets an object of translations for a given language with the current loader
* @param lang
Expand Down Expand Up @@ -272,8 +310,13 @@ export class TranslateService {
res = this.parser.interpolate(this.parser.getValue(translations, key), interpolateParams);
}

if(typeof res === "undefined" && this.defaultLang && this.defaultLang !== this.currentLang) {
res = this.parser.interpolate(this.parser.getValue(this.translations[this.defaultLang], key), interpolateParams);
if(typeof res === "undefined") {
if(!this.defaultLang) {
// load a default language
this.setDefaultLang('en');
} else if(this.defaultLang !== this.currentLang) {
res = this.parser.interpolate(this.parser.getValue(this.translations[this.defaultLang], key), interpolateParams);
}
}

if (!res && this.missingTranslationHandler) {
Expand Down Expand Up @@ -374,6 +417,15 @@ export class TranslateService {
this.onLangChange.emit({lang: lang, translations: this.translations[lang]});
}

/**
* Changes the default lang
* @param lang
*/
private changeDefaultLang(lang: string): void {
this.defaultLang = lang;
this.onDefaultLangChange.emit({lang: lang, translations: this.translations[lang]});
}

/**
* Allows to reload the lang file from the file
* @param lang
Expand Down
56 changes: 44 additions & 12 deletions tests/translate.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,42 @@ describe('TranslateService', () => {
it("should fallback to the default language", () => {
translate.use('fr');

translate.setDefaultLang('en');
translate.setTranslation('en', {"TEST": "This is a test"});
translate.get('TEST').subscribe((res: string) => {
expect(res).toEqual('TEST');

translate.setDefaultLang('nl');

translate.get('TEST').subscribe((res2: string) => {
expect(res2).toEqual('Dit is een test');
});

mockBackendResponse(connection, '{"TEST": "Dit is een test"}');
});

mockBackendResponse(connection, '{}');
});

it("should use the default language by default", () => {
translate.setDefaultLang('nl');

translate.get('TEST').subscribe((res: string) => {
expect(res).toEqual('This is a test');
expect(res).toEqual('Dit is een test');
});

mockBackendResponse(connection, '{"TEST": "Dit is een test"}');
});

it("should fallback to english if no default language was set", () => {
translate.use('fr');

translate.get('TEST').subscribe((res: string) => {
expect(res).toEqual('TEST');

translate.get('TEST').subscribe((res2: string) => {
expect(res2).toEqual('This is a test');
});

mockBackendResponse(connection, '{"TEST": "This is a test"}');
});

mockBackendResponse(connection, '{}');
Expand Down Expand Up @@ -225,7 +256,7 @@ describe('TranslateService', () => {
translate.use('en');
});

it('should be able to reset a lang', (done: Function) => {
it('should be able to reset a lang', () => {
translate.use('en');
spyOn(connection, 'mockRespond').and.callThrough();

Expand All @@ -239,14 +270,15 @@ describe('TranslateService', () => {

expect(translate.instant('TEST')).toEqual('TEST');

// use set timeout because no request is really made and we need to trigger zone to resolve the observable
setTimeout(() => {
translate.get('TEST').subscribe((res2: string) => {
expect(res2).toEqual('TEST'); // because the loader is "pristine" as if it was never called
expect(connection.mockRespond).toHaveBeenCalledTimes(1);
done();
});
}, 10);
spyOn(connection, 'mockRespond').and.callThrough();

// as current language does not contain the right translation any more, it will request the default language
translate.get('TEST').subscribe((res2: string) => {
expect(res2).toEqual('TEST'); // because the default language does not contain the right translation either
expect(connection.mockRespond).toHaveBeenCalledTimes(1);
});

mockBackendResponse(connection, '{}');
});

// mock response after the xhr request, otherwise it will be undefined
Expand Down

0 comments on commit 15a75c2

Please sign in to comment.