/* js intl, inspired by danabr/jsI18n */ class JsI18n { constructor() { this.locale = ""; //Current locale this.locales = new Array(); //Available locales } /* Method for automatically detecting the language, does not work in every browser. */ detectLanguage() { if (localStorage.getItem('language') || (window.navigator.language !== null && window.navigator.language !== undefined)) { this.setLocale(localStorage.getItem('language') || window.navigator.language.slice(0, 2)); } else { console.error('Language not found'); this.setLocale('fr'); } }; /* Translates tag contents and attributes depending on the value of key. */ translateTag(node, key) { if (key.indexOf("=") == -1) { //Simple key this.translateNodeContent(node, key); } else { //Attribute/key pairs var parts = key.split(";"); for (var i = 0; i < parts.length; i++) { var pair = parts[i].split("="); var attr = pair[0].toLowerCase().trim(); var k = pair[1].trim(); if (attr == "html") { this.translateNodeContent(node, k); } else { // https://git.startinblox.com/framework/sib-core/issues/733 if (attr.startsWith('label-')) { let label = node.querySelector('[name="' + attr.replace("label-", "") + '"] > label'); if (label != null) { this.translateNodeContent(label, k); } } this.translateNodeContent(node.attributes[attr], k); } } } } /** Replace the content of the given node if there is a translation for the given key. **/ translateNodeContent(node, key) { var translation = this.t(key); if (node != null && translation != undefined) { if (node.nodeType == 1) { //Element try { if (node.innerHTML != translation) node.innerHTML = translation; } catch (e) { if (node.text != translation) node.text = translation; } } else if (node.nodeType == 2) { //Attribute if (node.value != translation) node.value = translation; } } } /* Helper for translating a node and all its child nodes. */ processNode(node) { if (node != undefined) { if (node.nodeType == 1) { //Element node var key = node.attributes["data-trans"]; if (key != null) { this.translateTag(node, key.nodeValue); } } //Process child nodes var children = node.childNodes; for (var i = 0; i < children.length; i++) { this.processNode(children[i]); } } } /* Adds a locale to the list, replacing the translations if the locale is already defined. */ addLocale(locale, translations) { this.locales[locale.toString()] = translations; } /* Sets the locale to use when translating. */ setLocale(locale) { try { fetch(`/locales/${locale}.json`).then((result) => { if (result.ok) { result.json().then(e => { this.addLocale(locale, e); this.processPage(); }).catch(() => { if (locale != "fr") { console.warn(`Locale not found: ${locale}, fallback to french`); this.setLocale("fr"); } else { console.error("Language not found"); } }); } else { if (locale != "fr") { console.warn(`Locale not found: ${locale}, fallback to french`); this.setLocale("fr"); } else { console.error("Language not found"); } } }); this.locale = locale; } catch { if (locale != "fr") { console.warn(`Locale not found: ${locale}, fallback to french`); this.setLocale("fr"); } else { console.error("Language not found"); } } } /* Fetches the translation associated with the given key. */ t(key) { var translations = this.locales[this.locale]; if (translations != undefined) { let translation = key.toString().split('.').reduce((o, i) => (o ? o[i] : undefined), translations); if (typeof translation == "string") { return translation; } else { return translations[key.toString()]; } } return undefined; } /* Alias for JsI18n.t */ translate(key) { this.t(key); } /** Replaces the contents of all tags that have the data-trans attribute set. **/ processPage() { this.processNode(document.getElementsByTagName("html")[0]); } } //Global jsI18n = new JsI18n; document.addEventListener("DOMContentLoaded", () => { // Detect the lang & initialize, based on the browser or "language" item from localstorage jsI18n.detectLanguage(); let timer; (new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.target.attributes["data-trans"] != null) { // Render the target of the mutation instantly jsI18n.processNode(mutation.target); // Then wait one arbitrary second to re-render the whole document in case a widget re-rendered clearTimeout(timer); timer = setTimeout(() => jsI18n.processNode(document.querySelector('body')), 500); } }); }).observe(document.body, { subtree: true, childList: true })); document.addEventListener('widgetRendered', event => { jsI18n.processNode(event.target); // Then wait one arbitrary second to re-render the whole document in case a widget re-rendered clearTimeout(timer); timer = setTimeout(() => jsI18n.processNode(document.querySelector('body')), 500); }); });