// Lightweight i18n loader. Translation data lives in `i18n/.json`, one // file per language so translators can edit each file independently. const I18N_AVAILABLE = ["en", "de", "pl", "ru", "fr", "es", "pt"]; const I18N_NAMES = { en: "English", de: "Deutsch", pl: "Polski", ru: "Русский", fr: "Français", es: "Español", pt: "Português" }; const I18N_BASE_PATH = "i18n"; const I18N_DATA = {}; const I18N_PENDING = {}; let CURRENT_LANG = "en"; function i18nURL(lang) { return `${I18N_BASE_PATH}/${lang}.json`; } async function loadLang(lang) { if (!I18N_AVAILABLE.includes(lang)) lang = "en"; if (I18N_DATA[lang]) return I18N_DATA[lang]; if (I18N_PENDING[lang]) return I18N_PENDING[lang]; const p = fetch(i18nURL(lang), { cache: "no-cache" }) .then((r) => r.ok ? r.json() : Promise.reject(new Error("i18n_load_failed"))) .then((data) => { I18N_DATA[lang] = data; delete I18N_PENDING[lang]; return data; }) .catch((err) => { delete I18N_PENDING[lang]; throw err; }); I18N_PENDING[lang] = p; return p; } async function setLang(lang) { if (!I18N_AVAILABLE.includes(lang)) lang = "en"; // English is the fallback for missing keys, so make sure it is always loaded. if (!I18N_DATA.en) { try { await loadLang("en"); } catch (e) {} } if (lang !== "en") { try { await loadLang(lang); } catch (e) {} } CURRENT_LANG = lang; document.documentElement.lang = lang; } function t(key, vars) { const d = I18N_DATA[CURRENT_LANG] || I18N_DATA.en || {}; const fallback = I18N_DATA.en || {}; let str = d[key] || fallback[key] || key; if (vars) { for (const k in vars) { str = str.replaceAll("{" + k + "}", vars[k]); } } return str; } function detectInitialLang() { const navLang = (navigator.language || "en").slice(0, 2); return I18N_AVAILABLE.includes(navLang) ? navLang : "en"; }