Skip to content

Commit

Permalink
Made getTranslation deprecated
Browse files Browse the repository at this point in the history
Moved iteration code from parser to service.
  • Loading branch information
CodeAndWeb committed Sep 26, 2024
1 parent 79cb220 commit 816b263
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 72 deletions.
4 changes: 2 additions & 2 deletions projects/ngx-translate/src/lib/translate.compiler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {Injectable} from "@angular/core";
import {InterpolateFunction} from "./translate.parser";
import {InterpolatableTranslation, InterpolatableTranslationObject} from "./translate.service";
import {InterpolatableTranslation, InterpolatableTranslationObject, Translation} from "./translate.service";

export abstract class TranslateCompiler {
abstract compile(value: string, lang: string): InterpolatableTranslation;

abstract compileTranslations(translations: InterpolatableTranslationObject, lang: string): InterpolatableTranslationObject;
abstract compileTranslations(translations: Translation, lang: string): InterpolatableTranslationObject;
}

/**
Expand Down
8 changes: 4 additions & 4 deletions projects/ngx-translate/src/lib/translate.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
LangChangeEvent,
TranslateService,
TranslationChangeEvent,
Translation
Translation,
InterpolationParameters
} from "./translate.service";
import {equals, isDefined} from './util';
import {InterpolationParameters} from "./translate.parser";

interface ExtendedNode extends Text {
originalContent: string;
Expand All @@ -27,8 +27,8 @@ interface ExtendedNode extends Text {
})
export class TranslateDirective implements AfterViewChecked, OnDestroy {
key!: string;
lastParams: InterpolationParameters;
currentParams: InterpolationParameters;
lastParams?: InterpolationParameters;
currentParams?: InterpolationParameters;
onLangChangeSub!: Subscription;
onDefaultLangChangeSub!: Subscription;
onTranslationChangeSub!: Subscription;
Expand Down
12 changes: 4 additions & 8 deletions projects/ngx-translate/src/lib/translate.parser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {TranslateDefaultParser, TranslateParser} from "../public-api";
import {InterpolateFunction, TranslateDefaultParser, TranslateParser} from "../public-api";

describe('Parser', () => {
let parser: TranslateParser;
Expand Down Expand Up @@ -28,14 +28,10 @@ describe('Parser', () => {
expect(parser.interpolate("This is a {{ key1.key2.key3 }}", {key1: {key2: {key3: "value3"}}})).toEqual("This is a value3");
});

it('should interpolate strings with arrays', () => {
expect(parser.interpolate("This is a {{ key.0 }}", {key: ["A", "B", "C"]})).toEqual("This is a A");
expect(parser.interpolate("This is a {{ key.11 }}", {key: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]})).toEqual("This is a L");
expect(parser.interpolate("This is a {{ key.1.x }}", {key: ["A", {x: "B"}]})).toEqual("This is a B");
});

it('should support interpolation functions', () => {
expect(parser.interpolate((v: string) => v.toUpperCase() + ' YOU!', 'bless')).toBe('BLESS YOU!');
const uc:InterpolateFunction = (params) => (params?.['x'] ?? '').toUpperCase() + ' YOU!';

expect(parser.interpolate( uc , {"x":'bless'})).toBe('BLESS YOU!');
});

it('should handle edge cases: array', () => {
Expand Down
40 changes: 10 additions & 30 deletions projects/ngx-translate/src/lib/translate.parser.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import {Injectable} from "@angular/core";
import {getValue, isDefined, isObject} from "./util";
import {
InterpolatableTranslation,
InterpolatableTranslationObject, Translation,
TranslationObject
} from "./translate.service";
import {getValue, isDefined, isString, isFunction} from "./util";
import {InterpolationParameters} from "./translate.service";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type InterpolationParameters = any;

export type InterpolateFunction = (params: InterpolationParameters) => string;
export type InterpolateFunction = (params?: InterpolationParameters) => string;


export abstract class TranslateParser
Expand All @@ -20,7 +14,7 @@ export abstract class TranslateParser
* @param expr
* @param params
*/
abstract interpolate(expr: InterpolatableTranslation, params?: InterpolationParameters): Translation;
abstract interpolate(expr: InterpolateFunction|string, params?: InterpolationParameters): string|undefined;
}


