UNPKG

angular-l10n

Version:

An Angular library to translate messages, dates and numbers

405 lines 14 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ import * as tslib_1 from "tslib"; import { Injectable, Inject } from '@angular/core'; import { Observable, Subject, BehaviorSubject, race, combineLatest, merge, concat } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { LocaleService } from './locale.service'; import { TranslationProvider } from './translation-provider'; import { TranslationHandler } from './translation-handler'; import { InjectorRef } from '../models/injector-ref'; import { L10N_CONFIG } from "../models/l10n-config"; import { mergeDeep } from '../models/merge-deep'; import { ProviderType } from '../models/types'; /** * @record */ export function ITranslationService() { } if (false) { /** @type {?} */ ITranslationService.prototype.translationError; /** * @return {?} */ ITranslationService.prototype.getConfiguration = function () { }; /** * @return {?} */ ITranslationService.prototype.init = function () { }; /** * @return {?} */ ITranslationService.prototype.loadTranslation = function () { }; /** * @return {?} */ ITranslationService.prototype.translationChanged = function () { }; /** * @return {?} */ ITranslationService.prototype.latestTranslation = function () { }; /** * @param {?} keys * @param {?=} args * @param {?=} lang * @return {?} */ ITranslationService.prototype.translate = function (keys, args, lang) { }; /** * @param {?} keys * @param {?=} args * @param {?=} lang * @return {?} */ ITranslationService.prototype.translateAsync = function (keys, args, lang) { }; /** * @param {?} key * @param {?=} lang * @return {?} */ ITranslationService.prototype.has = function (key, lang) { }; } /** * Manages the translation data. */ export class TranslationService { /** * @param {?} configuration * @param {?} locale * @param {?} translationProvider * @param {?} translationHandler * @param {?} injector */ constructor(configuration, locale, translationProvider, translationHandler, injector) { this.configuration = configuration; this.locale = locale; this.translationProvider = translationProvider; this.translationHandler = translationHandler; this.injector = injector; /** * Fired when the translation data could not been loaded. Returns the error. */ this.translationError = new Subject(); this.translation = new BehaviorSubject(''); /** * The translation data: {language: {key: value}}. */ this.translationData = {}; this.injector.translations.push(this); } /** * @return {?} */ getConfiguration() { return this.configuration.translation; } /** * @return {?} */ init() { return tslib_1.__awaiter(this, void 0, void 0, function* () { // When the language or the default locale changes, loads translation data. race(this.locale.languageCodeChanged, this.locale.defaultLocaleChanged).subscribe(() => { this.loadTranslation() .catch((error) => null); }); yield this.loadTranslation() .catch((error) => { throw error; }); }); } /** * Forces the translation loading for the current language. * @return {?} */ loadTranslation() { return tslib_1.__awaiter(this, void 0, void 0, function* () { /** @type {?} */ let language; if (this.configuration.translation.composedLanguage) { language = this.locale.composeLocale(this.configuration.translation.composedLanguage); } else { language = this.locale.getCurrentLanguage(); } if (language) { this.translationData = {}; if (this.configuration.translation.translationData) { this.getTranslation(language); } if (this.configuration.translation.providers) { yield this.getTranslationAsync(language) .toPromise() .catch((error) => { throw error; }); } } }); } /** * Fired when the translation data has been loaded. Returns the translation language. * @return {?} */ translationChanged() { return this.translation.asObservable(); } /** * Fired when the latest 'translationChanged' is emitted. Returns the translation language. * Used when the reference to the service is not known, as in decorators. * @return {?} */ latestTranslation() { /** @type {?} */ const sequencesOfTranslation = []; for (const translation of this.injector.translations) { sequencesOfTranslation.push(translation.translationChanged()); } return combineLatest(sequencesOfTranslation).pipe(filter((languages) => languages.length == sequencesOfTranslation.length && languages.every((lang, i, arr) => lang == arr[0])), map((languages) => languages[0])); } /** * Translates a key or an array of keys. * @param {?} keys The key or an array of keys to be translated * @param {?=} args Optional parameters contained in the key * @param {?=} lang The current language of the service is used by default * @return {?} The translated value or an object: {key: value} */ translate(keys, args, lang) { if (Array.isArray(keys)) { /** @type {?} */ const data = {}; for (const key of keys) { data[key] = this.translateKey(key, args, lang || this.translation.getValue()); } return data; } return this.translateKey(keys, args, lang || this.translation.getValue()); } /** * @param {?} keys * @param {?=} args * @param {?=} lang * @return {?} */ translateAsync(keys, args, lang) { return new Observable((observer) => { /** @type {?} */ const values = this.translate(keys, args, lang); observer.next(values); observer.complete(); }); } /** * Checks if a translation exists. * @param {?} key The key to be tested * @param {?=} lang The current language of the service is used by default * @return {?} */ has(key, lang = this.translation.getValue()) { /** @type {?} */ const path = key; /** @type {?} */ const translation = { nested: this.translationData[lang] }; if (translation.nested) { key = this.extractKey(translation, path); return typeof translation.nested[key] !== "undefined"; } return false; } /** * @param {?} key * @param {?} args * @param {?} lang * @return {?} */ translateKey(key, args, lang) { // I18n plural. if (this.configuration.translation.i18nPlural && /^\d+\b/.exec(key)) { return this.translateI18nPlural(key, args, lang); } return this.getValue(key, args, lang); } /** * @param {?} key * @param {?} args * @param {?} lang * @return {?} */ getValue(key, args, lang) { /** @type {?} */ const path = key; /** @type {?} */ const translation = { nested: this.translationData[lang] }; /** @type {?} */ let value = null; if (translation.nested) { key = this.extractKey(translation, path); value = typeof translation.nested[key] === "undefined" ? translation.nested[this.configuration.translation.missingKey || ""] : translation.nested[key]; } return this.translationHandler.parseValue(path, key, value, args, lang); } /** * @param {?} translation * @param {?} path * @return {?} */ extractKey(translation, path) { if (!this.configuration.translation.composedKeySeparator) return path; // Composed key. /** @type {?} */ const sequences = path.split(this.configuration.translation.composedKeySeparator); /** @type {?} */ let key = (/** @type {?} */ (sequences.shift())); while (sequences.length > 0 && translation.nested[key]) { translation.nested = translation.nested[key]; key = (/** @type {?} */ (sequences.shift())); } return key; } /** * @param {?} key * @param {?} args * @param {?} lang * @return {?} */ translateI18nPlural(key, args, lang) { /** @type {?} */ let keyText = key.replace(/^\d+\b/, ""); keyText = keyText.trim(); /** @type {?} */ const keyNumber = parseFloat(key); if (!isNaN(keyNumber)) { key = key.replace(/^\d+/, this.locale.formatDecimal(keyNumber)); } return key.replace(keyText, this.getValue(keyText, args, lang)); } /** * @param {?} language * @return {?} */ getTranslation(language) { /** @type {?} */ const translations = (/** @type {?} */ (this.configuration.translation.translationData)).filter((value) => value.languageCode == language); for (const translation of translations) { this.addData(translation.data, language); } if (!this.configuration.translation.providers) { this.releaseTranslation(language); } } /** * @param {?} language * @return {?} */ getTranslationAsync(language) { return new Observable((observer) => { /** @type {?} */ const sequencesOfOrderedTranslationData = []; /** @type {?} */ const sequencesOfTranslationData = []; for (const provider of (/** @type {?} */ (this.configuration.translation.providers))) { if (typeof provider.type !== "undefined" && provider.type == ProviderType.Fallback) { /** @type {?} */ let fallbackLanguage = language; if (provider.fallbackLanguage) { fallbackLanguage = this.locale.composeLocale(provider.fallbackLanguage); } sequencesOfOrderedTranslationData.push(this.translationProvider.getTranslation(fallbackLanguage, provider)); } else { sequencesOfTranslationData.push(this.translationProvider.getTranslation(language, provider)); } } // Merges all the sequences into a single observable sequence. /** @type {?} */ const mergedSequencesOfTranslationData = merge(...sequencesOfTranslationData); // Adds to ordered sequences. sequencesOfOrderedTranslationData.push(mergedSequencesOfTranslationData); concat(...sequencesOfOrderedTranslationData).subscribe((data) => { this.addData(data, language); }, (error) => { this.handleError(error, language); observer.error(error); observer.complete(); }, () => { this.releaseTranslation(language); observer.next(null); observer.complete(); }); }); } /** * @param {?} data * @param {?} language * @return {?} */ addData(data, language) { this.translationData[language] = typeof this.translationData[language] !== "undefined" ? mergeDeep(this.translationData[language], data) : data; } /** * @param {?} language * @return {?} */ releaseTranslation(language) { this.translation.next(language); } /** * @param {?} error * @param {?} language * @return {?} */ handleError(error, language) { if (this.configuration.translation.rollbackOnError) { this.locale.rollback(); } else { this.releaseTranslation(language); } this.translationError.next(error); } } TranslationService.decorators = [ { type: Injectable } ]; /** @nocollapse */ TranslationService.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [L10N_CONFIG,] }] }, { type: LocaleService }, { type: TranslationProvider }, { type: TranslationHandler }, { type: InjectorRef } ]; if (false) { /** * Fired when the translation data could not been loaded. Returns the error. * @type {?} */ TranslationService.prototype.translationError; /** @type {?} */ TranslationService.prototype.translation; /** * The translation data: {language: {key: value}}. * @type {?} */ TranslationService.prototype.translationData; /** @type {?} */ TranslationService.prototype.configuration; /** @type {?} */ TranslationService.prototype.locale; /** @type {?} */ TranslationService.prototype.translationProvider; /** @type {?} */ TranslationService.prototype.translationHandler; /** @type {?} */ TranslationService.prototype.injector; } //# sourceMappingURL=translation.service.js.map