/* js intl, inspired by danabr/jsI18n */ class JsI18n { constructor() { this.locale = ""; //Current locale this.locales = new Array(); //Available locales this.overwrites = new Array(); this.defaultLocale = "fr"; } /* Method for automatically detecting the language, does not work in every browser. */ async detectLanguage() { const customLangs = document.querySelectorAll('orbit-lang'); if (customLangs) { for (let lang of customLangs) { let name = lang.getAttribute('lang'), file = lang.getAttribute('file'); let result = await fetch(file); if (result.ok) { let json = await result.json(); if(this.overwrites[name.toString()] != undefined) { this.mergeDeep(this.overwrites[name.toString()], json); } else { this.overwrites[name.toString()] = json; } } } } this.resumeDetection(); } resumeDetection() { const langComponent = document.querySelector('orbit-fallback-lang'); if (langComponent) { if (langComponent.hasAttribute('lang')) { this.defaultLocale = langComponent.getAttribute('lang'); if (langComponent.hasAttribute('force')) { localStorage.setItem('language', this.defaultLocale); } } } if (localStorage.getItem('language') || (window.navigator.language !== null && window.navigator.language !== undefined)) { this.fetchLocale(this.defaultLocale); this.setLocale(localStorage.getItem('language') || window.navigator.language.slice(0, 2)); } else { console.error('Language not found'); this.setLocale(this.defaultLocale); } }; isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); } mergeDeep(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (this.isObject(target) && this.isObject(source)) { for (const key in source) { if (this.isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); this.mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return this.mergeDeep(target, ...sources); } /* 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); } } // https://git.startinblox.com/framework/sib-core/issues/755 if (attr.startsWith('placeholder-')) { let placeholder = node.querySelector('[placeholder="' + attr.replace("placeholder-", "") + '"]'); if (placeholder != null) { this.translateNodeContent(placeholder.attributes['placeholder'], k); let input = node.querySelector('[name="' + attr.replace("placeholder-", "") + '"] > input'); if (input != null) { this.translateNodeContent(input.attributes['placeholder'], 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) { if (this.overwrites[locale.toString()] != undefined) { this.mergeDeep(translations, this.overwrites[locale.toString()]); } this.locales[locale.toString()] = translations; } fetchLocale(locale) { return fetch(`/locales/${locale}.json`).then((result) => { if (result.ok) { result.json().then(e => { this.addLocale(locale, e); }); } }); } /* Sets the locale to use when translating. */ setLocale(locale) { try { this.fetchLocale(locale).then(() => { if (this.locale) { localStorage.setItem('language', this.locale); } this.processPage(); this.locale = locale; }); } catch { if (locale != this.defaultLocale) { console.warn(`Locale not found: ${locale}, fallback to ${this.defaultLocale}`); this.setLocale(this.defaultLocale); } 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) { if (this.locales.length > 1) { translations = this.locales[this.defaultLocale]; } } if (translations != undefined) { let translation = key.toString().split('.').reduce((o, i) => (o ? o[i] : undefined), translations); if (typeof translation == "string") { return translation; } else { try { let keySplitted = key.toString().split('.'); let first = keySplitted.shift(); translation = translations[first][keySplitted.join('.')]; return translation; } catch { 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 window.orbit.intl = new JsI18n; document.addEventListener("DOMContentLoaded", () => { // Detect the lang & initialize, based on the browser or "language" item from localstorage window.orbit.intl.detectLanguage(); let timer; (new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.target.attributes["data-trans"] != null) { // Render the target of the mutation instantly window.orbit.intl.processNode(mutation.target); // Then wait one arbitrary second to re-render the whole document in case a widget re-rendered clearTimeout(timer); timer = setTimeout(() => window.orbit.intl.processNode(document.querySelector('body')), 500); } }); }).observe(document.body, { subtree: true, childList: true })); document.addEventListener('widgetRendered', event => { window.orbit.intl.processNode(event.target); // Then wait one arbitrary second to re-render the whole document in case a widget re-rendered clearTimeout(timer); timer = setTimeout(() => window.orbit.intl.processNode(document.querySelector('body')), 500); }); });