Expand All @@ -29,31 +23,17 @@ export class TranslateDefaultParser extends TranslateParser
{
templateMatcher = /{{\s?([^{}\s]*)\s?}}/g;

public interpolate(expr: InterpolatableTranslation, params?: InterpolationParameters): Translation
public interpolate(expr: InterpolateFunction|string, params?: InterpolationParameters): string|undefined
{
if (typeof expr === "string")
if (isString(expr))
{
return this.interpolateString(expr, params);
return this.interpolateString(expr as string, params);
}
else if (typeof expr === "function")
else if (isFunction(expr))
{
return this.interpolateFunction(expr, params);
}
else if (Array.isArray(expr))
{
return expr.map((item) => this.interpolate(item, params));
}
else if (isObject(expr)) {
const exprObject = expr as InterpolatableTranslationObject;
return Object.keys(exprObject).reduce((acc, key) => {
const value = this.interpolate(exprObject[key], params);
if(value !== undefined)
{
acc[key] = value;
}
return acc;
}, {} as TranslationObject);
return this.interpolateFunction(expr as InterpolateFunction, params);
}
return undefined;
}

private interpolateFunction(fn: InterpolateFunction, params?: InterpolationParameters): string
Expand Down
10 changes: 5 additions & 5 deletions projects/ngx-translate/src/lib/translate.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
LangChangeEvent,
TranslateService,
TranslationChangeEvent,
Translation
Translation,
InterpolationParameters
} from "./translate.service";
import {equals, isDefined} from './util';
import {equals, isDefined, isDict, isString} from "./util";
import {Subscription} from 'rxjs';
import {InterpolationParameters} from "./translate.parser";

