Files
fusion/packages/dashboard/scripts/sync-locales.mjs
gsxdsm cd71b92930 feat(dashboard): add i18n runtime with per-locale code-splitting (U3)
Initialize the browser i18next instance with lazy per-locale catalog loading,
localStorage->navigator->htmlTag detection, script-aware zh fallback, and the
shared @fusion/i18n config. Catalogs are synced from @fusion/i18n into a
gitignored app/locales/ (predev/prebuild) and imported app-relative so Vite
emits one chunk per locale/namespace (verified: 15 chunks, none inlined into
the main bundle). First paint is gated on i18nReady to avoid raw-key flashes;
<I18nextProvider> wraps the App provider stack; vendor-i18n manualChunk added.

A build-assertion script (verify:locale-chunks) guards the KTD3a splitting
invariant. Fallback/namespace behavior is covered by @fusion/i18n config tests;
the live instance is verified via the build assertion rather than a unit test
(the dynamic catalog backend is impractical to exercise in vitest).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:16:08 -07:00

31 lines
1.3 KiB
JavaScript

// Copies the authored @fusion/i18n catalogs into the dashboard tree so Vite can
// code-split them per locale via a plainly app-relative dynamic import. The
// generated app/locales/ dir is gitignored — @fusion/i18n/locales is the
// source-of-truth. Only the dashboard namespaces are copied (the terminal-only
// `cli` namespace is skipped). Runs as a predev/prebuild step.
import { cpSync, mkdirSync, readdirSync, rmSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url));
const dashboardRoot = join(here, "..");
const srcLocales = join(dashboardRoot, "..", "i18n", "locales");
const destLocales = join(dashboardRoot, "app", "locales");
// Keep in sync with DASHBOARD_NAMESPACES in @fusion/i18n config.ts.
const DASHBOARD_NAMESPACES = ["common", "app", "errors"];
const locales = readdirSync(srcLocales, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name);
rmSync(destLocales, { recursive: true, force: true });
for (const lng of locales) {
mkdirSync(join(destLocales, lng), { recursive: true });
for (const ns of DASHBOARD_NAMESPACES) {
cpSync(join(srcLocales, lng, `${ns}.json`), join(destLocales, lng, `${ns}.json`));
}
}
console.log(`Synced ${locales.length} locale(s) into app/locales: ${locales.join(", ")}`);