All checks were successful
CI / Format (push) Successful in 42s
CI / CUDA type-check (push) Successful in 1m41s
CI / Clippy (push) Successful in 3m9s
CI / Test (push) Successful in 6m30s
CI / Build cortex SRPM (push) Has been skipped
CI / Build neuron SRPM (push) Has been skipped
CI / Publish cortex to COPR (push) Has been skipped
CI / Publish neuron to COPR (push) Has been skipped
CI / Bump version in source (push) Has been skipped
The self-service account surface consuming helexa-upstream's /web/v1 (B4/B5), with a mock so it works before/independent of the live backend. - api/types.ts + api/account.ts: typed AccountApi over a same-origin `/api` prefix (vite-proxied in dev, nginx in prod) covering register/verify/ login/password-reset/keys(list,create,archive,limit)/account/redeem; ApiError carries the backend code. MockAccountApi behind VITE_USE_MOCK_ACCOUNT_API (in-memory account, raw-key-once, redeem). - auth/: context + useAuth, AuthProvider (JWT in localStorage, login fetches the account and runs claimAnonymousData → anon IndexedDB history is re-owned to the account, still client-side), RequireAuth guard (→ /login?next=). - pages/auth/: Login, Register (sends the FingerprintJS visitor id → triggers the silent abuse detection), VerifyEmail (?token), RequestReset, ResetPassword (?token, matches the backend /reset?token= link). - pages/account/: Dashboard (allocation balance + usage bar, redeem top-up, logout) and ApiKeys (list, create-modal showing the raw key ONCE with copy, per-key limit editor percent↔hardcap, archive). 401 → logout. - App wraps AuthProvider + routes (account guarded); Header auth cluster reflects useAuth (Account/Sign out vs Sign in/up). - `account` i18n namespace (53 keys) added + wired across all 32 langs. Validated: lint, typecheck, build, i18n:check, lang-labels all green. Explicit-path commit; no node_modules/dist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F6o3ddqmYNh9kzdwq6eowh
425 lines
12 KiB
TypeScript
425 lines
12 KiB
TypeScript
import i18n, { type Resource } from "i18next";
|
|
import { initReactI18next } from "react-i18next";
|
|
import {
|
|
SUPPORTED_LANGUAGES,
|
|
normalizeLocaleToLanguage,
|
|
isRtlLanguage,
|
|
} from "./languages";
|
|
import type { LanguageCode } from "./languages";
|
|
|
|
// Core languages
|
|
import enCommon from "./resources/en/common.json";
|
|
import ruCommon from "./resources/ru/common.json";
|
|
import enMission from "./resources/en/mission.json";
|
|
import ruMission from "./resources/ru/mission.json";
|
|
import enChat from "./resources/en/chat.json";
|
|
import enAccount from "./resources/en/account.json";
|
|
import ruChat from "./resources/ru/chat.json";
|
|
import ruAccount from "./resources/ru/account.json";
|
|
|
|
// Scandinavian & Nordic languages
|
|
import daCommon from "./resources/da/common.json";
|
|
import daMission from "./resources/da/mission.json";
|
|
import daChat from "./resources/da/chat.json";
|
|
import daAccount from "./resources/da/account.json";
|
|
|
|
import fiCommon from "./resources/fi/common.json";
|
|
import fiMission from "./resources/fi/mission.json";
|
|
import fiChat from "./resources/fi/chat.json";
|
|
import fiAccount from "./resources/fi/account.json";
|
|
|
|
import noCommon from "./resources/no/common.json";
|
|
import noMission from "./resources/no/mission.json";
|
|
import noChat from "./resources/no/chat.json";
|
|
import noAccount from "./resources/no/account.json";
|
|
|
|
import svCommon from "./resources/sv/common.json";
|
|
import svMission from "./resources/sv/mission.json";
|
|
import svChat from "./resources/sv/chat.json";
|
|
import svAccount from "./resources/sv/account.json";
|
|
|
|
import bgCommon from "./resources/bg/common.json";
|
|
import bgMission from "./resources/bg/mission.json";
|
|
import bgChat from "./resources/bg/chat.json";
|
|
import bgAccount from "./resources/bg/account.json";
|
|
|
|
import etCommon from "./resources/et/common.json";
|
|
import etMission from "./resources/et/mission.json";
|
|
import etChat from "./resources/et/chat.json";
|
|
import etAccount from "./resources/et/account.json";
|
|
|
|
// African & MENA languages
|
|
import swCommon from "./resources/sw/common.json";
|
|
import swMission from "./resources/sw/mission.json";
|
|
import swChat from "./resources/sw/chat.json";
|
|
import swAccount from "./resources/sw/account.json";
|
|
|
|
import arCommon from "./resources/ar/common.json";
|
|
import arMission from "./resources/ar/mission.json";
|
|
import arChat from "./resources/ar/chat.json";
|
|
import arAccount from "./resources/ar/account.json";
|
|
|
|
import faCommon from "./resources/fa/common.json";
|
|
import faMission from "./resources/fa/mission.json";
|
|
import faChat from "./resources/fa/chat.json";
|
|
import faAccount from "./resources/fa/account.json";
|
|
|
|
import haCommon from "./resources/ha/common.json";
|
|
import haMission from "./resources/ha/mission.json";
|
|
import haChat from "./resources/ha/chat.json";
|
|
import haAccount from "./resources/ha/account.json";
|
|
|
|
import amCommon from "./resources/am/common.json";
|
|
import amMission from "./resources/am/mission.json";
|
|
import amChat from "./resources/am/chat.json";
|
|
import amAccount from "./resources/am/account.json";
|
|
|
|
import yoCommon from "./resources/yo/common.json";
|
|
import yoMission from "./resources/yo/mission.json";
|
|
import yoChat from "./resources/yo/chat.json";
|
|
import yoAccount from "./resources/yo/account.json";
|
|
|
|
import zuCommon from "./resources/zu/common.json";
|
|
import zuMission from "./resources/zu/mission.json";
|
|
import zuChat from "./resources/zu/chat.json";
|
|
import zuAccount from "./resources/zu/account.json";
|
|
|
|
// Darija (Moroccan Arabic)
|
|
import maCommon from "./resources/ma/common.json";
|
|
import maMission from "./resources/ma/mission.json";
|
|
import maChat from "./resources/ma/chat.json";
|
|
import maAccount from "./resources/ma/account.json";
|
|
|
|
// European / other languages
|
|
import esCommon from "./resources/es/common.json";
|
|
import esMission from "./resources/es/mission.json";
|
|
import esChat from "./resources/es/chat.json";
|
|
import esAccount from "./resources/es/account.json";
|
|
|
|
import frCommon from "./resources/fr/common.json";
|
|
import frMission from "./resources/fr/mission.json";
|
|
import frChat from "./resources/fr/chat.json";
|
|
import frAccount from "./resources/fr/account.json";
|
|
|
|
import deCommon from "./resources/de/common.json";
|
|
import deMission from "./resources/de/mission.json";
|
|
import deChat from "./resources/de/chat.json";
|
|
import deAccount from "./resources/de/account.json";
|
|
|
|
import elCommon from "./resources/el/common.json";
|
|
import elMission from "./resources/el/mission.json";
|
|
import elChat from "./resources/el/chat.json";
|
|
import elAccount from "./resources/el/account.json";
|
|
|
|
import itCommon from "./resources/it/common.json";
|
|
import itMission from "./resources/it/mission.json";
|
|
import itChat from "./resources/it/chat.json";
|
|
import itAccount from "./resources/it/account.json";
|
|
|
|
import heCommon from "./resources/he/common.json";
|
|
import heMission from "./resources/he/mission.json";
|
|
import heChat from "./resources/he/chat.json";
|
|
import heAccount from "./resources/he/account.json";
|
|
|
|
import ptCommon from "./resources/pt/common.json";
|
|
import ptMission from "./resources/pt/mission.json";
|
|
import ptChat from "./resources/pt/chat.json";
|
|
import ptAccount from "./resources/pt/account.json";
|
|
|
|
import roCommon from "./resources/ro/common.json";
|
|
import roMission from "./resources/ro/mission.json";
|
|
import roChat from "./resources/ro/chat.json";
|
|
import roAccount from "./resources/ro/account.json";
|
|
|
|
import kaCommon from "./resources/ka/common.json";
|
|
import kaMission from "./resources/ka/mission.json";
|
|
import kaChat from "./resources/ka/chat.json";
|
|
import kaAccount from "./resources/ka/account.json";
|
|
|
|
import trCommon from "./resources/tr/common.json";
|
|
import trMission from "./resources/tr/mission.json";
|
|
import trChat from "./resources/tr/chat.json";
|
|
import trAccount from "./resources/tr/account.json";
|
|
|
|
import plCommon from "./resources/pl/common.json";
|
|
import plMission from "./resources/pl/mission.json";
|
|
import plChat from "./resources/pl/chat.json";
|
|
import plAccount from "./resources/pl/account.json";
|
|
|
|
import ukCommon from "./resources/uk/common.json";
|
|
import ukMission from "./resources/uk/mission.json";
|
|
import ukChat from "./resources/uk/chat.json";
|
|
import ukAccount from "./resources/uk/account.json";
|
|
|
|
import nlCommon from "./resources/nl/common.json";
|
|
import nlMission from "./resources/nl/mission.json";
|
|
import nlChat from "./resources/nl/chat.json";
|
|
import nlAccount from "./resources/nl/account.json";
|
|
|
|
import srCommon from "./resources/sr/common.json";
|
|
import srMission from "./resources/sr/mission.json";
|
|
import srChat from "./resources/sr/chat.json";
|
|
import srAccount from "./resources/sr/account.json";
|
|
|
|
import kkCommon from "./resources/kk/common.json";
|
|
import kkMission from "./resources/kk/mission.json";
|
|
import kkChat from "./resources/kk/chat.json";
|
|
import kkAccount from "./resources/kk/account.json";
|
|
|
|
import uzCommon from "./resources/uz/common.json";
|
|
import uzMission from "./resources/uz/mission.json";
|
|
import uzChat from "./resources/uz/chat.json";
|
|
import uzAccount from "./resources/uz/account.json";
|
|
|
|
/**
|
|
* Application translation resources, split by language and namespace.
|
|
*
|
|
* - `common`: shared UI elements (navigation, theme toggle, etc.)
|
|
* - `home`: marketing / narrative copy on the landing page
|
|
* - `chat`: copy for the chat workspace
|
|
*/
|
|
const resources: Resource = {
|
|
en: {
|
|
common: enCommon,
|
|
mission: enMission,
|
|
chat: enChat,
|
|
account: enAccount,
|
|
},
|
|
ru: {
|
|
common: ruCommon,
|
|
mission: ruMission,
|
|
chat: ruChat,
|
|
account: ruAccount,
|
|
},
|
|
bg: {
|
|
common: bgCommon,
|
|
mission: bgMission,
|
|
chat: bgChat,
|
|
account: bgAccount,
|
|
},
|
|
da: {
|
|
common: daCommon,
|
|
mission: daMission,
|
|
chat: daChat,
|
|
account: daAccount,
|
|
},
|
|
et: {
|
|
common: etCommon,
|
|
mission: etMission,
|
|
chat: etChat,
|
|
account: etAccount,
|
|
},
|
|
fi: {
|
|
common: fiCommon,
|
|
mission: fiMission,
|
|
chat: fiChat,
|
|
account: fiAccount,
|
|
},
|
|
kk: {
|
|
common: kkCommon,
|
|
mission: kkMission,
|
|
chat: kkChat,
|
|
account: kkAccount,
|
|
},
|
|
uz: {
|
|
common: uzCommon,
|
|
mission: uzMission,
|
|
chat: uzChat,
|
|
account: uzAccount,
|
|
},
|
|
|
|
// African & MENA languages (LTR unless marked RTL via isRtlLanguage)
|
|
sw: {
|
|
common: swCommon,
|
|
mission: swMission,
|
|
chat: swChat,
|
|
account: swAccount,
|
|
},
|
|
ar: {
|
|
common: arCommon,
|
|
mission: arMission,
|
|
chat: arChat,
|
|
account: arAccount,
|
|
},
|
|
fa: {
|
|
common: faCommon,
|
|
mission: faMission,
|
|
chat: faChat,
|
|
account: faAccount,
|
|
},
|
|
ha: {
|
|
common: haCommon,
|
|
mission: haMission,
|
|
chat: haChat,
|
|
account: haAccount,
|
|
},
|
|
am: {
|
|
common: amCommon,
|
|
mission: amMission,
|
|
chat: amChat,
|
|
account: amAccount,
|
|
},
|
|
yo: {
|
|
common: yoCommon,
|
|
mission: yoMission,
|
|
chat: yoChat,
|
|
account: yoAccount,
|
|
},
|
|
zu: {
|
|
common: zuCommon,
|
|
mission: zuMission,
|
|
chat: zuChat,
|
|
account: zuAccount,
|
|
},
|
|
ma: {
|
|
common: maCommon,
|
|
mission: maMission,
|
|
chat: maChat,
|
|
account: maAccount,
|
|
},
|
|
|
|
// European & other languages
|
|
es: {
|
|
common: esCommon,
|
|
mission: esMission,
|
|
chat: esChat,
|
|
account: esAccount,
|
|
},
|
|
fr: {
|
|
common: frCommon,
|
|
mission: frMission,
|
|
chat: frChat,
|
|
account: frAccount,
|
|
},
|
|
de: {
|
|
common: deCommon,
|
|
mission: deMission,
|
|
chat: deChat,
|
|
account: deAccount,
|
|
},
|
|
el: {
|
|
common: elCommon,
|
|
mission: elMission,
|
|
chat: elChat,
|
|
account: elAccount,
|
|
},
|
|
it: {
|
|
common: itCommon,
|
|
mission: itMission,
|
|
chat: itChat,
|
|
account: itAccount,
|
|
},
|
|
he: {
|
|
common: heCommon,
|
|
mission: heMission,
|
|
chat: heChat,
|
|
account: heAccount,
|
|
},
|
|
pt: {
|
|
common: ptCommon,
|
|
mission: ptMission,
|
|
chat: ptChat,
|
|
account: ptAccount,
|
|
},
|
|
ro: {
|
|
common: roCommon,
|
|
mission: roMission,
|
|
chat: roChat,
|
|
account: roAccount,
|
|
},
|
|
ka: {
|
|
common: kaCommon,
|
|
mission: kaMission,
|
|
chat: kaChat,
|
|
account: kaAccount,
|
|
},
|
|
tr: {
|
|
common: trCommon,
|
|
mission: trMission,
|
|
chat: trChat,
|
|
account: trAccount,
|
|
},
|
|
pl: {
|
|
common: plCommon,
|
|
mission: plMission,
|
|
chat: plChat,
|
|
account: plAccount,
|
|
},
|
|
uk: {
|
|
common: ukCommon,
|
|
mission: ukMission,
|
|
chat: ukChat,
|
|
account: ukAccount,
|
|
},
|
|
nl: {
|
|
common: nlCommon,
|
|
mission: nlMission,
|
|
chat: nlChat,
|
|
account: nlAccount,
|
|
},
|
|
sr: {
|
|
common: srCommon,
|
|
mission: srMission,
|
|
chat: srChat,
|
|
account: srAccount,
|
|
},
|
|
no: {
|
|
common: noCommon,
|
|
mission: noMission,
|
|
chat: noChat,
|
|
account: noAccount,
|
|
},
|
|
sv: {
|
|
common: svCommon,
|
|
mission: svMission,
|
|
chat: svChat,
|
|
account: svAccount,
|
|
},
|
|
};
|
|
|
|
// Determine initial language from browser, normalised to language-only.
|
|
const browserLang: LanguageCode =
|
|
typeof navigator !== "undefined"
|
|
? normalizeLocaleToLanguage(navigator.language)
|
|
: "en";
|
|
|
|
// Keep document direction (ltr/rtl) in sync with the active language.
|
|
if (typeof document !== "undefined") {
|
|
document.documentElement.dir = isRtlLanguage(browserLang) ? "rtl" : "ltr";
|
|
}
|
|
|
|
/**
|
|
* Initialize i18next with React bindings.
|
|
*
|
|
* This module is imported once in src/main.tsx before any React
|
|
* rendering so that `useTranslation` is ready everywhere.
|
|
*/
|
|
i18n.use(initReactI18next).init({
|
|
resources,
|
|
lng: browserLang,
|
|
fallbackLng: "en",
|
|
supportedLngs: SUPPORTED_LANGUAGES,
|
|
ns: ["common", "mission", "chat", "account"],
|
|
defaultNS: "common",
|
|
// Because we control the keys and interpolate only simple values.
|
|
interpolation: {
|
|
escapeValue: false,
|
|
},
|
|
// For now we stay language-only; we already normalise the browser locale.
|
|
load: "languageOnly",
|
|
// Be explicit about react options for clarity.
|
|
react: {
|
|
useSuspense: false,
|
|
},
|
|
});
|
|
|
|
// Ensure that when the language changes at runtime, document direction
|
|
// tracks the new language's natural writing direction.
|
|
i18n.on("languageChanged", (lng) => {
|
|
if (typeof document === "undefined") return;
|
|
const lang = normalizeLocaleToLanguage(lng);
|
|
document.documentElement.dir = isRtlLanguage(lang) ? "rtl" : "ltr";
|
|
});
|
|
|
|
export default i18n;
|