@jsverse/transloco
Version:
The internationalization (i18n) library for Angular
1 lines • 120 kB
Source Map (JSON)
{"version":3,"file":"jsverse-transloco.mjs","sources":["../../../../libs/transloco/src/lib/transloco.loader.ts","../../../../libs/transloco/src/lib/transloco.config.ts","../../../../libs/transloco/src/lib/utils/object.utils.ts","../../../../libs/transloco/src/lib/transloco.transpiler.ts","../../../../libs/transloco/src/lib/transloco-missing-handler.ts","../../../../libs/transloco/src/lib/transloco.interceptor.ts","../../../../libs/transloco/src/lib/transloco-fallback-strategy.ts","../../../../libs/transloco/src/lib/resolve-loader.ts","../../../../libs/transloco/src/lib/get-fallbacks-loaders.ts","../../../../libs/transloco/src/lib/utils/flat.utils.ts","../../../../libs/transloco/src/lib/utils/scope.utils.ts","../../../../libs/transloco/src/lib/transloco.service.ts","../../../../libs/transloco/src/lib/loader-component.component.ts","../../../../libs/transloco/src/lib/template-handler.ts","../../../../libs/transloco/src/lib/transloco-lang.ts","../../../../libs/transloco/src/lib/transloco-loading-template.ts","../../../../libs/transloco/src/lib/transloco-scope.ts","../../../../libs/transloco/src/lib/utils/pipe.utils.ts","../../../../libs/transloco/src/lib/utils/lang.utils.ts","../../../../libs/transloco/src/lib/lang-resolver.ts","../../../../libs/transloco/src/lib/scope-resolver.ts","../../../../libs/transloco/src/lib/transloco.directive.ts","../../../../libs/transloco/src/lib/transloco.pipe.ts","../../../../libs/transloco/src/lib/transloco.module.ts","../../../../libs/transloco/src/lib/transloco.providers.ts","../../../../libs/transloco/src/lib/transloco-testing.module.ts","../../../../libs/transloco/src/lib/transloco.signal.ts","../../../../libs/transloco/src/lib/utils/browser.utils.ts","../../../../libs/transloco/src/jsverse-transloco.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { Observable, of } from 'rxjs';\n\nimport { Translation } from './transloco.types';\n\nexport interface TranslocoLoader {\n getTranslation(\n lang: string,\n data?: TranslocoLoaderData,\n ): Observable<Translation> | Promise<Translation>;\n}\n\nexport type TranslocoLoaderData = {\n scope: string;\n};\n\nexport class DefaultLoader implements TranslocoLoader {\n constructor(private translations: Map<string, Translation>) {}\n\n getTranslation(lang: string): Observable<Translation> {\n return of(this.translations.get(lang) || {});\n }\n}\n\nexport const TRANSLOCO_LOADER =\n /* @__PURE__ */ new InjectionToken<TranslocoLoader>(\n ngDevMode ? 'TRANSLOCO_LOADER' : '',\n );\n","import { InjectionToken } from '@angular/core';\n\nimport { AvailableLangs } from './transloco.types';\n\nexport interface TranslocoConfig {\n defaultLang: string;\n reRenderOnLangChange: boolean;\n prodMode: boolean;\n fallbackLang?: string | string[];\n failedRetries: number;\n availableLangs: AvailableLangs;\n flatten: {\n aot: boolean;\n };\n missingHandler: {\n logMissingKey: boolean;\n useFallbackTranslation: boolean;\n allowEmpty: boolean;\n };\n interpolation: [string, string];\n scopes: {\n keepCasing?: boolean;\n autoPrefixKeys?: boolean;\n };\n}\n\nexport const TRANSLOCO_CONFIG =\n /* @__PURE__ */ new InjectionToken<TranslocoConfig>(\n ngDevMode ? 'TRANSLOCO_CONFIG' : '',\n {\n providedIn: 'root',\n factory: () => defaultConfig,\n },\n );\n\nexport const defaultConfig: TranslocoConfig = {\n defaultLang: 'en',\n reRenderOnLangChange: false,\n prodMode: false,\n failedRetries: 2,\n fallbackLang: [],\n availableLangs: [],\n missingHandler: {\n logMissingKey: true,\n useFallbackTranslation: false,\n allowEmpty: false,\n },\n flatten: {\n aot: false,\n },\n interpolation: ['{{', '}}'],\n scopes: {\n keepCasing: false,\n autoPrefixKeys: true,\n },\n};\n\ntype DeepPartial<T> =\n T extends Array<any>\n ? T\n : T extends object\n ? { [P in keyof T]?: DeepPartial<T[P]> }\n : T;\n\nexport type PartialTranslocoConfig = DeepPartial<TranslocoConfig>;\n\nexport function translocoConfig(\n config: PartialTranslocoConfig = {},\n): TranslocoConfig {\n return {\n ...defaultConfig,\n ...config,\n missingHandler: {\n ...defaultConfig.missingHandler,\n ...config.missingHandler,\n },\n flatten: {\n ...defaultConfig.flatten,\n ...config.flatten,\n },\n scopes: {\n ...defaultConfig.scopes,\n ...config.scopes,\n },\n };\n}\n","export function getValue<T>(obj: T, path: keyof T) {\n if (!obj) {\n return obj;\n }\n\n /* For cases where the key is like: 'general.something.thing' */\n if (Object.prototype.hasOwnProperty.call(obj, path)) {\n return obj[path];\n }\n\n return (path as string).split('.').reduce((p, c) => p?.[c], obj as any);\n}\n\nexport function setValue(obj: any, prop: string, val: any) {\n obj = { ...obj };\n\n const split = prop.split('.');\n const lastIndex = split.length - 1;\n\n split.reduce((acc, part, index) => {\n if (index === lastIndex) {\n acc[part] = val;\n } else {\n acc[part] = Array.isArray(acc[part])\n ? acc[part].slice()\n : { ...acc[part] };\n }\n\n return acc && acc[part];\n }, obj);\n\n return obj;\n}\n","import { inject, Injectable, InjectionToken, Injector } from '@angular/core';\nimport { isDefined, isObject, isString } from '@jsverse/utils';\n\nimport { Translation } from './transloco.types';\nimport {\n defaultConfig,\n TRANSLOCO_CONFIG,\n TranslocoConfig,\n} from './transloco.config';\nimport { HashMap } from './utils/type.utils';\nimport { getValue, setValue } from './utils/object.utils';\n\nexport const TRANSLOCO_TRANSPILER =\n /* @__PURE__ */ new InjectionToken<TranslocoTranspiler>(\n ngDevMode ? 'TRANSLOCO_TRANSPILER' : '',\n );\n\nexport interface TranslocoTranspiler {\n transpile(params: TranspileParams): any;\n\n onLangChanged?(lang: string): void;\n}\n\nexport interface TranspileParams<V = unknown> {\n value: V;\n params?: HashMap;\n translation: Translation;\n key: string;\n}\n\n@Injectable()\nexport class DefaultTranspiler implements TranslocoTranspiler {\n protected config =\n inject(TRANSLOCO_CONFIG, { optional: true }) ?? defaultConfig;\n\n protected get interpolationMatcher() {\n return resolveMatcher(this.config);\n }\n\n transpile({ value, params = {}, translation, key }: TranspileParams): any {\n if (isString(value)) {\n let paramMatch: RegExpExecArray | null;\n let parsedValue = value;\n\n while (\n (paramMatch = this.interpolationMatcher.exec(parsedValue)) !== null\n ) {\n const [match, paramValue] = paramMatch;\n parsedValue = parsedValue.replace(match, () => {\n const match = paramValue.trim();\n\n const param = getValue(params, match);\n if (isDefined(param)) {\n return param;\n }\n\n return isDefined(translation[match])\n ? this.transpile({\n params,\n translation,\n key,\n value: translation[match],\n })\n : '';\n });\n }\n\n return parsedValue;\n } else if (params) {\n if (isObject(value)) {\n value = this.handleObject({\n value,\n params,\n translation,\n key,\n });\n } else if (Array.isArray(value)) {\n value = this.handleArray({ value, params, translation, key });\n }\n }\n\n return value;\n }\n\n /**\n *\n * @example\n *\n * const en = {\n * a: {\n * b: {\n * c: \"Hello {{ value }}\"\n * }\n * }\n * }\n *\n * const params = {\n * \"b.c\": { value: \"Transloco \"}\n * }\n *\n * service.selectTranslate('a', params);\n *\n * // the first param will be the result of `en.a`.\n * // the second param will be `params`.\n * parser.transpile(value, params, {});\n *\n *\n */\n protected handleObject({\n value,\n params = {},\n translation,\n key,\n }: TranspileParams<Record<any, any>>) {\n let result = value;\n\n Object.keys(params).forEach((p) => {\n // transpile the value => \"Hello Transloco\"\n const transpiled = this.transpile({\n // get the value of \"b.c\" inside \"a\" => \"Hello {{ value }}\"\n value: getValue(result, p),\n // get the params of \"b.c\" => { value: \"Transloco\" }\n params: getValue(params, p),\n translation,\n key,\n });\n\n // set \"b.c\" to `transpiled`\n result = setValue(result, p, transpiled);\n });\n\n return result;\n }\n\n protected handleArray({ value, ...rest }: TranspileParams<unknown[]>) {\n return value.map((v) =>\n this.transpile({\n value: v,\n ...rest,\n }),\n );\n }\n}\n\nfunction resolveMatcher(config: TranslocoConfig): RegExp {\n const [start, end] = config.interpolation;\n\n return new RegExp(`${start}([^${start}${end}]*?)${end}`, 'g');\n}\n\nexport interface TranslocoTranspilerFunction {\n transpile(...args: string[]): any;\n}\n\nexport function getFunctionArgs(argsString: string): string[] {\n const splitted = argsString ? argsString.split(',') : [];\n const args = [];\n for (let i = 0; i < splitted.length; i++) {\n let value = splitted[i].trim();\n while (value[value.length - 1] === '\\\\') {\n i++;\n value = value.replace('\\\\', ',') + splitted[i];\n }\n args.push(value);\n }\n\n return args;\n}\n\n@Injectable()\nexport class FunctionalTranspiler\n extends DefaultTranspiler\n implements TranslocoTranspiler\n{\n protected injector = inject(Injector);\n\n transpile({ value, ...rest }: TranspileParams) {\n let transpiled = value;\n if (isString(value)) {\n transpiled = value.replace(\n /\\[\\[\\s*(\\w+)\\((.*?)\\)\\s*]]/g,\n (match: string, functionName: string, args: string) => {\n try {\n const func: TranslocoTranspilerFunction =\n this.injector.get(functionName);\n\n return func.transpile(...getFunctionArgs(args));\n } catch (e: unknown) {\n let message = `There is an error in: '${value}'. \n Check that the you used the right syntax in your translation and that the implementation of ${functionName} is correct.`;\n if ((e as Error).message.includes('NullInjectorError')) {\n message = `You are using the '${functionName}' function in your translation but no provider was found!`;\n }\n throw new Error(message);\n }\n },\n );\n }\n\n return super.transpile({ value: transpiled, ...rest });\n }\n}\n","import { Injectable, InjectionToken } from '@angular/core';\n\nimport { TranslocoConfig } from './transloco.config';\nimport { HashMap } from './utils/type.utils';\n\nexport const TRANSLOCO_MISSING_HANDLER =\n /* @__PURE__ */ new InjectionToken<TranslocoMissingHandler>(\n ngDevMode ? 'TRANSLOCO_MISSING_HANDLER' : '',\n );\n\nexport interface TranslocoMissingHandlerData extends TranslocoConfig {\n activeLang: string;\n}\n\nexport interface TranslocoMissingHandler {\n handle(key: string, data: TranslocoMissingHandlerData, params?: HashMap): any;\n}\n\n@Injectable()\nexport class DefaultMissingHandler implements TranslocoMissingHandler {\n handle(key: string, config: TranslocoConfig) {\n if (config.missingHandler.logMissingKey && !config.prodMode) {\n const msg = `Missing translation for '${key}'`;\n console.warn(`%c ${msg}`, 'font-size: 12px; color: red');\n }\n\n return key;\n }\n}\n","import { Injectable, InjectionToken } from '@angular/core';\n\nimport { Translation } from './transloco.types';\n\nexport const TRANSLOCO_INTERCEPTOR =\n /* @__PURE__ */ new InjectionToken<TranslocoInterceptor>(\n ngDevMode ? 'TRANSLOCO_INTERCEPTOR' : '',\n );\n\nexport interface TranslocoInterceptor {\n preSaveTranslation(translation: Translation, lang: string): Translation;\n\n preSaveTranslationKey(key: string, value: string, lang: string): string;\n}\n\n@Injectable()\nexport class DefaultInterceptor implements TranslocoInterceptor {\n preSaveTranslation(translation: Translation): Translation {\n return translation;\n }\n\n preSaveTranslationKey(_: string, value: string): string {\n return value;\n }\n}\n","import { Inject, Injectable, InjectionToken } from '@angular/core';\n\nimport { TRANSLOCO_CONFIG, TranslocoConfig } from './transloco.config';\n\nexport const TRANSLOCO_FALLBACK_STRATEGY =\n /* @__PURE__ */ new InjectionToken<TranslocoFallbackStrategy>(\n ngDevMode ? 'TRANSLOCO_FALLBACK_STRATEGY' : '',\n );\n\nexport interface TranslocoFallbackStrategy {\n getNextLangs(failedLang: string): string[];\n}\n\n@Injectable()\nexport class DefaultFallbackStrategy implements TranslocoFallbackStrategy {\n constructor(@Inject(TRANSLOCO_CONFIG) private userConfig: TranslocoConfig) {}\n\n getNextLangs() {\n const fallbackLang = this.userConfig.fallbackLang;\n if (!fallbackLang) {\n throw new Error(\n 'When using the default fallback, a fallback language must be provided in the config!',\n );\n }\n\n return Array.isArray(fallbackLang) ? fallbackLang : [fallbackLang];\n }\n}\n","import { isFunction } from '@jsverse/utils';\n\nimport { TranslocoLoader, TranslocoLoaderData } from './transloco.loader';\nimport { InlineLoader } from './transloco.types';\n\ninterface Options {\n inlineLoader?: InlineLoader;\n path: string;\n mainLoader: TranslocoLoader;\n data?: TranslocoLoaderData;\n}\n\nexport function resolveLoader(options: Options) {\n const { path, inlineLoader, mainLoader, data } = options;\n\n if (inlineLoader) {\n const pathLoader = inlineLoader[path];\n if (isFunction(pathLoader) === false) {\n throw `You're using an inline loader but didn't provide a loader for ${path}`;\n }\n\n return inlineLoader[path]().then((res) =>\n res.default ? res.default : res,\n );\n }\n\n return mainLoader.getTranslation(path, data);\n}\n","import { from, map } from 'rxjs';\n\nimport { resolveLoader } from './resolve-loader';\nimport { TranslocoLoader, TranslocoLoaderData } from './transloco.loader';\nimport { InlineLoader } from './transloco.types';\n\ninterface Options {\n path: string;\n fallbackPath?: string;\n inlineLoader?: InlineLoader;\n mainLoader: TranslocoLoader;\n data?: TranslocoLoaderData;\n}\n\nexport function getFallbacksLoaders({\n mainLoader,\n path,\n data,\n fallbackPath,\n inlineLoader,\n}: Options) {\n const paths = fallbackPath ? [path, fallbackPath] : [path];\n\n return paths.map((path) => {\n const loader = resolveLoader({ path, mainLoader, inlineLoader, data });\n\n return from(loader).pipe(\n map((translation) => ({\n translation,\n lang: path,\n })),\n );\n });\n}\n","import { isObject } from '@jsverse/utils';\n\nimport { Translation } from '../transloco.types';\n\nexport function flatten(obj: Translation): Translation {\n const result: Record<string, unknown> = {};\n\n function recurse(curr: unknown, prop: string) {\n if (curr === null) {\n result[prop] = null;\n } else if (isObject(curr)) {\n for (const [key, value] of Object.entries(curr)) {\n recurse(value, prop ? `${prop}.${key}` : key);\n }\n } else {\n result[prop] = curr;\n }\n }\n\n recurse(obj, '');\n return result;\n}\n\nexport function unflatten(obj: Translation): Translation {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const keys = key.split('.');\n let current = result;\n\n keys.forEach((key, i) => {\n if (i === keys.length - 1) {\n current[key] = value;\n } else {\n current[key] ??= {};\n current = current[key] as Record<string, unknown>;\n }\n });\n }\n\n return result;\n}\n","import { isObject } from '@jsverse/utils';\n\nimport {\n InlineLoader,\n LoadedEvent,\n ProviderScope,\n TranslocoScope,\n} from '../transloco.types';\n\n/*\n * @example\n *\n * given: lazy-page/en => lazy-page\n *\n */\nexport function getScopeFromLang(lang: string): string {\n if (!lang) {\n return '';\n }\n\n const split = lang.split('/');\n split.pop();\n\n return split.join('/');\n}\n\n/*\n * @example\n *\n * given: lazy-page/en => en\n *\n */\nexport function getLangFromScope(lang: string): string {\n if (!lang) {\n return '';\n }\n\n return lang.split('/').pop()!;\n}\n\nfunction prependScope(inlineLoader: InlineLoader, scope: string) {\n return Object.keys(inlineLoader).reduce(\n (acc, lang) => {\n acc[`${scope}/${lang}`] = inlineLoader[lang];\n\n return acc;\n },\n {} as Record<string, InlineLoader[keyof InlineLoader]>,\n );\n}\n\nexport function isScopeObject(item: any): item is ProviderScope {\n return typeof item?.scope === 'string';\n}\n\nexport function hasInlineLoader(item: any): item is ProviderScope {\n return item?.loader && isObject(item.loader);\n}\n\nexport function resolveInlineLoader(\n providerScope: TranslocoScope | null,\n scope?: string,\n): InlineLoader | undefined {\n return hasInlineLoader(providerScope)\n ? prependScope(providerScope.loader!, scope!)\n : undefined;\n}\n\nexport function getEventPayload(lang: string): LoadedEvent['payload'] {\n return {\n scope: getScopeFromLang(lang) || null,\n langName: getLangFromScope(lang),\n };\n}\n","import {\n DestroyRef,\n inject,\n Inject,\n Injectable,\n Optional,\n type Signal,\n} from '@angular/core';\nimport {\n BehaviorSubject,\n catchError,\n combineLatest,\n EMPTY,\n forkJoin,\n from,\n map,\n Observable,\n of,\n retry,\n shareReplay,\n Subject,\n switchMap,\n tap,\n} from 'rxjs';\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\nimport { isEmpty, isNil, isString, size, toCamelCase } from '@jsverse/utils';\n\nimport {\n DefaultLoader,\n TRANSLOCO_LOADER,\n TranslocoLoader,\n} from './transloco.loader';\nimport {\n TRANSLOCO_TRANSPILER,\n TranslocoTranspiler,\n} from './transloco.transpiler';\nimport {\n AvailableLangs,\n InlineLoader,\n LangDefinition,\n LoadOptions,\n SetTranslationOptions,\n TranslateObjectParams,\n TranslateParams,\n Translation,\n TranslocoEvents,\n TranslocoScope,\n} from './transloco.types';\nimport { TRANSLOCO_CONFIG, TranslocoConfig } from './transloco.config';\nimport {\n TRANSLOCO_MISSING_HANDLER,\n TranslocoMissingHandler,\n TranslocoMissingHandlerData,\n} from './transloco-missing-handler';\nimport {\n TRANSLOCO_INTERCEPTOR,\n TranslocoInterceptor,\n} from './transloco.interceptor';\nimport {\n TRANSLOCO_FALLBACK_STRATEGY,\n TranslocoFallbackStrategy,\n} from './transloco-fallback-strategy';\nimport { getFallbacksLoaders } from './get-fallbacks-loaders';\nimport { resolveLoader } from './resolve-loader';\nimport { flatten, unflatten } from './utils/flat.utils';\nimport { HashMap } from './utils/type.utils';\nimport {\n getEventPayload,\n getLangFromScope,\n getScopeFromLang,\n isScopeObject,\n resolveInlineLoader,\n} from './utils/scope.utils';\n\nlet service: TranslocoService;\n\nexport function translate<T = string>(\n key: TranslateParams,\n params: HashMap = {},\n lang?: string,\n): T {\n return service.translate<T>(key, params, lang);\n}\n\nexport function translateObject<T>(\n key: TranslateParams,\n params: HashMap = {},\n lang?: string,\n): T | T[] {\n return service.translateObject<T>(key, params, lang);\n}\n\nexport class TranslationLoadError extends Error {\n constructor(\n readonly lang: string,\n readonly fallbackLangs: string[],\n readonly isScope: boolean,\n ) {\n let msg = `Unable to load translation and all the fallback languages`;\n if (isScope) {\n msg += `, did you misspell the scope name?`;\n }\n super(msg);\n this.name = 'TranslationLoadError';\n }\n}\n\n@Injectable({ providedIn: 'root' })\nexport class TranslocoService {\n langChanges$: Observable<string>;\n\n private translations = new Map<string, Translation>();\n private cache = new Map<string, Observable<Translation>>();\n private firstFallbackLang: string | undefined;\n private defaultLang = '';\n private availableLangs: AvailableLangs = [];\n private isResolvedMissingOnce = false;\n private lang: BehaviorSubject<string>;\n private failedLangs = new Set<string>();\n private events = new Subject<TranslocoEvents>();\n\n events$ = this.events.asObservable();\n readonly config: TranslocoConfig & {\n scopeMapping?: HashMap<string>;\n };\n\n /**\n * A signal that reflects the currently active language.\n *\n * @example\n *\n * const upper = computed(() => this.transloco.activeLang().toUpperCase());\n *\n * const lang = linkedSignal(() => this.transloco.activeLang());\n */\n readonly activeLang: Signal<string>;\n\n private destroyRef = inject(DestroyRef);\n private destroyed = false;\n\n constructor(\n @Optional() @Inject(TRANSLOCO_LOADER) private loader: TranslocoLoader,\n @Inject(TRANSLOCO_TRANSPILER) private parser: TranslocoTranspiler,\n @Inject(TRANSLOCO_MISSING_HANDLER)\n private missingHandler: TranslocoMissingHandler,\n @Inject(TRANSLOCO_INTERCEPTOR) private interceptor: TranslocoInterceptor,\n @Inject(TRANSLOCO_CONFIG) userConfig: TranslocoConfig,\n @Inject(TRANSLOCO_FALLBACK_STRATEGY)\n private fallbackStrategy: TranslocoFallbackStrategy,\n ) {\n if (!this.loader) {\n this.loader = new DefaultLoader(this.translations);\n }\n service = this;\n this.config = JSON.parse(JSON.stringify(userConfig));\n\n this.setAvailableLangs(this.config.availableLangs || []);\n this.setFallbackLangForMissingTranslation(this.config);\n this.setDefaultLang(this.config.defaultLang);\n this.lang = new BehaviorSubject<string>(this.getDefaultLang());\n // Don't use distinctUntilChanged as we need the ability to update\n // the value when using setTranslation or setTranslationKeys\n this.langChanges$ = this.lang.asObservable();\n\n this.activeLang = toSignal(this.lang, { requireSync: true });\n\n /**\n * When we have a failure, we want to define the next language that succeeded as the active\n */\n this.events$.subscribe((e) => {\n if (e.type === 'translationLoadSuccess' && e.wasFailure) {\n this.setActiveLang(e.payload.langName);\n }\n });\n\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n // Complete subjects to release observers if users forget to unsubscribe manually.\n // This is important in server-side rendering.\n this.lang.complete();\n this.events.complete();\n // As a root provider, this service is destroyed with when the application is destroyed.\n // Cached values retain `this`, causing circular references that block garbage collection,\n // leading to memory leaks during server-side rendering.\n this.cache.clear();\n });\n }\n\n getDefaultLang() {\n return this.defaultLang;\n }\n\n setDefaultLang(lang: string) {\n this.defaultLang = lang;\n }\n\n getActiveLang() {\n return this.lang.getValue();\n }\n\n setActiveLang(lang: string) {\n this.parser.onLangChanged?.(lang);\n this.lang.next(lang);\n this.events.next({\n type: 'langChanged',\n payload: getEventPayload(lang),\n });\n return this;\n }\n\n setAvailableLangs(langs: AvailableLangs) {\n this.availableLangs = langs;\n }\n\n /**\n * Gets the available languages.\n *\n * @returns\n * An array of the available languages. Can be either a `string[]` or a `{ id: string; label: string }[]`\n * depending on how the available languages are set in your module.\n */\n getAvailableLangs() {\n return this.availableLangs;\n }\n\n load(path: string, options: LoadOptions = {}): Observable<Translation> {\n // If the application has already been destroyed, return an empty observable.\n // We use EMPTY instead of NEVER to ensure the observable completes.\n // This is important for operators like switchMap, which rely on the inner observable completing\n // before they can subscribe to the next one. NEVER would hang the chain indefinitely.\n if (this.destroyed) {\n return EMPTY;\n }\n\n const cached = this.cache.get(path);\n if (cached) {\n return cached;\n }\n\n let loadTranslation: Observable<\n Translation | { translation: Translation; lang: string }[]\n >;\n const isScope = this._isLangScoped(path);\n let scope: string;\n if (isScope) {\n scope = getScopeFromLang(path);\n }\n\n const loadersOptions = {\n path,\n mainLoader: this.loader,\n inlineLoader: options.inlineLoader,\n data: isScope ? { scope: scope! } : undefined,\n };\n\n if (this.useFallbackTranslation(path)) {\n // if the path is scope the fallback should be `scope/fallbackLang`;\n const fallback = isScope\n ? `${scope!}/${this.firstFallbackLang}`\n : this.firstFallbackLang;\n\n const loaders = getFallbacksLoaders({\n ...loadersOptions,\n fallbackPath: fallback!,\n });\n loadTranslation = forkJoin(loaders);\n } else {\n const loader = resolveLoader(loadersOptions);\n loadTranslation = from(loader);\n }\n\n const load$ = loadTranslation.pipe(\n retry(this.config.failedRetries),\n tap((translation) => {\n if (Array.isArray(translation)) {\n translation.forEach((t) => {\n this.handleSuccess(t.lang, t.translation);\n // Save the fallback in cache so we'll not create a redundant request\n if (t.lang !== path) {\n this.cache.set(t.lang, of({}));\n }\n });\n return;\n }\n this.handleSuccess(path, translation);\n }),\n catchError((error) => {\n if (!this.config.prodMode) {\n console.error(`Error while trying to load \"${path}\"`, error);\n }\n\n return this.handleFailure(path, options);\n }),\n shareReplay(1),\n takeUntilDestroyed(this.destroyRef),\n );\n\n this.cache.set(path, load$);\n\n return load$;\n }\n\n /**\n * Gets the instant translated value of a key\n *\n * @example\n *\n * translate<string>('hello')\n * translate('hello', { value: 'value' })\n * translate<string[]>(['hello', 'key'])\n * translate('hello', { }, 'en')\n * translate('scope.someKey', { }, 'en')\n */\n translate<T = string>(\n key: TranslateParams,\n params: HashMap = {},\n lang = this.getActiveLang(),\n ): T {\n if (!key) return key as any;\n\n const { scope, resolveLang } = this.resolveLangAndScope(lang);\n\n if (Array.isArray(key)) {\n return key.map((k) =>\n this.translate(\n this.config.scopes.autoPrefixKeys && scope ? `${scope}.${k}` : k,\n params,\n resolveLang,\n ),\n ) as any;\n }\n\n key = this.config.scopes.autoPrefixKeys && scope ? `${scope}.${key}` : key;\n\n const translation = this.getTranslation(resolveLang);\n const value = translation[key];\n\n if (!value) {\n return this._handleMissingKey(key, value, params);\n }\n\n return this.parser.transpile({\n value,\n params,\n translation,\n key,\n });\n }\n\n /**\n * Gets the translated value of a key as observable\n *\n * @example\n *\n * selectTranslate<string>('hello').subscribe(value => ...)\n * selectTranslate<string>('hello', {}, 'es').subscribe(value => ...)\n * selectTranslate<string>('hello', {}, 'todos').subscribe(value => ...)\n * selectTranslate<string>('hello', {}, { scope: 'todos' }).subscribe(value => ...)\n *\n */\n selectTranslate<T = any>(\n key: TranslateParams,\n params?: HashMap,\n lang?: string | TranslocoScope | TranslocoScope[],\n _isObject = false,\n ): Observable<T> {\n let inlineLoader: InlineLoader | undefined;\n const load = (lang: string, options?: LoadOptions) =>\n this.load(lang, options).pipe(\n map(() =>\n _isObject\n ? this.translateObject(key, params, lang)\n : this.translate(key, params, lang),\n ),\n );\n if (isNil(lang)) {\n return this.langChanges$.pipe(switchMap((lang) => load(lang)));\n }\n\n lang = Array.isArray(lang) ? lang[lang.length - 1] : lang;\n if (isScopeObject(lang)) {\n // it's a scope object.\n const providerScope = lang;\n lang = providerScope.scope;\n inlineLoader = resolveInlineLoader(providerScope, providerScope.scope);\n }\n\n lang = lang as string;\n if (this.isLang(lang) || this.isScopeWithLang(lang)) {\n return load(lang);\n }\n // it's a scope\n const scope = lang;\n return this.langChanges$.pipe(\n switchMap((lang) => load(`${scope}/${lang}`, { inlineLoader })),\n );\n }\n\n /**\n * Whether the scope with lang\n *\n * @example\n *\n * todos/en => true\n * todos => false\n */\n private isScopeWithLang(lang: string) {\n return this.isLang(getLangFromScope(lang));\n }\n\n /**\n * Translate the given path that returns an object\n *\n * @example\n *\n * service.translateObject('path.to.object', {'subpath': { value: 'someValue'}}) => returns translated object\n *\n */\n translateObject<T = any>(key: string, params?: HashMap, lang?: string): T;\n translateObject<T = any>(key: string[], params?: HashMap, lang?: string): T[];\n translateObject<T = any>(\n key: TranslateParams,\n params?: HashMap,\n lang?: string,\n ): T | T[];\n translateObject<T = any>(\n key: HashMap | Map<string, HashMap>,\n params?: null,\n lang?: string,\n ): T[];\n translateObject<T = any>(\n key: TranslateObjectParams,\n params: HashMap | null = {},\n lang = this.getActiveLang(),\n ): T | T[] {\n if (isString(key) || Array.isArray(key)) {\n const { resolveLang, scope } = this.resolveLangAndScope(lang);\n if (Array.isArray(key)) {\n return key.map((k) =>\n this.translateObject(\n this.config.scopes.autoPrefixKeys && scope ? `${scope}.${k}` : k,\n params!,\n resolveLang,\n ),\n ) as any;\n }\n\n const translation = this.getTranslation(resolveLang);\n key =\n this.config.scopes.autoPrefixKeys && scope ? `${scope}.${key}` : key;\n\n const value = unflatten(this.getObjectByKey(translation, key));\n /* If an empty object was returned we want to try and translate the key as a string and not an object */\n return isEmpty(value)\n ? this.translate(key, params!, lang)\n : this.parser.transpile({ value, params: params!, translation, key });\n }\n\n const translations: T[] = [];\n for (const [_key, _params] of this.getEntries(key)) {\n translations.push(this.translateObject(_key, _params, lang));\n }\n\n return translations;\n }\n\n selectTranslateObject<T = any>(\n key: string,\n params?: HashMap,\n lang?: string,\n ): Observable<T>;\n selectTranslateObject<T = any>(\n key: string[],\n params?: HashMap,\n lang?: string,\n ): Observable<T[]>;\n selectTranslateObject<T = any>(\n key: TranslateParams,\n params?: HashMap,\n lang?: string,\n ): Observable<T> | Observable<T[]>;\n selectTranslateObject<T = any>(\n key: HashMap | Map<string, HashMap>,\n params?: null,\n lang?: string,\n ): Observable<T[]>;\n selectTranslateObject<T = any>(\n key: TranslateObjectParams,\n params?: HashMap | null,\n lang?: string,\n ): Observable<T> | Observable<T[]> {\n if (isString(key) || Array.isArray(key)) {\n return this.selectTranslate<T>(key, params!, lang, true);\n }\n\n const [[firstKey, firstParams], ...rest] = this.getEntries(key);\n\n /* In order to avoid subscribing multiple times to the load language event by calling selectTranslateObject for each pair,\n * we listen to when the first key has been translated (the language is loaded) and translate the rest synchronously */\n return this.selectTranslateObject<T>(firstKey, firstParams, lang).pipe(\n map((value) => {\n const translations = [value];\n for (const [_key, _params] of rest) {\n translations.push(this.translateObject<T>(_key, _params, lang));\n }\n\n return translations;\n }),\n );\n }\n\n /**\n * Gets an object of translations for a given language\n *\n * @example\n *\n * getTranslation()\n * getTranslation('en')\n * getTranslation('admin-page/en')\n */\n getTranslation(): Map<string, Translation>;\n getTranslation(langOrScope: string): Translation;\n getTranslation(langOrScope?: string): Map<string, Translation> | Translation {\n if (langOrScope) {\n if (this.isLang(langOrScope)) {\n return this.translations.get(langOrScope) || {};\n } else {\n // This is a scope, build the scope value from the translation object\n const { scope, resolveLang } = this.resolveLangAndScope(langOrScope);\n const translation = this.translations.get(resolveLang) || {};\n\n return this.getObjectByKey(translation, scope);\n }\n }\n\n return this.translations;\n }\n\n /**\n * Gets an object of translations for a given language\n *\n * @example\n *\n * selectTranslation().subscribe() - will return the current lang translation\n * selectTranslation('es').subscribe()\n * selectTranslation('admin-page').subscribe() - will return the current lang scope translation\n * selectTranslation('admin-page/es').subscribe()\n */\n selectTranslation(lang?: string): Observable<Translation> {\n let language$ = this.langChanges$;\n if (lang) {\n const scopeLangSpecified = getLangFromScope(lang) !== lang;\n if (this.isLang(lang) || scopeLangSpecified) {\n language$ = of(lang);\n } else {\n language$ = this.langChanges$.pipe(\n map((currentLang) => `${lang}/${currentLang}`),\n );\n }\n }\n\n return language$.pipe(\n switchMap((language) =>\n this.load(language).pipe(map(() => this.getTranslation(language))),\n ),\n );\n }\n\n /**\n * Sets or merge a given translation object to current lang\n *\n * @example\n *\n * setTranslation({ ... })\n * setTranslation({ ... }, 'en')\n * setTranslation({ ... }, 'es', { merge: false } )\n * setTranslation({ ... }, 'todos/en', { merge: false } )\n */\n setTranslation(\n translation: Translation,\n lang = this.getActiveLang(),\n options: SetTranslationOptions = {},\n ) {\n const defaults = { merge: true, emitChange: true };\n const mergedOptions = { ...defaults, ...options };\n const scope = getScopeFromLang(lang);\n\n /**\n * If this isn't a scope we use the whole translation as is\n * otherwise we need to flat the scope and use it\n */\n let flattenScopeOrTranslation = translation;\n\n // Merged the scoped language into the active language\n if (scope) {\n const key = this.getMappedScope(scope);\n flattenScopeOrTranslation = flatten({ [key]: translation });\n }\n\n const currentLang = scope ? getLangFromScope(lang) : lang;\n\n const mergedTranslation = {\n ...(mergedOptions.merge && this.getTranslation(currentLang)),\n ...flattenScopeOrTranslation,\n };\n\n const flattenTranslation = this.config.flatten!.aot\n ? mergedTranslation\n : flatten(mergedTranslation);\n const withHook = this.interceptor.preSaveTranslation(\n flattenTranslation,\n currentLang,\n );\n this.translations.set(currentLang, withHook);\n mergedOptions.emitChange && this.setActiveLang(this.getActiveLang());\n }\n\n /**\n * Sets translation key with given value\n *\n * @example\n *\n * setTranslationKey('key', 'value')\n * setTranslationKey('key.nested', 'value')\n * setTranslationKey('key.nested', 'value', 'en')\n * setTranslationKey('key.nested', 'value', 'en', { emitChange: false } )\n */\n setTranslationKey(\n key: string,\n value: string,\n options: Omit<SetTranslationOptions, 'merge'> = {},\n ) {\n const lang = options.lang || this.getActiveLang();\n const withHook = this.interceptor.preSaveTranslationKey(key, value, lang);\n const newValue = {\n [key]: withHook,\n };\n\n this.setTranslation(newValue, lang, { ...options, merge: true });\n }\n\n /**\n * Sets the fallback lang for the currently active language\n * @param fallbackLang\n */\n setFallbackLangForMissingTranslation({\n fallbackLang,\n }: Pick<TranslocoConfig, 'fallbackLang'>) {\n const lang = Array.isArray(fallbackLang) ? fallbackLang[0] : fallbackLang;\n if (fallbackLang && this.useFallbackTranslation(lang)) {\n this.firstFallbackLang = lang!;\n }\n }\n\n /**\n * @internal\n */\n _handleMissingKey(key: string, value: any, params?: HashMap) {\n if (this.config.missingHandler!.allowEmpty && value === '') {\n return '';\n }\n\n if (!this.isResolvedMissingOnce && this.useFallbackTranslation()) {\n // We need to set it to true to prevent a loop\n this.isResolvedMissingOnce = true;\n const fallbackValue = this.translate(\n key,\n params,\n this.firstFallbackLang!,\n );\n this.isResolvedMissingOnce = false;\n\n return fallbackValue;\n }\n\n return this.missingHandler.handle(\n key,\n this.getMissingHandlerData(),\n params,\n );\n }\n\n /**\n * @internal\n */\n _isLangScoped(lang: string) {\n return this.getAvailableLangsIds().indexOf(lang) === -1;\n }\n\n /**\n * Checks if a given string is one of the specified available languages.\n * @returns\n * True if the given string is an available language.\n * False if the given string is not an available language.\n */\n isLang(lang: string): boolean {\n return this.getAvailableLangsIds().indexOf(lang) !== -1;\n }\n\n /**\n * @internal\n *\n * We always want to make sure the global lang is loaded\n * before loading the scope since you can access both via the pipe/directive.\n */\n _loadDependencies(\n path: string,\n inlineLoader?: InlineLoader,\n ): Observable<Translation | Translation[]> {\n const mainLang = getLangFromScope(path);\n\n if (this._isLangScoped(path) && !this.isLoadedTranslation(mainLang)) {\n return combineLatest([\n this.load(mainLang),\n this.load(path, { inlineLoader }),\n ]);\n }\n return this.load(path, { inlineLoader });\n }\n\n /**\n * @internal\n */\n _completeScopeWithLang(langOrScope: string) {\n if (\n this._isLangScoped(langOrScope) &&\n !this.isLang(getLangFromScope(langOrScope))\n ) {\n return `${langOrScope}/${this.getActiveLang()}`;\n }\n return langOrScope;\n }\n\n /**\n * @internal\n */\n _setScopeAlias(scope: string, alias: string) {\n if (!this.config.scopeMapping) {\n this.config.scopeMapping = {};\n }\n this.config.scopeMapping[scope] = alias;\n }\n\n private isLoadedTranslation(lang: string) {\n return size(this.getTranslation(lang));\n }\n\n private getAvailableLangsIds(): string[] {\n const first = this.getAvailableLangs()[0];\n\n if (isString(first)) {\n return this.getAvailableLangs() as string[];\n }\n\n return (this.getAvailableLangs() as LangDefinition[]).map((l) => l.id);\n }\n\n private getMissingHandlerData(): TranslocoMissingHandlerData {\n return {\n ...this.config,\n activeLang: this.getActiveLang(),\n availableLangs: this.availableLangs,\n defaultLang: this.defaultLang,\n };\n }\n\n /**\n * Use a fallback translation set for missing keys of the primary language\n * This is unrelated to the fallback language (which changes the active language)\n */\n private useFallbackTranslation(lang?: string) {\n return (\n this.config.missingHandler!.useFallbackTranslation &&\n lang !== this.firstFallbackLang\n );\n }\n\n private handleSuccess(lang: string, translation: Translation) {\n this.setTranslation(translation, lang, { emitChange: false });\n this.events.next({\n wasFailure: !!this.failedLangs.size,\n type: 'translationLoadSuccess',\n payload: getEventPayload(lang),\n });\n this.failedLangs.forEach((l) => this.cache.delete(l));\n this.failedLangs.clear();\n }\n\n private handleFailure(lang: string, loadOptions: LoadOptions) {\n // When starting to load a first choice language, initialize\n // the failed counter and resolve the fallback langs.\n if (isNil(loadOptions.failedCounter)) {\n loadOptions.failedCounter = 0;\n\n if (!loadOptions.fallbackLangs) {\n loadOptions.fallbackLangs = this.fallbackStrategy.getNextLangs(lang);\n }\n }\n\n const splitted = lang.split('/');\n const fallbacks = loadOptions.fallbackLangs;\n const nextLang = fallbacks![loadOptions.failedCounter!];\n this.failedLangs.add(lang);\n\n // This handles the case where a loaded fallback language is requested again\n if (this.cache.has(nextLang)) {\n this.handleSuccess(nextLang, this.getTranslation(nextLang));\n return EMPTY;\n }\n\n const isFallbackLang = nextLang === splitted[splitted.length - 1];\n\n if (!nextLang || isFallbackLang) {\n throw new TranslationLoadError(\n lang,\n fallbacks ?? [],\n splitted.length > 1,\n );\n }\n\n let resolveLang = nextLang;\n // if it's scoped lang\n if (splitted.length > 1) {\n // We need to resolve it to:\n // todos/langNotExists => todos/nextLang\n splitted[splitted.length - 1] = nextLang;\n resolveLang = splitted.join('/');\n }\n\n loadOptions.failedCounter!++;\n this.events.next({\n type: 'translationLoadFailure',\n payload: getEventPayload(lang),\n });\n\n return this.load(resolveLang, loadOptions);\n }\n\n private getMappedScope(scope: string): string {\n const { scopeMapping = {}, scopes = { keepCasing: false } } = this.config;\n return (\n scopeMapping[scope] || (scopes.keepCasing ? scope : toCamelCase(scope))\n );\n }\n\n /**\n * If lang is scope we need to check the following cases:\n * todos/es => in this case we should take `es` as lang\n * todos => in this case we should set the active lang as lang\n */\n private resolveLangAndScope(lang: string) {\n let resolveLang = lang;\n let scope;\n\n if (this._isLangScoped(lang)) {\n // en for example\n const langFromScope = getLangFromScope(lang);\n // en is lang\n const hasLang = this.isLang(langFromScope);\n // take en\n resolveLang = hasLang ? langFromScope : this.getActiveLang();\n // find the scope\n scope = this.getMappedScope(hasLang ? getScopeFromLang(lang) : lang);\n }\n\n return { scope, resolveLang };\n }\n\n private getObjectByKey(translation: Translation, key?: string) {\n const result: Translation = {};\n const prefix = `${key}.`;\n\n for (const currentKey in translation) {\n if (currentKey.startsWith(prefix)) {\n result[currentKey.replace(prefix, '')] = translation[currentKey];\n }\n }\n\n return result;\n }\n\n private getEntries(key: HashMap | Map<string, HashMap>) {\n return key instanceof Map ? key.entries() : Object.entries(key);\n }\n}\n","import { Component, Input } from '@angular/core';\n\n@Component({\n template: `\n <div class=\"transloco-loader-template\" [innerHTML]=\"html\"></div>\n `,\n standalone: true,\n})\nexport class TranslocoLoaderComponent {\n @Input() html: string | undefined;\n}\n","import { TemplateRef, Type, ViewContainerRef } from '@angular/core';\nimport { isString } from '@jsverse/utils';\n\nimport { TranslocoLoaderComponent } from './loader-component.component';\n\nexport type Content = string | TemplateRef<unknown> | Type<unknown>;\n\nexport class TemplateHandler {\n constructor(\n private view: Content,\n private vcr: ViewContainerRef,\n ) {}\n\n attachView() {\n if (this.view instanceof TemplateRef) {\n this.vcr.createEmbeddedView(this.view);\n } else if (isString(this.view)) {\n const componentRef = this.vcr.createComponent(TranslocoLoaderComponent);\n componentRef.instance.html = this.view;\n componentRef.hostView.detectChanges();\n } else {\n this.vcr.createComponent(this.view);\n }\n }\n\n detachView() {\n this.vcr.clear();\n }\n}\n","import { InjectionToken } from '@angular/core';\n\nexport const TRANSLOCO_LANG = /* @__PURE__ */ new InjectionToken<string>(\n ngDevMode ? 'TRANSLOCO_LANG' : '',\n);\n","import { InjectionToken } from '@angular/core';\n\nimport { Content } from './template-handler';\n\nexport const TRANSLOCO_LOADING_TEMPLATE =\n /* @__PURE__ */ new InjectionToken<Content>(\n ngDevMode ? 'TRANSLOCO_LOADING_TEMPLATE' : '',\n );\n","import { InjectionToken } from '@angular/core';\n\nimport { TranslocoScope } from './transloco.types';\n\nexport const TRANSLOCO_SCOPE =\n /* @__PURE__ */ new InjectionToken<TranslocoScope>(\n ngDevMode ? 'TRANSLOCO_SCOPE' : '',\n );\n","import { isString } from '@jsverse/utils';\n\n/**\n * @example\n *\n * getPipeValue('todos|scoped', 'scoped') [true, 'todos']\n * getPipeValue('en|static', 'static') [true, 'en']\n * getPipeValue('en', 'static') [false, 'en']\n */\nexport function getPipeValue(\n str: string | undefined,\n value: string,\n char = '|',\n): [boolean, string] {\n if (isString(str)) {\n const splitted = str.split(char);\n const lastItem = splitted.pop()!;\n\n return lastItem === value ? [true, splitted.toString()] : [false, lastItem];\n }\n\n return [false, ''];\n}\n","import { Observable, OperatorFunction, take } from 'rxjs';\n\nimport { TranslocoService } from '../transloco.service';\n\nimport { getPipeValue } from './pipe.utils';\n\nexport function shouldListenToLangChanges(\n service: TranslocoService,\n lang?: string,\n) {\n const [hasStatic] = getPipeValue(lang, 'static');\n if (!hasStatic) {\n // If we didn't get 'lang|static' check if it's set in the global level\n return !!service.config.reRenderOnLangChange;\n }\n\n // We have 'lang|static' so don't listen to lang changes\n return false;\n}\n\nexport function listenOrNotOperator<T>(\n listenToLangChange?: boolean,\n): OperatorFunction<T, T> {\n return listenToLangChange ? (source: Observable<T>) => source : take<T>(1);\n}\n","import { getPipeValue } from './utils/pipe.utils';\nimport { getLangFromScope, getScopeFromLang } from './utils/scope.utils';\n\ninterface LangResolverParams {\n inline?: string;\n provider: string | null | undefined;\n active: string;\n}\n\nexport class LangResolver {\n initialized = false;\n\n // inline => provider => active\n resolve({ inline, provider, active }: LangResolverParams): string {\n let lang = active;\n /**\n * When the user changes the lang we need to update\n * the view. Otherwise, the lang will remain the inline/provided lang\n */\n if (this.initialized) {\n lang = active;\n\n return lang;\n }\n\n if (provider) {\n const [, extracted] = getPipeValue(provider, 'static');\n lang = extracted;\n }\n\n if (inline) {\n const [, extracted] = getPipeValue(inline, 'static');\n lang = extracted;\n }\n\n this.initialized = true;\n\n return lang;\n }\n\n /**\n *\n * Resolve the lang\n *\n * @example\n *\n * resolveLangBasedOnScope('todos/en') => en\n * resolveLangBasedOnScope('en') => en\n *\n */\n resolveLangBasedOnScope(lang: string) {\n const scope = getScopeFromLang(lang);\n\n return scope ? getLangFromScope(lang) : lang;\n }\n\n /**\n *\n * Resolve the lang path for loading\n *\n * @example\n *\n * resolveLangPath('todos', 'en') => todos/en\n * resolveLangPath('en') => en\n *\n */\n resolveLangPath(lang: string, scope?: string) {\n return scope ? `${scope}/${lang}` : lang;\n }\n}\n","import { OrArray, toCamelCase } from '@jsverse/utils';\n\nimport { ProviderScope, TranslocoScope } from './transloco.types';\nimport { TranslocoService } from './transloco.service';\nimport { isScopeObject } from './utils/scope.utils';\n\ntype ScopeResolverParams = {\n inline: string | undefined;\n provider: OrArray<TranslocoScope> | null;\n};\n\nexport class ScopeResolver {\n constructor(private service: TranslocoService) {}\n\n // inline => provider\n resolve(params: ScopeResolverParams): string | undefined {\n const { inline, provider } = params;\n if (inline) {\n return inline;\n }\n\n if (provider) {\n if (isScopeObject(provider)) {\n const {\n scope,\n alias = this.service.config.scopes.keepCasing\n ? scope\n : toCamelCase(scope),\n } = provider as ProviderScope;\n this.service._setScopeAlias(scope, alias);\n\n return scope;\n }\n\n return provider as string;\n }\n\n return undefined;\n }\n}\n","import {\n ChangeDetectorRef,\n DestroyRef,\n Directive,\n ElementRef,\n EmbeddedViewRef,\n inject,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Renderer2,\n SimpleChanges,\n TemplateRef,\n ViewContainerRef,\n} from '@angular/core';\nimport { forkJoin, Observable, switchMap } from 'rxjs';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { OrArray } from '@jsverse/utils';\n\nimport { Content, TemplateHandler } from './template-handler';\nimport { TRANSLOCO_LANG } from './transloco-lang';\nimport { TRANSLOCO_LOADING_TEMPLATE } from './transloco-loading-template';\nimport { TRANSLOCO_SCOPE } from './transloco-scope';\nimport { TranslocoService } from './transloco.service';\nimport { Translation, TranslocoScope } fr