/** * Crash console i18n — JSON catalogs (en default), same key style as Android strings.xml. */ (function (global) { const STORAGE_KEY = 'crash_console_lang'; const SUPPORTED = ['en', 'ru']; const DEFAULT_LANG = 'en'; /** @type {Record} */ const LANG_DISPLAY = { en: { flag: '\uD83C\uDDEC\uD83C\uDDE7', code: 'EN' }, ru: { flag: '\uD83C\uDDF7\uD83C\uDDFA', code: 'RU' }, }; let lang = DEFAULT_LANG; let catalog = {}; let fallback = {}; let readyResolve; const ready = new Promise((resolve) => { readyResolve = resolve; }); function basePath() { const body = document.body; return body ? body.getAttribute('data-base-path') || '' : ''; } /** Close stray modal layers so fixed backdrops never block login/console UI. */ function dismissPlatformOverlays() { document.querySelectorAll('#graph-detail-overlay, #tag-modal, #hub-docs-modal').forEach((el) => { el.hidden = true; el.setAttribute('aria-hidden', 'true'); }); const dialog = document.getElementById('ticket-create-dialog'); if (dialog && dialog.open && typeof dialog.close === 'function') { dialog.close(); } } function storedLang() { try { const v = localStorage.getItem(STORAGE_KEY); return SUPPORTED.includes(v) ? v : DEFAULT_LANG; } catch { return DEFAULT_LANG; } } function persistLang(code) { try { localStorage.setItem(STORAGE_KEY, code); } catch { /* ignore */ } } function fetchCatalog(code) { const url = basePath() + '/assets/i18n/' + encodeURIComponent(code) + '.json'; return fetch(url, { credentials: 'same-origin' }).then((res) => { if (!res.ok) throw new Error('i18n load failed: ' + code); return res.json(); }); } /** * @param {string} key * @param {Record|undefined} params */ function t(key, params) { let s = catalog[key] ?? fallback[key] ?? key; if (params) { Object.keys(params).forEach((k) => { s = s.split('{' + k + '}').join(String(params[k])); }); } return s; } function apply(root) { const scope = root || document; scope.querySelectorAll('[data-i18n]').forEach((el) => { const key = el.getAttribute('data-i18n'); if (!key) return; if (el.hasAttribute('data-i18n-html')) { el.innerHTML = t(key); } else { el.textContent = t(key); } }); scope.querySelectorAll('[data-i18n-placeholder]').forEach((el) => { const key = el.getAttribute('data-i18n-placeholder'); if (key) el.setAttribute('placeholder', t(key)); }); scope.querySelectorAll('[data-i18n-title]').forEach((el) => { const key = el.getAttribute('data-i18n-title'); if (key) el.setAttribute('title', t(key)); }); scope.querySelectorAll('[data-i18n-aria]').forEach((el) => { const key = el.getAttribute('data-i18n-aria'); if (key) el.setAttribute('aria-label', t(key)); }); scope.querySelectorAll('select[data-i18n-options]').forEach((sel) => { const prefix = sel.getAttribute('data-i18n-options'); if (!prefix) return; sel.querySelectorAll('option[data-i18n]').forEach((opt) => { const k = opt.getAttribute('data-i18n'); if (k) opt.textContent = t(k); }); }); scope.querySelectorAll('.platform-footer__left[data-i18n], footer[data-i18n="footer.copyright"]').forEach((footer) => { const year = footer.getAttribute('data-year') || String(new Date().getFullYear()); footer.textContent = t(footer.getAttribute('data-i18n') || 'footer.copyright', { year }); }); scope.querySelectorAll('[data-i18n="detail.report_meta"]').forEach((el) => { el.textContent = t('detail.report_meta', { id: el.getAttribute('data-i18n-id') || '', type: el.getAttribute('data-i18n-type') || '', }); }); } function langOptionLabel(code) { const meta = LANG_DISPLAY[code] || LANG_DISPLAY[DEFAULT_LANG]; return meta.flag + ' ' + meta.code; } function syncLangSelect() { document.querySelectorAll('.lang-select').forEach((sel) => { SUPPORTED.forEach((code) => { const opt = sel.querySelector('option[value="' + code + '"]'); if (opt) opt.textContent = langOptionLabel(code); }); if (sel.value !== lang) sel.value = lang; }); syncLocaleDisplay(lang); } function syncLocaleDisplay(code) { const meta = LANG_DISPLAY[code] || LANG_DISPLAY[DEFAULT_LANG]; document.querySelectorAll('.locale-flag').forEach((el) => { el.textContent = meta.flag; }); document.querySelectorAll('.locale-code').forEach((el) => { el.textContent = meta.code; }); } async function setLang(code, options) { const opts = options || {}; if (!SUPPORTED.includes(code)) code = DEFAULT_LANG; fallback = await fetchCatalog(DEFAULT_LANG); catalog = code === DEFAULT_LANG ? { ...fallback } : await fetchCatalog(code); lang = code; document.documentElement.setAttribute('lang', code); persistLang(code); syncLangSelect(); if (!opts.silent) { apply(document); document.dispatchEvent(new CustomEvent('crash-i18n-change', { detail: { lang: code } })); } } function getLang() { return lang; } function initLangSelect() { document.querySelectorAll('.lang-select').forEach((sel) => { sel.value = lang; if (sel.dataset.i18nBound === '1') return; sel.dataset.i18nBound = '1'; sel.addEventListener('change', () => { setLang(sel.value).catch(() => setLang(DEFAULT_LANG)); }); }); } async function init() { dismissPlatformOverlays(); lang = storedLang(); document.documentElement.setAttribute('lang', lang); await setLang(lang, { silent: true }); apply(document); initLangSelect(); syncLocaleDisplay(lang); readyResolve(); } global.CrashI18n = { t, apply, setLang, getLang, init, ready, SUPPORTED, }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { init().catch(() => { lang = DEFAULT_LANG; readyResolve(); }); }); } else { init().catch(() => readyResolve()); } })(typeof window !== 'undefined' ? window : globalThis);