import pl from "../locales/pl.js"; import en from "../locales/en.js"; import de from "../locales/de.js"; const STORAGE_KEY = "karczma_lang"; const catalogs = { pl, en, de }; let currentLang = "pl"; /** @type {Set<(lang: string) => void>} */ const listeners = new Set(); function interpolate(template, vars = {}) { return String(template).replace(/\{(\w+)\}/g, (_, key) => { return vars[key] != null ? String(vars[key]) : `{${key}}`; }); } export function getAvailableLanguages() { const fromConfig = window.APP_CONFIG?.languages; if (Array.isArray(fromConfig) && fromConfig.length) { return fromConfig; } return [ { code: "pl", label: "Polski", flag: "🇵🇱" }, { code: "en", label: "English", flag: "🇬🇧" }, { code: "de", label: "Deutsch", flag: "🇩🇪" }, ]; } export function getLang() { return currentLang; } export function t(key, vars = {}) { const catalog = catalogs[currentLang] || catalogs.pl; const fallback = catalogs.pl; const value = catalog[key] ?? fallback[key] ?? key; return interpolate(value, vars); } export function applyTranslations(root = document) { root.querySelectorAll("[data-i18n]").forEach((el) => { const key = el.getAttribute("data-i18n"); if (!key) return; const html = el.hasAttribute("data-i18n-html"); const text = t(key); if (html) { el.innerHTML = text; } else { el.textContent = text; } }); root.querySelectorAll("[data-i18n-placeholder]").forEach((el) => { const key = el.getAttribute("data-i18n-placeholder"); if (!key) return; el.setAttribute("placeholder", t(key)); }); root.querySelectorAll("[data-i18n-aria]").forEach((el) => { const key = el.getAttribute("data-i18n-aria"); if (!key) return; el.setAttribute("aria-label", t(key)); }); document.documentElement.lang = t("html.lang"); document.title = t("doc.title"); } export function onLangChange(callback) { listeners.add(callback); return () => listeners.delete(callback); } export function setLang(lang, { persist = true, notify = true } = {}) { const next = catalogs[lang] ? lang : "pl"; const changed = next !== currentLang; currentLang = next; if (persist) { try { localStorage.setItem(STORAGE_KEY, currentLang); } catch { // ignore } } applyTranslations(); if (notify && changed) { listeners.forEach((cb) => { try { cb(currentLang); } catch (err) { console.warn("[i18n] listener failed", err); } }); } return currentLang; } export function initI18n() { let stored = ""; try { stored = localStorage.getItem(STORAGE_KEY) || ""; } catch { stored = ""; } const initial = catalogs[stored] ? stored : "pl"; return setLang(initial, { persist: true, notify: false }); } function flagImgHtml(code) { const safe = String(code || "pl").toLowerCase().replace(/[^a-z]/g, "") || "pl"; return ``; } export function createLanguagePicker({ idPrefix = "lang" } = {}) { const wrap = document.createElement("div"); wrap.className = "lang-picker"; wrap.dataset.langPicker = "1"; const btn = document.createElement("button"); btn.type = "button"; btn.className = "lang-picker-btn"; btn.id = `${idPrefix}Btn`; btn.setAttribute("aria-haspopup", "listbox"); btn.setAttribute("aria-expanded", "false"); const menu = document.createElement("div"); menu.className = "lang-picker-menu hidden"; menu.id = `${idPrefix}Menu`; menu.setAttribute("role", "listbox"); function refresh() { const langs = getAvailableLanguages(); const current = langs.find((l) => l.code === getLang()) || langs[0]; const code = current?.code || "pl"; btn.innerHTML = flagImgHtml(code); btn.setAttribute("aria-label", `${t("lang.aria")}: ${current?.label || code}`); menu.innerHTML = ""; langs.forEach((lang) => { const option = document.createElement("button"); option.type = "button"; option.className = "lang-picker-option" + (lang.code === getLang() ? " is-active" : ""); option.setAttribute("role", "option"); option.setAttribute("aria-selected", lang.code === getLang() ? "true" : "false"); option.innerHTML = `${flagImgHtml(lang.code)}${lang.label}`; option.addEventListener("click", (e) => { e.stopPropagation(); close(); if (lang.code !== getLang()) { setLang(lang.code); } }); menu.appendChild(option); }); } function open() { menu.classList.remove("hidden"); btn.setAttribute("aria-expanded", "true"); wrap.classList.add("is-open"); } function close() { menu.classList.add("hidden"); btn.setAttribute("aria-expanded", "false"); wrap.classList.remove("is-open"); } btn.addEventListener("click", (e) => { e.stopPropagation(); if (menu.classList.contains("hidden")) open(); else close(); }); document.addEventListener("click", (e) => { if (!wrap.contains(e.target)) close(); }); wrap.appendChild(btn); wrap.appendChild(menu); refresh(); onLangChange(() => refresh()); return { el: wrap, refresh, close }; }