@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
141 lines (140 loc) • 5.4 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import GirafeSingleton from '../../base/GirafeSingleton.js';
class I18nManager extends GirafeSingleton {
translations = {};
translationAliases = {};
loadingLanguagePromise = null;
constructor(context) {
super(context);
}
initializeSingleton() {
this.context.stateManager.subscribe('language', () => this.handleLanguageChange());
this.handleLanguageChange();
}
formatNumber(number) {
return Number.parseFloat(`${number}`).toLocaleString(this.context.configManager.Config.general.locale);
}
async ensureTranslationLoaded() {
if (!this.context.stateManager.state?.language) {
return false;
}
try {
await this.loadTranslations(this.context.stateManager.state.language);
}
catch (err) {
console.warn('Skipping translation due to config error:', err);
return false;
}
return true;
}
async loadTranslations(language) {
if (this.loadingLanguagePromise) {
// There's already a promise for loading translations
// => return it instead of starting another request
return this.loadingLanguagePromise;
}
if (language in this.translations) {
// Translation were already loaded.
// => stop here
return this.translations[language];
}
// Load translations
this.loadingLanguagePromise = this.context.configManager.loadConfig().then(async () => {
if (this.context.configManager.Config?.languages.translations &&
language in this.context.configManager.Config.languages.translations) {
let mergedTranslations = this.translations[language] ?? {};
// Translations are loaded in the order defined in the list of files
// If an element is present in both results, the last value overwrite all the others
for (const url of this.context.configManager.Config.languages.translations[language]) {
const response = await fetch(url);
const content = await response.json();
mergedTranslations = { ...mergedTranslations, ...content[language] };
}
this.translations[language] = mergedTranslations;
this.loadingLanguagePromise = null;
return this.translations[language];
}
else {
throw new Error('No languages found in config.json');
}
});
return this.loadingLanguagePromise;
}
getTranslation(key) {
const currentLanguage = this.context.stateManager?.state?.language ?? 'en';
const translationDict = this.translations[currentLanguage];
if (translationDict) {
// First look in translations
let translation = translationDict[key];
if (translation) {
return translation;
}
// Otherwise, try with translation alias
const alias = this.translationAliases[key];
translation = translationDict[alias];
if (translation) {
return translation;
}
}
// No translation found.
return key;
}
async translate(dom) {
const translationLoaded = await this.ensureTranslationLoaded();
if (!translationLoaded) {
return;
}
const toTranslate = dom.querySelectorAll('[i18n]');
for (const item of toTranslate) {
const key = item.getAttribute('i18n');
let translation;
if (item.hasAttribute('i18nFn')) {
translation = this.getFnTranslated(item, key);
}
else {
translation = this.getTranslation(key);
}
if (item.hasAttribute('placeholder')) {
item.setAttribute('placeholder', translation);
}
else {
// Default : simply set innerHTML.
item.innerHTML = translation;
}
}
const tooltips = dom.querySelectorAll('[tip]');
for (const item of tooltips) {
const key = item.getAttribute('tip');
const translation = this.getTranslation(key);
item.setAttribute('title', translation);
}
}
handleLanguageChange() {
const newLanguage = this.context.stateManager.state.language;
if (!newLanguage) {
return;
}
const htmlLangElement = document.querySelector('html[lang]');
htmlLangElement?.setAttribute('lang', newLanguage);
// Translate elements in main html page
this.translate(document.body);
}
getFnTranslated(item, key) {
const fnName = item.getAttribute('i18nFn');
if (fnName === 'formatNumber') {
return this.formatNumber(key);
}
return key;
}
/**
* Define a translation alias.
* The alias will use the same translation as the key.
* But if the key already has another translation, the alias will be ignored
*/
addTranslationAlias(key, alias) {
if (key !== alias) {
this.translationAliases[alias] = key;
}
}
}
export default I18nManager;