Newer
Older
import { Injectable, isDevMode } from "@angular/core";
francois.grand
committed
import { Message, MessageCode, Observable, Observer, CalculatorType, LoiDebit, Nub } from "jalhyd";
francois.grand
committed
import { StringMap } from "../../stringmap";
import { ApplicationSetupService } from "../app-setup/app-setup.service";
import { HttpService } from "../http/http.service";
import { fv } from "../../util";
francois.grand
committed
@Injectable()
export class I18nService extends Observable implements Observer {
/** current ISO 639-1 language code */
private _currentLanguage: string;
/** current available languages as ISO 639-1 codes => native name */
private _availableLanguages: any;
/** localized messages */
private _Messages: StringMap;
francois.grand
committed
/** localized messages in fallback language (the one in the config file) */
private _fallbackMessages: StringMap;
constructor(
private applicationSetupService: ApplicationSetupService,
this._availableLanguages = {
fr: "Français",
en: "English"
};
// load fallback language messages once for all
this.httpGetMessages(this.applicationSetupService.fallbackLanguage).then((res: any) => {
this._fallbackMessages = res;
});
// add language preferences observer
this.applicationSetupService.addObserver(this);
francois.grand
committed
}
francois.grand
committed
public get languages() {
francois.grand
committed
}
francois.grand
committed
francois.grand
committed
public get currentLanguage() {
francois.grand
committed
}
francois.grand
committed
public get currentMap() {
return this._Messages;
}
/**
* Defines the current language code from its ISO 639-1 code (2 characters) or locale code
* (ex: "fr", "en", "fr_FR", "en-US")
* @see this.languageCodeFromLocaleCode()
*
* @param code ISO 639-1 language code
*/
public setLanguage(code: string) {
// is language supported ?
if (! Object.keys(this._availableLanguages).includes(code)) {
throw new Error(`LANGUAGE_UNSUPPORTED "${code}"`);
// did language change ?
if (this._currentLanguage !== code) {
this._currentLanguage = code;
// @TODO keep old messages for backup-language mechanisms ?
François
committed
this._Messages = undefined;
this.httpGetMessages(code).then((res: any) => {
that._Messages = res;
François
committed
}
* Loads localized messages from JSON files for the given language
* (general messages files, not calculator-specific ones)
private httpGetMessages(lang: string): Promise<void> {
const fileName = "messages." + lang + ".json";
const filePath = "locale/" + fileName;
return this.httpService.httpGetRequestPromise(filePath).then((res: any) => {
return res;
});
francois.grand
committed
}
private getMessageFromCode(c: MessageCode): string {
return `*** messages not loaded yet ***`;
if (this._Messages[MessageCode[c]] === undefined) {
return `*** message not found ${MessageCode[c]} ***`;
return this._Messages[MessageCode[c]];
francois.grand
committed
}
* Translates a text from its key.
*
* In production mode, looks in different messages collections :
* 1. ${msg} if provided
* 2. messages for current language
* 3. messages for fallback language
*
* In dev mode, looks only in 1. if provided, else only in 2. which makes missing
* translations easier to detect
*
* @param textKey id du texte (ex: "ERROR_PARAM_NULL")
*/
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
public localizeText(textKey: string, msg?: StringMap) {
if (isDevMode()) {
// expose missing translations
if (msg) {
if (msg[textKey] === undefined) {
return `*** message not found: ${textKey} ***`;
}
return msg[textKey];
} else {
if (! this._Messages) {
return `*** messages not loaded: ${this._currentLanguage} ***`;
}
if (this._Messages[textKey] === undefined) {
return `*** message not found: ${textKey} ***`;
}
return this._Messages[textKey];
}
} else {
const messages = msg || this._Messages;
if (! messages) {
return `*** messages not loaded: ${this._currentLanguage} ***`;
}
if (messages[textKey] === undefined) {
// try fallback language before giving up
if (this._fallbackMessages[textKey] === undefined) {
return `*** message not found: ${textKey} ***`;
} else {
return this._fallbackMessages[textKey];
}
} else {
return messages[textKey];
}
francois.grand
committed
}
francois.grand
committed
/**
* Traduit un Message (classe Message de JaLHyd, pour les logs de calcul par exemple)
* @param r Message
francois.grand
committed
* @param nDigits nombre de chiffres à utiliser pour l'arrondi dans le cas de données numériques
*/
public localizeMessage(r: Message, nDigits: number = 3): string {
let m: string = this.getMessageFromCode(r.code);
francois.grand
committed
for (const k in r.extraVar) {
if (r.extraVar.hasOwnProperty(k)) {
const v: any = r.extraVar[k];
let s: string;
if (typeof v === "number") {
// @TODO use sprintf() with named parameters instead ?
francois.grand
committed
}
return m;
}
/**
* Finds the localized title for a LoiDebit item
* @TODO add StructureType context ? (ex: cem88d / cem88v)
*/
public localizeLoiDebit(l: LoiDebit) {
return this.localizeText("INFO_LOIDEBIT_" + LoiDebit[l]);
}
private replaceAll(str: string, find: string, replace: string) {
return str.replace(new RegExp(find, "g"), replace);
francois.grand
committed
/**
* Met en forme un result en fonction du libellé qui l'accompagne
* Les result avec le terme "ENUM_" sont traduit avec le message INFO_ENUM_[Nom de la variable après ENUM_]
francois.grand
committed
*/
public formatResult(label: string, value: number): string {
mathias.chouet
committed
if (value === undefined) {
return "";
}
const match = label.indexOf("ENUM_");
if (match > -1) {
return this.localizeText(`INFO_${label.substring(match).toUpperCase()}_${value}`);
David Dorchies
committed
}
francois.grand
committed
}
David Dorchies
committed
/**
* Returns the localized name for the children type of the current Nub
* @param plural if true, will return plural name
*/
public childName(nub: Nub, plural: boolean = false) {
const type: string = nub.childrenType;
let k = "INFO_CHILD_TYPE_" + type.toUpperCase();
if (plural) {
k += "_PLUR";
}
return this.localizeText(k);
}
// interface Observer
/**
* Should only be triggered once at app startup, when setup service tries loading language
* @param sender should always be ApplicationSetupService
* @param data object {
* action: should always be "languagePreferenceChanged"
* languages: languages codes to try until one works
* }
*/
public update(sender: any, data: any): void {
if (sender instanceof ApplicationSetupService) {
if (data.action === "languagePreferenceChanged") {
let languageEventuallyUsed: string;
for (let i = 0; i < data.languages.length && languageEventuallyUsed === undefined; i++) {
const l = data.languages[i];
if (l !== undefined) {
try {
languageEventuallyUsed = l;
} catch (e) {
console.error(e.toString());
}
}
}
// confirm to setup service which language was eventually used at app startup (triggers nothing more)
this.applicationSetupService.language = languageEventuallyUsed;
}
}
}