@ngx-translate/core
Version:
Translation library (i18n) for Angular
1 lines • 94.1 kB
Source Map (JSON)
{"version":3,"file":"ngx-translate-core.mjs","sources":["../../../projects/ngx-translate/src/lib/extraction-marker.ts","../../../projects/ngx-translate/src/lib/missing-translation-handler.ts","../../../projects/ngx-translate/src/lib/translate.compiler.ts","../../../projects/ngx-translate/src/lib/translate.loader.ts","../../../projects/ngx-translate/src/lib/util.ts","../../../projects/ngx-translate/src/lib/translate.parser.ts","../../../projects/ngx-translate/src/lib/translate.store.ts","../../../projects/ngx-translate/src/lib/translate.service.ts","../../../projects/ngx-translate/src/lib/translate.directive.ts","../../../projects/ngx-translate/src/lib/translate.pipe.ts","../../../projects/ngx-translate/src/lib/translate.providers.ts","../../../projects/ngx-translate/src/lib/translate.module.ts","../../../projects/ngx-translate/src/ngx-translate-core.ts"],"sourcesContent":["export function _<T extends string | string[]>(key: T): T {\n return key;\n}\n","import { Injectable } from \"@angular/core\";\nimport { Observable } from \"rxjs\";\nimport { TranslateService, StrictTranslation } from \"./translate.service\";\n\nexport interface MissingTranslationHandlerParams {\n /**\n * the key that's missing in translation files\n */\n key: string;\n\n /**\n * an instance of the service that was unable to translate the key.\n */\n translateService: TranslateService;\n\n /**\n * interpolation params that were passed along for translating the given key.\n */\n interpolateParams?: object;\n}\n\nexport abstract class MissingTranslationHandler {\n /**\n * A function that handles missing translations.\n *\n * @param params context for resolving a missing translation\n * @returns a value or an observable\n *\n * If it returns a value, then this value is used.\n * If it returns an observable, the value returned by this observable will be used (except if the method was \"instant\").\n * If it returns undefined, the key will be used as a value\n */\n abstract handle(\n params: MissingTranslationHandlerParams,\n ): StrictTranslation | Observable<StrictTranslation>;\n}\n\n/**\n * This handler is just a placeholder that does nothing; in case you don't need a missing translation handler at all\n */\n@Injectable()\nexport class DefaultMissingTranslationHandler implements MissingTranslationHandler {\n handle(params: MissingTranslationHandlerParams): string {\n return params.key;\n }\n}\n","import { Injectable } from \"@angular/core\";\nimport { InterpolateFunction } from \"./translate.parser\";\nimport {\n InterpolatableTranslation,\n InterpolatableTranslationObject,\n TranslationObject,\n} from \"./translate.service\";\n\nexport abstract class TranslateCompiler {\n abstract compile(value: string, lang: string): InterpolatableTranslation;\n\n abstract compileTranslations(\n translations: TranslationObject,\n lang: string,\n ): InterpolatableTranslationObject;\n}\n\n/**\n * This compiler is just a placeholder that does nothing; in case you don't need a compiler at all\n */\n@Injectable()\nexport class TranslateNoOpCompiler extends TranslateCompiler {\n compile(value: string, lang: string): string | InterpolateFunction {\n void lang;\n return value;\n }\n\n compileTranslations(\n translations: TranslationObject,\n lang: string,\n ): InterpolatableTranslationObject {\n void lang;\n return translations;\n }\n}\n","import { Injectable } from \"@angular/core\";\nimport { Observable, of } from \"rxjs\";\nimport { TranslationObject } from \"./translate.service\";\n\nexport abstract class TranslateLoader {\n abstract getTranslation(lang: string): Observable<TranslationObject>;\n}\n\n/**\n * This loader is just a placeholder that does nothing; in case you don't need a loader at all\n */\n@Injectable()\nexport class TranslateNoOpLoader extends TranslateLoader {\n getTranslation(lang: string): Observable<TranslationObject> {\n void lang;\n return of({});\n }\n}\n","import { InterpolatableTranslationObject } from \"./translate.service\";\n\n/**\n * Determines if two objects or two values are equivalent.\n *\n * Two objects or values are considered equivalent if at least one of the following is true:\n *\n * * Both objects or values pass `===` comparison.\n * * Both objects or values are of the same type and all of their properties are equal by\n * comparing them with `equals`.\n *\n * @param o1 Object or value to compare.\n * @param o2 Object or value to compare.\n * @returns true if arguments are equal.\n */\nexport function equals(o1: unknown, o2: unknown): boolean {\n if (o1 === o2) return true;\n if (o1 === null || o2 === null) return false;\n if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN\n\n const t1 = typeof o1,\n t2 = typeof o2;\n let length: number;\n\n if (t1 == t2 && t1 == \"object\") {\n if (Array.isArray(o1)) {\n if (!Array.isArray(o2)) return false;\n if ((length = o1.length) == o2.length) {\n for (let key = 0; key < length; key++) {\n if (!equals(o1[key], o2[key])) return false;\n }\n return true;\n }\n } else {\n if (Array.isArray(o2)) {\n return false;\n }\n if (isDict(o1) && isDict(o2)) {\n const keySet = Object.create(null);\n for (const key in o1) {\n if (!equals(o1[key], o2[key])) {\n return false;\n }\n keySet[key] = true;\n }\n for (const key in o2) {\n if (!(key in keySet) && typeof o2[key] !== \"undefined\") {\n return false;\n }\n }\n return true;\n }\n }\n }\n return false;\n}\n\nexport function isDefinedAndNotNull<T>(value: T | null | undefined): value is T {\n return typeof value !== \"undefined\" && value !== null;\n}\n\nexport function isDefined<T>(value: T | null | undefined): value is T | null {\n return value !== undefined;\n}\n\nexport function isDict(value: unknown): value is InterpolatableTranslationObject {\n return isObject(value) && !isArray(value) && value !== null;\n}\n\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function isArray(value: unknown): value is unknown[] {\n return Array.isArray(value);\n}\n\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nexport function isFunction(value: unknown): boolean {\n return typeof value === \"function\";\n}\n\nfunction cloneDeep(value: unknown): unknown {\n if (isArray(value)) {\n return value.map((item) => cloneDeep(item));\n } else if (isDict(value)) {\n const cloned: Record<string, unknown> = {};\n Object.keys(value).forEach((key) => {\n cloned[key] = cloneDeep((value as Record<string, unknown>)[key]);\n });\n return cloned;\n } else {\n return value;\n }\n}\n\n/* eslint-disable-next-line @typescript-eslint/no-explicit-any */\nexport function mergeDeep(target: Readonly<unknown>, source: Readonly<unknown>): any {\n if (!isObject(target)) {\n return cloneDeep(source);\n }\n\n const output = cloneDeep(target);\n\n if (isObject(output) && isObject(source)) {\n /* eslint-disable-next-line @typescript-eslint/no-explicit-any */\n Object.keys(source).forEach((key: any) => {\n if (isDict(source[key])) {\n if (key in target) {\n output[key] = mergeDeep(target[key] as Readonly<unknown>, source[key]);\n } else {\n Object.assign(output, { [key]: source[key] });\n }\n } else {\n Object.assign(output, { [key]: source[key] });\n }\n });\n }\n return output;\n}\n\n/**\n * Retrieves a value from a nested object using a dot-separated key path.\n *\n * Example usage:\n * ```ts\n * getValue({ key1: { keyA: 'valueI' }}, 'key1.keyA'); // returns 'valueI'\n * ```\n *\n * @param target The source object from which to retrieve the value.\n * @param key Dot-separated key path specifying the value to retrieve.\n * @returns The value at the specified key path, or `undefined` if not found.\n */\nexport function getValue(target: unknown, key: string): unknown {\n const keys = key.split(\".\");\n\n key = \"\";\n do {\n key += keys.shift();\n const isLastKey = !keys.length;\n\n if (isDefinedAndNotNull(target)) {\n if (\n isDict(target) &&\n isDefined(target[key]) &&\n (isDict(target[key]) || isArray(target[key]) || isLastKey)\n ) {\n target = target[key];\n key = \"\";\n continue;\n }\n\n if (isArray(target)) {\n const index = parseInt(key, 10);\n if (\n isDefined(target[index]) &&\n (isDict(target[index]) || isArray(target[index]) || isLastKey)\n ) {\n target = target[index];\n key = \"\";\n continue;\n }\n }\n }\n\n if (isLastKey) {\n target = undefined;\n continue;\n }\n key += \".\";\n } while (keys.length);\n\n return target;\n}\n\n/**\n * Sets a value on object using a dot separated key.\n * This function modifies the object in place\n * parser.setValue({a:{b:{c: \"test\"}}}, 'a.b.c', \"test2\") ==> {a:{b:{c: \"test2\"}}}\n * @param target an object\n * @param key E.g. \"a.b.c\"\n * @param value to set\n * @deprecated use insertValue() instead\n */\nexport function setValue(target: Record<string, unknown>, key: string, value: unknown): void {\n const keys: string[] = key.split(\".\");\n let current: Record<string, unknown> = target;\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n\n if (i === keys.length - 1) {\n current[key] = value;\n } else {\n current = current[key] && isDict(current[key]) ? current[key] : {};\n }\n }\n}\n\n/**\n * Sets a value on object using a dot separated key.\n * Returns a clone of the object without modifying it\n * parser.setValue({a:{b:{c: \"test\"}}}, 'a.b.c', \"test2\") ==> {a:{b:{c: \"test2\"}}}\n * @param target an object\n * @param key E.g. \"a.b.c\"\n * @param value to set\n */\nexport function insertValue<T>(target: Readonly<T>, key: string, value: unknown): T {\n return mergeDeep(target, createNestedObject(key, value) as Readonly<unknown>);\n}\n\nfunction createNestedObject(\n dotSeparatedKey: string,\n value: unknown,\n): Record<string, unknown> | unknown {\n return dotSeparatedKey.split(\".\").reduceRight<unknown>((acc, key) => ({ [key]: acc }), value);\n}\n","import { Injectable } from \"@angular/core\";\nimport { InterpolationParameters } from \"./translate.service\";\nimport { getValue, isString, isFunction, isArray, isObject } from \"./util\";\n\nexport type InterpolateFunction = (params?: InterpolationParameters) => string;\n\nexport abstract class TranslateParser {\n /**\n * Interpolates a string to replace parameters\n * \"This is a {{ key }}\" ==> \"This is a value\", with params = { key: \"value\" }\n * @param expr\n * @param params\n */\n abstract interpolate(\n expr: InterpolateFunction | string,\n params?: InterpolationParameters,\n ): string | undefined;\n}\n\n@Injectable()\nexport class TranslateDefaultParser extends TranslateParser {\n templateMatcher = /{{\\s?([^{}\\s]*)\\s?}}/g;\n\n public interpolate(\n expr: InterpolateFunction | string,\n params?: InterpolationParameters,\n ): string | undefined {\n if (isString(expr)) {\n return this.interpolateString(expr as string, params);\n } else if (isFunction(expr)) {\n return this.interpolateFunction(expr as InterpolateFunction, params);\n }\n return undefined;\n }\n\n protected interpolateFunction(\n fn: InterpolateFunction,\n params?: InterpolationParameters,\n ): string {\n return fn(params);\n }\n\n protected interpolateString(expr: string, params?: InterpolationParameters): string {\n if (!params) {\n return expr;\n }\n\n return expr.replace(this.templateMatcher, (substring: string, key: string) => {\n const replacement = this.getInterpolationReplacement(params, key);\n return replacement !== undefined ? replacement : substring;\n });\n }\n\n /**\n * Returns the replacement for an interpolation parameter\n * @params:\n */\n protected getInterpolationReplacement(\n params: InterpolationParameters,\n key: string,\n ): string | undefined {\n return this.formatValue(getValue(params, key));\n }\n\n /**\n * Converts a value into a useful string representation.\n * @param value The value to format.\n * @returns A string representation of the value.\n */\n protected formatValue(value: unknown): string | undefined {\n if (isString(value)) {\n return value;\n }\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return value.toString();\n }\n if (value === null) {\n return \"null\";\n }\n if (isArray(value)) {\n return value.join(\", \");\n }\n if (isObject(value)) {\n if (\n typeof value.toString === \"function\" &&\n value.toString !== Object.prototype.toString\n ) {\n return value.toString();\n }\n return JSON.stringify(value); // Pretty-print JSON if no meaningful toString()\n }\n\n return undefined;\n }\n}\n","import {\n InterpolatableTranslationObject,\n FallbackLangChangeEvent,\n LangChangeEvent,\n TranslationChangeEvent,\n Language,\n InterpolatableTranslation,\n} from \"./translate.service\";\nimport { Observable, Subject } from \"rxjs\";\nimport { getValue, mergeDeep } from \"./util\";\nimport { Injectable } from \"@angular/core\";\n\nexport type DeepReadonly<T> = {\n readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];\n};\n\n@Injectable()\nexport class TranslateStore {\n private _onTranslationChange: Subject<TranslationChangeEvent> =\n new Subject<TranslationChangeEvent>();\n private _onLangChange: Subject<LangChangeEvent> = new Subject<LangChangeEvent>();\n private _onFallbackLangChange: Subject<FallbackLangChangeEvent> =\n new Subject<FallbackLangChangeEvent>();\n\n private fallbackLang: Language | null = null;\n private currentLang!: Language;\n\n private translations: Record<Language, InterpolatableTranslationObject> = {};\n private languages: Language[] = [];\n\n public getTranslations(language: Language): DeepReadonly<InterpolatableTranslationObject> {\n return this.translations[language];\n }\n\n public setTranslations(\n language: Language,\n translations: InterpolatableTranslationObject,\n extend: boolean,\n ): void {\n this.translations[language] =\n extend && this.hasTranslationFor(language)\n ? mergeDeep(this.translations[language], translations)\n : translations;\n this.addLanguages([language]);\n this._onTranslationChange.next({\n lang: language,\n translations: this.getTranslations(language),\n });\n }\n\n public getLanguages(): readonly Language[] {\n return this.languages;\n }\n\n public getCurrentLang(): Language {\n return this.currentLang;\n }\n\n public getFallbackLang(): Language | null {\n return this.fallbackLang;\n }\n\n /**\n * Changes the fallback lang\n */\n public setFallbackLang(lang: string, emitChange = true): void {\n this.fallbackLang = lang;\n if (emitChange) {\n this._onFallbackLangChange.next({ lang: lang, translations: this.translations[lang] });\n }\n }\n\n public setCurrentLang(lang: string, emitChange = true): void {\n this.currentLang = lang;\n if (emitChange) {\n this._onLangChange.next({ lang: lang, translations: this.translations[lang] });\n }\n }\n\n /**\n * An Observable to listen to translation change events\n * onTranslationChange.subscribe((params: TranslationChangeEvent) => {\n * // do something\n * });\n */\n get onTranslationChange(): Observable<TranslationChangeEvent> {\n return this._onTranslationChange.asObservable();\n }\n\n /**\n * An Observable to listen to lang change events\n * onLangChange.subscribe((params: LangChangeEvent) => {\n * // do something\n * });\n */\n get onLangChange(): Observable<LangChangeEvent> {\n return this._onLangChange.asObservable();\n }\n\n /**\n * An Observable to listen to fallback lang change events\n * onFallbackLangChange.subscribe((params: FallbackLangChangeEvent) => {\n * // do something\n * });\n */\n get onFallbackLangChange(): Observable<FallbackLangChangeEvent> {\n return this._onFallbackLangChange.asObservable();\n }\n\n public addLanguages(languages: Language[]): void {\n this.languages = Array.from(new Set([...this.languages, ...languages]));\n }\n\n public hasTranslationFor(lang: string) {\n return typeof this.translations[lang] !== \"undefined\";\n }\n\n public deleteTranslations(lang: string) {\n delete this.translations[lang];\n }\n\n public getTranslation(key: string): InterpolatableTranslation {\n let text = this.getValue(this.currentLang, key);\n\n if (\n text === undefined &&\n this.fallbackLang != null &&\n this.fallbackLang !== this.currentLang\n ) {\n text = this.getValue(this.fallbackLang, key);\n }\n return text;\n }\n\n protected getValue(language: Language, key: string): InterpolatableTranslation {\n return getValue(this.getTranslations(language), key) as InterpolatableTranslation;\n }\n}\n","import { inject, Injectable, InjectionToken } from \"@angular/core\";\nimport { concat, defer, forkJoin, isObservable, Observable, of } from \"rxjs\";\nimport { concatMap, map, shareReplay, switchMap, take } from \"rxjs/operators\";\nimport { MissingTranslationHandler } from \"./missing-translation-handler\";\nimport { TranslateCompiler } from \"./translate.compiler\";\nimport { TranslateLoader } from \"./translate.loader\";\nimport { InterpolateFunction, TranslateParser } from \"./translate.parser\";\nimport { TranslateStore } from \"./translate.store\";\nimport { insertValue, isArray, isDefinedAndNotNull, isDict, isString } from \"./util\";\n\n/**\n * Configuration object for the translation service.\n *\n * Provides options to customize translation behavior, including setting the primary language,\n * specifying a fallback language, and other deprecated flags for legacy support.\n */\nexport interface TranslateServiceConfig {\n lang?: Language;\n fallbackLang?: Language | null;\n extend: boolean;\n}\n\nexport const TRANSLATE_SERVICE_CONFIG = new InjectionToken<TranslateServiceConfig>(\n \"TRANSLATE_CONFIG\",\n);\n\n/* eslint-disable-next-line @typescript-eslint/no-explicit-any */\nexport type InterpolationParameters = Record<string, any>;\n\nexport type StrictTranslation = string | StrictTranslation[] | TranslationObject | undefined | null;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type Translation = StrictTranslation | any;\n\nexport interface TranslationObject {\n [key: string]: StrictTranslation;\n}\n\nexport type InterpolatableTranslation =\n | string\n | InterpolatableTranslation[]\n | InterpolateFunction\n | InterpolatableTranslationObject\n | undefined\n | null;\n\nexport interface InterpolatableTranslationObject {\n [key: string]: InterpolatableTranslation;\n}\n\nexport type Language = string;\n\nexport interface TranslationChangeEvent {\n translations: InterpolatableTranslationObject;\n lang: string;\n}\n\nexport interface LangChangeEvent {\n lang: string;\n translations: InterpolatableTranslationObject;\n}\n\nexport interface FallbackLangChangeEvent {\n lang: string;\n translations: InterpolatableTranslationObject;\n}\n\n/** @deprecated use `FallbackLangChangeEvent` */\nexport type DefaultLangChangeEvent = FallbackLangChangeEvent;\n\ndeclare interface Window {\n navigator: {\n languages?: string[];\n language?: string;\n browserLanguage?: string;\n userLanguage?: string;\n };\n}\n\ndeclare const window: Window;\n\nconst makeObservable = <T>(value: T | Observable<T>): Observable<T> => {\n return isObservable(value) ? value : of(value);\n};\n\nexport abstract class ITranslateService {\n public abstract readonly onTranslationChange: Observable<TranslationChangeEvent>;\n public abstract readonly onLangChange: Observable<LangChangeEvent>;\n public abstract readonly onFallbackLangChange: Observable<FallbackLangChangeEvent>;\n\n public abstract use(lang: Language): Observable<InterpolatableTranslationObject>;\n\n public abstract setFallbackLang(lang: Language): Observable<InterpolatableTranslationObject>;\n public abstract getFallbackLang(): Language | null;\n\n public abstract addLangs(languages: Language[]): void;\n public abstract getLangs(): readonly Language[];\n public abstract reloadLang(lang: Language): Observable<InterpolatableTranslationObject>;\n public abstract resetLang(lang: Language): void;\n\n public abstract instant(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Translation;\n public abstract stream(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Observable<Translation>;\n public abstract getStreamOnTranslationChange(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Observable<Translation>;\n\n public abstract set(\n key: string,\n translation: string | TranslationObject,\n lang?: Language,\n ): void;\n public abstract get(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Observable<Translation>;\n\n public abstract setTranslation(\n lang: Language,\n translations: TranslationObject,\n shouldMerge?: boolean,\n ): void;\n public abstract getParsedResult(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): StrictTranslation | Observable<StrictTranslation>;\n\n public abstract getBrowserLang(): Language | undefined;\n public abstract getBrowserCultureLang(): Language | undefined;\n\n /**\n * Returns the current language\n * @deprecated use `getCurrentLang()`\n */\n public abstract readonly currentLang: Language;\n\n /**\n * Returns a list of known languages - either loaded\n * or set by using `addLangs()`\n * @deprecated use `getLangs()`\n */\n public abstract readonly langs: readonly Language[];\n\n /**\n * Sets the fallback language\n * @param lang The language to set\n * @deprecated use `setFallbackLang(lang)`\n */\n public abstract setDefaultLang(lang: Language): Observable<InterpolatableTranslationObject>;\n\n /**\n * Gets the fallback language\n * @deprecated use `getFallbackLang()`\n */\n public abstract getDefaultLang(): Language | null;\n\n /**\n * Returns the fallback language\n * @deprectated use `getFallbackLang()`\n */\n public abstract readonly defaultLang: Language | null;\n\n /**\n * @deprectated use `getFallbackLang()`\n */\n public abstract readonly onDefaultLangChange: Observable<DefaultLangChangeEvent>;\n}\n\n@Injectable()\nexport class TranslateService implements ITranslateService {\n private loadingTranslations!: Observable<InterpolatableTranslationObject>;\n private pending = false;\n private _translationRequests: Record<Language, Observable<TranslationObject>> = {};\n private lastUseLanguage: Language | null = null;\n\n public currentLoader = inject(TranslateLoader);\n public compiler = inject(TranslateCompiler);\n private parser = inject(TranslateParser);\n private missingTranslationHandler = inject(MissingTranslationHandler);\n private store: TranslateStore = inject(TranslateStore);\n\n private readonly extend: boolean = false;\n\n /**\n * An Observable to listen to translation change events\n * onTranslationChange.subscribe((params: TranslationChangeEvent) => {\n * // do something\n * });\n */\n public get onTranslationChange(): Observable<TranslationChangeEvent> {\n return this.store.onTranslationChange;\n }\n\n /**\n * An Observable to listen to lang change events\n * onLangChange.subscribe((params: LangChangeEvent) => {\n * // do something\n * });\n */\n get onLangChange(): Observable<LangChangeEvent> {\n return this.store.onLangChange;\n }\n\n /**\n * An Observable to listen to fallback lang change events\n * onFallbackLangChange.subscribe((params: FallbackLangChangeEvent) => {\n * // do something\n * });\n */\n get onFallbackLangChange(): Observable<FallbackLangChangeEvent> {\n return this.store.onFallbackLangChange;\n }\n\n /**\n * @deprecated Use onFallbackLangChange() instead\n */\n get onDefaultLangChange(): Observable<DefaultLangChangeEvent> {\n return this.store.onFallbackLangChange;\n }\n\n constructor() {\n const config: TranslateServiceConfig = {\n extend: false,\n fallbackLang: null,\n\n ...inject<TranslateServiceConfig>(TRANSLATE_SERVICE_CONFIG, {\n optional: true,\n }),\n };\n\n if (config.lang) {\n this.use(config.lang);\n }\n\n if (config.fallbackLang) {\n this.setFallbackLang(config.fallbackLang);\n }\n\n if (config.extend) {\n this.extend = true;\n }\n }\n\n /**\n * Sets the fallback language to use if a translation is not found in the\n * current language\n */\n public setFallbackLang(lang: Language): Observable<InterpolatableTranslationObject> {\n if (!this.getFallbackLang()) {\n // on init set the fallbackLang immediately, but do not emit a change yet\n this.store.setFallbackLang(lang, false);\n }\n\n const pending = this.loadOrExtendLanguage(lang);\n if (isObservable(pending)) {\n pending.pipe(take(1)).subscribe({\n next: () => {\n this.store.setFallbackLang(lang);\n },\n error: () => {\n /* ignore here - user can handle it */\n },\n });\n return pending;\n }\n\n this.store.setFallbackLang(lang);\n return of(this.store.getTranslations(lang));\n }\n\n /**\n * Changes the lang currently used\n */\n public use(lang: Language): Observable<InterpolatableTranslationObject> {\n // remember the language that was called\n // we need this with multiple fast calls to use()\n // where translation loads might complete in random order\n this.lastUseLanguage = lang;\n\n if (!this.getCurrentLang()) {\n // on init set the currentLang immediately, but do not emit a change yet\n this.store.setCurrentLang(lang, false);\n }\n\n const pending = this.loadOrExtendLanguage(lang);\n if (isObservable(pending)) {\n pending.pipe(take(1)).subscribe({\n next: () => {\n this.changeLang(lang);\n },\n error: () => {\n /* ignore here - use can handle it */\n },\n });\n return pending;\n }\n\n this.changeLang(lang);\n return of(this.store.getTranslations(lang));\n }\n\n /**\n * Retrieves the given translations\n */\n private loadOrExtendLanguage(lang: Language): Observable<TranslationObject> | undefined {\n // if this language is unavailable or extend is true, ask for it\n if (!this.store.hasTranslationFor(lang) || this.extend) {\n this._translationRequests[lang] =\n this._translationRequests[lang] || this.loadAndCompileTranslations(lang);\n return this._translationRequests[lang];\n }\n\n return undefined;\n }\n\n /**\n * Changes the current lang\n */\n private changeLang(lang: Language): void {\n if (lang !== this.lastUseLanguage) {\n // received new language data,\n // but this was not the one requested last\n return;\n }\n\n this.store.setCurrentLang(lang);\n }\n\n public getCurrentLang(): Language {\n return this.store.getCurrentLang();\n }\n\n private loadAndCompileTranslations(\n lang: Language,\n ): Observable<InterpolatableTranslationObject> {\n this.pending = true;\n\n const loadingTranslations = this.currentLoader\n .getTranslation(lang)\n .pipe(shareReplay(1), take(1));\n\n this.loadingTranslations = loadingTranslations.pipe(\n map((res: TranslationObject) => this.compiler.compileTranslations(res, lang)),\n shareReplay(1),\n take(1),\n );\n\n this.loadingTranslations.subscribe({\n next: (res: InterpolatableTranslationObject) => {\n this.store.setTranslations(lang, res, this.extend);\n this.pending = false;\n },\n error: (err) => {\n void err;\n this.pending = false;\n },\n });\n\n return loadingTranslations;\n }\n\n /**\n * Manually sets an object of translations for a given language\n * after passing it through the compiler\n */\n public setTranslation(\n lang: Language,\n translations: TranslationObject,\n shouldMerge = false,\n ): void {\n const interpolatableTranslations: InterpolatableTranslationObject =\n this.compiler.compileTranslations(translations, lang);\n this.store.setTranslations(lang, interpolatableTranslations, shouldMerge || this.extend);\n }\n\n public getLangs(): readonly Language[] {\n return this.store.getLanguages();\n }\n\n /**\n * Add available languages\n */\n public addLangs(languages: Language[]): void {\n this.store.addLanguages(languages);\n }\n\n private getParsedResultForKey(\n key: string,\n interpolateParams?: InterpolationParameters,\n ): StrictTranslation | Observable<StrictTranslation> {\n const textToInterpolate = this.getTextToInterpolate(key);\n\n if (isDefinedAndNotNull(textToInterpolate)) {\n return this.runInterpolation(textToInterpolate, interpolateParams);\n }\n\n const res = this.missingTranslationHandler.handle({\n key,\n translateService: this,\n ...(interpolateParams !== undefined && { interpolateParams }),\n });\n\n return res !== undefined ? res : key;\n }\n\n /**\n * Gets the fallback language. null if none is defined\n */\n public getFallbackLang(): Language | null {\n return this.store.getFallbackLang();\n }\n\n private getTextToInterpolate(key: string): InterpolatableTranslation | undefined {\n return this.store.getTranslation(key);\n }\n\n private runInterpolation(\n translations: InterpolatableTranslation,\n interpolateParams?: InterpolationParameters,\n ): StrictTranslation {\n if (!isDefinedAndNotNull(translations)) {\n return;\n }\n\n if (isArray(translations)) {\n return this.runInterpolationOnArray(translations, interpolateParams);\n }\n\n if (isDict(translations)) {\n return this.runInterpolationOnDict(translations, interpolateParams);\n }\n\n return this.parser.interpolate(translations, interpolateParams);\n }\n\n private runInterpolationOnArray(\n translations: InterpolatableTranslation,\n interpolateParams: InterpolationParameters | undefined,\n ) {\n return (translations as StrictTranslation[]).map((translation) =>\n this.runInterpolation(translation, interpolateParams),\n );\n }\n\n private runInterpolationOnDict(\n translations: InterpolatableTranslationObject,\n interpolateParams: InterpolationParameters | undefined,\n ) {\n const result: TranslationObject = {};\n for (const key in translations) {\n const res = this.runInterpolation(translations[key], interpolateParams);\n if (res !== undefined) {\n result[key] = res;\n }\n }\n return result;\n }\n\n /**\n * Returns the parsed result of the translations\n */\n public getParsedResult(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): StrictTranslation | Observable<StrictTranslation> {\n return key instanceof Array\n ? this.getParsedResultForArray(key, interpolateParams)\n : this.getParsedResultForKey(key, interpolateParams);\n }\n\n private getParsedResultForArray(\n key: string[],\n interpolateParams: InterpolationParameters | undefined,\n ) {\n const result: Record<string, StrictTranslation | Observable<StrictTranslation>> = {};\n\n let observables = false;\n for (const k of key) {\n result[k] = this.getParsedResultForKey(k, interpolateParams);\n observables = observables || isObservable(result[k]);\n }\n\n if (!observables) {\n return result as TranslationObject;\n }\n\n const sources: Observable<StrictTranslation>[] = key.map((k) => makeObservable(result[k]));\n return forkJoin(sources).pipe(\n map((arr: StrictTranslation[]) => {\n const obj: TranslationObject = {};\n arr.forEach((value: StrictTranslation, index: number) => {\n obj[key[index]] = value;\n });\n return obj;\n }),\n );\n }\n\n /**\n * Gets the translated value of a key (or an array of keys)\n * @returns the translated key, or an object of translated keys\n */\n public get(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Observable<Translation> {\n if (!isDefinedAndNotNull(key) || !key.length) {\n throw new Error(`Parameter \"key\" is required and cannot be empty`);\n }\n // check if we are loading a new translation to use\n if (this.pending) {\n return this.loadingTranslations.pipe(\n concatMap(() => {\n return makeObservable(this.getParsedResult(key, interpolateParams));\n }),\n );\n }\n\n return makeObservable(this.getParsedResult(key, interpolateParams));\n }\n\n /**\n * Returns a stream of translated values of a key (or an array of keys) which updates\n * whenever the translation changes.\n * @returns A stream of the translated key, or an object of translated keys\n */\n public getStreamOnTranslationChange(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Observable<Translation> {\n if (!isDefinedAndNotNull(key) || !key.length) {\n throw new Error(`Parameter \"key\" is required and cannot be empty`);\n }\n\n return concat(\n defer(() => this.get(key, interpolateParams)),\n this.onTranslationChange.pipe(\n switchMap(() => {\n const res = this.getParsedResult(key, interpolateParams);\n return makeObservable(res);\n }),\n ),\n );\n }\n\n /**\n * Returns a stream of translated values of a key (or an array of keys) which updates\n * whenever the language changes.\n * @returns A stream of the translated key, or an object of translated keys\n */\n public stream(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Observable<Translation> {\n if (!isDefinedAndNotNull(key) || !key.length) {\n throw new Error(`Parameter \"key\" required`);\n }\n\n return concat(\n defer(() => this.get(key, interpolateParams)),\n this.onLangChange.pipe(\n switchMap(() => {\n const res = this.getParsedResult(key, interpolateParams);\n return makeObservable(res);\n }),\n ),\n );\n }\n\n /**\n * Returns a translation instantly from the internal state of loaded translation.\n * All rules regarding the current language, the preferred language of even fallback languages\n * will be used except any promise handling.\n */\n public instant(\n key: string | string[],\n interpolateParams?: InterpolationParameters,\n ): Translation {\n if (!isDefinedAndNotNull(key) || key.length === 0) {\n throw new Error('Parameter \"key\" is required and cannot be empty');\n }\n\n const result = this.getParsedResult(key, interpolateParams);\n\n if (isObservable(result)) {\n if (Array.isArray(key)) {\n return key.reduce((acc: Record<string, string>, currKey: string) => {\n acc[currKey] = currKey;\n return acc;\n }, {});\n }\n return key;\n }\n\n return result;\n }\n\n /**\n * Sets the translated value of a key, after compiling it\n */\n public set(\n key: string,\n translation: string | TranslationObject,\n lang: Language = this.getCurrentLang(),\n ): void {\n this.store.setTranslations(\n lang,\n insertValue(\n this.store.getTranslations(lang),\n key,\n isString(translation)\n ? this.compiler.compile(translation, lang)\n : this.compiler.compileTranslations(translation, lang),\n ),\n false,\n );\n }\n\n /**\n * Allows reloading the lang file from the file\n */\n public reloadLang(lang: Language): Observable<InterpolatableTranslationObject> {\n this.resetLang(lang);\n return this.loadAndCompileTranslations(lang);\n }\n\n /**\n * Deletes inner translation\n */\n public resetLang(lang: Language): void {\n delete this._translationRequests[lang];\n this.store.deleteTranslations(lang);\n }\n\n /**\n * Returns the language code name from the browser, e.g. \"de\"\n */\n public static getBrowserLang(): Language | undefined {\n if (typeof window === \"undefined\" || !window.navigator) {\n return undefined;\n }\n\n const browserLang = this.getBrowserCultureLang();\n\n return browserLang ? browserLang.split(/[-_]/)[0] : undefined;\n }\n\n /**\n * Returns the culture language code name from the browser, e.g. \"de-DE\"\n */\n public static getBrowserCultureLang(): Language | undefined {\n if (typeof window === \"undefined\" || typeof window.navigator === \"undefined\") {\n return undefined;\n }\n\n return window.navigator.languages\n ? window.navigator.languages[0]\n : window.navigator.language ||\n window.navigator.browserLanguage ||\n window.navigator.userLanguage;\n }\n\n public getBrowserLang(): Language | undefined {\n return TranslateService.getBrowserLang();\n }\n\n public getBrowserCultureLang(): Language | undefined {\n return TranslateService.getBrowserCultureLang();\n }\n\n /** Deprecations **/\n\n /**\n * @deprecated use `getFallbackLang()`\n */\n get defaultLang(): Language | null {\n return this.getFallbackLang();\n }\n\n /**\n * The lang currently used\n * @deprecated use `getCurrentLang()`\n */\n get currentLang(): Language {\n return this.store.getCurrentLang();\n }\n\n /**\n * @deprecated use `getLangs()`\n */\n get langs(): readonly Language[] {\n return this.store.getLanguages();\n }\n\n /**\n * Sets the language to use as a fallback\n * @deprecated use setFallbackLanguage()\n */\n public setDefaultLang(lang: Language): Observable<InterpolatableTranslationObject> {\n return this.setFallbackLang(lang);\n }\n\n /**\n * Gets the fallback language used\n * @deprecated use getFallbackLang()\n */\n public getDefaultLang(): Language | null {\n return this.getFallbackLang();\n }\n}\n","import {\n AfterViewChecked,\n ChangeDetectorRef,\n Directive,\n ElementRef,\n inject,\n Input,\n OnDestroy,\n} from \"@angular/core\";\nimport { Subscription, isObservable } from \"rxjs\";\nimport {\n FallbackLangChangeEvent,\n InterpolatableTranslation,\n LangChangeEvent,\n TranslateService,\n TranslationChangeEvent,\n StrictTranslation,\n InterpolationParameters,\n} from \"./translate.service\";\nimport { equals, isDefinedAndNotNull, isString } from \"./util\";\n\ninterface ExtendedNode extends Text {\n originalContent: string;\n currentValue: string;\n lookupKey: string;\n lastKey: string | null;\n data: string;\n}\n\n@Directive({\n // eslint-disable-next-line @angular-eslint/directive-selector\n selector: \"[translate],[ngx-translate]\",\n standalone: true,\n})\nexport class TranslateDirective implements AfterViewChecked, OnDestroy {\n private translateService: TranslateService = inject(TranslateService);\n private element: ElementRef = inject(ElementRef);\n private _ref: ChangeDetectorRef = inject(ChangeDetectorRef);\n\n private key!: string;\n private lastParams?: InterpolationParameters;\n private currentParams?: InterpolationParameters;\n private readonly onLangChangeSub!: Subscription;\n private readonly onFallbackLangChangeSub!: Subscription;\n private readonly onTranslationChangeSub!: Subscription;\n\n @Input() set translate(key: string) {\n if (key) {\n this.key = key;\n this.checkNodes();\n }\n }\n\n @Input() set translateParams(params: InterpolationParameters) {\n if (!equals(this.currentParams, params)) {\n this.currentParams = params;\n this.checkNodes(true);\n }\n }\n\n constructor() {\n // subscribe to onTranslationChange event, in case the translations of the current lang change\n if (!this.onTranslationChangeSub) {\n this.onTranslationChangeSub = this.translateService.onTranslationChange.subscribe(\n (event: TranslationChangeEvent) => {\n if (event.lang === this.translateService.currentLang) {\n this.checkNodes(true, event.translations);\n }\n },\n );\n }\n\n // subscribe to onLangChange event, in case the language changes\n if (!this.onLangChangeSub) {\n this.onLangChangeSub = this.translateService.onLangChange.subscribe(\n (event: LangChangeEvent) => {\n this.checkNodes(true, event.translations);\n },\n );\n }\n\n // subscribe to onFallbackLangChange event, in case the fallback language changes\n if (!this.onFallbackLangChangeSub) {\n this.onFallbackLangChangeSub = this.translateService.onFallbackLangChange.subscribe(\n (event: FallbackLangChangeEvent) => {\n void event;\n this.checkNodes(true);\n },\n );\n }\n }\n\n ngAfterViewChecked() {\n this.checkNodes();\n }\n\n checkNodes(forceUpdate = false, translations?: InterpolatableTranslation) {\n let nodes: NodeList = this.element.nativeElement.childNodes;\n // if the element is empty\n if (!nodes.length) {\n // we add the key as content\n this.setContent(this.element.nativeElement, this.key);\n nodes = this.element.nativeElement.childNodes;\n }\n\n nodes.forEach((n) => {\n const node = n as ExtendedNode;\n if (node.nodeType === 3) {\n // node type 3 is a text node\n let key!: string;\n if (forceUpdate) {\n node.lastKey = null;\n }\n if (isDefinedAndNotNull(node.lookupKey)) {\n key = node.lookupKey;\n } else if (this.key) {\n key = this.key;\n } else {\n const content = this.getContent(node);\n const trimmedContent = content.trim();\n if (trimmedContent.length) {\n node.lookupKey = trimmedContent;\n // we want to use the content as a key, not the translation value\n if (content !== node.currentValue) {\n key = trimmedContent;\n // the content was changed from the user, we'll use it as a reference if needed\n node.originalContent = content || node.originalContent;\n } else if (node.originalContent) {\n // the content seems ok, but the lang has changed\n // the current content is the translation, not the key, use the last real content as key\n key = node.originalContent.trim();\n }\n }\n }\n this.updateValue(key, node, translations);\n }\n });\n }\n\n updateValue(key: string, node: ExtendedNode, translations?: InterpolatableTranslation) {\n if (key) {\n if (node.lastKey === key && this.lastParams === this.currentParams) {\n return;\n }\n\n this.lastParams = this.currentParams;\n\n const onTranslation = (res: StrictTranslation) => {\n if (res !== key || !node.lastKey) {\n node.lastKey = key;\n }\n if (!node.originalContent) {\n node.originalContent = this.getContent(node);\n }\n\n if (isString(res)) {\n node.currentValue = res;\n } else if (!isDefinedAndNotNull(res)) {\n node.currentValue = node.originalContent || key;\n } else {\n node.currentValue = JSON.stringify(res);\n }\n\n // we replace in the original content to preserve spaces that we might have trimmed\n this.setContent(\n node,\n this.key\n ? node.currentValue\n : node.originalContent.replace(key, node.currentValue),\n );\n this._ref.markForCheck();\n };\n\n if (isDefinedAndNotNull(translations)) {\n const res = this.translateService.getParsedResult(key, this.currentParams);\n if (isObservable(res)) {\n res.subscribe({ next: onTranslation });\n } else {\n onTranslation(res);\n }\n } else {\n this.translateService.get(key, this.currentParams).subscribe(onTranslation);\n }\n }\n }\n\n getContent(node: ExtendedNode): string {\n return (isDefinedAndNotNull(node.textContent) ? node.textContent : node.data) as string;\n }\n\n setContent(node: ExtendedNode, content: string): void {\n if (isDefinedAndNotNull(node.textContent)) {\n node.textContent = content;\n } else {\n node.data = content;\n }\n }\n\n ngOnDestroy() {\n if (this.onLangChangeSub) {\n this.onLangChangeSub.unsubscribe();\n }\n\n if (this.onFallbackLangChangeSub) {\n this.onFallbackLangChangeSub.unsubscribe();\n }\n\n if (this.onTranslationChangeSub) {\n this.onTranslationChangeSub.unsubscribe();\n }\n }\n}\n","import {\n ChangeDetectorRef,\n inject,\n In