Files
chimera-Public/renderer/modules/i18n.js
T

85 lines
3.1 KiB
JavaScript

// ── 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),
);