Files

60 lines
1.8 KiB
JavaScript

// Lightweight i18n loader. Translation data lives in `i18n/<lang>.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";
}