// Tiny reactive i18n for the SPA (vue-i18n is deliberately not pulled in) —
// паттерн хендбука (10-platform/i18n.md), референс gnexus-creds: locale ref,
// t() с {param}-интерполяцией и славскими плюрализациями, плоские словари по
// областям. Дефолт ставится из бэкенда: locale_effective (override в user_prefs
// → profile.locale аккаунта → en) после me(); меняется в Настройках (PATCH /me).
import { ref, watch } from "vue";
import en from "./messages/en.js";
import ru from "./messages/ru.js";
import uk from "./messages/uk.js";
const messages = { en, uk, ru };
// Language tags normalize to their 2-letter prefix ("en-US" -> "en");
// anything the dictionaries don't ship falls back to English.
export const SUPPORTED_LOCALES = Object.keys(messages);
export const locale = ref("en");
function normalizeTag(tag) {
const prefix = String(tag || "").trim().toLowerCase().split(/[-_]/)[0];
return messages[prefix] ? prefix : null;
}
export function setLocale(tag) {
locale.value = normalizeTag(tag) || "en";
}
watch(locale, (lang) => {
if (typeof document !== "undefined") {
document.documentElement.lang = lang;
// Заголовок таба — из словаря (hard-panel паттерн): index.html несёт
// статический русский дефолт, словарь правит его на лету.
document.title = messages[lang]["app.docTitle"] || document.title;
}
});
function pluralCategory(lang, n) {
// ru/uk: n%10==1 (except 11) -> one; 2-4 (except 12-14) -> few; else many
const mod10 = n % 10;
const mod100 = n % 100;
if (lang === "ru" || lang === "uk") {
if (mod10 === 1 && mod100 !== 11) return "one";
if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return "few";
return "many";
}
return n === 1 ? "one" : "other";
}
function interpolate(text, params) {
return text.replace(/\{(\w+)\}/g, (match, key) =>
params && key in params ? String(params[key]) : match
);
}
function lookup(slug) {
return messages[locale.value]?.[slug] ?? messages.en[slug];
}
export function t(slug, params) {
let text = lookup(slug);
if (text != null && typeof text === "object") {
// plural dictionary {one, few, many, other}; a missing form falls to other
const n = Number(params?.n ?? 0);
text = text[pluralCategory(locale.value, n)] ?? text.other;
}
return text != null ? interpolate(String(text), params) : slug;
}