UNPKG

ng-dynamic-mf

Version:

[![npm](https://img.shields.io/npm/v/ng-dynamic-mf?color=%2300d26a&style=for-the-badge)](https://www.npmjs.com/package/ng-dynamic-mf) [![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/LoaderB0T_ng-dynamic-mf?server=https%3A%2F%2Fsonarcloud

249 lines (244 loc) 11 kB
import * as i0 from '@angular/core'; import { inject, Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { resourceMapper } from 'ng-dynamic-mf'; import { BehaviorSubject, filter, combineLatest, startWith, map, distinctUntilChanged, firstValueFrom } from 'rxjs'; function isAssetResolver(resolver) { return resolver.moduleName !== undefined; } /** * This service is used to dynamically load translations from different modules. * It uses @ngx-translate/core internally, so make sure you have it installed and * the TranslateModule is imported correctly. */ class DynamicTranslationService { constructor() { this._translateService = inject(TranslateService); this._translationsInvalidated = new BehaviorSubject(false); this._translationsUpdated = new BehaviorSubject(undefined); this._translationStore = {}; this._locale = this._translateService.currentLang || this._translateService.defaultLang || 'en'; this._debugMode = false; this._translateService.onLangChange.subscribe(e => { this._locale = e.lang; this.invalidateTranslations(); }); this._translationsInvalidated.pipe(filter(x => x)).subscribe(() => { this.loadTranslations(); }); } /** * Sets the debug mode. In debug mode, the translation values are replaced with their keys. * @param debugMode Whether to enable debug mode */ setDebugMode(debugMode) { this._debugMode = debugMode; this.invalidateTranslations(); } /** * Registers a resolver for translations for a specific locale. * @param translationSetIdentifier The identifier of the translations (e.g. the name of the module) * @param locale The locale of the translations * @param translationResolver The resolver for the translations */ registerTranslationSetForLocale(translationSetIdentifier, locale, translationResolver) { this.registerSingleTranslation(translationSetIdentifier, locale, translationResolver); this.invalidateTranslations(); } /** * Registers resolvers for translations for multiple locales. * @param translationSetIdentifier The identifier of the translations (e.g. the name of the module) * @param translationResolvers The resolvers for the translations */ registerTranslationSet(translationSetIdentifier, translationResolvers) { this.doRegisterTranslationSet(translationSetIdentifier, translationResolvers); this.invalidateTranslations(); } /** * Registers multiple translation resolvers for multiple locales. * @param set The set of resolvers for the translations */ registerMultipleTranslationSets(set) { Object.keys(set).forEach(translationSetKey => { this.doRegisterTranslationSet(translationSetKey, set[translationSetKey]); }); this.invalidateTranslations(); } doRegisterTranslationSet(translationSetIdentifier, translationResolvers) { Object.keys(translationResolvers).forEach(locale => { this.registerSingleTranslation(translationSetIdentifier, locale, translationResolvers[locale]); }); } registerSingleTranslation(translationSetIdentifier, locale, translationResolver) { const translationResolvers = this._translationStore[locale] || []; const existing = translationResolvers.find(r => r.key === translationSetIdentifier); if (existing) { existing.resolver = translationResolver; existing.loadedTranslations = null; } else { translationResolvers.push({ resolver: translationResolver, key: translationSetIdentifier, loadedTranslations: null, }); this._translationStore[locale] ??= translationResolvers; } } /** * Removes a translation set for a specific locale. * @param translationSetIdentifier The identifier of the translations (e.g. the name of the module) * @param locale The locale of the translations. If not specified, the translations will be removed for all locales. */ removeTranslation(translationSetIdentifier, locale) { if (!locale) { Object.keys(this._translationStore).forEach(l => { this.doRemoveTranslation(translationSetIdentifier, l); }); } else { this.doRemoveTranslation(translationSetIdentifier, locale); } this.invalidateTranslations(); } /** * Asynchronously checks if a translation set is loaded for the current locale. * @param translationSetIdentifier The identifier of the translations (e.g. the name of the module) * @returns Observable that emits true if the translation set is loaded, false otherwise */ isTranslationSetLoaded(translationSetIdentifier) { return combineLatest([ this._translateService.onLangChange.pipe(startWith(undefined)), this._translationsUpdated, ]).pipe(map(() => this.isTranslationSetLoadedSync(translationSetIdentifier)), distinctUntilChanged()); } /** * Resolves if a translation set is loaded for the current locale. * @param translationSetIdentifier The identifier of the translations (e.g. the name of the module) * @returns Promise that resolves if the translation set is loaded, never rejects (use isTranslationSetLoaded() to check if the translation set is loaded) */ async ensureTranslationSetIsLoaded(translationSetIdentifier) { await firstValueFrom(this.isTranslationSetLoaded(translationSetIdentifier).pipe(filter(loaded => loaded))); } /** * Checks if a translation set is loaded for the current locale. * @param translationSetIdentifier The identifier of the translations (e.g. the name of the module) * @returns true if the translation set is loaded, false otherwise */ isTranslationSetLoadedSync(translationSetIdentifier) { if (!this._locale) { return false; } const set = this._translationStore[this._locale]; if (!set) { return false; } return set.some(r => r.key === translationSetIdentifier && !!r.loadedTranslations); } doRemoveTranslation(translationSetIdentifier, locale) { const translationResolvers = this._translationStore[locale] ?? []; const index = translationResolvers.findIndex(r => r.key === translationSetIdentifier); if (index !== -1) { translationResolvers.splice(index, 1); } } invalidateTranslations() { this._translationsInvalidated.next(true); } async loadTranslations() { const locale = this._locale; if (!locale) { return; } const translationResolvers = this._translationStore[locale] ?? []; await this.resolveTranslations(translationResolvers); const loadedTranslations = this.mergeLoadedTranslations(translationResolvers); this._translateService.setTranslation(locale, loadedTranslations, true); if (this._locale) { this._translateService.use(this._locale); } this._translationsUpdated.next(); } async resolveTranslations(translationResolvers) { const promises = translationResolvers.map(async (translationResolver) => { if (translationResolver.loadedTranslations) { return; } const loadedTranslations = await this.resolveTranslation(translationResolver.resolver); translationResolver.loadedTranslations = loadedTranslations; }); await Promise.all(promises); } mergeLoadedTranslations(translationResolvers) { const translations = {}; translationResolvers.forEach(translationResolver => { if (!translationResolver.loadedTranslations) { return; } if (this._debugMode) { const debugTranslations = this.putTranslationsIntoDebugMode(translationResolver.loadedTranslations); Object.assign(translations, debugTranslations); } else { Object.assign(translations, translationResolver.loadedTranslations); } }); return translations; } async resolveTranslation(translationResolver) { let promiseResolver; // check if translationResolver is a promise if (translationResolver instanceof Promise) { promiseResolver = translationResolver; } else if (typeof translationResolver === 'function') { const fnResult = translationResolver(); if (fnResult instanceof Promise) { promiseResolver = fnResult; } else { promiseResolver = Promise.resolve(fnResult); } } else if (isAssetResolver(translationResolver)) { promiseResolver = this.loadLocaleFromAssetPath(translationResolver.moduleName, translationResolver.path); } else { promiseResolver = Promise.resolve(translationResolver); } const loadedTranslations = await promiseResolver; return loadedTranslations; } async loadLocaleFromAssetPath(moduleName, path) { const resourceUrl = resourceMapper(moduleName, path); const response = await fetch(resourceUrl); const content = (await response.json()); return content; } putTranslationsIntoDebugMode(translations, prefix = '') { const debugTranslations = {}; Object.keys(translations).forEach(key => { const value = translations[key]; if (typeof value === 'string') { debugTranslations[key] = `${prefix}${key}`; } else { debugTranslations[key] = this.putTranslationsIntoDebugMode(value, `${prefix}${key}.`); } }); return debugTranslations; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: DynamicTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: DynamicTranslationService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: DynamicTranslationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [] }); /** * Generated bundle index. Do not edit. */ export { DynamicTranslationService, isAssetResolver }; //# sourceMappingURL=ng-dynamic-mf-translate.mjs.map