Files
AgentHub/client/src/i18n/index.ts
T
nntrivi2001 22de5009ff feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the
  Artifact tool's hook), pinned workspace files, skills and plugins, each
  kept as a snapshot; shared ones appear in a Hub and a skill or plugin
  installs into the viewer's own account (server/lib/artifacts.js,
  /api/artifacts, docs/plans/artifacts/).
- Workspace extra folders: clone one or more related repos beside the
  workspace and add them in one step (POST /api/lanes/:id/sources/clone).
- A pane that ended by itself while tmux kept running is no longer offered
  for resume (tmux.serverStartedAt in reconcileOrphans).
- Changes panel: a folder that is not a git repo opens on Files, and its
  files open from there.
- Plus the parallel sessions' work in this tree: File Explorer button in the
  panel header, user delete, RSA SSH keys, chat/terminal and pet fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:06:54 +07:00

134 lines
5.0 KiB
TypeScript

/**
* @file index.ts
* @description i18next bootstrap for the dashboard client. Loads the JSON locale
* files for English (`en`) and Vietnamese (`vi`) lazily — only the active language
* (and the `en` fallback) — wires browser language detection, and exports the
* configured singleton consumed by `react-i18next` plus `i18nReady`, the promise
* the entry point awaits before the first render.
*
* ## Namespaces
* Translations are split by feature area (`dashboard`, `sessions`, `settings`,
* etc.) so pages import only the keys they need via `useTranslation("ns")`.
* `common` is the default namespace for shared labels and buttons.
*
* ## Detection order
* 1. `localStorage` key `i18nextLng` (user override from Settings)
* 2. `navigator.language` fallback
*
* Unknown regional variants (e.g. `en-US`) map to supported base languages via
* `nonExplicitSupportedLngs`.
*
* `<html lang>` follows the resolved language on init and on every change.
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
/* =============================================================================
* MODULE_GUIDE — extended in-file reference (comments only; safe to read, never executed)
* =============================================================================
* **Purpose:** Dashboard module consumed by the React client, MCP tools, or desktop shell depending on deployment mode.
*
* ## Design constraints
* - Local-first: no telemetry leaves the machine unless the user configures webhooks.
* - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that
* philosophy by degrading gracefully (empty states, stale badges, reconnect loops).
* - Destructive flows stay behind explicit confirmation modals and server-side gates.
* - Internationalization: user-visible strings belong in i18n JSON, not literals here.
*
* ## Remote data & SSH
* Remote Data Sources let operators aggregate multiple machines. SSH entries describe
* how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every
* scoped GET via `?sources=`. Health checks and import history surface in Settings.
*
* ## Internal dependencies
* - `./locales/<lng>/<ns>.json` (lazy, via `import.meta.glob`)
*
* ## Testing pointers
* - Prefer colocated `__tests__` with Vitest + Testing Library for UI.
* - Server contract changes require `npm run test:server` and OpenAPI sync.
*
* ## Related docs
* - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline.
* - `docs/API.md` — REST reference.
* - `skills/file-headers/` — mandatory `@author` header policy.
* ============================================================================= */
import i18n, { type BackendModule, type ResourceKey } from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
// Every locale file is its own lazy chunk (vite.config.ts groups them into one
// chunk per language), so a page loads only the language it shows. i18next
// reads them through this inline backend: `init` fetches the detected language
// (plus the `en` fallback), and `changeLanguage` fetches a new one BEFORE it
// switches, so no raw key is ever painted.
const localeFiles = import.meta.glob<Record<string, unknown>>("./locales/*/*.json", {
import: "default",
});
const lazyLocales: BackendModule = {
type: "backend",
init() {},
read(lng, ns, cb) {
const load = localeFiles[`./locales/${lng}/${ns}.json`];
if (!load) return cb(null, {});
load().then(
(data) => cb(null, data as ResourceKey),
(err) => cb(err, false)
);
},
};
/** Keep `<html lang>` on the active language, so a screen reader speaks the
* page with the right voice and the browser hyphenates/translates correctly.
* Registered before `init`, which fires the first `languageChanged`. */
export function syncDocumentLang(): void {
if (typeof document === "undefined") return;
document.documentElement.lang = i18n.resolvedLanguage || i18n.language || "en";
}
i18n.on("languageChanged", syncDocumentLang);
/** Resolves once the detected language's namespaces are loaded; `main.tsx`
* awaits it before the first render. */
export const i18nReady = i18n
.use(lazyLocales)
.use(LanguageDetector)
.use(initReactI18next)
.init({
supportedLngs: ["en", "vi"],
nonExplicitSupportedLngs: true,
fallbackLng: "en",
ns: [
"common",
"nav",
"dashboard",
"sessions",
"activity",
"analytics",
"workflows",
"settings",
"kanban",
"errors",
"updates",
"ccConfig",
"codexConfig",
"run",
"alerts",
"splash",
"lanes",
"users",
"onboarding",
"artifacts",
],
defaultNS: "common",
interpolation: { escapeValue: false },
detection: {
order: ["localStorage", "navigator"],
caches: ["localStorage"],
lookupLocalStorage: "i18nextLng",
},
});
syncDocumentLang();
/** Configured i18next instance — import side effects start {@link init}. */
export default i18n;