Debug Func to track missing dynamic translations - Add debug function to the debug menu that enables tracking of missing dynamic translations (via `t` or `translate`) once enabled - Only works with non-English language loaded - Prints it together with the existing debug print

08e184de261491810f31a2724bc8ec86c445ae91

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +57 -7Ignore whitespace
public/scripts/i18n.js+57 -7
@@ -9,6 +9,9 @@ var langs;
9// eslint-disable-next-line prefer-const9// eslint-disable-next-line prefer-const
10var localeData;10var localeData;
1111
12/** @type {Array<string>|null} Array of translations keys if they should be tracked - if not tracked then null */
13let trackMissingDynamicTranslate = null;
14
12export const getCurrentLocale = () => localeFile;15export const getCurrentLocale = () => localeFile;
1316
14/**17/**
@@ -97,6 +100,9 @@ export function t(strings, ...values) {
97 */100 */
98export function translate(text, key = null) {101export function translate(text, key = null) {
99 const translationKey = key || text;102 const translationKey = key || text;
103 if (trackMissingDynamicTranslate && localeData && !Object.hasOwn(localeData, translationKey) && !trackMissingDynamicTranslate.includes(translationKey)) {
104 trackMissingDynamicTranslate.push(translationKey);
105 }
100 return localeData?.[translationKey] || text;106 return localeData?.[translationKey] || text;
101}107}
102108
@@ -153,13 +159,28 @@ function translateElement(element) {
153 }159 }
154}160}
155161
162/**
163 * Checks if the given locale is supported and not English.
164 * @param {string} [locale=null] The locale to check (defaults to the current locale)
165 * @returns {boolean} True if the locale is not English and supported
166 */
167function isSupportedNonEnglish(locale = null) {
168 const lang = locale || localeFile;
169 return lang && lang != 'en' && findLang(lang);
170}
156171
157async function getMissingTranslations() {172async function getMissingTranslations() {
173 /** @type {Array<{key: string, language: string, value: string}>} */
158 const missingData = [];174 const missingData = [];
159175
176 if (trackMissingDynamicTranslate) {
177 for (const key of trackMissingDynamicTranslate) {
178 missingData.push({ key, language: localeFile, value: key });
179 }
180 }
181
160 // Determine locales to search for untranslated strings182 // Determine locales to search for untranslated strings
161 const isNotSupported = !findLang(localeFile);183 const langsToProcess = isSupportedNonEnglish() ? [findLang(localeFile)] : langs;
162 const langsToProcess = (isNotSupported || localeFile == 'en') ? langs : [findLang(localeFile)];
163184
164 for (const language of langsToProcess) {185 for (const language of langsToProcess) {
165 const localeData = await getLocaleData(language.lang);186 const localeData = await getLocaleData(language.lang);
@@ -170,7 +191,7 @@ async function getMissingTranslations() {
170 if (attributeMatch) { // attribute-tagged key191 if (attributeMatch) { // attribute-tagged key
171 const localizedValue = localeData?.[attributeMatch[2]];192 const localizedValue = localeData?.[attributeMatch[2]];
172 if (!localizedValue) {193 if (!localizedValue) {
173 missingData.push({ key, language: language.lang, value: $(this).attr(attributeMatch[1]) });194 missingData.push({ key, language: language.lang, value: String($(this).attr(attributeMatch[1])) });
174 }195 }
175 } else { // No attribute tag, treat as 'text'196 } else { // No attribute tag, treat as 'text'
176 const localizedValue = localeData?.[key];197 const localizedValue = localeData?.[key];
@@ -196,15 +217,19 @@ async function getMissingTranslations() {
196 // Map to { language: { key: value } }217 // Map to { language: { key: value } }
197 let missingDataMap = {};218 let missingDataMap = {};
198 for (const { key, value } of uniqueMissingData) {219 for (const { key, value } of uniqueMissingData) {
199 if (!missingDataMap) {
200 missingDataMap = {};
201 }
202 missingDataMap[key] = value;220 missingDataMap[key] = value;
203 }221 }
204222
223 console.log('Missing Translations:');
205 console.table(uniqueMissingData);224 console.table(uniqueMissingData);
225 console.log('Full map of missing data:');
206 console.log(missingDataMap);226 console.log(missingDataMap);
207227
228 if (trackMissingDynamicTranslate) {
229 console.log('Dynamic translations missing:');
230 console.log(trackMissingDynamicTranslate);
231 }
232
208 toastr.success(`Found ${uniqueMissingData.length} missing translations. See browser console for details.`);233 toastr.success(`Found ${uniqueMissingData.length} missing translations. See browser console for details.`);
209}234}
210235
@@ -266,6 +291,31 @@ export async function initLocales() {
266 attributeFilter: ['data-i18n'],291 attributeFilter: ['data-i18n'],
267 });292 });
268293
269 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);294 if (localStorage.getItem('trackDynamicTranslate') === 'true' && isSupportedNonEnglish()) {
295 trackMissingDynamicTranslate = [];
296 }
297
298 registerDebugFunction('getMissingTranslations', 'Get missing translations',
299 'Detects missing localization data in the current locale and dumps the data into the browser console. ' +
300 'If the current locale is English, searches all other locales.',
301 getMissingTranslations);
302 registerDebugFunction('trackDynamicTranslate', 'Track dynamic translation',
303 'Toggles tracking of dynamic translations, which will be dumped into the missing translations translations too. ' +
304 'This includes things translated via the t`...` function and translate(). It will only track strings translated <b>after</b> this is toggled on, '
305 + 'and when they actually pop up, so refreshing the page and opening popups, etc, is needed. Will only track if the current locale is not English.',
306 () => {
307 const isTracking = localStorage.getItem('trackDynamicTranslate') !== 'true';
308 localStorage.setItem('trackDynamicTranslate', isTracking ? 'true' : 'false');
309 if (isTracking && isSupportedNonEnglish()) {
310 trackMissingDynamicTranslate = [];
311 toastr.success('Dynamic translation tracking enabled.');
312 } else if (isTracking) {
313 trackMissingDynamicTranslate = null;
314 toastr.warning('Dynamic translation tracking enabled, but will not be tracked with locale English.');
315 } else {
316 trackMissingDynamicTranslate = null;
317 toastr.info('Dynamic translation tracking disabled.');
318 }
319 });
270 registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);320 registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);
271}321}