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
| @@ -9,6 +9,9 @@ var langs; | |||
| 9 | // eslint-disable-next-line prefer-const | 9 | // eslint-disable-next-line prefer-const |
| 10 | var localeData; | 10 | var localeData; |
| 11 | 11 | ||
| 12 | /** @type {Array<string>|null} Array of translations keys if they should be tracked - if not tracked then null */ | ||
| 13 | let trackMissingDynamicTranslate = null; | ||
| 14 | |||
| 12 | export const getCurrentLocale = () => localeFile; | 15 | export const getCurrentLocale = () => localeFile; |
| 13 | 16 | ||
| 14 | /** | 17 | /** |
| @@ -97,6 +100,9 @@ export function t(strings, ...values) { | |||
| 97 | */ | 100 | */ |
| 98 | export function translate(text, key = null) { | 101 | export 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 | } |
| 102 | 108 | ||
| @@ -153,13 +159,28 @@ function translateElement(element) { | |||
| 153 | } | 159 | } |
| 154 | } | 160 | } |
| 155 | 161 | ||
| 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 | */ | ||
| 167 | function isSupportedNonEnglish(locale = null) { | ||
| 168 | const lang = locale || localeFile; | ||
| 169 | return lang && lang != 'en' && findLang(lang); | ||
| 170 | } | ||
| 156 | 171 | ||
| 157 | async function getMissingTranslations() { | 172 | async function getMissingTranslations() { |
| 173 | /** @type {Array<{key: string, language: string, value: string}>} */ | ||
| 158 | const missingData = []; | 174 | const missingData = []; |
| 159 | 175 | ||
| 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 strings | 182 | // 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)]; | ||
| 163 | 184 | ||
| 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 key | 191 | 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 | } |
| 204 | 222 | ||
| 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); |
| 207 | 227 | ||
| 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 | } |
| 210 | 235 | ||
| @@ -266,6 +291,31 @@ export async function initLocales() { | |||
| 266 | attributeFilter: ['data-i18n'], | 291 | attributeFilter: ['data-i18n'], |
| 267 | }); | 292 | }); |
| 268 | 293 | ||
| 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 | } |