b6b40b0c1b
/users becomes /admin (redirected), a tab shell over the existing Users view and a new Server tab backed by GET /api/host (server/lib/host-stats.js): hostname/OS/uptime, CPU and load, memory and swap, every real disk with HDD/SSD, NVIDIA GPUs, and each user's live share from two /proc samples — attributed by the member's Linux uid (S3) or, for host processes, by the inherited AGENTHUB_LANE_ID's workspace owner. Read-only, staff-only (/host in STAFF_ONLY_PREFIXES), comm names only. Members' tmux card moves to the Server tab.
135 lines
5.0 KiB
TypeScript
135 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",
|
|
"admin",
|
|
"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;
|