Skip to content

Translation transformer handler #1554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
12 changes: 11 additions & 1 deletion projects/ngx-translate/src/lib/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {TranslateLoader} from "./translate.loader";
import {InterpolateFunction, TranslateParser} from "./translate.parser";
import {TranslateStore} from "./translate.store";
import {isDefinedAndNotNull, isArray, isString, isDict, insertValue} from "./util";
import {TranslationTransformerHandler} from "./translation-transformer-handler";

export const ISOLATE_TRANSLATE_SERVICE = new InjectionToken<string>('ISOLATE_TRANSLATE_SERVICE');
export const USE_DEFAULT_LANG = new InjectionToken<string>('USE_DEFAULT_LANG');
Expand Down Expand Up @@ -153,6 +154,7 @@ export class TranslateService {
* @param compiler An instance of the compiler currently used
* @param parser An instance of the parser currently used
* @param missingTranslationHandler A handler for missing translations.
* @param translationTransformerHandler A handler to transform retrieved raw translation before interpolation
* @param useDefaultLang whether we should use default language translation when current language translation is missing.
* @param isolate whether this service should use the store or not
* @param extend To make a child module extend (and use) translations from parent modules.
Expand All @@ -163,6 +165,7 @@ export class TranslateService {
public compiler: TranslateCompiler,
public parser: TranslateParser,
public missingTranslationHandler: MissingTranslationHandler,
public translationTransformerHandler: TranslationTransformerHandler,
@Inject(USE_DEFAULT_LANG) private useDefaultLang = true,
@Inject(ISOLATE_TRANSLATE_SERVICE) isolate = false,
@Inject(USE_EXTEND) private extend = false,
Expand Down Expand Up @@ -347,7 +350,14 @@ export class TranslateService {

private getTextToInterpolate(key: string): InterpolatableTranslation | undefined
{
return this.store.getTranslation(key, this.useDefaultLang);
const rawTranslation = this.store.getTranslation(key, this.useDefaultLang);

return this.translationTransformerHandler.handle(
{
key,
rawTranslation,
}
);
}

private runInterpolation(translations: InterpolatableTranslation, interpolateParams?: InterpolationParameters): Translation
Expand Down
119 changes: 119 additions & 0 deletions projects/ngx-translate/src/lib/translation-transformer-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {TranslationObject} from "./translate.service";
import {isDict, provideTranslateService, TranslateService, TranslationTransformerHandler} from "../public-api";
import {Injectable} from "@angular/core";
import {TranslationTransformerHandlerParams} from "./translation-transformer-handler";
import {TestBed} from "@angular/core/testing";
import {TranslateLoader} from "./translate.loader";
import {Observable, of, tap} from "rxjs";

const translations: TranslationObject = {
"TEST": "This is a test",
"dictionary": {
"_": "default translation",
"first": "First translation {{placeholder}}",
}
};

@Injectable()
class FakeLoader implements TranslateLoader {
getTranslation(lang: string): Observable<TranslationObject> {
void lang;
return of(translations);
}
}

describe('TranslationTransformerHandler', () => {
let translate: TranslateService;
let translationHandler: TranslationTransformerHandler;

@Injectable()
class CustomTranslationHandlerService implements TranslationTransformerHandler {
handle(params: TranslationTransformerHandlerParams): unknown {
if (isDict(params.rawTranslation)) {
return params.key;
}

return params.rawTranslation;
}
}

const prepareCustomHandler = () => {
TestBed.configureTestingModule({
providers: [
provideTranslateService({
loader: {provide: TranslateLoader, useClass: FakeLoader},
translationTransformerHandler: {provide: TranslationTransformerHandler, useClass: CustomTranslationHandlerService},
})
]
});

translate = TestBed.inject(TranslateService);
translationHandler = TestBed.inject(TranslationTransformerHandler);
translate.use('en');
};

const prepareDefaultHandler = () => {
TestBed.configureTestingModule({
providers: [
provideTranslateService({
loader: {provide: TranslateLoader, useClass: FakeLoader},
})
]
});

translate = TestBed.inject(TranslateService);
translationHandler = TestBed.inject(TranslationTransformerHandler);
translate.use('en');
};

it('should use the standard FakeTranslationHandler and return an interpolated dictionary', () => {
prepareDefaultHandler();
translate.get("dictionary", {placeholder: "testPlaceholder"})
.pipe(
tap((result) => {
expect(result).toEqual({
"_": "default translation",
"first": "First translation testPlaceholder",
});
})
)
.subscribe();
})

it('should use the standard FakeTranslationHandler and return the translation string', () => {
prepareDefaultHandler();
translate.get("TEST")
.pipe(
tap((result) => {
expect(result).toEqual(translations["TEST"]);
})
)
.subscribe();
})

it('should use CustomTranslationHandler and return the translation string', () => {
prepareCustomHandler();
spyOn(translationHandler, 'handle').and.callThrough();
translate.get("TEST")
.pipe(
tap((result) => {
expect(translationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({key: "TEST", rawTranslation: translations["TEST"]}));
expect(result).toEqual(translations["TEST"]);
})
)
.subscribe();
})

it('should use CustomTranslationHandler and return the key of the dictionary', () => {
prepareCustomHandler();
spyOn(translationHandler, 'handle').and.callThrough();
translate.get("dictionary")
.pipe(
tap((result) => {
expect(translationHandler.handle).toHaveBeenCalledWith(jasmine.objectContaining({key: "dictionary", rawTranslation: translations["dictionary"]}));
expect(result).toEqual("dictionary");
})
)
.subscribe();
})
})
36 changes: 36 additions & 0 deletions projects/ngx-translate/src/lib/translation-transformer-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {Injectable} from "@angular/core";
import {InterpolatableTranslation} from "./translate.service";

export interface TranslationTransformerHandlerParams {
/**
* the key present in the translation files
*/
key: string;

/**
* interpolable translation object or string returned from the store
*/
rawTranslation: InterpolatableTranslation;
}

export abstract class TranslationTransformerHandler {
/**
* A function that transforms the interpolable translation retrieved from the translation file.
*
* @param params key and returned interpolable translation from the store
* @returns a transformed interpolable translation
*
* It returns the interpolable translation transformed by the handler logic
*/
abstract handle(params: TranslationTransformerHandlerParams): InterpolatableTranslation;
}

/**
* This handler is just a placeholder that does nothing, in case you don't need a translation transformer handler at all
*/
@Injectable()
export class FakeTranslationTransformerHandler implements TranslationTransformerHandler {
handle(params: TranslationTransformerHandlerParams): InterpolatableTranslation {
return params.rawTranslation;
}
}
6 changes: 6 additions & 0 deletions projects/ngx-translate/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ISOLATE_TRANSLATE_SERVICE,
TranslateService
} from "./lib/translate.service";
import {FakeTranslationTransformerHandler, TranslationTransformerHandler} from "./lib/translation-transformer-handler";

export * from "./lib/translate.loader";
export * from "./lib/translate.service";
Expand All @@ -24,12 +25,14 @@ export * from "./lib/translate.pipe";
export * from "./lib/translate.store";
export * from "./lib/extraction-marker";
export * from "./lib/util"
export * from "./lib/translation-transformer-handler"

export interface TranslateModuleConfig {
loader?: Provider;
compiler?: Provider;
parser?: Provider;
missingTranslationHandler?: Provider;
translationTransformerHandler?: Provider;
// isolate the service instance, only works for lazy loaded modules or components with the "providers" property
isolate?: boolean;
// extends translations for a given language instead of ignoring them if present
Expand All @@ -45,6 +48,7 @@ export const provideTranslateService = (config: TranslateModuleConfig = {}): Env
config.compiler || {provide: TranslateCompiler, useClass: TranslateFakeCompiler},
config.parser || {provide: TranslateParser, useClass: TranslateDefaultParser},
config.missingTranslationHandler || {provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler},
config.translationTransformerHandler || {provide: TranslationTransformerHandler, useClass: FakeTranslationTransformerHandler},
TranslateStore,
{provide: ISOLATE_TRANSLATE_SERVICE, useValue: config.isolate},
{provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang},
Expand Down Expand Up @@ -77,6 +81,7 @@ export class TranslateModule {
config.compiler || {provide: TranslateCompiler, useClass: TranslateFakeCompiler},
config.parser || {provide: TranslateParser, useClass: TranslateDefaultParser},
config.missingTranslationHandler || {provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler},
config.translationTransformerHandler || {provide: TranslationTransformerHandler, useClass: FakeTranslationTransformerHandler},
TranslateStore,
{provide: ISOLATE_TRANSLATE_SERVICE, useValue: config.isolate},
{provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang},
Expand All @@ -98,6 +103,7 @@ export class TranslateModule {
config.compiler || {provide: TranslateCompiler, useClass: TranslateFakeCompiler},
config.parser || {provide: TranslateParser, useClass: TranslateDefaultParser},
config.missingTranslationHandler || {provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler},
config.translationTransformerHandler || {provide: TranslationTransformerHandler, useClass: FakeTranslationTransformerHandler},
{provide: ISOLATE_TRANSLATE_SERVICE, useValue: config.isolate},
{provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang},
{provide: USE_EXTEND, useValue: config.extend},
Expand Down