Observer to find new elements with i18n attribute

This commit is contained in:
Wolfsblvt 2024-06-28 23:01:54 +02:00
parent a287ccfca2
commit be08e62fc1

View File

@ -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);
}