Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions projects/ngx-translate/src/lib/translate.service.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InterpolateFunction } from "./translate.parser";
import { Observable } from "rxjs";
import { DeepReadonly } from "./translate.store";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type InterpolationParameters = Record<string, any>;
Expand Down Expand Up @@ -89,6 +90,12 @@ export abstract class ITranslateService {
translations: TranslationObject,
shouldMerge?: boolean,
): void;
public abstract getTranslations(
language: Language
): DeepReadonly<InterpolatableTranslationObject>
public abstract loadTranslations(
lang: Language
): Observable<DeepReadonly<InterpolatableTranslationObject>>

public abstract getParsedResult(
key: string | string[],
Expand Down
62 changes: 38 additions & 24 deletions projects/ngx-translate/src/lib/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,27 @@ export class TranslateService implements ITranslateService {
this.store.setCurrentLang(lang, false);
}

const loadingLanguage = this.loadTranslations(lang);
loadingLanguage.pipe(take(1)).subscribe({
next: () => {
this.changeLang(lang);
},
error: () => {
/* ignore here - use can handle it */
},
});

return loadingLanguage;
}

/**
* Load and get a specific language without changing the current language
*/
public loadTranslations(lang: Language): Observable<DeepReadonly<InterpolatableTranslationObject>> {
const pending = this.loadOrExtendLanguage(lang);
if (isObservable(pending)) {
pending.pipe(take(1)).subscribe({
next: () => {
this.changeLang(lang);
},
error: () => {
/* ignore here - use can handle it */
},
});
return pending;
}

this.changeLang(lang);
return of(this.store.getTranslations(lang));
return isObservable(pending)
? pending : of(this.store.getTranslations(lang));
}

