@ngx-translate/core
Version:
Translation library (i18n) for Angular
1,255 lines (1,240 loc) • 46.6 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, inject, ElementRef, ChangeDetectorRef, Input, Directive, Pipe, NgModule } from '@angular/core';
import { of, Subject, isObservable, forkJoin, concat, defer } from 'rxjs';
import { take, shareReplay, map, concatMap, switchMap } from 'rxjs/operators';
function _(key) {
return key;
}
class MissingTranslationHandler {
}
/**
* This handler is just a placeholder that does nothing; in case you don't need a missing translation handler at all
*/
class DefaultMissingTranslationHandler {
handle(params) {
return params.key;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: DefaultMissingTranslationHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: DefaultMissingTranslationHandler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: DefaultMissingTranslationHandler, 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 TranslateNoOpCompiler 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: "20.0.6", ngImport: i0, type: TranslateNoOpCompiler, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateNoOpCompiler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateNoOpCompiler, decorators: [{
type: Injectable
}] });
class TranslateLoader {
}
/**
* This loader is just a placeholder that does nothing; in case you don't need a loader at all
*/
class TranslateNoOpLoader extends TranslateLoader {
getTranslation(lang) {
void lang;
return of({});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateNoOpLoader, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateNoOpLoader });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateNoOpLoader, decorators: [{
type: Injectable
}] });
/**
* 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;
if (t1 == t2 && t1 == "object") {
if (Array.isArray(o1)) {
if (!Array.isArray(o2))
return false;
if ((length = o1.length) == o2.length) {
for (let key = 0; key < length; key++) {
if (!equals(o1[key], o2[key]))
return false;
}
return true;
}
}
else {
if (Array.isArray(o2)) {
return false;
}
if (isDict(o1) && isDict(o2)) {
const keySet = Object.create(null);
for (const key in o1) {
if (!equals(o1[key], o2[key])) {
return false;
}
keySet[key] = true;
}
for (const 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 isDefined(value) {
return value !== undefined;
}
function isDict(value) {
return isObject(value) && !isArray(value) && value !== null;
}
function isObject(value) {
return typeof value === "object" && value !== null;
}
function isArray(value) {
return Array.isArray(value);
}
function isString(value) {
return typeof value === "string";
}
function isFunction(value) {
return typeof value === "function";
}
function cloneDeep(value) {
if (isArray(value)) {
return value.map((item) => cloneDeep(item));
}
else if (isDict(value)) {
const cloned = {};
Object.keys(value).forEach((key) => {
cloned[key] = cloneDeep(value[key]);
});
return cloned;
}
else {
return value;
}
}
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
function mergeDeep(target, source) {
if (!isObject(target)) {
return cloneDeep(source);
}
const output = cloneDeep(target);
if (isObject(output) && isObject(source)) {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
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();
const isLastKey = !keys.length;
if (isDefinedAndNotNull(target)) {
if (isDict(target) &&
isDefined(target[key]) &&
(isDict(target[key]) || isArray(target[key]) || isLastKey)) {
target = target[key];
key = "";
continue;
}
if (isArray(target)) {
const index = parseInt(key, 10);
if (isDefined(target[index]) &&
(isDict(target[index]) || isArray(target[index]) || isLastKey)) {
target = target[index];
key = "";
continue;
}
}
}
if (isLastKey) {
target = undefined;
continue;
}
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 (i === keys.length - 1) {
current[key] = value;
}
else {
current = current[key] && isDict(current[key]) ? 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.
*/
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: "20.0.6", ngImport: i0, type: TranslateDefaultParser, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateDefaultParser });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateDefaultParser, decorators: [{
type: Injectable
}] });
class TranslateStore {
_onTranslationChange = new Subject();
_onLangChange = new Subject();
_onFallbackLangChange = new Subject();
fallbackLang = null;
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;
}
getCurrentLang() {
return this.currentLang;
}
getFallbackLang() {
return this.fallbackLang;
}
/**
* Changes the fallback lang
*/
setFallbackLang(lang, emitChange = true) {
this.fallbackLang = lang;
if (emitChange) {
this._onFallbackLangChange.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 fallback lang change events
* onFallbackLangChange.subscribe((params: FallbackLangChangeEvent) => {
* // do something
* });
*/
get onFallbackLangChange() {
return this._onFallbackLangChange.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) {
let text = this.getValue(this.currentLang, key);
if (text === undefined &&
this.fallbackLang != null &&
this.fallbackLang !== this.currentLang) {
text = this.getValue(this.fallbackLang, key);
}
return text;
}
getValue(language, key) {
return getValue(this.getTranslations(language), key);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateStore });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateStore, decorators: [{
type: Injectable
}] });
const TRANSLATE_SERVICE_CONFIG = new InjectionToken("TRANSLATE_CONFIG");
const makeObservable = (value) => {
return isObservable(value) ? value : of(value);
};
class ITranslateService {
}
class TranslateService {
loadingTranslations;
pending = false;
_translationRequests = {};
lastUseLanguage = null;
currentLoader = inject(TranslateLoader);
compiler = inject(TranslateCompiler);
parser = inject(TranslateParser);
missingTranslationHandler = inject(MissingTranslationHandler);
store = inject(TranslateStore);
extend = false;
/**
* 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 fallback lang change events
* onFallbackLangChange.subscribe((params: FallbackLangChangeEvent) => {
* // do something
* });
*/
get onFallbackLangChange() {
return this.store.onFallbackLangChange;
}
/**
* @deprecated Use onFallbackLangChange() instead
*/
get onDefaultLangChange() {
return this.store.onFallbackLangChange;
}
constructor() {
const config = {
extend: false,
fallbackLang: null,
...inject(TRANSLATE_SERVICE_CONFIG, {
optional: true,
}),
};
if (config.lang) {
this.use(config.lang);
}
if (config.fallbackLang) {
this.setFallbackLang(config.fallbackLang);
}
if (config.extend) {
this.extend = true;
}
}
/**
* Sets the fallback language to use if a translation is not found in the
* current language
*/
setFallbackLang(lang) {
if (!this.getFallbackLang()) {
// on init set the fallbackLang immediately, but do not emit a change yet
this.store.setFallbackLang(lang, false);
}
const pending = this.loadOrExtendLanguage(lang);
if (isObservable(pending)) {
pending.pipe(take(1)).subscribe({
next: () => {
this.store.setFallbackLang(lang);
},
error: () => {
/* ignore here - user can handle it */
},
});
return pending;
}
this.store.setFallbackLang(lang);
return of(this.store.getTranslations(lang));
}
/**
* 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.getCurrentLang()) {
// 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({
next: () => {
this.changeLang(lang);
},
error: () => {
/* ignore here - use can handle it */
},
});
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);
}
getCurrentLang() {
return this.store.getCurrentLang();
}
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;
}
/**
* Gets the fallback language. null if none is defined
*/
getFallbackLang() {
return this.store.getFallbackLang();
}
getTextToInterpolate(key) {
return this.store.getTranslation(key);
}
runInterpolation(translations, interpolateParams) {
if (!isDefinedAndNotNull(translations)) {
return;
}
if (isArray(translations)) {
return this.runInterpolationOnArray(translations, interpolateParams);
}
if (isDict(translations)) {
return this.runInterpolationOnDict(translations, interpolateParams);
}
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.getCurrentLang()) {
this.store.setTranslations(lang, insertValue(this.store.getTranslations(lang), key, isString(translation)
? this.compiler.compile(translation, lang)
: this.compiler.compileTranslations(translation, lang)), false);
}
/**
* Allows reloading 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"
*/
static 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"
*/
static 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;
}
getBrowserLang() {
return TranslateService.getBrowserLang();
}
getBrowserCultureLang() {
return TranslateService.getBrowserCultureLang();
}
/** Deprecations **/
/**
* @deprecated use `getFallbackLang()`
*/
get defaultLang() {
return this.getFallbackLang();
}
/**
* The lang currently used
* @deprecated use `getCurrentLang()`
*/
get currentLang() {
return this.store.getCurrentLang();
}
/**
* @deprecated use `getLangs()`
*/
get langs() {
return this.store.getLanguages();
}
/**
* Sets the language to use as a fallback
* @deprecated use setFallbackLanguage()
*/
setDefaultLang(lang) {
return this.setFallbackLang(lang);
}
/**
* Gets the fallback language used
* @deprecated use getFallbackLang()
*/
getDefaultLang() {
return this.getFallbackLang();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateService, decorators: [{
type: Injectable
}], ctorParameters: () => [] });
class TranslateDirective {
translateService = inject(TranslateService);
element = inject(ElementRef);
_ref = inject(ChangeDetectorRef);
key;
lastParams;
currentParams;
onLangChangeSub;
onFallbackLangChangeSub;
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() {
// 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 onFallbackLangChange event, in case the fallback language changes
if (!this.onFallbackLangChangeSub) {
this.onFallbackLangChangeSub = this.translateService.onFallbackLangChange.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);
}
if (isString(res)) {
node.currentValue = res;
}
else if (!isDefinedAndNotNull(res)) {
node.currentValue = node.originalContent || key;
}
else {
node.currentValue = JSON.stringify(res);
}
// 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.onFallbackLangChangeSub) {
this.onFallbackLangChangeSub.unsubscribe();
}
if (this.onTranslationChangeSub) {
this.onTranslationChangeSub.unsubscribe();
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.6", type: TranslateDirective, isStandalone: true, selector: "[translate],[ngx-translate]", inputs: { translate: "translate", translateParams: "translateParams" }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: "[translate],[ngx-translate]",
standalone: true,
}]
}], ctorParameters: () => [], propDecorators: { translate: [{
type: Input
}], translateParams: [{
type: Input
}] } });
class TranslatePipe {
translate = inject(TranslateService);
_ref = inject(ChangeDetectorRef);
value = "";
lastKey = null;
lastParams = [];
onTranslationChange;
onLangChange;
onFallbackLangChange;
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'}
// this 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.getCurrentLang()) ||
event.lang === this.translate.getFallbackLang()) {
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 fallback language changes
if (!this.onFallbackLangChange) {
this.onFallbackLangChange = this.translate.onFallbackLangChange.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.onFallbackLangChange !== "undefined") {
this.onFallbackLangChange.unsubscribe();
this.onFallbackLangChange = undefined;
}
}
ngOnDestroy() {
this._dispose();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.0.6", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslatePipe });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", 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
}]
}] });
function provideTranslateLoader(loader) {
return { provide: TranslateLoader, useClass: loader };
}
function provideTranslateCompiler(compiler) {
return { provide: TranslateCompiler, useClass: compiler };
}
function provideTranslateParser(parser) {
return { provide: TranslateParser, useClass: parser };
}
function provideMissingTranslationHandler(handler) {
return { provide: MissingTranslationHandler, useClass: handler };
}
function provideTranslateService(config = {}) {
return defaultProviders({
compiler: provideTranslateCompiler(TranslateNoOpCompiler),
parser: provideTranslateParser(TranslateDefaultParser),
loader: provideTranslateLoader(TranslateNoOpLoader),
missingTranslationHandler: provideMissingTranslationHandler(DefaultMissingTranslationHandler),
...config,
}, true);
}
function provideChildTranslateService(config = {}) {
return defaultProviders({ extend: true, ...config }, false);
}
function defaultProviders(config = {}, provideStore) {
const providers = [];
if (config.loader) {
providers.push(config.loader);
}
if (config.compiler) {
providers.push(config.compiler);
}
if (config.parser) {
providers.push(config.parser);
}
if (config.missingTranslationHandler) {
providers.push(config.missingTranslationHandler);
}
if (provideStore) {
providers.push(TranslateStore);
}
if (config.useDefaultLang || config.defaultLanguage) {
console.warn("The `useDefaultLang` and `defaultLanguage` options are deprecated. Please use `fallbackLang` instead.");
if (config.useDefaultLang === true && config.defaultLanguage) {
config.fallbackLang = config.defaultLanguage;
}
}
const serviceConfig = {
fallbackLang: config.fallbackLang ?? null,
lang: config.lang,
extend: config.extend ?? false,
};
providers.push({
provide: TRANSLATE_SERVICE_CONFIG,
useValue: serviceConfig,
});
providers.push({
provide: TranslateService,
useClass: TranslateService,
deps: [
TranslateStore,
TranslateLoader,
TranslateCompiler,
TranslateParser,
MissingTranslationHandler,
TRANSLATE_SERVICE_CONFIG,
],
});
return providers;
}
class TranslateModule {
/**
* Use this method in your root module to provide the TranslateService
*/
static forRoot(config = {}) {
return {
ngModule: TranslateModule,
providers: [
...defaultProviders({
compiler: provideTranslateCompiler(TranslateNoOpCompiler),
parser: provideTranslateParser(TranslateDefaultParser),
loader: provideTranslateLoader(TranslateNoOpLoader),
missingTranslationHandler: provideMissingTranslationHandler(DefaultMissingTranslationHandler),
...config,
}, true),
],
};
}
/**
* Use this method in your other (non-root) modules to import the directive/pipe
*/
static forChild(config = {}) {
return {
ngModule: TranslateModule,
providers: [...defaultProviders(config, config.isolate ?? false)],
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.6", ngImport: i0, type: TranslateModule, imports: [TranslatePipe, TranslateDirective], exports: [TranslatePipe, TranslateDirective] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslateModule, decorators: [{
type: NgModule,
args: [{
imports: [TranslatePipe, TranslateDirective],
exports: [TranslatePipe, TranslateDirective],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { DefaultMissingTranslationHandler, ITranslateService, MissingTranslationHandler, TRANSLATE_SERVICE_CONFIG, TranslateCompiler, TranslateDefaultParser, TranslateDirective, TranslateLoader, TranslateModule, TranslateNoOpCompiler, TranslateNoOpLoader, TranslateParser, TranslatePipe, TranslateService, TranslateStore, _, defaultProviders, equals, getValue, insertValue, isArray, isDefined, isDefinedAndNotNull, isDict, isFunction, isObject, isString, mergeDeep, provideChildTranslateService, provideMissingTranslationHandler, provideTranslateCompiler, provideTranslateLoader, provideTranslateParser, provideTranslateService, setValue };
//# sourceMappingURL=ngx-translate-core.mjs.map