diff --git a/src/app/translation/thing-translator.parser.ts b/src/app/translation/thing-translator.parser.ts new file mode 100644 index 00000000..d4cb4570 --- /dev/null +++ b/src/app/translation/thing-translator.parser.ts @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Injectable} from '@angular/core'; +import {isDefined} from '@ngx-translate/core/lib/util'; + +// tslint:disable: member-ordering prefer-function-over-method completed-docs + +export abstract class TranslateParser { + /** + * Gets a value from an object by composed key + * parser.getValue({ key1: { keyA: 'valueI' }}, 'key1.keyA') ==> 'valueI' + */ + abstract getValue(target: unknown, key: string): string; + + /** + * TODO + */ + abstract getValueFromKeyPath(instance: object, keypath: string): unknown; +} + +@Injectable() +export class TranslateDefaultParser extends TranslateParser { + + /** + * TODO + * + */ + getValue(target: unknown, key: string): string { + const keys = typeof key === 'string' ? key.split('.') : [key]; + let aKey = ''; + // tslint:disable-next-line: no-any + let newTarget = target as any; + do { + aKey += keys.shift(); + if (isDefined(newTarget) && isDefined(newTarget[aKey]) && (typeof newTarget[aKey] === 'object' || keys.length === 0)) { + newTarget = newTarget[aKey]; + aKey = ''; + } else if (keys.length === 0) { + newTarget = undefined; + } else { + aKey += '.'; + } + } while (keys.length > 0); + + return newTarget; + } + + + getValueFromKeyPath(instance: object, keypath: string): unknown { + // keypath = aproperty[0].anotherproperty["arrayproperty"][42].finalproperty + let path = keypath.replace(/(?:\"|\')'"/gmi, ''); + // path = aproperty[0].anotherproperty[.arrayproperty.][42].finalproperty + path = path.replace(/(?:\[|\])/gmi, '.'); + // path = aproperty.0..anotherproperty..arrayproperty...42..finalproperty + path = path.replace(/\.{2,}/gmi, '.'); + // path = aproperty.0.anotherproperty.arrayproperty.42.finalproperty + + const keypathChain = keypath.split('.'); + + // tslint:disable-next-line: no-any + let property = instance as any; + + for(const key of keypathChain) { + property = property[key] ?? undefined; + } + + return property; + } +} diff --git a/src/app/translation/thing-translator.pipe.ts b/src/app/translation/thing-translator.pipe.ts new file mode 100644 index 00000000..2a3f2e85 --- /dev/null +++ b/src/app/translation/thing-translator.pipe.ts @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core'; +import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; +import {isThing, SCThings} from '@openstapps/core'; +import {Subscription} from 'rxjs'; +import {ThingTranslatorService} from './thing-translator.service'; + +// tslint:disable: member-ordering prefer-function-over-method completed-docs + + +@Injectable() +@Pipe({ + name: 'translate', + pure: false, // required to update the value when the promise is resolved +}) +export class ThingTranslatePipe implements PipeTransform, OnDestroy { + value = ''; + lastKey?: string; + lastThing: SCThings; + onLangChange: Subscription; + + constructor(private readonly translate: TranslateService, + // private readonly _ref: ChangeDetectorRef, + private readonly thingTranslate: ThingTranslatorService) { + } + + updateValue(key: string, thing: SCThings): void { + this.value = String(this.thingTranslate.get(thing, key)); + } + + transform(query: string, thing: SCThings): string { + if (typeof query !== 'string' || query.length <= 0) { + return query; + } + + if (!isThing(thing)){ + throw new SyntaxError(`Wrong parameter in ThingTranslatePipe. Expected a valid SCThing, received: ${thing}`); + } + + // store the query, in case it changes + this.lastKey = query; + + // store the params, in case they change + this.lastThing = thing; + + // set the value + this.updateValue(query, thing); + + // if there is a subscription to onLangChange, clean it + this._dispose(); + + // subscribe to onLangChange event, in case the language changes + if (typeof this.onLangChange === 'undefined' || this.onLangChange.closed ) { + this.onLangChange = this.translate.onLangChange.subscribe((_event: LangChangeEvent) => { + if (typeof this.lastKey === 'string') { + this.lastKey = undefined; // we want to make sure it doesn't return the same value until it's been updated + this.updateValue(query, thing); + } + }); + } + + return this.value; + } + + /** + * Clean any existing subscription to change events + */ + private _dispose(): void { + if (this.onLangChange?.closed) { + this.onLangChange?.unsubscribe(); + } + } + + ngOnDestroy(): void { + this._dispose(); + } +} diff --git a/src/app/translation/thing-translator.service.ts b/src/app/translation/thing-translator.service.ts new file mode 100644 index 00000000..b5ceecf2 --- /dev/null +++ b/src/app/translation/thing-translator.service.ts @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +import {Injectable, OnDestroy} from '@angular/core'; +import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; +import {isDefined} from '@ngx-translate/core/lib/util'; +import {SCLanguage, SCThings, SCThingTranslator, SCTranslations} from '@openstapps/core'; +import {Subscription} from 'rxjs'; +import {TranslateParser} from './thing-translator.parser'; + +// export const DEFAULT_LANGUAGE = new InjectionToken('DEFAULT_LANGUAGE'); + +// tslint:disable: member-ordering prefer-function-over-method newline-per-chained-call completed-docs + +@Injectable({ + providedIn: 'root', +}) +export class ThingTranslatorService implements OnDestroy { + + onLangChange: Subscription; + translator: SCThingTranslator; + + /** + * + * @param translateService Instance of Angular TranslateService + * @param parser An instance of the parser currently used + * @param language TODO + */ + constructor(private readonly translateService: TranslateService, + public parser: TranslateParser, + language?: keyof SCTranslations) { + + this.translator = new SCThingTranslator(language ?? this.translateService.currentLang as keyof SCTranslations); + + /** set the default language from configuration */ + this.onLangChange = this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { + this.translator.language = event.lang as keyof SCTranslations; + }); + + } + + /** + * Returns the parsed result of the translations + */ + // tslint:disable-next-line: no-any + getParsedResult(target: object, key: string): any { + return this.parser.getValueFromKeyPath(target, key); + } + + /** + * Gets the translated value of a key (or an array of keys) + * @returns the translated key, or an object of translated keys + */ + public get(thing: SCThings, keyPath: string | string[]): string | number | boolean { + if (!isDefined(keyPath) || keyPath.length === 0) { + throw new Error(`Parameter "key" required`); + } + if (keyPath instanceof Array) { + return this.getParsedResult(thing, keyPath.join('.')); + } + + return this.getParsedResult(thing, keyPath); + } + + // tslint:disable-next-line: completed-docs + ngOnDestroy() { + if (!this.onLangChange.closed) { + this.onLangChange.unsubscribe(); + } + } +}