feat: add service and pipe for core translator

This commit is contained in:
Rainer Killinger
2021-01-13 16:46:49 +01:00
parent e3d9ef40cc
commit 456560026c
3 changed files with 256 additions and 0 deletions

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<string>('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<SCLanguage>) {
this.translator = new SCThingTranslator(language ?? this.translateService.currentLang as keyof SCTranslations<SCLanguage>);
/** set the default language from configuration */
this.onLangChange = this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.translator.language = event.lang as keyof SCTranslations<SCLanguage>;
});
}
/**
* 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();
}
}
}