UNPKG

@ngx-translate/core

Version:

Translation library (i18n) for Angular

1,255 lines (1,240 loc) 46.6 kB
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