angular-l10n
Version:
An Angular library to translate messages, dates and numbers
405 lines • 14 kB
JavaScript
/**
* @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