// ── Chimera i18n engine ─────────────────────────────────────────────────────── // Loaded first so window.t() is available to all modules. // Call window.i18n.load(code) to fetch a lang file and re-apply all translations. window.chimeraI18nReady = new Promise(resolve => { window.chimeraResolveI18nReady = resolve; }); window.i18n = { current: 'en', strings: {}, _observer: null, /** Translate a key. Falls back to `fallback` if provided, then the key itself. */ t(key, fallback) { const v = this.strings[key]; if (v !== undefined) return v; return fallback !== undefined ? fallback : key; }, /** Walk every [data-i18n] element and set its textContent to the translated string. * If no translation exists for a key the original text is left unchanged. */ apply(root = document) { document.documentElement.lang = this.current || 'en'; const select = selector => [ ...(root.matches?.(selector) ? [root] : []), ...(root.querySelectorAll?.(selector) || []), ]; select('[data-i18n]').forEach(el => { const val = this.strings[el.dataset.i18n]; if (val !== undefined) el.textContent = val; }); select('[data-i18n-placeholder]').forEach(el => { const val = this.strings[el.dataset.i18nPlaceholder]; if (val !== undefined) el.placeholder = val; }); select('[data-i18n-title]').forEach(el => { const val = this.strings[el.dataset.i18nTitle]; if (val !== undefined) el.title = val; }); select('[data-i18n-aria-label]').forEach(el => { const val = this.strings[el.dataset.i18nAriaLabel]; if (val !== undefined) el.setAttribute('aria-label', val); }); if (!this._observer && document.body) { this._observer = new MutationObserver(records => { for (const record of records) { for (const node of record.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) this.apply(node); } } }); this._observer.observe(document.body, { childList: true, subtree: true }); } }, /** Load a language file by code (e.g. "en", "fr", "de") via IPC, then apply. */ async load(code) { if (!code) code = 'en'; try { const data = await window.pork.langLoad(code); if (data && typeof data.strings === 'object') { this.strings = data.strings; this.current = data.meta?.code || code; } } catch (e) { console.warn('[i18n] Could not load language "' + code + '":', e.message || e); // Fall back to empty (original HTML text remains visible) this.strings = {}; this.current = 'en'; } this.apply(); window.dispatchEvent(new CustomEvent('chimera:language-changed', { detail: { code: this.current }, })); }, }; /** Shorthand global translate function */ window.t = (key, fallback) => window.i18n.t(key, fallback); window.tf = (key, fallback, values = {}) => Object.entries(values).reduce( (text, [name, value]) => text.replaceAll(`{${name}}`, String(value)), window.i18n.t(key, fallback), );