From be08e62fc1ae56f23cbbc6802e417cb5c931e36f Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 28 Jun 2024 23:01:54 +0200 Subject: [PATCH] Observer to find new elements with i18n attribute --- public/scripts/i18n.js | 70 ++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/public/scripts/i18n.js b/public/scripts/i18n.js index df86a3c26..1a1f9135d 100644 --- a/public/scripts/i18n.js +++ b/public/scripts/i18n.js @@ -9,6 +9,28 @@ const langs = await fetch('/locales/lang.json').then(response => response.json() // eslint-disable-next-line prefer-const var localeData = await getLocaleData(localeFile); +/** + * An observer that will check if any new i18n elements are added to the document + * @type {MutationObserver} + */ +const observer = new MutationObserver(mutations => { + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.nodeType === Node.ELEMENT_NODE && node instanceof Element) { + if (node.hasAttribute('data-i18n')) { + translateElement(node); + } + node.querySelectorAll('[data-i18n]').forEach(element => { + translateElement(element); + }); + } + }); + if (mutation.attributeName === 'data-i18n' && mutation.target instanceof Element) { + translateElement(mutation.target); + } + }); +}); + /** * Fetches the locale data for the given language. * @param {string} language Language code @@ -40,6 +62,29 @@ function findLang(language) { return supportedLang; } +/** + * Translates a given element based on its data-i18n attribute. + * @param {Element} element The element to translate + */ +function translateElement(element) { + const keys = element.getAttribute('data-i18n').split(';'); // Multi-key entries are ; delimited + for (const key of keys) { + const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key + if (attributeMatch) { // attribute-tagged key + const localizedValue = localeData?.[attributeMatch[2]]; + if (localizedValue || localizedValue === '') { + element.setAttribute(attributeMatch[1], localizedValue); + } + } else { // No attribute tag, treat as 'text' + const localizedValue = localeData?.[key]; + if (localizedValue || localizedValue === '') { + element.textContent = localizedValue; + } + } + } +} + + async function getMissingTranslations() { const missingData = []; @@ -103,22 +148,7 @@ export function applyLocale(root = document) { //find all the elements with `data-i18n` attribute $root.find('[data-i18n]').each(function () { - //read the translation from the language data - const keys = $(this).data('i18n').split(';'); // Multi-key entries are ; delimited - for (const key of keys) { - const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key - if (attributeMatch) { // attribute-tagged key - const localizedValue = localeData?.[attributeMatch[2]]; - if (localizedValue || localizedValue == '') { - $(this).attr(attributeMatch[1], localizedValue); - } - } else { // No attribute tag, treat as 'text' - const localizedValue = localeData?.[key]; - if (localizedValue || localizedValue == '') { - $(this).text(localizedValue); - } - } - } + translateElement(this); }); if (root !== document) { @@ -126,7 +156,6 @@ export function applyLocale(root = document) { } } - function addLanguagesToDropdown() { const uiLanguageSelects = $('#ui_language_select, #onboarding_ui_language_select'); for (const langObj of langs) { // Set the value to the language code @@ -159,6 +188,13 @@ export function initLocales() { location.reload(); }); + observer.observe(document, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['data-i18n'], + }); + registerDebugFunction('getMissingTranslations', 'Get missing translations', 'Detects missing localization data in the current locale and dumps the data into the browser console. If the current locale is English, searches all other locales.', getMissingTranslations); registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale); }