UNPKG

@ngneat/transloco

Version:

The internationalization (i18n) library for Angular

1,569 lines 59.8 kB
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Optional, Inject, Component, Input, TemplateRef, inject, ChangeDetectorRef, ElementRef, ViewContainerRef, Renderer2, Directive, Pipe, NgModule, makeEnvironmentProviders, APP_INITIALIZER } from '@angular/core';
import { of, take, from, map, Subject, BehaviorSubject, forkJoin, retry, tap, catchError, shareReplay, switchMap, combineLatest, EMPTY } from 'rxjs';
import { unflatten as unflatten$1, flatten as flatten$1 } from 'flat';

class DefaultLoader {
    translations;
    constructor(translations) {
        this.translations = translations;
    }
    getTranslation(lang) {
        return of(this.translations.get(lang) || {});
    }
}
const TRANSLOCO_LOADER = new InjectionToken('TRANSLOCO_LOADER');

function getValue(obj, path) {
    if (!obj) {
        return obj;
    }
    /* For cases where the key is like: 'general.something.thing' */
    if (Object.prototype.hasOwnProperty.call(obj, path)) {
        return obj[path];
    }
    return path.split('.').reduce((p, c) => p?.[c], obj);
}
function setValue(obj, prop, val) {
    obj = { ...obj };
    const split = prop.split('.');
    const lastIndex = split.length - 1;
    split.reduce((acc, part, index) => {
        if (index === lastIndex) {
            acc[part] = val;
        }
        else {
            acc[part] = Array.isArray(acc[part])
                ? acc[part].slice()
                : { ...acc[part] };
        }
        return acc && acc[part];
    }, obj);
    return obj;
}
function size(collection) {
    if (!collection) {
        return 0;
    }
    if (Array.isArray(collection)) {
        return collection.length;
    }
    if (isObject(collection)) {
        return Object.keys(collection).length;
    }
    return collection ? collection.length : 0;
}
function isEmpty(collection) {
    return size(collection) === 0;
}
function isFunction(val) {
    return typeof val === 'function';
}
function isString(val) {
    return typeof val === 'string';
}
function isNumber(val) {
    return typeof val === 'number';
}
function isObject(item) {
    return !!item && typeof item === 'object' && !Array.isArray(item);
}
function coerceArray(value) {
    return Array.isArray(value) ? value : [value];
}
/*
 * @example
 *
 * given: path-to-happiness => pathToHappiness
 * given: path_to_happiness => pathToHappiness
 * given: path-to_happiness => pathToHappiness
 *
 */
function toCamelCase(str) {
    return str
        .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => index == 0 ? word.toLowerCase() : word.toUpperCase())
        .replace(/\s+|_|-|\//g, '');
}
function isBrowser() {
    return typeof window !== 'undefined';
}
function isNil(value) {
    return value === null || value === undefined;
}
function isDefined(value) {
    return isNil(value) === false;
}
function toNumber(value) {
    if (isNumber(value))
        return value;
    if (isString(value) && !isNaN(Number(value) - parseFloat(value))) {
        return Number(value);
    }
    return null;
}
function isScopeObject(item) {
    return item && typeof item.scope === 'string';
}
function isScopeArray(item) {
    return Array.isArray(item) && item.every(isScopeObject);
}
function hasInlineLoader(item) {
    return item && isObject(item.loader);
}
function unflatten(obj) {
    return unflatten$1(obj);
}
function flatten(obj) {
    return flatten$1(obj, { safe: true });
}

const TRANSLOCO_CONFIG = new InjectionToken('TRANSLOCO_CONFIG', {
    providedIn: 'root',
    factory: () => defaultConfig,
});
const defaultConfig = {
    defaultLang: 'en',
    reRenderOnLangChange: false,
    prodMode: false,
    failedRetries: 2,
    fallbackLang: [],
    availableLangs: [],
    missingHandler: {
        logMissingKey: true,
        useFallbackTranslation: false,
        allowEmpty: false,
    },
    flatten: {
        aot: false,
    },
    interpolation: ['{{', '}}'],
};
function translocoConfig(config = {}) {
    return {
        ...defaultConfig,
        ...config,
        missingHandler: {
            ...defaultConfig.missingHandler,
            ...config.missingHandler,
        },
        flatten: {
            ...defaultConfig.flatten,
            ...config.flatten,
        },
    };
}

const TRANSLOCO_TRANSPILER = new InjectionToken('TRANSLOCO_TRANSPILER');
class DefaultTranspiler {
    interpolationMatcher;
    constructor(config) {
        this.interpolationMatcher = resolveMatcher(config ?? defaultConfig);
    }
    transpile(value, params = {}, translation, key) {
        if (isString(value)) {
            return value.replace(this.interpolationMatcher, (_, match) => {
                match = match.trim();
                if (isDefined(params[match])) {
                    return params[match];
                }
                return isDefined(translation[match])
                    ? this.transpile(translation[match], params, translation, key)
                    : '';
            });
        }
        else if (params) {
            if (isObject(value)) {
                value = this.handleObject(value, params, translation, key);
            }
            else if (Array.isArray(value)) {
                value = this.handleArray(value, params, translation, key);
            }
        }
        return value;
    }
    /**
     *
     * @example
     *
     * const en = {
     *  a: {
     *    b: {
     *      c: "Hello {{ value }}"
     *    }
     *  }
     * }
     *
     * const params =  {
     *  "b.c": { value: "Transloco "}
     * }
     *
     * service.selectTranslate('a', params);
     *
     * // the first param will be the result of `en.a`.
     * // the second param will be `params`.
     * parser.transpile(value, params, {});
     *
     *
     */
    handleObject(value, params = {}, translation, key) {
        let result = value;
        Object.keys(params).forEach((p) => {
            // get the value of "b.c" inside "a" => "Hello {{ value }}"
            const v = getValue(result, p);
            // get the params of "b.c" => { value: "Transloco" }
            const getParams = getValue(params, p);
            // transpile the value => "Hello Transloco"
            const transpiled = this.transpile(v, getParams, translation, key);
            // set "b.c" to `transpiled`
            result = setValue(result, p, transpiled);
        });
        return result;
    }
    handleArray(value, params = {}, translation, key) {
        return value.map((v) => this.transpile(v, params, translation, key));
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultTranspiler, deps: [{ token: TRANSLOCO_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultTranspiler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultTranspiler, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return [{ type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [TRANSLOCO_CONFIG]
                }] }]; } });
