2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 import { Injectable, Inject } from "@angular/core";
22 import { ITranslateServiceConfig, TranslateServiceConfigToken } from "./translate.service.config";
23 import { Observer, Subscription, Observable, ConnectableObservable } from 'rxjs/Rx';
24 import { HttpClient } from "@angular/common/http";
26 export { ITranslateServiceConfig, TranslateServiceConfigToken };
28 export interface ITranslateLanguageJson {
29 [index:string]: string;
32 export interface ITranslateArgs {
36 export class PhraseTranslator {
37 private _observable:ConnectableObservable<string>;
38 private _observer:Observer<string>;
39 private _languageChangeSubscription:Subscription;
41 private _phraseKey:string;
42 private _args:ITranslateArgs;
43 private _language:string;
45 private _markForCheck:boolean = false;
46 private _lastParams: {
48 args: {[index: string]: any};
56 constructor(private translateService:TranslateService) {
57 this._observable = Observable.create(observer => {
58 this._observer = observer;
59 this._languageChangeSubscription = this.translateService.languageChangedObservable.subscribe(language => {
60 // using the active language, then force update
61 if (!this._language) {
65 }).publishReplay(1).refCount();
68 public get observable() {
69 return this._observable;
73 this._observer.complete();
74 this._languageChangeSubscription.unsubscribe();
76 delete this._observable;
77 delete this._observer;
78 delete this._languageChangeSubscription;
81 public shouldUpdate() : boolean {
82 if (!this._markForCheck) {
85 this._markForCheck = false;
87 this._language !== this._lastParams.language ||
88 this._args !== this._lastParams.args ||
89 this._phraseKey !== this._lastParams.phraseKey
93 public update(forceUpdate:boolean=false) : void {
94 // only update translation when having subscriptions connected.
95 if (this._observer && !this._observer.closed) {
96 if (forceUpdate || this.shouldUpdate()) {
98 phraseKey: this._phraseKey,
100 language: this._language
102 this._markForCheck = false;
104 const translated = this.translateService.translate(this._phraseKey, this._args, this._language);
105 this._observer.next(translated);
110 private _changeParam(paramKey:string, value:any, update:boolean) : void {
111 this[`_${paramKey}`] = value;
112 this._markForCheck = true;
118 public changePhraseKey(phraseKey:string, update:boolean=true) : void {
119 this._changeParam('phraseKey', phraseKey, update);
122 public changeArgs(args:ITranslateArgs, update:boolean=true) :void {
123 this._changeParam('args', args, update);
126 public changeLangauge(language:string, update:boolean=true) :void {
127 this._changeParam('language', language, update);
130 public changeParams(phraseKey:string, args:ITranslateArgs={}, language?:string, forceUpdate:boolean=false) {
131 this._phraseKey = phraseKey;
133 this._language = language;
134 this._markForCheck = true;
135 this.update(forceUpdate);
141 export class TranslateService {
142 private _activeLanguage:string;
143 private _languageChangedObservable:ConnectableObservable<string>;
144 private _languageChangedObserver:Observer<string>;
145 private _cacheLanguagesJsons:{[index:string]:ITranslateLanguageJson} = {};
146 private _cacheLanguagesLoaders:{[index:string]:Observable<ITranslateLanguageJson>} = {};
148 constructor(@Inject(TranslateServiceConfigToken) private config:ITranslateServiceConfig, private http: HttpClient) {
149 this.initLanguageObservable();
150 this.loadAndActivateLanguage(this.config.defaultLanguage);
153 public get languageChangedObservable() : Observable<string> {
154 return this._languageChangedObservable;
157 public get activeLanguage() {
158 return this._activeLanguage;
161 private initLanguageObservable() {
162 this._languageChangedObservable = ConnectableObservable.create(observer => {
163 this._languageChangedObserver = observer;
164 }).publishReplay(1); // replay last emitted change on subscribe
165 this._languageChangedObservable.connect();
168 private loadLanguageJsonFile(language:string, emitOnLoad:boolean=true) : Observable<ITranslateLanguageJson> {
169 if (this.config.allowedLanguages.indexOf(language) === -1) {
170 return Observable.throw(`Language "${language}" is not available.`);
173 if (this._cacheLanguagesJsons[language]) {
174 return Observable.of(this._cacheLanguagesJsons[language]);
177 if (!(language in this._cacheLanguagesLoaders)) {
178 const filePath = `${this.config.filePrefix}${language}${this.config.fileSuffix}`;
179 this._cacheLanguagesLoaders[language] = this.http.get<ITranslateLanguageJson>(filePath)
180 .catch(() => Observable.throw(`Failed to load language file for "${language}"`))
182 (<ConnectableObservable<ITranslateLanguageJson>>this._cacheLanguagesLoaders[language]).connect();
183 this._cacheLanguagesLoaders[language].subscribe(languageJson => {
184 this._cacheLanguagesJsons[language] = languageJson;
185 delete this._cacheLanguagesLoaders[language];
187 this._languageChangedObserver.next(language);
192 return this._cacheLanguagesLoaders[language];
195 public activateLanguage(language:string) : boolean {
196 if (this._cacheLanguagesJsons[language]) {
197 if (language !== this._activeLanguage) {
198 this._activeLanguage = language;
199 this._languageChangedObserver.next(this._activeLanguage);
206 public loadAndActivateLanguage(language:string) : void {
208 const loadLanguageObservable = this.loadLanguageJsonFile(language, false);
209 loadLanguageObservable.subscribe(() => {
210 this.activateLanguage(language);
214 public translate(phraseKey:string, args:ITranslateArgs={}, language:string=this._activeLanguage) : string {
215 const phrase:string = (this._cacheLanguagesJsons[language] || {})[phraseKey] || '';
216 let translated:string;
217 if (typeof(phrase) === 'string') {
220 /(^|[^\\]|\\\\)\{\{(\w+)\}\}/g,
221 (m, p1, p2) => `${p1}${args[p2]||''}`
223 .replace('\\{{', '{{')
224 .replace('\\\\', '\\');
229 public createPhraseTranslator(phraseKey?:string, args?:ITranslateArgs, language?:string) : PhraseTranslator {
230 const phraseTranslator = new PhraseTranslator(this);
231 phraseTranslator.changeParams(phraseKey, args, language);
232 return phraseTranslator;