@wamasimba/ngx-translate-core
Version:
Translation library (i18n) for Angular
1,162 lines (1,148 loc) • 45.9 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, Inject, Input, Directive, Pipe, makeEnvironmentProviders, NgModule } from '@angular/core';
import { of, Subject, isObservable, forkJoin, concat, defer } from 'rxjs';
import { take, shareReplay, map, concatMap, switchMap } from 'rxjs/operators';
class TranslateLoader {
}
/**
* This loader is just a placeholder that does nothing, in case you don't need a loader at all
*/
class TranslateFakeLoader extends TranslateLoader {
getTranslation(lang) {
void lang;
return of({});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateFakeLoader, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateFakeLoader });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateFakeLoader, decorators: [{
type: Injectable
}] });
class MissingTranslationHandler {
}
/**
* This handler is just a placeholder that does nothing, in case you don't need a missing translation handler at all
*/
class FakeMissingTranslationHandler {
handle(params) {
return params.key;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: FakeMissingTranslationHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: FakeMissingTranslationHandler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: FakeMissingTranslationHandler, decorators: [{
type: Injectable
}] });
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Determines if two objects or two values are equivalent.
*
* Two objects or values are considered equivalent if at least one of the following is true:
*
* * Both objects or values pass `===` comparison.
* * Both objects or values are of the same type and all of their properties are equal by
* comparing them with `equals`.
*
* @param o1 Object or value to compare.
* @param o2 Object or value to compare.
* @returns true if arguments are equal.
*/
function equals(o1, o2) {
if (o1 === o2)
return true;
if (o1 === null || o2 === null)
return false;
if (o1 !== o1 && o2 !== o2)
return true; // NaN === NaN
const t1 = typeof o1, t2 = typeof o2;
let length, key, keySet;
if (t1 == t2 && t1 == 'object') {
if (Array.isArray(o1)) {
if (!Array.isArray(o2))
return false;
if ((length = o1.length) == o2.length) {
for (key = 0; key < length; key++) {
if (!equals(o1[key], o2[key]))
return false;
}
return true;
}
}
else {
if (Array.isArray(o2)) {
return false;
}
keySet = Object.create(null);
for (key in o1) {
if (!equals(o1[key], o2[key])) {
return false;
}
keySet[key] = true;
}
for (key in o2) {
if (!(key in keySet) && typeof o2[key] !== 'undefined') {
return false;
}
}
return true;
}
}
return false;
}
function isDefinedAndNotNull(value) {
return typeof value !== 'undefined' && value !== null;
}
function isDict(value) {
return isObject(value) && !isArray(value) && value !== null;
}
function isObject(value) {
return typeof value === 'object';
}
function isArray(value) {
return Array.isArray(value);
}
function isString(value) {
return typeof value === 'string';
}
function isFunction(value) {
return typeof value === "function";
}
function cloneDeep(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => cloneDeep(item));
}
const clonedObj = {};
Object.keys(obj).forEach((key) => {
clonedObj[key] = cloneDeep(obj[key]);
});
return clonedObj;
}
function mergeDeep(target, source) {
const output = cloneDeep(target);
if (!isObject(target)) {
return cloneDeep(source);
}
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach((key) => {
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] });
}
});
}
return output;
}
/**
* Retrieves a value from a nested object using a dot-separated key path.
*
* Example usage:
* ```ts
* getValue({ key1: { keyA: 'valueI' }}, 'key1.keyA'); // returns 'valueI'
* ```
*
* @param target The source object from which to retrieve the value.
* @param key Dot-separated key path specifying the value to retrieve.
* @returns The value at the specified key path, or `undefined` if not found.
*/
function getValue(target, key) {
const keys = key.split(".");
key = "";
do {
key += keys.shift();
if (isDefinedAndNotNull(target) &&
(isDefinedAndNotNull(target[key]) || target[key] === null) &&
(isDict(target[key]) || isArray(target[key]) || !keys.length)) {
target = target[key];
key = "";
}
else if (!keys.length) {
target = undefined;
}
else {
key += ".";
}
} while (keys.length);
return target;
}
/**
* Sets a value on object using a dot separated key.
* This function modifies the object in place
* parser.setValue({a:{b:{c: "test"}}}, 'a.b.c', "test2") ==> {a:{b:{c: "test2"}}}
* @param target an object
* @param key E.g. "a.b.c"
* @param value to set
* @deprecated use insertValue() instead
*/
function setValue(target, key, value) {
const keys = key.split('.');
let current = target;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// If we're at the last key, set the value
if (i === keys.length - 1) {
current[key] = value;
}
else {
// If the key doesn't exist or isn't an object, create an empty object
if (!current[key] || !isDict(current[key])) {
current[key] = {};
}
current = current[key];
}
}
}
/**
* Sets a value on object using a dot separated key.
* Returns a clone of the object without modifying it
* parser.setValue({a:{b:{c: "test"}}}, 'a.b.c', "test2") ==> {a:{b:{c: "test2"}}}
* @param target an object
* @param key E.g. "a.b.c"
* @param value to set
*/
function insertValue(target, key, value) {
return mergeDeep(target, createNestedObject(key, value));
}
function createNestedObject(dotSeparatedKey, value) {
return dotSeparatedKey.split('.').reduceRight((acc, key) => ({ [key]: acc }), value);
}
class TranslateParser {
}
class TranslateDefaultParser extends TranslateParser {
templateMatcher = /{{\s?([^{}\s]*)\s?}}/g;
interpolate(expr, params) {
if (isString(expr)) {
return this.interpolateString(expr, params);
}
else if (isFunction(expr)) {
return this.interpolateFunction(expr, params);
}
return undefined;
}
interpolateFunction(fn, params) {
return fn(params);
}
interpolateString(expr, params) {
if (!params) {
return expr;
}
return expr.replace(this.templateMatcher, (substring, key) => {
const replacement = this.getInterpolationReplacement(params, key);
return replacement !== undefined ? replacement : substring;
});
}
/**
* Returns the replacement for an interpolation parameter
* @params:
*/
getInterpolationReplacement(params, key) {
return this.formatValue(getValue(params, key));
}
/**
* Converts a value into a useful string representation.
* @param value The value to format.
* @returns A string representation of the value.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formatValue(value) {
if (isString(value)) {
return value;
}
if (typeof value === "number" || typeof value === "boolean") {
return value.toString();
}
if (value === null) {
return "null";
}
if (isArray(value)) {
return value.join(", ");
}
if (isObject(value)) {
if (typeof value.toString === "function" && value.toString !== Object.prototype.toString) {
return value.toString();
}
return JSON.stringify(value); // Pretty-print JSON if no meaningful toString()
}
return undefined;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateDefaultParser, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateDefaultParser });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateDefaultParser, decorators: [{
type: Injectable
}] });
class TranslateCompiler {
}
/**
* This compiler is just a placeholder that does nothing, in case you don't need a compiler at all
*/
class TranslateFakeCompiler extends TranslateCompiler {
compile(value, lang) {
void lang;
return value;
}
compileTranslations(translations, lang) {
void lang;
return translations;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateFakeCompiler, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateFakeCompiler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateFakeCompiler, decorators: [{
type: Injectable
}] });
class TranslateStore {
_onTranslationChange = new Subject();
_onLangChange = new Subject();
_onDefaultLangChange = new Subject();
defaultLang;
currentLang;
translations = {};
languages = [];
getTranslations(language) {
return this.translations[language];
}
setTranslations(language, translations, extend) {
this.translations[language] = (extend && this.hasTranslationFor(language)) ? mergeDeep(this.translations[language], translations) : translations;
this.addLanguages([language]);
this._onTranslationChange.next({ lang: language, translations: this.getTranslations(language) });
}
getLanguages() {
return this.languages;
}
getCurrentLanguage() {
return this.currentLang;
}
getDefaultLanguage() {
return this.defaultLang;
}
/**
* Changes the default lang
*/
setDefaultLang(lang, emitChange = true) {
this.defaultLang = lang;
if (emitChange) {
this._onDefaultLangChange.next({ lang: lang, translations: this.translations[lang] });
}
}
setCurrentLang(lang, emitChange = true) {
this.currentLang = lang;
if (emitChange) {
this._onLangChange.next({ lang: lang, translations: this.translations[lang] });
}
}
/**
* An Observable to listen to translation change events
* onTranslationChange.subscribe((params: TranslationChangeEvent) => {
* // do something
* });
*/
get onTranslationChange() {
return this._onTranslationChange.asObservable();
}
/**
* An Observable to listen to lang change events
* onLangChange.subscribe((params: LangChangeEvent) => {
* // do something
* });
*/
get onLangChange() {
return this._onLangChange.asObservable();
}
/**
* An Observable to listen to default lang change events
* onDefaultLangChange.subscribe((params: DefaultLangChangeEvent) => {
* // do something
* });
*/
get onDefaultLangChange() {
return this._onDefaultLangChange.asObservable();
}
addLanguages(languages) {
this.languages = Array.from(new Set([...this.languages, ...languages]));
}
hasTranslationFor(lang) {
return (typeof this.translations[lang] !== "undefined");
}
deleteTranslations(lang) {
delete this.translations[lang];
}
getTranslation(key, useDefaultLang) {
let text = this.getValue(this.currentLang, key);
if (text === undefined && this.defaultLang != null && this.defaultLang !== this.currentLang && useDefaultLang) {
text = this.getValue(this.defaultLang, key);
}
return text;
}
getValue(language, key) {
return getValue(this.getTranslations(language), key);
}
}
const ISOLATE_TRANSLATE_SERVICE = new InjectionToken('ISOLATE_TRANSLATE_SERVICE');
const USE_DEFAULT_LANG = new InjectionToken('USE_DEFAULT_LANG');
const DEFAULT_LANGUAGE = new InjectionToken('DEFAULT_LANGUAGE');
const USE_EXTEND = new InjectionToken('USE_EXTEND');
const makeObservable = (value) => {
return isObservable(value) ? value : of(value);
};
class TranslateService {
store;
currentLoader;
compiler;
parser;
missingTranslationHandler;
useDefaultLang;
extend;
loadingTranslations;
pending = false;
_translationRequests = {};
lastUseLanguage = null;
/**
* An Observable to listen to translation change events
* onTranslationChange.subscribe((params: TranslationChangeEvent) => {
* // do something
* });
*/
get onTranslationChange() {
return this.store.onTranslationChange;
}
/**
* An Observable to listen to lang change events
* onLangChange.subscribe((params: LangChangeEvent) => {
* // do something
* });
*/
get onLangChange() {
return this.store.onLangChange;
}
/**
* An Observable to listen to default lang change events
* onDefaultLangChange.subscribe((params: DefaultLangChangeEvent) => {
* // do something
* });
*/
get onDefaultLangChange() {
return this.store.onDefaultLangChange;
}
/**
* The default lang to fallback when translations are missing on the current lang
*/
get defaultLang() {
return this.store.getDefaultLanguage();
}
/**
* The lang currently used
*/
get currentLang() {
return this.store.getCurrentLanguage();
}
/**
* an array of langs
*/
get langs() {
return this.store.getLanguages();
}
/**
*
* @param store an instance of the store (that is supposed to be unique)
* @param currentLoader An instance of the loader currently used
* @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 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.
* @param defaultLanguage Set the default language using configuration
*/
constructor(store, currentLoader, compiler, parser, missingTranslationHandler, useDefaultLang = true, isolate = false, extend = false, defaultLanguage) {
this.store = store;
this.currentLoader = currentLoader;
this.compiler = compiler;
this.parser = parser;
this.missingTranslationHandler = missingTranslationHandler;
this.useDefaultLang = useDefaultLang;
this.extend = extend;
if (isolate) {
this.store = new TranslateStore();
}
if (defaultLanguage) {
this.setDefaultLang(defaultLanguage);
}
}
/**
* Sets the default language to use as a fallback
*/
setDefaultLang(lang) {
if (!this.defaultLang) {
// on init set the defaultLang immediately, but do not emit a change yet
this.store.setDefaultLang(lang, false);
}
const pending = this.loadOrExtendLanguage(lang);
if (isObservable(pending)) {
pending.pipe(take(1)).subscribe(() => { this.store.setDefaultLang(lang); });
return pending;
}
this.store.setDefaultLang(lang);
return of(this.store.getTranslations(lang));
}
/**
* Gets the default language used
*/
getDefaultLang() {
return this.defaultLang;
}
/**
* Changes the lang currently used
*/
use(lang) {
// remember the language that was called
// we need this with multiple fast calls to use()
// where translation loads might complete in random order
this.lastUseLanguage = lang;
if (!this.currentLang) {
// on init set the currentLang immediately, but do not emit a change yet
this.store.setCurrentLang(lang, false);
}
const pending = this.loadOrExtendLanguage(lang);
if (isObservable(pending)) {
pending.pipe(take(1)).subscribe(() => { this.changeLang(lang); });
return pending;
}
this.changeLang(lang);
return of(this.store.getTranslations(lang));
}
/**
* Retrieves the given translations
*/
loadOrExtendLanguage(lang) {
// if this language is unavailable or extend is true, ask for it
if (!this.store.hasTranslationFor(lang) || this.extend) {
this._translationRequests[lang] = this._translationRequests[lang] || this.loadAndCompileTranslations(lang);
return this._translationRequests[lang];
}
return undefined;
}
/**
* Changes the current lang
*/
changeLang(lang) {
if (lang !== this.lastUseLanguage) {
// received new language data,
// but this was not the one requested last
return;
}
this.store.setCurrentLang(lang);
if (this.defaultLang == null) {
// if there is no default lang, use the one that we just set
this.store.setDefaultLang(lang);
}
}
loadAndCompileTranslations(lang) {
this.pending = true;
const loadingTranslations = this.currentLoader.getTranslation(lang).pipe(shareReplay(1), take(1));
this.loadingTranslations = loadingTranslations.pipe(map((res) => this.compiler.compileTranslations(res, lang)), shareReplay(1), take(1));
this.loadingTranslations
.subscribe({
next: (res) => {
this.store.setTranslations(lang, res, this.extend);
this.pending = false;
},
error: (err) => {
void err;
this.pending = false;
}
});
return loadingTranslations;
}
/**
* Manually sets an object of translations for a given language
* after passing it through the compiler
*/
setTranslation(lang, translations, shouldMerge = false) {
const interpolatableTranslations = this.compiler.compileTranslations(translations, lang);
this.store.setTranslations(lang, interpolatableTranslations, (shouldMerge || this.extend));
}
getLangs() {
return this.store.getLanguages();
}
/**
* Add available languages
*/
addLangs(languages) {
this.store.addLanguages(languages);
}
getParsedResultForKey(key, interpolateParams) {
const textToInterpolate = this.getTextToInterpolate(key);
if (isDefinedAndNotNull(textToInterpolate)) {
return this.runInterpolation(textToInterpolate, interpolateParams);
}
const res = this.missingTranslationHandler.handle({
key,
translateService: this,
...(interpolateParams !== undefined && { interpolateParams })
});
return res !== undefined ? res : key;
}
getTextToInterpolate(key) {
return this.store.getTranslation(key, this.useDefaultLang);
}
runInterpolation(translations, interpolateParams) {
if (isArray(translations)) {
return this.runInterpolationOnArray(translations, interpolateParams);
}
else if (isDict(translations)) {
return this.runInterpolationOnDict(translations, interpolateParams);
}
else {
return this.parser.interpolate(translations, interpolateParams);
}
}
runInterpolationOnArray(translations, interpolateParams) {
return translations.map((translation) => this.runInterpolation(translation, interpolateParams));
}
runInterpolationOnDict(translations, interpolateParams) {
const result = {};
for (const key in translations) {
const res = this.runInterpolation(translations[key], interpolateParams);
if (res !== undefined) {
result[key] = res;
}
}
return result;
}
/**
* Returns the parsed result of the translations
*/
getParsedResult(key, interpolateParams) {
return (key instanceof Array) ? this.getParsedResultForArray(key, interpolateParams) : this.getParsedResultForKey(key, interpolateParams);
}
getParsedResultForArray(key, interpolateParams) {
const result = {};
let observables = false;
for (const k of key) {
result[k] = this.getParsedResultForKey(k, interpolateParams);
observables = observables || isObservable(result[k]);
}
if (!observables) {
return result;
}
const sources = key.map(k => makeObservable(result[k]));
return forkJoin(sources).pipe(map((arr) => {
const obj = {};
arr.forEach((value, index) => {
obj[key[index]] = value;
});
return obj;
}));
}
/**
* Gets the translated value of a key (or an array of keys)
* @returns the translated key, or an object of translated keys
*/
get(key, interpolateParams) {
if (!isDefinedAndNotNull(key) || !key.length) {
throw new Error(`Parameter "key" is required and cannot be empty`);
}
// check if we are loading a new translation to use
if (this.pending) {
return this.loadingTranslations.pipe(concatMap(() => {
return makeObservable(this.getParsedResult(key, interpolateParams));
}));
}
return makeObservable(this.getParsedResult(key, interpolateParams));
}
/**
* Returns a stream of translated values of a key (or an array of keys) which updates
* whenever the translation changes.
* @returns A stream of the translated key, or an object of translated keys
*/
getStreamOnTranslationChange(key, interpolateParams) {
if (!isDefinedAndNotNull(key) || !key.length) {
throw new Error(`Parameter "key" is required and cannot be empty`);
}
return concat(defer(() => this.get(key, interpolateParams)), this.onTranslationChange.pipe(switchMap(() => {
const res = this.getParsedResult(key, interpolateParams);
return makeObservable(res);
})));
}
/**
* Returns a stream of translated values of a key (or an array of keys) which updates
* whenever the language changes.
* @returns A stream of the translated key, or an object of translated keys
*/
stream(key, interpolateParams) {
if (!isDefinedAndNotNull(key) || !key.length) {
throw new Error(`Parameter "key" required`);
}
return concat(defer(() => this.get(key, interpolateParams)), this.onLangChange.pipe(switchMap(() => {
const res = this.getParsedResult(key, interpolateParams);
return makeObservable(res);
})));
}
/**
* Returns a translation instantly from the internal state of loaded translation.
* All rules regarding the current language, the preferred language of even fallback languages
* will be used except any promise handling.
*/
instant(key, interpolateParams) {
if (!isDefinedAndNotNull(key) || key.length === 0) {
throw new Error('Parameter "key" is required and cannot be empty');
}
const result = this.getParsedResult(key, interpolateParams);
if (isObservable(result)) {
if (Array.isArray(key)) {
return key.reduce((acc, currKey) => {
acc[currKey] = currKey;
return acc;
}, {});
}
return key;
}
return result;
}
/**
* Sets the translated value of a key, after compiling it
*/
set(key, translation, lang = this.currentLang) {
this.store.setTranslations(lang, insertValue(this.store.getTranslations(lang), key, isString(translation)
? this.compiler.compile(translation, lang)
: this.compiler.compileTranslations(translation, lang)), false);
}
/**
* Allows to reload the lang file from the file
*/
reloadLang(lang) {
this.resetLang(lang);
return this.loadAndCompileTranslations(lang);
}
/**
* Deletes inner translation
*/
resetLang(lang) {
delete this._translationRequests[lang];
this.store.deleteTranslations(lang);
}
/**
* Returns the language code name from the browser, e.g. "de"
*/
getBrowserLang() {
if (typeof window === 'undefined' || !window.navigator) {
return undefined;
}
const browserLang = this.getBrowserCultureLang();
return browserLang ? browserLang.split(/[-_]/)[0] : undefined;
}
/**
* Returns the culture language code name from the browser, e.g. "de-DE"
*/
getBrowserCultureLang() {
if (typeof window === 'undefined' || typeof window.navigator === 'undefined') {
return undefined;
}
return window.navigator.languages
? window.navigator.languages[0]
: (window.navigator.language || window.navigator.browserLanguage || window.navigator.userLanguage);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateService, deps: [{ token: TranslateStore }, { token: TranslateLoader }, { token: TranslateCompiler }, { token: TranslateParser }, { token: MissingTranslationHandler }, { token: USE_DEFAULT_LANG }, { token: ISOLATE_TRANSLATE_SERVICE }, { token: USE_EXTEND }, { token: DEFAULT_LANGUAGE }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: TranslateStore }, { type: TranslateLoader }, { type: TranslateCompiler }, { type: TranslateParser }, { type: MissingTranslationHandler }, { type: undefined, decorators: [{
type: Inject,
args: [USE_DEFAULT_LANG]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [ISOLATE_TRANSLATE_SERVICE]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [USE_EXTEND]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [DEFAULT_LANGUAGE]
}] }] });
class TranslateDirective {
translateService;
element;
_ref;
key;
lastParams;
currentParams;
onLangChangeSub;
onDefaultLangChangeSub;
onTranslationChangeSub;
set translate(key) {
if (key) {
this.key = key;
this.checkNodes();
}
}
set translateParams(params) {
if (!equals(this.currentParams, params)) {
this.currentParams = params;
this.checkNodes(true);
}
}
constructor(translateService, element, _ref) {
this.translateService = translateService;
this.element = element;
this._ref = _ref;
// subscribe to onTranslationChange event, in case the translations of the current lang change
if (!this.onTranslationChangeSub) {
this.onTranslationChangeSub = this.translateService.onTranslationChange.subscribe((event) => {
if (event.lang === this.translateService.currentLang) {
this.checkNodes(true, event.translations);
}
});
}
// subscribe to onLangChange event, in case the language changes
if (!this.onLangChangeSub) {
this.onLangChangeSub = this.translateService.onLangChange.subscribe((event) => {
this.checkNodes(true, event.translations);
});
}
// subscribe to onDefaultLangChange event, in case the default language changes
if (!this.onDefaultLangChangeSub) {
this.onDefaultLangChangeSub = this.translateService.onDefaultLangChange.subscribe((event) => {
void event;
this.checkNodes(true);
});
}
}
ngAfterViewChecked() {
this.checkNodes();
}
checkNodes(forceUpdate = false, translations) {
let nodes = this.element.nativeElement.childNodes;
// if the element is empty
if (!nodes.length) {
// we add the key as content
this.setContent(this.element.nativeElement, this.key);
nodes = this.element.nativeElement.childNodes;
}
nodes.forEach((n) => {
const node = n;
if (node.nodeType === 3) { // node type 3 is a text node
let key;
if (forceUpdate) {
node.lastKey = null;
}
if (isDefinedAndNotNull(node.lookupKey)) {
key = node.lookupKey;
}
else if (this.key) {
key = this.key;
}
else {
const content = this.getContent(node);
const trimmedContent = content.trim();
if (trimmedContent.length) {
node.lookupKey = trimmedContent;
// we want to use the content as a key, not the translation value
if (content !== node.currentValue) {
key = trimmedContent;
// the content was changed from the user, we'll use it as a reference if needed
node.originalContent = content || node.originalContent;
}
else if (node.originalContent) { // the content seems ok, but the lang has changed
// the current content is the translation, not the key, use the last real content as key
key = node.originalContent.trim();
}
}
}
this.updateValue(key, node, translations);
}
});
}
updateValue(key, node, translations) {
if (key) {
if (node.lastKey === key && this.lastParams === this.currentParams) {
return;
}
this.lastParams = this.currentParams;
const onTranslation = (res) => {
if (res !== key || !node.lastKey) {
node.lastKey = key;
}
if (!node.originalContent) {
node.originalContent = this.getContent(node);
}
node.currentValue = isDefinedAndNotNull(res) ? res : (node.originalContent || key);
// we replace in the original content to preserve spaces that we might have trimmed
this.setContent(node, this.key ? node.currentValue : node.originalContent.replace(key, node.currentValue));
this._ref.markForCheck();
};
if (isDefinedAndNotNull(translations)) {
const res = this.translateService.getParsedResult(key, this.currentParams);
if (isObservable(res)) {
res.subscribe({ next: onTranslation });
}
else {
onTranslation(res);
}
}
else {
this.translateService.get(key, this.currentParams).subscribe(onTranslation);
}
}
}
getContent(node) {
return (isDefinedAndNotNull(node.textContent) ? node.textContent : node.data);
}
setContent(node, content) {
if (isDefinedAndNotNull(node.textContent)) {
node.textContent = content;
}
else {
node.data = content;
}
}
ngOnDestroy() {
if (this.onLangChangeSub) {
this.onLangChangeSub.unsubscribe();
}
if (this.onDefaultLangChangeSub) {
this.onDefaultLangChangeSub.unsubscribe();
}
if (this.onTranslationChangeSub) {
this.onTranslationChangeSub.unsubscribe();
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateDirective, deps: [{ token: TranslateService }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.3", type: TranslateDirective, isStandalone: true, selector: "[translate],[ngx-translate]", inputs: { translate: "translate", translateParams: "translateParams" }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[translate],[ngx-translate]',
standalone: true
}]
}], ctorParameters: () => [{ type: TranslateService }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { translate: [{
type: Input
}], translateParams: [{
type: Input
}] } });
class TranslatePipe {
translate;
_ref;
value = '';
lastKey = null;
lastParams = [];
onTranslationChange;
onLangChange;
onDefaultLangChange;
constructor(translate, _ref) {
this.translate = translate;
this._ref = _ref;
}
updateValue(key, interpolateParams, translations) {
const onTranslation = (res) => {
this.value = res !== undefined ? res : key;
this.lastKey = key;
this._ref.markForCheck();
};
if (translations) {
const res = this.translate.getParsedResult(key, interpolateParams);
if (isObservable(res)) {
res.subscribe(onTranslation);
}
else {
onTranslation(res);
}
}
this.translate.get(key, interpolateParams).subscribe(onTranslation);
}
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
transform(query, ...args) {
if (!query || !query.length) {
return query;
}
// if we ask another time for the same key, return the last value
if (equals(query, this.lastKey) && equals(args, this.lastParams)) {
return this.value;
}
let interpolateParams = undefined;
if (isDefinedAndNotNull(args[0]) && args.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 = args[0]
.replace(/(')?([a-zA-Z0-9_]+)(')?(\s)?:/g, '"$2":')
.replace(/:(\s)?(')(.*?)(')/g, ':"$3"');
try {
interpolateParams = JSON.parse(validArgs);
}
catch (e) {
void e;
throw new SyntaxError(`Wrong parameter in TranslatePipe. Expected a valid Object, received: ${args[0]}`);
}
}
else if (isDict(args[0])) {
interpolateParams = args[0];
}
}
// store the query, in case it changes
this.lastKey = query;
// store the params, in case they change
this.lastParams = args;
// set the value
this.updateValue(query, interpolateParams);
// if there is a subscription to onLangChange, clean it
this._dispose();
// subscribe to onTranslationChange event, in case the translations change
if (!this.onTranslationChange) {
this.onTranslationChange = this.translate.onTranslationChange.subscribe((event) => {
if (this.lastKey && event.lang === this.translate.currentLang) {
this.lastKey = null;
this.updateValue(query, interpolateParams, event.translations);
}
});
}
// subscribe to onLangChange event, in case the language changes
if (!this.onLangChange) {
this.onLangChange = this.translate.onLangChange.subscribe((event) => {
if (this.lastKey) {
this.lastKey = null; // we want to make sure it doesn't return the same value until it's been updated
this.updateValue(query, interpolateParams, event.translations);
}
});
}
// subscribe to onDefaultLangChange event, in case the default language changes
if (!this.onDefaultLangChange) {
this.onDefaultLangChange = this.translate.onDefaultLangChange.subscribe(() => {
if (this.lastKey) {
this.lastKey = null; // we want to make sure it doesn't return the same value until it's been updated
this.updateValue(query, interpolateParams);
}
});
}
return this.value;
}
/**
* Clean any existing subscription to change events
*/
_dispose() {
if (typeof this.onTranslationChange !== 'undefined') {
this.onTranslationChange.unsubscribe();
this.onTranslationChange = undefined;
}
if (typeof this.onLangChange !== 'undefined') {
this.onLangChange.unsubscribe();
this.onLangChange = undefined;
}
if (typeof this.onDefaultLangChange !== 'undefined') {
this.onDefaultLangChange.unsubscribe();
this.onDefaultLangChange = undefined;
}
}
ngOnDestroy() {
this._dispose();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslatePipe, deps: [{ token: TranslateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.3", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslatePipe });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslatePipe, decorators: [{
type: Injectable
}, {
type: Pipe,
args: [{
name: 'translate',
standalone: true,
pure: false // required to update the value when the promise is resolved
}]
}], ctorParameters: () => [{ type: TranslateService }, { type: i0.ChangeDetectorRef }] });
function _(key) {
return key;
}
const provideTranslateService = (config = {}) => {
return makeEnvironmentProviders([
config.loader || { provide: TranslateLoader, useClass: TranslateFakeLoader },
config.compiler || { provide: TranslateCompiler, useClass: TranslateFakeCompiler },
config.parser || { provide: TranslateParser, useClass: TranslateDefaultParser },
config.missingTranslationHandler || { provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler },
TranslateStore,
{ provide: ISOLATE_TRANSLATE_SERVICE, useValue: config.isolate },
{ provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang },
{ provide: USE_EXTEND, useValue: config.extend },
{ provide: DEFAULT_LANGUAGE, useValue: config.defaultLanguage },
TranslateService
]);
};
class TranslateModule {
/**
* Use this method in your root module to provide the TranslateService
*/
static forRoot(config = {}) {
return {
ngModule: TranslateModule,
providers: [
config.loader || { provide: TranslateLoader, useClass: TranslateFakeLoader },
config.compiler || { provide: TranslateCompiler, useClass: TranslateFakeCompiler },
config.parser || { provide: TranslateParser, useClass: TranslateDefaultParser },
config.missingTranslationHandler || { provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler },
TranslateStore,
{ provide: ISOLATE_TRANSLATE_SERVICE, useValue: config.isolate },
{ provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang },
{ provide: USE_EXTEND, useValue: config.extend },
{ provide: DEFAULT_LANGUAGE, useValue: config.defaultLanguage },
TranslateService
]
};
}
/**
* Use this method in your other (non-root) modules to import the directive/pipe
*/
static forChild(config = {}) {
return {
ngModule: TranslateModule,
providers: [
config.loader || { provide: TranslateLoader, useClass: TranslateFakeLoader },
config.compiler || { provide: TranslateCompiler, useClass: TranslateFakeCompiler },
config.parser || { provide: TranslateParser, useClass: TranslateDefaultParser },
config.missingTranslationHandler || { provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler },
{ provide: ISOLATE_TRANSLATE_SERVICE, useValue: config.isolate },
{ provide: USE_DEFAULT_LANG, useValue: config.useDefaultLang },
{ provide: USE_EXTEND, useValue: config.extend },
{ provide: DEFAULT_LANGUAGE, useValue: config.defaultLanguage },
TranslateService
]
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.3", ngImport: i0, type: TranslateModule, imports: [TranslatePipe,
TranslateDirective], exports: [TranslatePipe,
TranslateDirective] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.3", ngImport: i0, type: TranslateModule, decorators: [{
type: NgModule,
args: [{
imports: [
TranslatePipe,
TranslateDirective
],
exports: [
TranslatePipe,
TranslateDirective
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { DEFAULT_LANGUAGE, FakeMissingTranslationHandler, ISOLATE_TRANSLATE_SERVICE, MissingTranslationHandler, TranslateCompiler, TranslateDefaultParser, TranslateDirective, TranslateFakeCompiler, TranslateFakeLoader, TranslateLoader, TranslateModule, TranslateParser, TranslatePipe, TranslateService, TranslateStore, USE_DEFAULT_LANG, USE_EXTEND, _, cloneDeep, equals, getValue, insertValue, isArray, isDefinedAndNotNull, isDict, isFunction, isObject, isString, mergeDeep, provideTranslateService, setValue };
//# sourceMappingURL=wamasimba-ngx-translate-core.mjs.map