Observer to find new elements with i18n attribute

be08e62fc1ae56f23cbbc6802e417cb5c931e36f

Wolfsblvt <wolfsblvt@gmx.de>

1 files changed, +53 -17Ignore whitespace
public/scripts/i18n.js+53 -17
@@ -10,6 +10,28 @@ const langs = await fetch('/locales/lang.json').then(response => response.json()
1010var localeData = await getLocaleData(localeFile);
1111
1212/**
13+ * An observer that will check if any new i18n elements are added to the document
14+ * @type {MutationObserver}
15+ */
16+const observer = new MutationObserver(mutations => {
17+ mutations.forEach(mutation => {
18+ mutation.addedNodes.forEach(node => {
19+ if (node.nodeType === Node.ELEMENT_NODE && node instanceof Element) {
20+ if (node.hasAttribute('data-i18n')) {
21+ translateElement(node);
22+ }
23+ node.querySelectorAll('[data-i18n]').forEach(element => {
24+ translateElement(element);
25+ });
26+ }
27+ });
28+ if (mutation.attributeName === 'data-i18n' && mutation.target instanceof Element) {
29+ translateElement(mutation.target);
30+ }
31+ });
32+});
33+
34+/**
1335 * Fetches the locale data for the given language.
1436 * @param {string} language Language code
1537 * @returns {Promise<Record<string, string>>} Locale data
@@ -40,6 +62,29 @@ function findLang(language) {
4062 return supportedLang;
4163}
4264
65+/**
66+ * Translates a given element based on its data-i18n attribute.
67+ * @param {Element} element The element to translate
68+ */
69+function translateElement(element) {
70+ const keys = element.getAttribute('data-i18n').split(';'); // Multi-key entries are ; delimited
71+ for (const key of keys) {
72+ const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
73+ if (attributeMatch) { // attribute-tagged key
74+ const localizedValue = localeData?.[attributeMatch[2]];
75+ if (localizedValue || localizedValue === '') {
76+ element.setAttribute(attributeMatch[1], localizedValue);
77+ }
78+ } else { // No attribute tag, treat as 'text'
79+ const localizedValue = localeData?.[key];
80+ if (localizedValue || localizedValue === '') {
81+ element.textContent = localizedValue;
82+ }
83+ }
84+ }
85+}
86+
87+
4388async function getMissingTranslations() {
4489 const missingData = [];
4590
@@ -103,22 +148,7 @@ export function applyLocale(root = document) {
103148
104149 //find all the elements with `data-i18n` attribute
105150 $root.find('[data-i18n]').each(function () {
106- //read the translation from the language data
151+ translateElement(this);
107- const keys = $(this).data('i18n').split(';'); // Multi-key entries are ; delimited
108- for (const key of keys) {
109- const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
110- if (attributeMatch) { // attribute-tagged key
111- const localizedValue = localeData?.[attributeMatch[2]];
112- if (localizedValue || localizedValue == '') {
113- $(this).attr(attributeMatch[1], localizedValue);
114- }
115- } else { // No attribute tag, treat as 'text'
116- const localizedValue = localeData?.[key];
117- if (localizedValue || localizedValue == '') {
118- $(this).text(localizedValue);
119- }
120- }
121- }
122152 });
123153
124154 if (root !== document) {
@@ -126,7 +156,6 @@ export function applyLocale(root = document) {
126156 }
127157}
128158
129-
130159function addLanguagesToDropdown() {
131160 const uiLanguageSelects = $('#ui_language_select, #onboarding_ui_language_select');
132161 for (const langObj of langs) { // Set the value to the language code
@@ -159,6 +188,13 @@ export function initLocales() {
159188 location.reload();
160189 });
161190
191+ observer.observe(document, {
192+ childList: true,
193+ subtree: true,
194+ attributes: true,
195+ attributeFilter: ['data-i18n'],
196+ });
197+
162198 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);
163199 registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);
164200}