Files
tutti/tools/scripts/check-i18n.test.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

543 lines
15 KiB
JavaScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
const scriptPath = resolve(
dirname(fileURLToPath(import.meta.url)),
"check-i18n.mjs"
);
test("passes aligned locale resources and valid i18n usage", async () => {
const workspaceRoot = await createFixtureWorkspace({
rendererFiles: {
"app/App.tsx": `
export function App() {
const { t } = useTranslation();
return <button aria-label={t("common.submitButton")}>{t("common.submitButton")}</button>;
}
`
},
packageFiles: {
"packages/workspace/file-manager/src/ui/FileManager.tsx": `
export function FileManager({ copy }) {
return <button>{copy.t("uploadLabel")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(result.stdout, /i18n check passed/);
});
test("rejects locale keys missing from a non-default locale", async () => {
const workspaceRoot = await createFixtureWorkspace({
zhCN: `
export const zhCN = {
common: {
hello: "你好"
}
} as const;
`
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /locale-key-missing/);
assert.match(result.stderr, /common.submitButton/);
});
test("rejects placeholder mismatches across locales", async () => {
const workspaceRoot = await createFixtureWorkspace({
zhCN: `
export const zhCN = {
common: {
hello: "你好",
submitButton: "提交",
readyStatus: "已就绪"
}
} as const;
`
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /locale-placeholder/);
assert.match(result.stderr, /common.readyStatus/);
});
test("rejects hardcoded JSX copy in renderer source", async () => {
const workspaceRoot = await createFixtureWorkspace({
rendererFiles: {
"app/App.tsx": `
export function App() {
return <button>Save workspace</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /hardcoded-jsx-text/);
assert.match(result.stderr, /Save workspace/);
});
test("rejects missing literal i18n key references", async () => {
const workspaceRoot = await createFixtureWorkspace({
rendererFiles: {
"app/App.tsx": `
export function App() {
const { t } = useTranslation();
return <button>{t("common.missingButton")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /i18n-key-missing/);
assert.match(result.stderr, /common.missingButton/);
});
test("accepts valid copy.t keys from shared package resources", async () => {
const workspaceRoot = await createFixtureWorkspace({
packageFiles: {
"packages/workspace/file-manager/src/ui/FileManager.tsx": `
export function FileManager({ copy }) {
return <button>{copy.t("uploadLabel")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(result.stdout, /i18n check passed/);
});
test("accepts scoped keys from multiple i18n modules in the same source root", async () => {
const workspaceRoot = await createFixtureWorkspace({
packageFiles: {
"packages/workbench/surface/src/host/WorkbenchHost.tsx": `
export function WorkbenchHost({ copy }) {
return <button aria-label={copy.t("actions.close")}>{copy.t("dockLabel")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(result.stdout, /i18n check passed/);
});
test("accepts product-neutral package i18n manifest names", async () => {
const workspaceRoot = await createFixtureWorkspace({
packageFiles: {
"packages/browser/workbench-node/src/i18n/browserNodeI18n.ts":
defaultBrowserNodeI18n(),
"packages/browser/workbench-node/src/react/BrowserNode.tsx": `
export function BrowserNode({ copy }) {
return <button aria-label={copy.t("actions.reload")}>{copy.t("title")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(result.stdout, /i18n check passed/);
});
test("accepts package locale-object manifests with source roots", async () => {
const workspaceRoot = await createFixtureWorkspace({
packageFiles: {
"packages/agent/gui/app/renderer/i18n/index.ts": `
import { createLocaleObjectI18nModuleManifest } from "@tutti-os/ui-i18n-runtime";
export const agentGuiI18nModule = createLocaleObjectI18nModuleManifest({
fileByLocale: {
en: "packages/agent/gui/app/renderer/i18n/locales/en.ts",
"zh-CN": "packages/agent/gui/app/renderer/i18n/locales/zh-CN.ts"
},
name: "agent-gui",
sourceRoot: "packages/agent/gui"
});
`,
"packages/agent/gui/app/renderer/i18n/locales/en.ts": `
export const en = {
common: {
home: "Home"
}
} as const;
`,
"packages/agent/gui/app/renderer/i18n/locales/zh-CN.ts": `
export const zhCN = {
common: {
home: "首页"
}
} as const;
`,
"packages/agent/gui/shared/workspaceFileManager/workspaceFileManagerModel.ts": `
export function buildBreadcrumb(copy) {
return [{ label: copy.t("common.home"), path: "/" }];
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.match(result.stdout, /i18n check passed/);
});
test("rejects missing copy.t keys from shared package resources", async () => {
const workspaceRoot = await createFixtureWorkspace({
packageFiles: {
"packages/workspace/file-manager/src/ui/FileManager.tsx": `
export function FileManager({ copy }) {
return <button>{copy.t("missingLabel")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /i18n-key-missing/);
assert.match(result.stderr, /missingLabel/);
});
test("rejects package locale placeholder mismatches across locales", async () => {
const workspaceRoot = await createFixtureWorkspace({
workspaceFileManagerZhCN: `
const workspaceFileManagerZhCN = {
uploadLabel: "上传",
uploadConflictDescription: "存在冲突"
} as const;
`
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /locale-placeholder/);
assert.match(result.stderr, /workspaceFileManager.uploadConflictDescription/);
});
test("still validates translator.t desktop keys", async () => {
const workspaceRoot = await createFixtureWorkspace({
main: `
export function openDialog(translator) {
return translator.t("common.missingButton");
}
`
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /i18n-key-missing/);
assert.match(result.stderr, /common.missingButton/);
});
test("rejects generated i18n key names", async () => {
const workspaceRoot = await createFixtureWorkspace({
en: `
export const en = {
common: {
hello: "Hello",
readyStatus: "{{count}} ready",
submitButton: "Submit",
l12c34: "Generated key"
}
} as const;
`,
zhCN: `
export const zhCN = {
common: {
hello: "你好",
readyStatus: "已就绪 {{count}} 个",
submitButton: "提交",
l12c34: "生成键"
}
} as const;
`,
rendererFiles: {
"app/App.tsx": `
export function App() {
const { t } = useTranslation();
return <button>{t("common.l12c34")}</button>;
}
`
}
});
const result = runI18nCheck(workspaceRoot);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /i18n-key-semantic/);
assert.match(result.stderr, /common.l12c34/);
});
async function createFixtureWorkspace(options = {}) {
const workspaceRoot = await mkdtemp(join(tmpdir(), "tutti-i18n-"));
const files = {
"apps/desktop/src/shared/i18n/locales/en.ts":
options.en ?? defaultEnglishLocale(),
"apps/desktop/src/shared/i18n/locales/zh-CN.ts":
options.zhCN ?? defaultChineseLocale(),
"apps/desktop/src/shared/i18n/i18nManifest.ts":
defaultDesktopI18nManifest(),
"apps/desktop/src/main/index.ts":
options.main ?? "export const main = true;\n",
"packages/workspace/file-manager/src/i18n/workspaceFileManagerI18n.ts":
defaultWorkspaceFileManagerI18n(options.workspaceFileManagerZhCN),
"packages/workbench/surface/src/host/workbenchHostI18n.ts":
defaultWorkbenchHostI18n(),
"packages/workbench/surface/src/react/workbenchWindowI18n.ts":
defaultWorkbenchWindowI18n()
};
const rendererFiles = options.rendererFiles ?? {
"app/App.tsx": `
export function App() {
const { t } = useTranslation();
return <span>{t("common.hello")}</span>;
}
`
};
for (const [path, content] of Object.entries(rendererFiles)) {
files[`apps/desktop/src/renderer/src/${path}`] = content;
}
for (const [path, content] of Object.entries(options.packageFiles ?? {})) {
files[path] = content;
}
for (const [path, content] of Object.entries(files)) {
const absolutePath = join(workspaceRoot, path);
await mkdir(dirname(absolutePath), { recursive: true });
await writeFile(absolutePath, `${content.trim()}\n`, "utf8");
}
return workspaceRoot;
}
function defaultEnglishLocale() {
return `
export const en = {
common: {
hello: "Hello",
readyStatus: "{{count}} ready",
submitButton: "Submit"
}
} as const;
`;
}
function defaultChineseLocale() {
return `
export const zhCN = {
common: {
hello: "你好",
readyStatus: "已就绪 {{count}} 个",
submitButton: "提交"
}
} as const;
`;
}
function defaultDesktopI18nManifest() {
return `
import { createLocaleObjectI18nModuleManifest } from "@tutti-os/ui-i18n-runtime";
export const tuttiI18nModule = createLocaleObjectI18nModuleManifest({
fileByLocale: {
en: "apps/desktop/src/shared/i18n/locales/en.ts",
"zh-CN": "apps/desktop/src/shared/i18n/locales/zh-CN.ts"
},
name: "desktop-locales"
});
`;
}
function defaultWorkspaceFileManagerI18n(zhCNOverride) {
return `
import { createScopedLocaleObjectsI18nModuleManifest } from "@tutti-os/ui-i18n-runtime";
export const workspaceFileManagerI18nNamespace = "workspaceFileManager";
export const tuttiI18nModule = createScopedLocaleObjectsI18nModuleManifest({
localeObjectByLocale: {
en: "workspaceFileManagerEn",
"zh-CN": "workspaceFileManagerZhCN"
},
name: "workspace-file-manager",
namespace: "workspaceFileManager",
sourceRoot: "packages/workspace/file-manager/src"
});
const workspaceFileManagerEn = {
uploadLabel: "Upload",
uploadConflictDescription: "{{count}} conflicts"
} as const;
${(
zhCNOverride ??
`
const workspaceFileManagerZhCN = {
uploadLabel: "上传",
uploadConflictDescription: "{{count}} 个冲突"
} as const;
`
).trim()}
export const workspaceFileManagerI18nResources = {
en: {
[workspaceFileManagerI18nNamespace]: workspaceFileManagerEn
},
"zh-CN": {
[workspaceFileManagerI18nNamespace]: workspaceFileManagerZhCN
}
} as const;
`;
}
function defaultWorkbenchWindowI18n() {
return `
import { createScopedLocaleObjectsI18nModuleManifest } from "@tutti-os/ui-i18n-runtime";
export const workbenchWindowChromeI18nNamespace = "workbenchWindowChrome";
export const tuttiI18nModule = createScopedLocaleObjectsI18nModuleManifest({
localeObjectByLocale: {
en: "workbenchWindowChromeEn",
"zh-CN": "workbenchWindowChromeZhCN"
},
name: "workbench-window-chrome",
namespace: "workbenchWindowChrome",
sourceRoot: "packages/workbench/surface/src"
});
const workbenchWindowChromeEn = {
layoutMenu: "Window layout"
} as const;
const workbenchWindowChromeZhCN = {
layoutMenu: "窗口布局"
} as const;
export const workbenchWindowChromeI18nResources = {
en: {
[workbenchWindowChromeI18nNamespace]: workbenchWindowChromeEn
},
"zh-CN": {
[workbenchWindowChromeI18nNamespace]: workbenchWindowChromeZhCN
}
} as const;
`;
}
function defaultWorkbenchHostI18n() {
return `
import { createScopedLocaleObjectsI18nModuleManifest } from "@tutti-os/ui-i18n-runtime";
export const workbenchHostI18nNamespace = "workbenchHost";
export const tuttiI18nModule = createScopedLocaleObjectsI18nModuleManifest({
localeObjectByLocale: {
en: "workbenchHostEn",
"zh-CN": "workbenchHostZhCN"
},
name: "workbench-host",
namespace: "workbenchHost",
sourceRoot: "packages/workbench/surface/src"
});
const workbenchHostEn = {
actions: {
close: "Close",
minimize: "Minimize"
},
dockLabel: "Workbench dock",
launch: "Open {{title}}"
} as const;
const workbenchHostZhCN = {
actions: {
close: "关闭",
minimize: "最小化"
},
dockLabel: "工作台程序坞",
launch: "打开 {{title}}"
} as const;
export const workbenchHostI18nResources = {
en: {
[workbenchHostI18nNamespace]: workbenchHostEn
},
"zh-CN": {
[workbenchHostI18nNamespace]: workbenchHostZhCN
}
} as const;
`;
}
function defaultBrowserNodeI18n() {
return `
import { createScopedLocaleObjectsI18nModuleManifest } from "@tutti-os/ui-i18n-runtime";
export const browserNodeI18nNamespace = "browserNode";
export const browserNodeI18nModule = createScopedLocaleObjectsI18nModuleManifest({
localeObjectByLocale: {
en: "browserNodeEn",
"zh-CN": "browserNodeZhCN"
},
name: "browser-node",
namespace: "browserNode",
sourceRoot: "packages/browser/workbench-node/src"
});
const browserNodeEn = {
actions: {
reload: "Reload"
},
title: "Browser"
} as const;
const browserNodeZhCN = {
actions: {
reload: "重新加载"
},
title: "浏览器"
} as const;
`;
}
function runI18nCheck(workspaceRoot) {
return spawnSync(process.execPath, [scriptPath], {
cwd: workspaceRoot,
encoding: "utf8",
env: {
...process.env,
TUTTI_WORKSPACE_ROOT: workspaceRoot
}
});
}