Newer
Older
francois.grand
committed
import { Message, MessageCode, Observable, Observer } from "jalhyd";
francois.grand
committed
import { StringMap } from "../../stringmap";
import { ApplicationSetupService } from "../app-setup/app-setup.service";
import { HttpService } from "../http/http.service";
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
constructor(
private applicationSetupService: ApplicationSetupService,
this._availableLanguages = {
fr: "Français",
en: "English"
};
// 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(`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;
// reload all messages
const that = this;
this.httpGetMessages().then((res) => {
François
committed
}
/**
* Loads localized messages from JSON files, for the current language
* (general message file, not calculator-specific ones)
*/
const that = this;
const fileName = "messages." + this._currentLanguage + ".json";
return this.httpService.httpGetRequestPromise("locale/" + fileName).then(
(res: any) => { that._Messages = res; }
francois.grand
committed
}
private getMessageFromCode(c: MessageCode): string {
if (! this._Messages) {
return `*** Messages not loaded yet ***`;
}
if (this._Messages[MessageCode[c]] === undefined) {
return `*** Message ${MessageCode[c]} non traduit ***`;
}
return this._Messages[MessageCode[c]];
francois.grand
committed
}
/**
* Traduit un texte défini dans un fichier de langue, à partir de sa clé
* @param textKey id du texte (ex: "ERROR_PARAM_NULL")
*/
public localizeText(textKey: string, messages = this._Messages) {
if (messages === undefined) {
return `*** messages not loaded: ${this._currentLanguage} ***`;
}
if (messages[textKey] === undefined) {
return `*** message not found: ${textKey} ***`;
}
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") {
s = v.toFixed(nDigits);
} else {
s = v;
}
// @TODO use sprintf() with named parameters instead ?
francois.grand
committed
}
return m;
}
private replaceAll(str: string, find: string, replace: string) {
return str.replace(new RegExp(find, "g"), replace);
francois.grand
committed
/**
* analyse un libellé du type ouvrage[n].XX
*/
private parseLabel(lbl: string) {
const re = /([A-Z,a-z]+)\[(\d+)\]\.(.+)/;
return re.exec(lbl);
}
/**
David Dorchies
committed
* Traduit un libellé qui peut être un code
francois.grand
committed
*/
public getExtraResLabel(s: string) {
David Dorchies
committed
const key = "INFO_EXTRARES_LIB_";
const match = this.parseLabel(s);
if (match) {
// Code du type "Ouvrage[n].XXX"
// Les libellés correspondants sont INFO OUVRAGE et INFO_EXTRARES_LIB_OUVRAGE_XXX
return this.localizeText(`INFO_${match[1].toUpperCase()}`) + " n°" + (+match[2] + 1) + ": " +
this.localizeText(`${key}${match[1].toUpperCase()}_${match[3].toUpperCase()}`);
} else {
// Autres codes INFO_EXTRARES_LIB_XXX
return this.localizeText(`${key}${s.toUpperCase()}`);
}
francois.grand
committed
}
francois.grand
committed
/**
* Met en forme un extraResult en fonction du libellé qui l'accompagne
* Les extraResult avec le terme "ENUM_" sont traduit avec le message INFO_EXTRARES_ENUM_[Nom de la variable après ENUM_]
francois.grand
committed
*/
public formatResult(label: string, value: number): string {
const match = label.indexOf("ENUM_");
if (match > -1) {
return this.localizeText(`INFO_EXTRARES_${label.substring(match).toUpperCase()}_${value}`);
David Dorchies
committed
}
const nDigits = this.applicationSetupService.displayDigits;
francois.grand
committed
return value.toFixed(nDigits);
}
David Dorchies
committed
// 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;
}
}
}