function resolveMatcher(config) {
    const [start, end] = config.interpolation;
    return new RegExp(`${start}(.*?)${end}`, 'g');
}
function getFunctionArgs(argsString) {
    const splitted = argsString ? argsString.split(',') : [];
    const args = [];
    for (let i = 0; i < splitted.length; i++) {
        let value = splitted[i].trim();
        while (value[value.length - 1] === '\\') {
            i++;
            value = value.replace('\\', ',') + splitted[i];
        }
        args.push(value);
    }
    return args;
}
class FunctionalTranspiler extends DefaultTranspiler {
    injector;
    constructor(injector) {
        super();
        this.injector = injector;
    }
    transpile(value, params = {}, translation, key) {
        let transpiled = value;
        if (isString(value)) {
            transpiled = value.replace(/\[\[\s*(\w+)\((.*?)\)\s*]]/g, (match, functionName, args) => {
                try {
                    const func = this.injector.get(functionName);
                    return func.transpile(...getFunctionArgs(args));
                }
                catch (e) {
                    let message = `There is an error in: '${value}'. 
                          Check that the you used the right syntax in your translation and that the implementation of ${functionName} is correct.`;
                    if (e.message.includes('NullInjectorError')) {
                        message = `You are using the '${functionName}' function in your translation but no provider was found!`;
                    }
                    throw new Error(message);
                }
            });
        }
        return super.transpile(transpiled, params, translation, key);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: FunctionalTranspiler, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: FunctionalTranspiler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: FunctionalTranspiler, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return [{ type: i0.Injector }]; } });

const TRANSLOCO_MISSING_HANDLER = new InjectionToken('TRANSLOCO_MISSING_HANDLER');
class DefaultHandler {
    handle(key, config) {
        if (config.missingHandler.logMissingKey && !config.prodMode) {
            const msg = `Missing translation for '${key}'`;
            console.warn(`%c ${msg}`, 'font-size: 12px; color: red');
        }
        return key;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultHandler });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultHandler, decorators: [{
            type: Injectable
        }] });

const TRANSLOCO_INTERCEPTOR = new InjectionToken('TRANSLOCO_INTERCEPTOR');
class DefaultInterceptor {
    preSaveTranslation(translation) {
        return translation;
    }
    preSaveTranslationKey(_, value) {
        return value;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultInterceptor });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultInterceptor, decorators: [{
            type: Injectable
        }] });

const TRANSLOCO_FALLBACK_STRATEGY = new InjectionToken('TRANSLOCO_FALLBACK_STRATEGY');
class DefaultFallbackStrategy {
    userConfig;
    constructor(userConfig) {
        this.userConfig = userConfig;
    }
    getNextLangs() {
        const fallbackLang = this.userConfig.fallbackLang;
        if (!fallbackLang) {
            throw new Error('When using the default fallback, a fallback language must be provided in the config!');
        }
        return Array.isArray(fallbackLang) ? fallbackLang : [fallbackLang];
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultFallbackStrategy, deps: [{ token: TRANSLOCO_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultFallbackStrategy });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: DefaultFallbackStrategy, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return [{ type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_CONFIG]
                }] }]; } });

/*
 * @example
 *
 * given: lazy-page/en => lazy-page
 *
 */
function getScopeFromLang(lang) {
    if (!lang) {
        return '';
    }
    const split = lang.split('/');
    split.pop();
    return split.join('/');
}
/*
 * @example
 *
 * given: lazy-page/en => en
 *
 */
function getLangFromScope(lang) {
    if (!lang) {
        return '';
    }
    return lang.split('/').pop();
}
/**
 * @example
 *
 * getPipeValue('todos|scoped', 'scoped') [true, 'todos']
 * getPipeValue('en|static', 'static') [true, 'en']
 * getPipeValue('en', 'static') [false, 'en']
 */
function getPipeValue(str, value, char = '|') {
    if (isString(str)) {
        const splitted = str.split(char);
        const lastItem = splitted.pop();
        return lastItem === value ? [true, splitted.toString()] : [false, lastItem];
    }
    return [false, ''];
}
function shouldListenToLangChanges(service, lang) {
    const [hasStatic] = getPipeValue(lang, 'static');
    if (!hasStatic) {
        // If we didn't get 'lang|static' check if it's set in the global level
        return !!service.config.reRenderOnLangChange;
    }
    // We have 'lang|static' so don't listen to lang changes
    return false;
}
function listenOrNotOperator(listenToLangChange) {
    return listenToLangChange ? (source) => source : take(1);
}
function prependScope(inlineLoader, scope) {
    return Object.keys(inlineLoader).reduce((acc, lang) => {
        acc[`${scope}/${lang}`] = inlineLoader[lang];
        return acc;
    }, {});
}
function resolveInlineLoader(providerScope, scope) {
    return hasInlineLoader(providerScope)
        ? prependScope(providerScope.loader, scope)
        : undefined;
}
function getEventPayload(lang) {
    return {
        scope: getScopeFromLang(lang) || null,
        langName: getLangFromScope(lang),
    };
}

function resolveLoader(options) {
    const { path, inlineLoader, mainLoader, data } = options;
    if (inlineLoader) {
        const pathLoader = inlineLoader[path];
        if (isFunction(pathLoader) === false) {
            throw `You're using an inline loader but didn't provide a loader for ${path}`;
        }
        return inlineLoader[path]().then((res) => res.default ? res.default : res);
    }
    return mainLoader.getTranslation(path, data);
}

function getFallbacksLoaders({ mainLoader, path, data, fallbackPath, inlineLoader, }) {
    const paths = fallbackPath ? [path, fallbackPath] : [path];
    return paths.map((path) => {
        const loader = resolveLoader({ path, mainLoader, inlineLoader, data });
        return from(loader).pipe(map((translation) => ({
            translation,
            lang: path,
        })));
    });
}

