Skip to content

Commit 15a75c2

Browse files
author
Patrick de Wit
committed
feat: Added default fallback language to English
Default fallback language was never set and setting a default language did not request for the translations file if the language was not recognized.
1 parent 5b778c5 commit 15a75c2

File tree

3 files changed

+121
-22
lines changed

3 files changed

+121
-22
lines changed

src/translate.pipe.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {PipeTransform, Pipe, Injectable, EventEmitter, OnDestroy, ChangeDetectorRef} from '@angular/core';
2-
import {TranslateService, LangChangeEvent, TranslationChangeEvent} from './translate.service';
2+
import {TranslateService, LangChangeEvent, TranslationChangeEvent, DefaultLangChangeEvent} from './translate.service';
33

44
@Injectable()
55
@Pipe({
@@ -12,6 +12,7 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
1212
lastParams: any[];
1313
onTranslationChange: EventEmitter<TranslationChangeEvent>;
1414
onLangChange: EventEmitter<LangChangeEvent>;
15+
onDefaultLangChange: EventEmitter<DefaultLangChangeEvent>;
1516

1617
constructor(private translate: TranslateService, private _ref: ChangeDetectorRef) {
1718
}
@@ -146,6 +147,16 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
146147
});
147148
}
148149

150+
// subscribe to onDefaultLangChange event, in case the default language changes
151+
if(!this.onDefaultLangChange) {
152+
this.onDefaultLangChange = this.translate.onDefaultLangChange.subscribe((event: DefaultLangChangeEvent) => {
153+
if(this.lastKey) {
154+
this.lastKey = null; // we want to make sure it doesn't return the same value until it's been updated
155+
this.updateValue(query, interpolateParams, event.translations);
156+
}
157+
});
158+
}
159+
149160
return this.value;
150161
}
151162

@@ -162,6 +173,10 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
162173
this.onLangChange.unsubscribe();
163174
this.onLangChange = undefined;
164175
}
176+
if(typeof this.onDefaultLangChange !== 'undefined') {
177+
this.onDefaultLangChange.unsubscribe();
178+
this.onDefaultLangChange = undefined;
179+
}
165180
}
166181