@Injectable()
@Pipe({
Expand Down Expand Up @@ -58,7 +58,7 @@ export class TranslatePipe implements PipeTransform, OnDestroy {

let interpolateParams: object | undefined = undefined;
if (isDefined(args[0]) && args.length) {
if (typeof args[0] === 'string' && args[0].length) {
if (isString(args[0]) && args[0].length) {
// we accept objects written in the template such as {n:1}, {'n':1}, {n:'v'}
// which is why we might need to change it to real JSON objects such as {"n":1} or {"n":"v"}
const validArgs: string = args[0]
Expand All @@ -70,7 +70,7 @@ export class TranslatePipe implements PipeTransform, OnDestroy {
void e;
throw new SyntaxError(`Wrong parameter in TranslatePipe. Expected a valid Object, received: ${args[0]}`);
}
} else if (typeof args[0] === 'object' && !Array.isArray(args[0])) {
} else if (isDict(args[0])) {
interpolateParams = args[0];
}
}
Expand Down
66 changes: 52 additions & 14 deletions projects/ngx-translate/src/lib/translate.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {TranslateLoader} from "./translate.loader";
import {InterpolateFunction, TranslateParser} from "./translate.parser";

import {TranslateStore} from "./translate.store";
import {getValue, isDefined, mergeDeep, setValue} from "./util";
import {getValue, isDefined, isArray, isString, mergeDeep, setValue, isDict} from "./util";

export const ISOALTE_TRANSLATE_SERVICE = new InjectionToken<string>('ISOALTE_TRANSLATE_SERVICE');
export const USE_DEFAULT_LANG = new InjectionToken<string>('USE_DEFAULT_LANG');
export const DEFAULT_LANGUAGE = new InjectionToken<string>('DEFAULT_LANGUAGE');
export const USE_EXTEND = new InjectionToken<string>('USE_EXTEND');

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type InterpolationParameters = Record<string, any>;

export type Translation =
string |
Expand Down Expand Up @@ -288,18 +290,30 @@ export class TranslateService {

// if this language is unavailable or extend is true, ask for it
if (typeof this.translations[lang] === "undefined" || this.extend) {
this._translationRequests[lang] = this._translationRequests[lang] || this.getTranslation(lang);
this._translationRequests[lang] = this._translationRequests[lang] || this.loadAndCompileTranslations(lang);
pending = this._translationRequests[lang];
}

return pending;
}



/**
* Gets an object of translations for a given language with the current loader
* and passes it through the compiler
*
* @deprecated This function is meant for internal use. There should
* be no reason to use outside this service. You can plug into this
* functionality by using a customer TranslateLoader or TranslateCompiler.
* To load a new language use setDefaultLang() and/or use()
*/
public getTranslation(lang: string): Observable<TranslationObject> {
public getTranslation(lang: string): Observable<InterpolatableTranslationObject>
{
return this.loadAndCompileTranslations(lang);
}

private loadAndCompileTranslations(lang: string): Observable<InterpolatableTranslationObject> {
this.pending = true;
const loadingTranslations = this.currentLoader.getTranslation(lang).pipe(
shareReplay(1),
Expand Down Expand Up @@ -368,16 +382,16 @@ export class TranslateService {
this.addLangs(Object.keys(this.translations));
}

private getParsedResultForKey(translations: InterpolatableTranslation, key: string, interpolateParams?: object): Translation|Observable<Translation>
private getParsedResultForKey(translations: InterpolatableTranslation, key: string, interpolateParams?: InterpolationParameters): Translation|Observable<Translation>
{
let res: Translation | Observable<Translation> | undefined;

if (translations) {
res = this.parser.interpolate(getValue(translations, key), interpolateParams);
res = this.runInterpolation(getValue(translations, key), interpolateParams);
}

if (res === undefined && this.defaultLang != null && this.defaultLang !== this.currentLang && this.useDefaultLang) {
res = this.parser.interpolate(getValue(this.translations[this.defaultLang], key), interpolateParams);
res = this.runInterpolation(getValue(this.translations[this.defaultLang], key), interpolateParams);
}

if (res === undefined) {
Expand All @@ -391,10 +405,30 @@ export class TranslateService {
return res !== undefined ? res : key;
}

private runInterpolation(translations: InterpolatableTranslation, interpolateParams?: InterpolationParameters): Translation
{
if(isArray(translations))
{
return (translations as Translation[]).map((translation) => this.runInterpolation(translation, interpolateParams));
}
else if (isDict(translations))
{
const result: TranslationObject = {};
for (const key in translations) {
result[key] = this.runInterpolation(translations[key], interpolateParams);
}
return result;
}
else
{
return this.parser.interpolate(translations, interpolateParams);
}
}

/**
* Returns the parsed result of the translations
*/
public getParsedResult(translations: InterpolatableTranslation, key: string | string[], interpolateParams?: object): Translation|TranslationObject|Observable<Translation|TranslationObject> {
public getParsedResult(translations: InterpolatableTranslation, key: string | string[], interpolateParams?: InterpolationParameters): Translation|TranslationObject|Observable<Translation|TranslationObject> {

// handle a bunch of keys
if (key instanceof Array) {
Expand Down Expand Up @@ -429,7 +463,7 @@ export class TranslateService {
* Gets the translated value of a key (or an array of keys)
* @returns the translated key, or an object of translated keys
*/
public get(key: string | string[], interpolateParams?: object): Observable<Translation|TranslationObject> {
public get(key: string | string[], interpolateParams?: InterpolationParameters): Observable<Translation|TranslationObject> {
if (!isDefined(key) || !key.length) {
throw new Error(`Parameter "key" is required and cannot be empty`);
}
Expand All @@ -450,7 +484,7 @@ export class TranslateService {
* whenever the translation changes.
* @returns A stream of the translated key, or an object of translated keys
*/
public getStreamOnTranslationChange(key: string | string[], interpolateParams?: object): Observable<Translation|TranslationObject> {
public getStreamOnTranslationChange(key: string | string[], interpolateParams?: InterpolationParameters): Observable<Translation|TranslationObject> {
if (!isDefined(key) || !key.length) {
throw new Error(`Parameter "key" is required and cannot be empty`);
}
Expand All @@ -471,7 +505,7 @@ export class TranslateService {
* whenever the language changes.
* @returns A stream of the translated key, or an object of translated keys
*/
public stream(key: string | string[], interpolateParams?: object): Observable<Translation|TranslationObject> {
public stream(key: string | string[], interpolateParams?: InterpolationParameters): Observable<Translation|TranslationObject> {
if (!isDefined(key) || !key.length) {
throw new Error(`Parameter "key" required`);
}
Expand All @@ -491,7 +525,7 @@ export class TranslateService {
* All rules regarding the current language, the preferred language of even fallback languages
* will be used except any promise handling.
*/
public instant(key: string | string[], interpolateParams?: object): Translation|TranslationObject
public instant(key: string | string[], interpolateParams?: InterpolationParameters): Translation|TranslationObject
{
if (!isDefined(key) || key.length === 0) {
throw new Error('Parameter "key" is required and cannot be empty');
Expand All @@ -515,8 +549,12 @@ export class TranslateService {
/**
* Sets the translated value of a key, after compiling it
*/
public set(key: string, value: Translation, lang: string = this.currentLang): void {
setValue(this.translations[lang], key, this.compiler.compile(value, lang));
public set(key: string, translation: Translation, lang: string = this.currentLang): void {
setValue(this.translations[lang], key,
isString(translation)
? this.compiler.compile(translation, lang)
: this.compiler.compileTranslations(translation, lang)
);
this.updateLangs();
this.onTranslationChange.emit({lang: lang, translations: this.translations[lang]});
}
Expand Down Expand Up @@ -547,7 +585,7 @@ export class TranslateService {
*/
public reloadLang(lang: string): Observable<InterpolatableTranslationObject> {
this.resetLang(lang);
return this.getTranslation(lang);
return this.loadAndCompileTranslations(lang);
}

/**
Expand Down
39 changes: 30 additions & 9 deletions projects/ngx-translate/src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,29 @@ export function isDefined(value: any): boolean {
return typeof value !== 'undefined' && value !== null;
}

export function isObject(item: any): boolean {
return (item && typeof item === 'object' && !Array.isArray(item));

export function isDict(value: any): boolean {
return isObject(value) && !isArray(value);
}


export function isObject(value: any): boolean {
return typeof value === 'object';
}

export function isArray(value: any): boolean {
return Array.isArray(value);
}

export function isString(value: any): boolean {
return typeof value === 'string';
}

export function isFunction(value: any):boolean {
return typeof value === "function"
}


export function mergeDeep(target: any, source: any): any {
const output = Object.assign({}, target);

Expand All @@ -67,11 +86,11 @@ export function mergeDeep(target: any, source: any): any {

if (isObject(target) && isObject(source)) {
Object.keys(source).forEach((key: any) => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, {[key]: source[key]});
} else {
if (isDict(source[key])) {
if (key in target) {
output[key] = mergeDeep(target[key], source[key]);
} else {
Object.assign(output, {[key]: source[key]});
}
} else {
Object.assign(output, {[key]: source[key]});
Expand All @@ -96,7 +115,7 @@ export function getValue(target: any, key: string): any
do
{
key += keys.shift();
if (isDefined(target) && isDefined(target[key]) && (typeof target[key] === "object" || !keys.length))
if (isDefined(target) && isDefined(target[key]) && (isDict(target[key]) ||isArray(target[key]) || !keys.length))
{
target = target[key];
key = "";
Expand Down Expand Up @@ -133,10 +152,12 @@ export function setValue(target: any, key: string, value: any): void {
current[key] = value;
} else {
// If the key doesn't exist or isn't an object, create an empty object
if (!current[key] || typeof current[key] !== 'object') {
if (!current[key] || !isDict(current[key])) {
current[key] = {};
}
current = current[key];
}
}
}
}


0 comments on commit 816b263

Please sign in to comment.