14c83e182a
Add react-i18next with two key-parity-checked catalogs (sq default/fallback, en). Active language driven by the logged-in user's stored preference (applied after /me resolves); SQ/EN toggle in the header persists via PUT /api/auth/language. Translate the booth (screen, pay/exit modal, active sessions, snapshots, status), Login, ShiftControl, SiteSettings, PermitManager, TariffComposer. SetupWizard deferred (its content is server-provided; needs backend catalog i18n).
34 lines
1.2 KiB
TypeScript
34 lines
1.2 KiB
TypeScript
import i18n from "i18next";
|
|
import { initReactI18next } from "react-i18next";
|
|
import { sq } from "./sq.js";
|
|
import { en } from "./en.js";
|
|
|
|
// i18next setup for the operator UI. Albanian (sq) is the DEFAULT and the fallback;
|
|
// English (en) is the second language. The active language is the LOGGED-IN USER's
|
|
// stored preference (users.language), applied via setLanguage() after auth resolves
|
|
// — not localStorage, not the browser. Printed tickets are NOT governed by this
|
|
// (always Albanian, customer-facing). See wiki/concepts/i18n.md.
|
|
|
|
export type Lang = "sq" | "en";
|
|
|
|
// Single flat namespace; keys are dot-paths (e.g. "booth.processTicket"). Nested
|
|
// objects in the catalogs are walked by i18next's keySeparator.
|
|
void i18n.use(initReactI18next).init({
|
|
resources: {
|
|
sq: { translation: sq },
|
|
en: { translation: en },
|
|
},
|
|
lng: "sq",
|
|
fallbackLng: "sq",
|
|
interpolation: { escapeValue: false }, // React already escapes
|
|
returnNull: false,
|
|
});
|
|
|
|
/** Apply a language (e.g. after login resolves the user's preference). No-op if
|
|
* already active. */
|
|
export function setLanguage(lang: Lang): void {
|
|
if (i18n.language !== lang) void i18n.changeLanguage(lang);
|
|
}
|
|
|
|
export default i18n;
|