let service;
function translate(key, params = {}, lang) {
    return service.translate(key, params, lang);
}
function translateObject(key, params = {}, lang) {
    return service.translateObject(key, params, lang);
}
class TranslocoService {
    loader;
    parser;
    missingHandler;
    interceptor;
    fallbackStrategy;
    langChanges$;
    subscription = null;
    translations = new Map();
    cache = new Map();
    firstFallbackLang;
    defaultLang = '';
    availableLangs = [];
    isResolvedMissingOnce = false;
    lang;
    failedLangs = new Set();
    events = new Subject();
    events$ = this.events.asObservable();
    config;
    constructor(loader, parser, missingHandler, interceptor, userConfig, fallbackStrategy) {
        this.loader = loader;
        this.parser = parser;
        this.missingHandler = missingHandler;
        this.interceptor = interceptor;
        this.fallbackStrategy = fallbackStrategy;
        if (!this.loader) {
            this.loader = new DefaultLoader(this.translations);
        }
        service = this;
        this.config = JSON.parse(JSON.stringify(userConfig));
        this.setAvailableLangs(this.config.availableLangs || []);
        this.setFallbackLangForMissingTranslation(this.config);
        this.setDefaultLang(this.config.defaultLang);
        this.lang = new BehaviorSubject(this.getDefaultLang());
        // Don't use distinctUntilChanged as we need the ability to update
        // the value when using setTranslation or setTranslationKeys
        this.langChanges$ = this.lang.asObservable();
        /**
         * When we have a failure, we want to define the next language that succeeded as the active
         */
        this.subscription = this.events$.subscribe((e) => {
            if (e.type === 'translationLoadSuccess' && e.wasFailure) {
                this.setActiveLang(e.payload.langName);
            }
        });
    }
    getDefaultLang() {
        return this.defaultLang;
    }
    setDefaultLang(lang) {
        this.defaultLang = lang;
    }
    getActiveLang() {
        return this.lang.getValue();
    }
    setActiveLang(lang) {
        this.parser.onLangChanged?.(lang);
        this.lang.next(lang);
        this.events.next({
            type: 'langChanged',
            payload: getEventPayload(lang),
        });
        return this;
    }
    setAvailableLangs(langs) {
        this.availableLangs = langs;
    }
    /**
     * Gets the available languages.
     *
     * @returns
     * An array of the available languages. Can be either a `string[]` or a `{ id: string; label: string }[]`
     * depending on how the available languages are set in your module.
     */
    getAvailableLangs() {
        return this.availableLangs;
    }
    load(path, options = {}) {
        const cached = this.cache.get(path);
        if (cached) {
            return cached;
        }
        let loadTranslation;
        const isScope = this._isLangScoped(path);
        let scope;
        if (isScope) {
            scope = getScopeFromLang(path);
        }
        const loadersOptions = {
            path,
            mainLoader: this.loader,
            inlineLoader: options.inlineLoader,
            data: isScope ? { scope: scope } : undefined,
        };
        if (this.useFallbackTranslation(path)) {
            // if the path is scope the fallback should be `scope/fallbackLang`;
            const fallback = isScope
                ? `${scope}/${this.firstFallbackLang}`
                : this.firstFallbackLang;
            const loaders = getFallbacksLoaders({
                ...loadersOptions,
                fallbackPath: fallback,
            });
            loadTranslation = forkJoin(loaders);
        }
        else {
            const loader = resolveLoader(loadersOptions);
            loadTranslation = from(loader);
        }
        const load$ = loadTranslation.pipe(retry(this.config.failedRetries), tap((translation) => {
            if (Array.isArray(translation)) {
                translation.forEach((t) => {
                    this.handleSuccess(t.lang, t.translation);
                    // Save the fallback in cache so we'll not create a redundant request
                    if (t.lang !== path) {
                        this.cache.set(t.lang, of({}));
                    }
                });
                return;
            }
            this.handleSuccess(path, translation);
        }), catchError((error) => {
            if (!this.config.prodMode) {
                console.error(`Error while trying to load "${path}"`, error);
            }
            return this.handleFailure(path, options);
        }), shareReplay(1));
        this.cache.set(path, load$);
        return load$;
    }
    /**
     * Gets the instant translated value of a key
     *
     * @example
     *
     * translate<string>('hello')
     * translate('hello', { value: 'value' })
     * translate<string[]>(['hello', 'key'])
     * translate('hello', { }, 'en')
     * translate('scope.someKey', { }, 'en')
     */
    translate(key, params = {}, lang = this.getActiveLang()) {
        if (!key)
            return key;
        const { scope, resolveLang } = this.resolveLangAndScope(lang);
        if (Array.isArray(key)) {
            return key.map((k) => this.translate(scope ? `${scope}.${k}` : k, params, resolveLang));
        }
        key = scope ? `${scope}.${key}` : key;
        const translation = this.getTranslation(resolveLang);
        const value = translation[key];
        if (!value) {
            return this._handleMissingKey(key, value, params);
        }
        return this.parser.transpile(value, params, translation, key);
    }
    /**
     * Gets the translated value of a key as observable
     *
     * @example
     *
     * selectTranslate<string>('hello').subscribe(value => ...)
     * selectTranslate<string>('hello', {}, 'es').subscribe(value => ...)
     * selectTranslate<string>('hello', {}, 'todos').subscribe(value => ...)
     * selectTranslate<string>('hello', {}, { scope: 'todos' }).subscribe(value => ...)
     *
     */
    selectTranslate(key, params, lang, _isObject = false) {
        let inlineLoader;
        const load = (lang, options) => this.load(lang, options).pipe(map(() => _isObject
            ? this.translateObject(key, params, lang)
            : this.translate(key, params, lang)));
        if (isNil(lang)) {
            return this.langChanges$.pipe(switchMap((lang) => load(lang)));
        }
        if (isScopeArray(lang) || isScopeObject(lang)) {
            // it's a scope object.
            const providerScope = Array.isArray(lang) ? lang[0] : lang;
            lang = providerScope.scope;
            inlineLoader = resolveInlineLoader(providerScope, providerScope.scope);
        }
        lang = lang;
        if (this.isLang(lang) || this.isScopeWithLang(lang)) {
            return load(lang);
        }
        // it's a scope
        const scope = lang;
        return this.langChanges$.pipe(switchMap((lang) => load(`${scope}/${lang}`, { inlineLoader })));
    }
    /**
     * Whether the scope with lang
     *
     * @example
     *
     * todos/en => true
     * todos => false
     */
    isScopeWithLang(lang) {
        return this.isLang(getLangFromScope(lang));
    }
    translateObject(key, params = {}, lang = this.getActiveLang()) {
        if (isString(key) || Array.isArray(key)) {
            const { resolveLang, scope } = this.resolveLangAndScope(lang);
            if (Array.isArray(key)) {
                return key.map((k) => this.translateObject(scope ? `${scope}.${k}` : k, params, resolveLang));
            }
            const translation = this.getTranslation(resolveLang);
            key = scope ? `${scope}.${key}` : key;
            const value = unflatten(this.getObjectByKey(translation, key));
            /* If an empty object was returned we want to try and translate the key as a string and not an object */
            return isEmpty(value)
                ? this.translate(key, params, lang)
                : this.parser.transpile(value, params, translation, key);
        }
        const translations = [];
        for (const [_key, _params] of this.getEntries(key)) {
            translations.push(this.translateObject(_key, _params, lang));
        }
        return translations;
    }
    selectTranslateObject(key, params, lang) {
        if (isString(key) || Array.isArray(key)) {
            return this.selectTranslate(key, params, lang, true);
        }
        const [[firstKey, firstParams], ...rest] = this.getEntries(key);
        /* In order to avoid subscribing multiple times to the load language event by calling selectTranslateObject for each pair,
         * we listen to when the first key has been translated (the language is loaded) and translate the rest synchronously */
        return this.selectTranslateObject(firstKey, firstParams, lang).pipe(map((value) => {
            const translations = [value];
            for (const [_key, _params] of rest) {
                translations.push(this.translateObject(_key, _params, lang));
            }
            return translations;
        }));
    }
    getTranslation(langOrScope) {
        if (langOrScope) {
            if (this.isLang(langOrScope)) {
                return this.translations.get(langOrScope) || {};
            }
            else {
                // This is a scope, build the scope value from the translation object
                const { scope, resolveLang } = this.resolveLangAndScope(langOrScope);
                const translation = this.translations.get(resolveLang) || {};
                return this.getObjectByKey(translation, scope);
            }
        }
        return this.translations;
    }
    /**
     * Gets an object of translations for a given language
     *
     * @example
     *
     * selectTranslation().subscribe() - will return the current lang translation
     * selectTranslation('es').subscribe()
     * selectTranslation('admin-page').subscribe() - will return the current lang scope translation
     * selectTranslation('admin-page/es').subscribe()
     */
    selectTranslation(lang) {
        let language$ = this.langChanges$;
        if (lang) {
            const scopeLangSpecified = getLangFromScope(lang) !== lang;
            if (this.isLang(lang) || scopeLangSpecified) {
                language$ = of(lang);
            }
            else {
                language$ = this.langChanges$.pipe(map((currentLang) => `${lang}/${currentLang}`));
            }
        }
        return language$.pipe(switchMap((language) => this.load(language).pipe(map(() => this.getTranslation(language)))));
    }
    /**
     * Sets or merge a given translation object to current lang
     *
     * @example
     *
     * setTranslation({ ... })
     * setTranslation({ ... }, 'en')
     * setTranslation({ ... }, 'es', { merge: false } )
     * setTranslation({ ... }, 'todos/en', { merge: false } )
     */
    setTranslation(translation, lang = this.getActiveLang(), options = {}) {
        const defaults = { merge: true, emitChange: true };
        const mergedOptions = { ...defaults, ...options };
        const scope = getScopeFromLang(lang);
        /**
         * If this isn't a scope we use the whole translation as is
         * otherwise we need to flat the scope and use it
         */
        let flattenScopeOrTranslation = translation;
        // Merged the scoped language into the active language
        if (scope) {
            const key = this.getMappedScope(scope);
            flattenScopeOrTranslation = flatten({ [key]: translation });
        }
        const currentLang = scope ? getLangFromScope(lang) : lang;
        const mergedTranslation = {
            ...(mergedOptions.merge && this.getTranslation(currentLang)),
            ...flattenScopeOrTranslation,
        };
        const flattenTranslation = this.config.flatten.aot
            ? mergedTranslation
            : flatten(mergedTranslation);
        const withHook = this.interceptor.preSaveTranslation(flattenTranslation, currentLang);
        this.translations.set(currentLang, withHook);
        mergedOptions.emitChange && this.setActiveLang(this.getActiveLang());
    }
    /**
     * Sets translation key with given value
     *
     * @example
     *
     * setTranslationKey('key', 'value')
     * setTranslationKey('key.nested', 'value')
     * setTranslationKey('key.nested', 'value', 'en')
     * setTranslationKey('key.nested', 'value', 'en', { emitChange: false } )
     */
    setTranslationKey(key, value, 
    // Todo: Add the lang to the options in v3
    lang = this.getActiveLang(), options = {}) {
        const withHook = this.interceptor.preSaveTranslationKey(key, value, lang);
        const newValue = {
            [key]: withHook,
        };
        this.setTranslation(newValue, lang, { ...options, merge: true });
    }
    /**
     * Sets the fallback lang for the currently active language
     * @param fallbackLang
     */
    setFallbackLangForMissingTranslation({ fallbackLang, }) {
        const lang = Array.isArray(fallbackLang) ? fallbackLang[0] : fallbackLang;
        if (fallbackLang && this.useFallbackTranslation(lang)) {
            this.firstFallbackLang = lang;
        }
    }
    /**
     * @internal
     */
    _handleMissingKey(key, value, params) {
        if (this.config.missingHandler.allowEmpty && value === '') {
            return '';
        }
        if (!this.isResolvedMissingOnce && this.useFallbackTranslation()) {
            // We need to set it to true to prevent a loop
            this.isResolvedMissingOnce = true;
            const fallbackValue = this.translate(key, params, this.firstFallbackLang);
            this.isResolvedMissingOnce = false;
            return fallbackValue;
        }
        return this.missingHandler.handle(key, this.getMissingHandlerData(), params);
    }
    /**
     * @internal
     */
    _isLangScoped(lang) {
        return this.getAvailableLangsIds().indexOf(lang) === -1;
    }
    /**
     * Checks if a given string is one of the specified available languages.
     * @returns
     * True if the given string is an available language.
     * False if the given string is not an available language.
     */
    isLang(lang) {
        return this.getAvailableLangsIds().indexOf(lang) !== -1;
    }
    /**
     * @internal
     *
     * We always want to make sure the global lang is loaded
     * before loading the scope since you can access both via the pipe/directive.
     */
    _loadDependencies(path, inlineLoader) {
        const mainLang = getLangFromScope(path);
        if (this._isLangScoped(path) && !this.isLoadedTranslation(mainLang)) {
            return combineLatest([
                this.load(mainLang),
                this.load(path, { inlineLoader }),
            ]);
        }
        return this.load(path, { inlineLoader });
    }
    /**
     * @internal
     */
    _completeScopeWithLang(langOrScope) {
        if (this._isLangScoped(langOrScope) &&
            !this.isLang(getLangFromScope(langOrScope))) {
            return `${langOrScope}/${this.getActiveLang()}`;
        }
        return langOrScope;
    }
    /**
     * @internal
     */
    _setScopeAlias(scope, alias) {
        if (!this.config.scopeMapping) {
            this.config.scopeMapping = {};
        }
        this.config.scopeMapping[scope] = alias;
    }
    ngOnDestroy() {
        if (this.subscription) {
            this.subscription.unsubscribe();
            // Caretaker note: it's important to clean up references to subscriptions since they save the `next`
            // callback within its `destination` property, preventing classes from being GC'd.
            this.subscription = null;
        }
        // Caretaker note: since this is the root provider, it'll be destroyed when the `NgModuleRef.destroy()` is run.
        // Cached values capture `this`, thus leading to a circular reference and preventing the `TranslocoService` from
        // being GC'd. This would lead to a memory leak when server-side rendering is used since the service is created
        // and destroyed per each HTTP request, but any service is not getting GC'd.
        this.cache.clear();
    }
    isLoadedTranslation(lang) {
        return size(this.getTranslation(lang));
    }
    getAvailableLangsIds() {
        const first = this.getAvailableLangs()[0];
        if (isString(first)) {
            return this.getAvailableLangs();
        }
        return this.getAvailableLangs().map((l) => l.id);
    }
    getMissingHandlerData() {
        return {
            ...this.config,
            activeLang: this.getActiveLang(),
            availableLangs: this.availableLangs,
            defaultLang: this.defaultLang,
        };
    }
    /**
     * Use a fallback translation set for missing keys of the primary language
     * This is unrelated to the fallback language (which changes the active language)
     */
    useFallbackTranslation(lang) {
        return (this.config.missingHandler.useFallbackTranslation &&
            lang !== this.firstFallbackLang);
    }
    handleSuccess(lang, translation) {
        this.setTranslation(translation, lang, { emitChange: false });
        this.events.next({
            wasFailure: !!this.failedLangs.size,
            type: 'translationLoadSuccess',
            payload: getEventPayload(lang),
        });
        this.failedLangs.forEach((l) => this.cache.delete(l));
        this.failedLangs.clear();
    }
    handleFailure(lang, loadOptions) {
        // When starting to load a first choice language, initialize
        // the failed counter and resolve the fallback langs.
        if (isNil(loadOptions.failedCounter)) {
            loadOptions.failedCounter = 0;
            if (!loadOptions.fallbackLangs) {
                loadOptions.fallbackLangs = this.fallbackStrategy.getNextLangs(lang);
            }
        }
        const splitted = lang.split('/');
        const fallbacks = loadOptions.fallbackLangs;
        const nextLang = fallbacks[loadOptions.failedCounter];
        this.failedLangs.add(lang);
        // This handles the case where a loaded fallback language is requested again
        if (this.cache.has(nextLang)) {
            this.handleSuccess(nextLang, this.getTranslation(nextLang));
            return EMPTY;
        }
        const isFallbackLang = nextLang === splitted[splitted.length - 1];
        if (!nextLang || isFallbackLang) {
            let msg = `Unable to load translation and all the fallback languages`;
            if (splitted.length > 1) {
                msg += `, did you misspelled the scope name?`;
            }
            throw new Error(msg);
        }
        let resolveLang = nextLang;
        // if it's scoped lang
        if (splitted.length > 1) {
            // We need to resolve it to:
            // todos/langNotExists => todos/nextLang
            splitted[splitted.length - 1] = nextLang;
            resolveLang = splitted.join('/');
        }
        loadOptions.failedCounter++;
        this.events.next({
            type: 'translationLoadFailure',
            payload: getEventPayload(lang),
        });
        return this.load(resolveLang, loadOptions);
    }
    getMappedScope(scope) {
        const { scopeMapping = {} } = this.config;
        return scopeMapping[scope] || toCamelCase(scope);
    }
    /**
     * If lang is scope we need to check the following cases:
     * todos/es => in this case we should take `es` as lang
     * todos => in this case we should set the active lang as lang
     */
    resolveLangAndScope(lang) {
        let resolveLang = lang;
        let scope;
        if (this._isLangScoped(lang)) {
            // en for example
            const langFromScope = getLangFromScope(lang);
            // en is lang
            const hasLang = this.isLang(langFromScope);
            // take en
            resolveLang = hasLang ? langFromScope : this.getActiveLang();
            // find the scope
            scope = this.getMappedScope(hasLang ? getScopeFromLang(lang) : lang);
        }
        return { scope, resolveLang };
    }
    getObjectByKey(translation, key) {
        const result = {};
        const prefix = `${key}.`;
        for (const currentKey in translation) {
            if (currentKey.startsWith(prefix)) {
                result[currentKey.replace(prefix, '')] = translation[currentKey];
            }
        }
        return result;
    }
    getEntries(key) {
        return key instanceof Map ? key.entries() : Object.entries(key);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoService, deps: [{ token: TRANSLOCO_LOADER, optional: true }, { token: TRANSLOCO_TRANSPILER }, { token: TRANSLOCO_MISSING_HANDLER }, { token: TRANSLOCO_INTERCEPTOR }, { token: TRANSLOCO_CONFIG }, { token: TRANSLOCO_FALLBACK_STRATEGY }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: function () { return [{ type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [TRANSLOCO_LOADER]
                }] }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_TRANSPILER]
                }] }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_MISSING_HANDLER]
                }] }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_INTERCEPTOR]
                }] }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_CONFIG]
                }] }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_FALLBACK_STRATEGY]
                }] }]; } });

