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()
10var localeData = await getLocaleData(localeFile);10var localeData = await getLocaleData(localeFile);
1111
12/**12/**
13 * An observer that will check if any new i18n elements are added to the document
14 * @type {MutationObserver}
15 */
16const 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/**
13 * Fetches the locale data for the given language.35 * Fetches the locale data for the given language.
14 * @param {string} language Language code36 * @param {string} language Language code
15 * @returns {Promise<Record<string, string>>} Locale data37 * @returns {Promise<Record<string, string>>} Locale data
@@ -40,6 +62,29 @@ function findLang(language) {
40 return supportedLang;62 return supportedLang;
41}63}
4264
65/**
66 * Translates a given element based on its data-i18n attribute.
67 * @param {Element} element The element to translate
68 */
69function 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
43async function getMissingTranslations() {88async function getMissingTranslations() {
44 const missingData = [];89 const missingData = [];
4590
@@ -103,22 +148,7 @@ export function applyLocale(root = document) {
103148
104 //find all the elements with `data-i18n` attribute149 //find all the elements with `data-i18n` attribute
105 $root.find('[data-i18n]').each(function () {150 $root.find('[data-i18n]').each(function () {
106 //read the translation from the language data151 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 }
122 });152 });
123153
124 if (root !== document) {154 if (root !== document) {
@@ -126,7 +156,6 @@ export function applyLocale(root = document) {
126 }156 }
127}157}
128158
129
130function addLanguagesToDropdown() {159function addLanguagesToDropdown() {
131 const uiLanguageSelects = $('#ui_language_select, #onboarding_ui_language_select');160 const uiLanguageSelects = $('#ui_language_select, #onboarding_ui_language_select');
132 for (const langObj of langs) { // Set the value to the language code161 for (const langObj of langs) { // Set the value to the language code
@@ -159,6 +188,13 @@ export function initLocales() {
159 location.reload();188 location.reload();
160 });189 });
161190
191 observer.observe(document, {
192 childList: true,
193 subtree: true,
194 attributes: true,
195 attributeFilter: ['data-i18n'],
196 });
197
162 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);198 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);
163 registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);199 registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);
164}200}