167182
ngOnDestroy(): void {

src/translate.service.ts

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ export interface LangChangeEvent {
2020
translations: any;
2121
}
2222

23+
export interface DefaultLangChangeEvent {
24+
lang: string;
25+
translations: any;
26+
}
27+
2328
export interface MissingTranslationHandlerParams {
2429
/**
2530
* the key that's missing in translation files
@@ -106,6 +111,15 @@ export class TranslateService {
106111
*/
107112
public onLangChange: EventEmitter<LangChangeEvent> = new EventEmitter<LangChangeEvent>();
108113

114+
/**
115+
* An EventEmitter to listen to default lang change events
116+
* onDefaultLangChange.subscribe((params: DefaultLangChangeEvent) => {
117+
* // do something
118+
* });
119+
* @type {EventEmitter<DefaultLangChangeEvent>}
120+
*/
121+
public onDefaultLangChange: EventEmitter<DefaultLangChangeEvent> = new EventEmitter<DefaultLangChangeEvent>();
122+
109123
private pending: any;
110124
private translations: any = {};
111125
private defaultLang: string;
@@ -125,7 +139,21 @@ export class TranslateService {
125139
* @param lang
126140
*/
127141
public setDefaultLang(lang: string): void {
128-
this.defaultLang = lang;
142+
let pending: Observable<any> = this.retrieveTranslations(lang);
143+
144+
if(typeof pending !== "undefined") {
145+
// on init set the defaultLang immediately
146+
if(!this.defaultLang) {
147+
this.defaultLang = lang;
148+
}
149+
150+
pending.subscribe((res: any) => {
151+
this.changeDefaultLang(lang);
152+
}, (err: any) => {
153+
});
154+
} else { // we already have this language
155+
this.changeDefaultLang(lang);
156+
}
129157
}
130158

131159
/**
@@ -142,12 +170,7 @@ export class TranslateService {
142170
* @returns {Observable<*>}
143171
*/
144172
public use(lang: string): Observable<any> {
145-
let pending: Observable<any>;
146-
// check if this language is available
147-
if(typeof this.translations[lang] === "undefined") {
148-
// not available, ask for it
149-
pending = this.getTranslation(lang);
150-
}
173+
let pending: Observable<any> = this.retrieveTranslations(lang);
151174

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

193+
/**
194+
* Retrieves the given translations
195+
* @param lang
196+
* @returns {Observable<*>}
197+
*/
198+
private retrieveTranslations(lang: string): Observable<any> {
199+
let pending: Observable<any>;
200+
// check if this language is available
201+
if(typeof this.translations[lang] === "undefined") {
202+
// not available, ask for it
203+
pending = this.getTranslation(lang);
204+
}
205+
return pending;
206+
}
207+
170208
/**
171209
* Gets an object of translations for a given language with the current loader
172210
* @param lang
@@ -272,8 +310,13 @@ export class TranslateService {
272310
res = this.parser.interpolate(this.parser.getValue(translations, key), interpolateParams);
273311
}
274312

275-
if(typeof res === "undefined" && this.defaultLang && this.defaultLang !== this.currentLang) {
276-
res = this.parser.interpolate(this.parser.getValue(this.translations[this.defaultLang], key), interpolateParams);
313+
if(typeof res === "undefined") {
314+
if(!this.defaultLang) {
315+
// load a default language
316+
this.setDefaultLang('en');
317+
} else if(this.defaultLang !== this.currentLang) {
318+
res = this.parser.interpolate(this.parser.getValue(this.translations[this.defaultLang], key), interpolateParams);
319+
}
277320
}
278321

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

420+
/**
421+
* Changes the default lang
422+
* @param lang
423+
*/
424+
private changeDefaultLang(lang: string): void {
425+
this.defaultLang = lang;
426+
this.onDefaultLangChange.emit({lang: lang, translations: this.translations[lang]});
427+
}
428+
377429
/**
378430
* Allows to reload the lang file from the file
379431
* @param lang

tests/translate.service.spec.ts

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,42 @@ describe('TranslateService', () => {
8383
it("should fallback to the default language", () => {
8484
translate.use('fr');
8585

86-
translate.setDefaultLang('en');
87-
translate.setTranslation('en', {"TEST": "This is a test"});
86+
translate.get('TEST').subscribe((res: string) => {
87+
expect(res).toEqual('TEST');
88+
89+
translate.setDefaultLang('nl');
90+
91+
translate.get('TEST').subscribe((res2: string) => {
92+
expect(res2).toEqual('Dit is een test');
93+
});
94+
95+
mockBackendResponse(connection, '{"TEST": "Dit is een test"}');
96+
});
97+
98+
mockBackendResponse(connection, '{}');
99+
});
100+
101+
it("should use the default language by default", () => {
102+
translate.setDefaultLang('nl');
88103

89104
translate.get('TEST').subscribe((res: string) => {
90-
expect(res).toEqual('This is a test');
105+
expect(res).toEqual('Dit is een test');
106+
});
107+
108+
mockBackendResponse(connection, '{"TEST": "Dit is een test"}');
109+
});
110+
111+
it("should fallback to english if no default language was set", () => {
112+
translate.use('fr');
113+
114+
translate.get('TEST').subscribe((res: string) => {
115+
expect(res).toEqual('TEST');
116+
117+
translate.get('TEST').subscribe((res2: string) => {
118+
expect(res2).toEqual('This is a test');
119+
});
120+
121+
mockBackendResponse(connection, '{"TEST": "This is a test"}');
91122
});
92123

93124
mockBackendResponse(connection, '{}');
@@ -225,7 +256,7 @@ describe('TranslateService', () => {
225256
translate.use('en');
226257
});
227258

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

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

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

242-
// use set timeout because no request is really made and we need to trigger zone to resolve the observable
243-
setTimeout(() => {
244-
translate.get('TEST').subscribe((res2: string) => {
245-
expect(res2).toEqual('TEST'); // because the loader is "pristine" as if it was never called
246-
expect(connection.mockRespond).toHaveBeenCalledTimes(1);
247-
done();
248-
});
249-
}, 10);
273+
spyOn(connection, 'mockRespond').and.callThrough();
274+
275+
// as current language does not contain the right translation any more, it will request the default language
276+
translate.get('TEST').subscribe((res2: string) => {
277+
expect(res2).toEqual('TEST'); // because the default language does not contain the right translation either
278+
expect(connection.mockRespond).toHaveBeenCalledTimes(1);
279+
});
280+
281+
mockBackendResponse(connection, '{}');
250282
});
251283

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

0 commit comments

Comments
 (0)