/* ============================================================
i18n engine + language switcher
- persists choice in localStorage ("al_lang"), shared across pages
- t(path) reads the active language from window.I18N (i18n_strings.jsx)
- useT() subscribes a component so it re-renders on language change
- replaces the old heart button (РУССКИЙ / ENGLISH /
Português / Español)
============================================================ */
const I18N_LANGS = [
{ code: "ru", label: "Русский", short: "RU" },
{ code: "en", label: "English", short: "EN" },
{ code: "pt", label: "Português", short: "PT" },
{ code: "es", label: "Español", short: "ES" },
{ code: "zh", label: "中文", short: "ZH" },
];
/* ---- minimal CSS flag chips (no emoji — render identically everywhere) ---- */
function Flag({ code, h = 12 }) {
const w = Math.round(h * 1.45);
// each flag is built from simple bands / a cross — kept deliberately flat
const flags = {
ru: { background: "linear-gradient(#fff 0 33.33%, #0039A6 33.33% 66.66%, #D52B1E 66.66% 100%)" },
en: { background: "#fff" }, // St George cross drawn with bars below
pt: { background: "linear-gradient(90deg, #006633 0 40%, #DA291C 40% 100%)" },
es: { background: "linear-gradient(#AA151B 0 25%, #F1BF00 25% 75%, #AA151B 75% 100%)" },
zh: { background: "#DE2910" }, // star approximated with a dot below
};
return (
{code === "zh" && (
)}
{code === "en" && (
)}
);
}
let _lang = (function () {
try { return localStorage.getItem("al_lang") || "ru"; } catch (e) { return "ru"; }
})();
const _subs = new Set();
function getLang() { return _lang; }
function setLang(code) {
if (!I18N_LANGS.some((l) => l.code === code)) return;
_lang = code;
try { localStorage.setItem("al_lang", code); } catch (e) {}
try { document.documentElement.lang = code; } catch (e) {}
_subs.forEach((fn) => fn(code));
}
function _dig(obj, path) {
return path.split(".").reduce((o, k) => (o == null ? o : o[k]), obj);
}
/* t("home.m1.sub") → string/array for the active language, with ru fallback */
function t(path, fallback) {
const D = window.I18N || {};
let v = _dig(D[_lang] || {}, path);
if (v == null) v = _dig(D.ru || {}, path);
if (v == null) return fallback != null ? fallback : path;
return v;
}
function useT() {
const [, force] = React.useState(0);
React.useEffect(() => {
const fn = () => force((n) => n + 1);
_subs.add(fn);
return () => { _subs.delete(fn); };
}, []);
return { t, lang: _lang, setLang };
}
/* keep in sync on first paint */
try { document.documentElement.lang = _lang; } catch (e) {}
/* ---- the switcher (drop-in replacement for the heart square) ---- */
function LangSwitcher({ tone = "out", menuAlign = "left", height = 40 }) {
const { lang, setLang } = useT();
const [open, setOpen] = React.useState(false);
const ref = React.useRef(null);
React.useEffect(() => {
if (!open) return;
const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
document.addEventListener("mousedown", onDoc);
document.addEventListener("keydown", onKey);
return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
}, [open]);
React.useEffect(() => {}, []);
const cur = I18N_LANGS.find((l) => l.code === lang) || I18N_LANGS[0];
// tone "out" = light square (matches the old heart over dark hero); "ink" = solid dark
const btn = tone === "ink"
? { background: "var(--ink)", color: "var(--paper-on-ink)", border: "1px solid var(--ink)" }
: { background: open ? "var(--surface)" : "var(--paper)", color: "var(--ink)", border: "1px solid var(--hairline-2)" };
const compact = height <= 28; // slim variant to match the thin menu bar
const flagH = compact ? 11 : 13;
const pad = compact ? "0 8px" : "0 11px";
const gap = compact ? 6 : 7;
return (
{I18N_LANGS.map((l) => {
const active = l.code === lang;
return (
);
})}
);
}
Object.assign(window, { I18N_LANGS, getLang, setLang, t, useT, LangSwitcher, Flag });