CrossRelay stage 0+1: Go gateway with Anthropic<->Chat Completions relay, Tauri shell, bilingual management UI

- Go gateway (stdlib only): /v1/messages non-stream + SSE, /v1/messages/count_tokens,
  management API /api/* (providers CRUD, apply/restore, settings, backups, logs)
- Protocol conversion: Anthropic Messages <-> Chat Completions with a streaming
  state machine keyed by content block index (text, tool_use, parallel calls)
- ~/.claude/settings.json guarded read/write: timestamped backups, managed env
  keys only, one-click restore
- Tauri 2 shell: spawns the gateway sidecar, parses the random port from stdout,
  stdin pipe watchdog prevents orphans, kills the child on exit
- React frontend: Dashboard/Providers/Settings, zh-CN + en-US, TanStack Query
- Tests: Go table-driven + httptest integration (all green), vitest + RTL
This commit is contained in:
kate522 committed 2026-10-18 17:19:13 +08:00
1 parent 8ee5e98ea3
commit f8acf31ebc
71 files changed
+15727 -1

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { enUS, zhCN } from "./resources";
function flatten(value: unknown, prefix = ""): string[] {
if (typeof value !== "object" || value === null) return [prefix];
return Object.entries(value as Record<string, unknown>).flatMap(([key, child]) =>
flatten(child, prefix ? `${prefix}.${key}` : key),
);
}
describe("i18n resources", () => {
it("zh-CN and en-US define the same keys", () => {
const zhKeys = flatten(zhCN.translation).sort();
const enKeys = flatten(enUS.translation).sort();
expect(zhKeys).toEqual(enKeys);
});
it("has no empty strings", () => {
for (const key of flatten(zhCN.translation)) {
const value = key
.split(".")
.reduce<unknown>((acc, part) => (acc as Record<string, unknown>)?.[part], zhCN.translation);
expect(typeof value).toBe("string");
expect((value as string).length).toBeGreaterThan(0);
}
});
});
+19
View File
@@ -0,0 +1,19 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { enUS, zhCN } from "./resources";
export const SUPPORTED_LANGUAGES = ["zh-CN", "en-US"] as const;
export type Language = (typeof SUPPORTED_LANGUAGES)[number];
void i18n.use(initReactI18next).init({
resources: {
"zh-CN": zhCN,
"en-US": enUS,
},
lng: "zh-CN",
fallbackLng: "en-US",
interpolation: { escapeValue: false },
returnNull: false,
});
export default i18n;
+213
View File
@@ -0,0 +1,213 @@
export const zhCN = {
translation: {
app: {
name: "CrossRelay",
tagline: "Claude Code 与 Codex 的本地协议网关",
},
nav: {
dashboard: "仪表盘",
providers: "供应商",
settings: "设置",
},
common: {
save: "保存",
cancel: "取消",
delete: "删除",
edit: "编辑",
add: "新增",
activate: "激活",
restoring: "还原中",
restore: "还原官方配置",
loading: "加载中",
error: "出错了",
retry: "重试",
none: "无",
optional: "选填",
language: "语言",
actions: "操作",
saved: "已保存",
copyFailed: "操作失败",
yes: "是",
no: "否",
},
dashboard: {
title: "仪表盘",
gateway: "网关",
running: "运行中",
stopped: "已停止",
port: "端口",
uptime: "运行时长",
version: "版本",
activeProvider: "当前供应商",
noActiveProvider: "尚未激活任何供应商",
requests: "请求总数",
failed: "失败请求",
tokensIn: "输入 Token",
tokensOut: "输出 Token",
recentRequests: "最近请求",
quickApply: "写入 Claude 配置",
restoreDone: "已还原为官方配置",
tableTime: "时间",
tableModel: "模型",
tableStatus: "状态",
tableDuration: "耗时",
tableTokens: "Token",
emptyLogs: "暂无请求记录",
statsTitle: "统计",
},
providers: {
title: "供应商",
add: "新增供应商",
edit: "编辑供应商",
name: "名称",
baseUrl: "Base URL",
apiKey: "API Key",
preset: "预设",
presetCustom: "自定义",
models: "模型映射",
defaultModel: "默认模型",
sonnetModel: "Sonnet 模型",
haikuModel: "Haiku 模型",
opusModel: "Opus 模型",
extraMappings: "精确映射",
extraKey: "请求的模型名",
extraValue: "上游模型名",
addMapping: "添加一行",
activated: "已激活",
activate: "激活并写入",
empty: "还没有供应商,先添加一个",
validationName: "请填写名称",
validationBaseUrl: "请填写 http(s) Base URL",
validationApiKey: "请填写 API Key",
appliedEnv: "已写入 ~/.claude/settings.json 的环境变量",
created: "供应商已创建",
updated: "供应商已更新",
deleted: "供应商已删除",
deleteConfirm: "确定删除这个供应商吗?",
keyHint: "留空表示保持原有 Key 不变",
enabledClaude: "对 Claude Code 启用",
},
settings: {
title: "设置",
gateway: "网关",
portMode: "端口模式",
portAuto: "自动(随机端口)",
portFixed: "固定端口",
fixedPort: "固定端口号",
portRestartHint: "端口模式在网关重启后生效",
language: "界面语言",
backups: "配置备份",
backupsEmpty: "暂无备份",
restoreBackup: "还原",
restored: "已还原",
saveFailed: "保存失败",
},
},
};
export const enUS = {
translation: {
app: {
name: "CrossRelay",
tagline: "Local protocol gateway for Claude Code and Codex",
},
nav: {
dashboard: "Dashboard",
providers: "Providers",
settings: "Settings",
},
common: {
save: "Save",
cancel: "Cancel",
delete: "Delete",
edit: "Edit",
add: "Add",
activate: "Activate",
restoring: "Restoring",
restore: "Restore official config",
loading: "Loading",
error: "Something went wrong",
retry: "Retry",
none: "None",
optional: "optional",
language: "Language",
actions: "Actions",
saved: "Saved",
copyFailed: "Action failed",
yes: "Yes",
no: "No",
},
dashboard: {
title: "Dashboard",
gateway: "Gateway",
running: "Running",
stopped: "Stopped",
port: "Port",
uptime: "Uptime",
version: "Version",
activeProvider: "Active provider",
noActiveProvider: "No provider activated yet",
requests: "Requests",
failed: "Failed",
tokensIn: "Input tokens",
tokensOut: "Output tokens",
recentRequests: "Recent requests",
quickApply: "Write to Claude config",
restoreDone: "Official config restored",
tableTime: "Time",
tableModel: "Model",
tableStatus: "Status",
tableDuration: "Duration",
tableTokens: "Tokens",
emptyLogs: "No requests yet",
statsTitle: "Stats",
},
providers: {
title: "Providers",
add: "Add provider",
edit: "Edit provider",
name: "Name",
baseUrl: "Base URL",
apiKey: "API key",
preset: "Preset",
presetCustom: "Custom",
models: "Model mapping",
defaultModel: "Default model",
sonnetModel: "Sonnet model",
haikuModel: "Haiku model",
opusModel: "Opus model",
extraMappings: "Exact mappings",
extraKey: "Requested model",
extraValue: "Upstream model",
addMapping: "Add row",
activated: "Active",
activate: "Activate & apply",
empty: "No providers yet",
validationName: "Name is required",
validationBaseUrl: "An http(s) base URL is required",
validationApiKey: "API key is required",
appliedEnv: "Environment written to ~/.claude/settings.json",
created: "Provider created",
updated: "Provider updated",
deleted: "Provider deleted",
deleteConfirm: "Delete this provider?",
keyHint: "Leave empty to keep the current key",
enabledClaude: "Enabled for Claude Code",
},
settings: {
title: "Settings",
gateway: "Gateway",
portMode: "Port mode",
portAuto: "Automatic (random port)",
portFixed: "Fixed port",
fixedPort: "Fixed port number",
portRestartHint: "Port mode applies after the gateway restarts",
language: "Language",
backups: "Config backups",
backupsEmpty: "No backups yet",
restoreBackup: "Restore",
restored: "Restored",
saveFailed: "Save failed",
},
},
};