/* ============================================================ 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 });