/**
Expand Down Expand Up @@ -280,8 +286,9 @@ export class TranslateService implements ITranslateService {
protected getParsedResultForKey(
key: string,
interpolateParams?: InterpolationParameters,
overrideLang?: Language,
): StrictTranslation | Observable<StrictTranslation> {
const textToInterpolate = this.getTextToInterpolate(key);
const textToInterpolate = this.getTextToInterpolate(key, overrideLang);

if (isDefinedAndNotNull(textToInterpolate)) {
return this.runInterpolation(textToInterpolate, interpolateParams);
Expand All @@ -303,8 +310,11 @@ export class TranslateService implements ITranslateService {
return this.store.getFallbackLang();
}

protected getTextToInterpolate(key: string): InterpolatableTranslation | undefined {
return this.store.getTranslation(key);
protected getTextToInterpolate(
key: string,
overrideLang?: Language,
): InterpolatableTranslation | undefined {
return this.store.getTranslation(key, overrideLang);
}

protected runInterpolation(
Expand Down Expand Up @@ -355,21 +365,23 @@ export class TranslateService implements ITranslateService {
public getParsedResult(
key: string | string[],
interpolateParams?: InterpolationParameters,
overrideLang?: Language,
): StrictTranslation | Observable<StrictTranslation> {
return key instanceof Array
? this.getParsedResultForArray(key, interpolateParams)
: this.getParsedResultForKey(key, interpolateParams);
? this.getParsedResultForArray(key, interpolateParams, overrideLang)
: this.getParsedResultForKey(key, interpolateParams, overrideLang);
}

protected getParsedResultForArray(
key: string[],
interpolateParams: InterpolationParameters | undefined,
overrideLang?: Language
) {
const result: Record<string, StrictTranslation | Observable<StrictTranslation>> = {};

let observables = false;
for (const k of key) {
result[k] = this.getParsedResultForKey(k, interpolateParams);
result[k] = this.getParsedResultForKey(k, interpolateParams, overrideLang);
observables = observables || isObservable(result[k]);
}

Expand All @@ -396,21 +408,22 @@ export class TranslateService implements ITranslateService {
public get(
key: string | string[],
interpolateParams?: InterpolationParameters,
overrideLang?: Language,
): Observable<Translation> {
if (!isDefinedAndNotNull(key) || !key.length) {
return of("");
}

// check if we are loading a new translation to use
if (this.lastUseLanguage && this.loadingTranslations[this.lastUseLanguage]) {
return this.loadingTranslations[this.store.getCurrentLang()].pipe(
if (this.lastUseLanguage && this.loadingTranslations[overrideLang ?? this.lastUseLanguage]) {
return this.loadingTranslations[overrideLang ?? this.store.getCurrentLang()].pipe(
concatMap(() => {
return makeObservable(this.getParsedResult(key, interpolateParams));
return makeObservable(this.getParsedResult(key, interpolateParams, overrideLang));
}),
);
}

return makeObservable(this.getParsedResult(key, interpolateParams));
return makeObservable(this.getParsedResult(key, interpolateParams, overrideLang));
}

/**
Expand Down Expand Up @@ -469,12 +482,13 @@ export class TranslateService implements ITranslateService {
public instant(
key: string | string[],
interpolateParams?: InterpolationParameters,
overrideLang?: Language,
): Translation {
if (!isDefinedAndNotNull(key) || key.length === 0) {
return "";
}

const result = this.getParsedResult(key, interpolateParams);
const result = this.getParsedResult(key, interpolateParams, overrideLang);

return isObservable(result) ? this.keyToObject(key) : result;
}
Expand Down
7 changes: 5 additions & 2 deletions projects/ngx-translate/src/lib/translate.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,11 @@ export class TranslateStore {
delete this.translations[lang];
}

public getTranslation(key: string): InterpolatableTranslation {
let text = this.getValue(this.currentLang, key);
public getTranslation(
key: string,
overrideLanguage?: Language,
): InterpolatableTranslation {
let text = this.getValue(overrideLanguage ?? this.currentLang, key);

if (
(text === undefined || text === null) &&
Expand Down
114 changes: 113 additions & 1 deletion projects/ngx-translate/src/tests/translate.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, inject } from "@angular/core";
import { fakeAsync, TestBed, tick } from "@angular/core/testing";
import { defer, EMPTY, Observable, of, timer, zip } from "rxjs";
import { defer, EMPTY, firstValueFrom, Observable, of, timer, zip } from "rxjs";
import { first, map, take, toArray } from "rxjs/operators";
import {
InterpolationParameters,
Expand Down Expand Up @@ -1248,3 +1248,115 @@ describe("TranslateService (Error Conditions and Recovery)", () => {
});
});
});

describe("TranslateService - Override language support", () => {
let translate: TestableTranslateService
const translationStatic: any = {
en: {
TEST: 'English',
parent: {
child: 'test-en'
}
},
nl: {
TEST: 'Nederlands',
parent: {
child: 'test-nl'
}
}
}
class FakeMultiLangLoader implements TranslateLoader {
getTranslation(lang: string): Observable<TranslationObject> {
return of(translationStatic[lang]);
}
}

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideTestableTranslateService({ loader: provideTranslateLoader(FakeMultiLangLoader) }),
],
});
translate = TestBed.inject(TranslateService) as TestableTranslateService;
});

it("loadTranslation - should load the additional language if not done already", async () => {
translate.use('en')

const result = translate.loadTranslations('nl')
expect(result).toBeDefined()

const values = await firstValueFrom(result)
expect(values as TranslationObject).toEqual(translationStatic.nl as TranslationObject)
})


it("getTranslations - should return if loaded", async () => {
translate.use('en')


const unloadedTranslations = translate.getTranslations('nl')

await firstValueFrom(translate.loadTranslations('nl'))

const translations = translate.getTranslations('nl')

expect(unloadedTranslations).toBeUndefined()
expect(translations).toBeDefined()
expect(translations as any).toEqual(translationStatic.nl)
})

it("instant - should return the key when additional language isn't loaded", async () => {
translate.use('en')

const fallbackResult = translate.instant('TEST', undefined, 'nl')
const fallbackResultTwo = translate.instant('parent.child', undefined, 'nl')

expect(fallbackResult).toEqual('TEST')
expect(fallbackResultTwo).toEqual('parent.child')
})

it("instant - should return the correct translation value when additional language is loaded", async () => {
translate.use('en')
translate.setFallbackLang('en')

await firstValueFrom(translate.loadTranslations('nl'))

const correctResult = translate.instant('TEST', undefined, 'nl')
const correctResultTwo = translate.instant('parent.child', undefined, 'nl')

expect(correctResult).toEqual('Nederlands')
expect(correctResultTwo).toEqual('test-nl')
})

it("getParsedResult - should parse key correctly for overriden translation", async () => {
translate.use('en')
translate.setFallbackLang('en')

await firstValueFrom(translate.loadTranslations('nl'))

const normalTranslation = translate.getParsedResult('TEST')
const overridenTranslation = translate.getParsedResult('TEST', undefined, 'nl')


expect(normalTranslation as string).toEqual('English')
expect(overridenTranslation as string).toEqual('Nederlands')
})

it("get - should return overriden language translation correctly", async () => {
translate.use('en')
translate.setFallbackLang('en')

await firstValueFrom(translate.loadTranslations('nl'))

const normalTranslation = await firstValueFrom(translate.get('TEST'))
const overridenTranslation = await firstValueFrom(translate.get('TEST', undefined, 'nl'))


expect(normalTranslation as Translation)
.toEqual('English')

expect(overridenTranslation as Translation)
.toEqual('Nederlands')
})
})