class TranslocoLoaderComponent {
    html;
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.7", type: TranslocoLoaderComponent, isStandalone: true, selector: "ng-component", inputs: { html: "html" }, ngImport: i0, template: `
    <div class="transloco-loader-template" [innerHTML]="html"></div>
  `, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoLoaderComponent, decorators: [{
            type: Component,
            args: [{
                    template: `
    <div class="transloco-loader-template" [innerHTML]="html"></div>
  `,
                    standalone: true,
                }]
        }], propDecorators: { html: [{
                type: Input
            }] } });

class TemplateHandler {
    view;
    vcr;
    constructor(view, vcr) {
        this.view = view;
        this.vcr = vcr;
    }
    attachView() {
        if (this.view instanceof TemplateRef) {
            this.vcr.createEmbeddedView(this.view);
        }
        else if (isString(this.view)) {
            const componentRef = this.vcr.createComponent(TranslocoLoaderComponent);
            componentRef.instance.html = this.view;
            componentRef.hostView.detectChanges();
        }
        else {
            this.vcr.createComponent(this.view);
        }
    }
    detachView() {
        this.vcr.clear();
    }
}

const TRANSLOCO_LANG = new InjectionToken('TRANSLOCO_LANG');

const TRANSLOCO_LOADING_TEMPLATE = new InjectionToken('TRANSLOCO_LOADING_TEMPLATE');

const TRANSLOCO_SCOPE = new InjectionToken('TRANSLOCO_SCOPE');

class LangResolver {
    initialized = false;
    // inline => provider => active
    resolve({ inline, provider, active }) {
        let lang = active;
        /**
         * When the user changes the lang we need to update
         * the view. Otherwise, the lang will remain the inline/provided lang
         */
        if (this.initialized) {
            lang = active;
            return lang;
        }
        if (provider) {
            const [, extracted] = getPipeValue(provider, 'static');
            lang = extracted;
        }
        if (inline) {
            const [, extracted] = getPipeValue(inline, 'static');
            lang = extracted;
        }
        this.initialized = true;
        return lang;
    }
    /**
     *
     * Resolve the lang
     *
     * @example
     *
     * resolveLangBasedOnScope('todos/en') => en
     * resolveLangBasedOnScope('en') => en
     *
     */
    resolveLangBasedOnScope(lang) {
        const scope = getScopeFromLang(lang);
        return scope ? getLangFromScope(lang) : lang;
    }
    /**
     *
     * Resolve the lang path for loading
     *
     * @example
     *
     * resolveLangPath('todos', 'en') => todos/en
     * resolveLangPath('en') => en
     *
     */
    resolveLangPath(lang, scope) {
        return scope ? `${scope}/${lang}` : lang;
    }
}

class ScopeResolver {
    service;
    constructor(service) {
        this.service = service;
    }
    // inline => provider
    resolve(params) {
        const { inline, provider } = params;
        if (inline) {
            return inline;
        }
        if (provider) {
            if (isScopeObject(provider)) {
                const { scope, alias = toCamelCase(scope) } = provider;
                this.service._setScopeAlias(scope, alias);
                return scope;
            }
            return provider;
        }
        return undefined;
    }
}

class TranslocoDirective {
    service = inject(TranslocoService);
    tpl = inject(TemplateRef, {
        optional: true,
    });
    providerLang = inject(TRANSLOCO_LANG, { optional: true });
    providerScope = inject(TRANSLOCO_SCOPE, { optional: true });
    providedLoadingTpl = inject(TRANSLOCO_LOADING_TEMPLATE, {
        optional: true,
    });
    cdr = inject(ChangeDetectorRef);
    host = inject(ElementRef);
    vcr = inject(ViewContainerRef);
    renderer = inject(Renderer2);
    subscription = null;
    view;
    translationMemo = {};
    key;
    params = {};
    inlineScope;
    inlineRead;
    inlineLang;
    inlineTpl;
    currentLang;
    loaderTplHandler;
    // Whether we already rendered the view once
    initialized = false;
    path;
    langResolver = new LangResolver();
    scopeResolver = new ScopeResolver(this.service);
    strategy = this.tpl === null ? 'attribute' : 'structural';
    static ngTemplateContextGuard(dir, ctx) {
        return true;
    }
    ngOnInit() {
        const listenToLangChange = shouldListenToLangChanges(this.service, this.providerLang || this.inlineLang);
        this.subscription = this.service.langChanges$
            .pipe(switchMap((activeLang) => {
            const lang = this.langResolver.resolve({
                inline: this.inlineLang,
                provider: this.providerLang,
                active: activeLang,
            });
            return Array.isArray(this.providerScope)
                ? forkJoin(this.providerScope.map((providerScope) => this.resolveScope(lang, providerScope)))
                : this.resolveScope(lang, this.providerScope);
        }), listenOrNotOperator(listenToLangChange))
            .subscribe(() => {
            this.currentLang = this.langResolver.resolveLangBasedOnScope(this.path);
            this.strategy === 'attribute'
                ? this.attributeStrategy()
                : this.structuralStrategy(this.currentLang, this.inlineRead);
            this.cdr.markForCheck();
            this.initialized = true;
        });
        if (!this.initialized) {
            const loadingContent = this.resolveLoadingContent();
            if (loadingContent) {
                this.loaderTplHandler = new TemplateHandler(loadingContent, this.vcr);
                this.loaderTplHandler.attachView();
            }
        }
    }
    ngOnChanges(changes) {
        // We need to support dynamic keys/params, so if this is not the first change CD cycle
        // we need to run the function again in order to update the value
        if (this.strategy === 'attribute') {
            const notInit = Object.keys(changes).some((v) => !changes[v].firstChange);
            notInit && this.attributeStrategy();
        }
    }
    attributeStrategy() {
        this.detachLoader();
        this.renderer.setProperty(this.host.nativeElement, 'innerText', this.service.translate(this.key, this.params, this.currentLang));
    }
    structuralStrategy(lang, read) {
        this.translationMemo = {};
        if (this.view) {
            // when the lang changes we need to change the reference so Angular will update the view
            this.view.context['$implicit'] = this.getTranslateFn(lang, read);
            this.view.context['currentLang'] = this.currentLang;
        }
        else {
            this.detachLoader();
            this.view = this.vcr.createEmbeddedView(this.tpl, {
                $implicit: this.getTranslateFn(lang, read),
                currentLang: this.currentLang,
            });
        }
    }
    getTranslateFn(lang, read) {
        return (key, params) => {
            const withRead = read ? `${read}.${key}` : key;
            const withParams = params
                ? `${withRead}${JSON.stringify(params)}`
                : withRead;
            if (Object.prototype.hasOwnProperty.call(this.translationMemo, withParams)) {
                return this.translationMemo[withParams].value;
            }
            this.translationMemo[withParams] = {
                params,
                value: this.service.translate(withRead, params, lang),
            };
            return this.translationMemo[withParams].value;
        };
    }
    resolveLoadingContent() {
        return this.inlineTpl || this.providedLoadingTpl;
    }
    ngOnDestroy() {
        if (this.subscription) {
            this.subscription.unsubscribe();
            // Caretaker note: it's important to clean up references to subscriptions since they save the `next`
            // callback within its `destination` property, preventing classes from being GC'd.
            this.subscription = null;
        }
    }
    detachLoader() {
        this.loaderTplHandler?.detachView();
    }
    resolveScope(lang, providerScope) {
        const resolvedScope = this.scopeResolver.resolve({
            inline: this.inlineScope,
            provider: providerScope,
        });
        this.path = this.langResolver.resolveLangPath(lang, resolvedScope);
        const inlineLoader = resolveInlineLoader(providerScope, resolvedScope);
        return this.service._loadDependencies(this.path, inlineLoader);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.7", type: TranslocoDirective, isStandalone: true, selector: "[transloco]", inputs: { key: ["transloco", "key"], params: ["translocoParams", "params"], inlineScope: ["translocoScope", "inlineScope"], inlineRead: ["translocoRead", "inlineRead"], inlineLang: ["translocoLang", "inlineLang"], inlineTpl: ["translocoLoadingTpl", "inlineTpl"] }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[transloco]',
                    standalone: true,
                }]
        }], propDecorators: { key: [{
                type: Input,
                args: ['transloco']
            }], params: [{
                type: Input,
                args: ['translocoParams']
            }], inlineScope: [{
                type: Input,
                args: ['translocoScope']
            }], inlineRead: [{
                type: Input,
                args: ['translocoRead']
            }], inlineLang: [{
                type: Input,
                args: ['translocoLang']
            }], inlineTpl: [{
                type: Input,
                args: ['translocoLoadingTpl']
            }] } });

class TranslocoPipe {
    service;
    providerScope;
    providerLang;
    cdr;
    subscription = null;
    lastValue = '';
    lastKey;
    path;
    langResolver = new LangResolver();
    scopeResolver;
    constructor(service, providerScope, providerLang, cdr) {
        this.service = service;
        this.providerScope = providerScope;
        this.providerLang = providerLang;
        this.cdr = cdr;
        this.scopeResolver = new ScopeResolver(this.service);
    }
    // null is for handling strict mode + async pipe types https://github.com/ngneat/transloco/issues/311
    // null is for handling strict mode + optional chaining types https://github.com/ngneat/transloco/issues/488
    transform(key, params, inlineLang) {
        if (!key) {
            return key;
        }
        const keyName = params ? `${key}${JSON.stringify(params)}` : key;
        if (keyName === this.lastKey) {
            return this.lastValue;
        }
        this.lastKey = keyName;
        this.subscription?.unsubscribe();
        const listenToLangChange = shouldListenToLangChanges(this.service, this.providerLang || inlineLang);
        this.subscription = this.service.langChanges$
            .pipe(switchMap((activeLang) => {
            const lang = this.langResolver.resolve({
                inline: inlineLang,
                provider: this.providerLang,
                active: activeLang,
            });
            return Array.isArray(this.providerScope)
                ? forkJoin(this.providerScope.map((providerScope) => this.resolveScope(lang, providerScope)))
                : this.resolveScope(lang, this.providerScope);
        }), listenOrNotOperator(listenToLangChange))
            .subscribe(() => this.updateValue(key, params));
        return this.lastValue;
    }
    ngOnDestroy() {
        this.subscription?.unsubscribe();
        // Caretaker note: it's important to clean up references to subscriptions since they save the `next`
        // callback within its `destination` property, preventing classes from being GC'd.
        this.subscription = null;
    }
    updateValue(key, params) {
        const lang = this.langResolver.resolveLangBasedOnScope(this.path);
        this.lastValue = this.service.translate(key, params, lang);
        this.cdr.markForCheck();
    }
    resolveScope(lang, providerScope) {
        const resolvedScope = this.scopeResolver.resolve({
            inline: undefined,
            provider: providerScope,
        });
        this.path = this.langResolver.resolveLangPath(lang, resolvedScope);
        const inlineLoader = resolveInlineLoader(providerScope, resolvedScope);
        return this.service._loadDependencies(this.path, inlineLoader);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoPipe, deps: [{ token: TranslocoService }, { token: TRANSLOCO_SCOPE, optional: true }, { token: TRANSLOCO_LANG, optional: true }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Pipe });
    static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.1.7", ngImport: i0, type: TranslocoPipe, isStandalone: true, name: "transloco", pure: false });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoPipe, decorators: [{
            type: Pipe,
            args: [{
                    name: 'transloco',
                    pure: false,
                    standalone: true,
                }]
        }], ctorParameters: function () { return [{ type: TranslocoService }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [TRANSLOCO_SCOPE]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [TRANSLOCO_LANG]
                }] }, { type: i0.ChangeDetectorRef }]; } });

const decl = [TranslocoDirective, TranslocoPipe];
class TranslocoModule {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
    static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.7", ngImport: i0, type: TranslocoModule, imports: [TranslocoDirective, TranslocoPipe], exports: [TranslocoDirective, TranslocoPipe] });
    static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: decl,
                    exports: decl,
                }]
        }] });

function provideTransloco(options) {
    const providers = [
        provideTranslocoTranspiler(DefaultTranspiler),
        provideTranslocoMissingHandler(DefaultHandler),
        provideTranslocoInterceptor(DefaultInterceptor),
        provideTranslocoFallbackStrategy(DefaultFallbackStrategy),
    ];
    if (options.config) {
        providers.push(provideTranslocoConfig(options.config));
    }
    if (options.loader) {
        providers.push(provideTranslocoLoader(options.loader));
    }
    return providers;
}
function provideTranslocoConfig(config) {
    return makeEnvironmentProviders([
        {
            provide: TRANSLOCO_CONFIG,
            useValue: translocoConfig(config),
        },
    ]);
}
function provideTranslocoLoader(loader) {
    return makeEnvironmentProviders([
        { provide: TRANSLOCO_LOADER, useClass: loader },
    ]);
}
function provideTranslocoScope(scope) {
    return {
        provide: TRANSLOCO_SCOPE,
        useValue: scope,
        multi: true,
    };
}
function provideTranslocoLoadingTpl(content) {
    return {
        provide: TRANSLOCO_LOADING_TEMPLATE,
        useValue: content,
    };
}
function provideTranslocoTranspiler(transpiler) {
    return makeEnvironmentProviders([
        {
            provide: TRANSLOCO_TRANSPILER,
            useClass: transpiler,
            deps: [TRANSLOCO_CONFIG],
        },
    ]);
}
function provideTranslocoFallbackStrategy(strategy) {
    return makeEnvironmentProviders([
        {
            provide: TRANSLOCO_FALLBACK_STRATEGY,
            useClass: strategy,
            deps: [TRANSLOCO_CONFIG],
        },
    ]);
}
function provideTranslocoMissingHandler(handler) {
    return makeEnvironmentProviders([
        {
            provide: TRANSLOCO_MISSING_HANDLER,
            useClass: handler,
        },
    ]);
}
function provideTranslocoInterceptor(interceptor) {
    return makeEnvironmentProviders([
        {
            provide: TRANSLOCO_INTERCEPTOR,
            useClass: interceptor,
        },
    ]);
}
function provideTranslocoLang(lang) {
    return {
        provide: TRANSLOCO_LANG,
        useValue: lang,
    };
}

const TRANSLOCO_TEST_LANGS = new InjectionToken('TRANSLOCO_TEST_LANGS - Available testing languages');
const TRANSLOCO_TEST_OPTIONS = new InjectionToken('TRANSLOCO_TEST_OPTIONS - Testing options');
class TestingLoader {
    langs;
    constructor(langs) {
        this.langs = langs;
    }
    getTranslation(lang) {
        return of(this.langs[lang]);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TestingLoader, deps: [{ token: TRANSLOCO_TEST_LANGS }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TestingLoader });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TestingLoader, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return [{ type: undefined, decorators: [{
                    type: Inject,
                    args: [TRANSLOCO_TEST_LANGS]
                }] }]; } });
function initTranslocoService(service, langs = {}, options) {
    const preloadAllLangs = () => options.preloadLangs
        ? Promise.all(Object.keys(langs).map((lang) => service.load(lang).toPromise()))
        : Promise.resolve();
    return preloadAllLangs;
}
class TranslocoTestingModule {
    static forRoot(options) {
        return {
            ngModule: TranslocoTestingModule,
            providers: [
                provideTransloco({
                    loader: TestingLoader,
                    config: {
                        prodMode: true,
                        missingHandler: { logMissingKey: false },
                        ...options.translocoConfig,
                    },
                }),
                {
                    provide: TRANSLOCO_TEST_LANGS,
                    useValue: options.langs,
                },
                {
                    provide: TRANSLOCO_TEST_OPTIONS,
                    useValue: options,
                },
                {
                    provide: APP_INITIALIZER,
                    useFactory: initTranslocoService,
                    deps: [
                        TranslocoService,
                        TRANSLOCO_TEST_LANGS,
                        TRANSLOCO_TEST_OPTIONS,
                    ],
                    multi: true,
                },
            ],
        };
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoTestingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
    static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.7", ngImport: i0, type: TranslocoTestingModule, exports: [TranslocoModule] });
    static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoTestingModule, imports: [TranslocoModule] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.7", ngImport: i0, type: TranslocoTestingModule, decorators: [{
            type: NgModule,
            args: [{
                    exports: [TranslocoModule],
                }]
        }] });

/**
 * Returns the language code name from the browser, e.g. "en"
 */
function getBrowserLang() {
    let browserLang = getBrowserCultureLang();
    if (!browserLang || !isBrowser()) {
        return undefined;
    }
    if (browserLang.indexOf('-') !== -1) {
        browserLang = browserLang.split('-')[0];
    }
    if (browserLang.indexOf('_') !== -1) {
        browserLang = browserLang.split('_')[0];
    }
    return browserLang;
}
/**
 * Returns the culture language code name from the browser, e.g. "en-US"
 */
function getBrowserCultureLang() {
    if (!isBrowser()) {
        return '';
    }
    const navigator = window.navigator;
    return navigator.languages?.[0] ?? navigator.language;
}

/**
 * Generated bundle index. Do not edit.
 */

export { DefaultFallbackStrategy, DefaultTranspiler, FunctionalTranspiler, TRANSLOCO_CONFIG, TRANSLOCO_FALLBACK_STRATEGY, TRANSLOCO_INTERCEPTOR, TRANSLOCO_LANG, TRANSLOCO_LOADER, TRANSLOCO_LOADING_TEMPLATE, TRANSLOCO_MISSING_HANDLER, TRANSLOCO_SCOPE, TRANSLOCO_TRANSPILER, TestingLoader, TranslocoDirective, TranslocoModule, TranslocoPipe, TranslocoService, TranslocoTestingModule, coerceArray, defaultConfig, flatten, getBrowserCultureLang, getBrowserLang, getFunctionArgs, getLangFromScope, getPipeValue, getScopeFromLang, getValue, hasInlineLoader, isBrowser, isDefined, isEmpty, isFunction, isNil, isNumber, isObject, isScopeArray, isScopeObject, isString, provideTransloco, provideTranslocoConfig, provideTranslocoFallbackStrategy, provideTranslocoInterceptor, provideTranslocoLang, provideTranslocoLoader, provideTranslocoLoadingTpl, provideTranslocoMissingHandler, provideTranslocoScope, provideTranslocoTranspiler, setValue, size, toCamelCase, toNumber, translate, translateObject, translocoConfig, unflatten };
//# sourceMappingURL=ngneat-transloco.mjs.map