Skip to content

Commit 2d898a6

Browse files
committed
Merge PR #187: extend Simplified Chinese (zh-CN) i18n coverage in Desktop
2 parents 02ecea2 + e7e45eb commit 2d898a6

10 files changed

Lines changed: 256 additions & 112 deletions

File tree

desktop/src/App.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {
3030
import { App } from "./App";
3131
import { __resetComposerBehaviorForTests } from "./app/composerBehavior";
3232
import { __resetEscapeLayersForTests } from "./app/escapeLayer";
33+
import { __setLocaleForTests } from "./app/i18n";
3334
import type {
3435
AnyRpcNotification,
3536
DesktopRuntime,
@@ -1505,6 +1506,7 @@ describe("desktop command center", () => {
15051506
localStorage.clear();
15061507
__resetComposerBehaviorForTests();
15071508
__resetEscapeLayersForTests();
1509+
__setLocaleForTests("en");
15081510
});
15091511
afterEach(() => {
15101512
cleanup();

desktop/src/app/i18n.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,29 @@ const ZH_CN: Record<string, string> = {
5959
"settings.composer.queue": "排队为下一个 Turn",
6060
"settings.composer.note":
6161
"仅在忙碌时生效;Cmd/Ctrl+Enter 执行另一种行为。空闲时 Enter 始终发送。本设置立即生效,仅保存在本机。",
62+
// Updates card
63+
"settings.updates.eyebrow": "签名发布通道",
64+
"settings.updates.title": "应用更新",
65+
"settings.updates.check": "检查更新",
66+
"settings.updates.checking": "正在检查…",
67+
"settings.updates.install": "安装",
68+
"settings.updates.preparing": "准备中…",
69+
"settings.updates.downloading": "下载中…",
70+
"settings.updates.installing": "安装中…",
71+
"settings.updates.upToDate": "当前已是最新版本。",
72+
"settings.updates.available":
73+
"DeepCode {{version}} 可用。安装前会验证软件包签名。",
74+
"settings.updates.idle":
75+
"仅在手动请求时检查更新。开发版本可能未配置发布通道。",
76+
"settings.updates.checkingNote": "正在检查签名发布通道。",
77+
// Diagnostics card
78+
"settings.diagnostics.eyebrow": "故障排除",
79+
"settings.diagnostics.title": "诊断",
80+
"settings.diagnostics.export": "导出报告",
81+
"settings.diagnostics.exporting": "导出中…",
82+
"settings.diagnostics.runChecks": "运行检查",
83+
"settings.diagnostics.savedTo": "已保存脱敏诊断报告至 {{path}}",
84+
"settings.diagnostics.noProject": "未选择项目",
6285
// Sidebar
6386
"sidebar.threads": "会话",
6487
"sidebar.automations": "自动化",
@@ -67,12 +90,106 @@ const ZH_CN: Record<string, string> = {
6790
"sidebar.mcp": "MCP",
6891
"sidebar.settings": "设置",
6992
"sidebar.projects": "项目",
93+
"sidebar.newThread": "新建会话",
94+
"sidebar.searchSessions": "搜索会话",
95+
"sidebar.openFolder": "打开本地文件夹",
96+
"sidebar.openFolderHint": "CLI 中的会话将在此显示。",
97+
"sidebar.noResults": "无匹配会话",
98+
"sidebar.noResultsHint": "可按标题、项目或工作空间路径搜索。",
99+
"sidebar.noSessions": "暂无会话",
100+
"sidebar.showMore": "显示更多 {{count}} 个",
101+
"sidebar.showLess": "收起",
102+
"sidebar.previousSessions": "历史会话",
103+
"sidebar.folderUnavailable": "原始文件夹不可用",
104+
"sidebar.localAgent": "本地智能体",
105+
"sidebar.agentReady": "本地智能体就绪",
106+
"sidebar.sharedHistory": "共享会话历史",
70107
// Composer high-traffic strings
71108
"composer.hint.send": "↵ 发送",
72109
"composer.hint.steerQueue": "↵ 引导 · ⌘↵ 排队",
73110
"composer.hint.queueSteer": "↵ 排队 · ⌘↵ 引导",
74111
"composer.hint.newline": "⇧↵ 换行",
75112
"composer.queueNext": "排队下一个",
113+
// Thread header
114+
"thread.startThread": "开始本地编码会话",
115+
"thread.folderUnavailable": "文件夹不可用",
116+
"thread.noLocalFolder": "无本地文件夹",
117+
"thread.previousSessions": "历史会话",
118+
"thread.trustFolder": "信任文件夹",
119+
"thread.trusted": "已信任",
120+
"thread.trustedTooltip": "允许在此文件夹中执行",
121+
"thread.folderUnavailableTooltip": "原始会话文件夹不再可用",
122+
"thread.paper": "论文",
123+
"thread.paperTooltip": "创建 Paper2Code 会话",
124+
"thread.fork": "分叉",
125+
"thread.forkTooltip": "分叉到独立工作树",
126+
"thread.review": "审查",
127+
"thread.closeReview": "关闭审查面板",
128+
"thread.openReview": "打开审查面板",
129+
// Approval card
130+
"approval.label": "需要审批",
131+
"approval.decision": "决定: {{status}}",
132+
"approval.allowContinue": "允许 {{tool}} 继续?",
133+
"approval.reviewOperation": "在智能体继续之前,请审查此操作。",
134+
"approval.sensitiveOperation": "敏感操作",
135+
"approval.reviewArguments": "查看参数",
136+
"approval.allowOnce": "允许一次",
137+
"approval.allowSession": "允许本次会话",
138+
"approval.deny": "拒绝",
139+
// Runtime notice
140+
"runtime.offline": "本地应用服务器不可用。",
141+
"runtime.restart": "重启服务",
142+
// Goal rail
143+
"goal.setGoal": "设定目标",
144+
"goal.setGoalHint": "在普通 Turn 之间保持一个持久的目标",
145+
"goal.sessionGoal": "会话目标",
146+
"goal.turn": "Turn",
147+
"goal.turns": "Turns",
148+
"goal.tokens": "tokens",
149+
"goal.continue": "继续",
150+
"goal.editGoal": "编辑目标",
151+
"goal.pause": "暂停",
152+
"goal.pauseTooltip": "暂停自动继续;不会中断当前 Turn",
153+
"goal.editReopen": "编辑并重新开启",
154+
"goal.resume": "恢复",
155+
"goal.edit": "编辑",
156+
"goal.newGoal": "新建目标",
157+
"goal.objectiveHint":
158+
"目标编辑保持同一 Goal 身份,并到达活跃的 Turn。",
159+
"goal.outcome": "目标结果",
160+
"goal.tokenBudget": "Token 预算",
161+
"goal.tokenBudgetOptional": "可选",
162+
"goal.noLimit": "无限制",
163+
"goal.skills": "技能",
164+
"goal.cancel": "取消",
165+
"goal.saveResume": "保存并恢复",
166+
"goal.saveGoal": "保存目标",
167+
"goal.startGoal": "开始目标",
168+
"goal.clearGoal": "清除目标",
169+
"goal.clearConfirm": "确定清除此会话目标?",
170+
"goal.completionOutcome": "完成结果",
171+
"goal.blockedOutcome": "阻塞结果",
172+
"goal.relatedActivity": "相关活动",
173+
"goal.noSkills": "未选择目标相关技能。",
174+
"goal.activeTime": "{{duration}} 活跃时间",
175+
"goal.noUsage": "尚无已完成的 Goal Turn 用量",
176+
"goal.tokenBudgetLabel": "{{budget}} token 预算",
177+
"goal.noTokenBudget": "无 token 预算",
178+
"goal.decidingTurn": "决定 Turn",
179+
"goal.goalDescription":
180+
"目标 {{id}} 附属于此会话。普通后续对话引导工作方向,而不重写目标。",
181+
"goal.closeEditor": "关闭目标编辑器",
182+
"goal.describeOutcome": "描述 DeepCode 应达成的完整目标。",
183+
// Inspector
184+
"inspector.label": "检查器",
185+
"inspector.views": "检查器视图",
186+
"inspector.closeReview": "关闭审查面板",
187+
"inspector.tab.changes": "变更",
188+
"inspector.tab.files": "文件",
189+
"inspector.tab.artifacts": "产物",
190+
"inspector.tab.tests": "测试",
191+
"inspector.tab.terminal": "终端",
192+
"inspector.tab.details": "详情",
76193
};
77194

78195
function readLocale(): Locale {

desktop/src/components/RuntimeNotice.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AlertTriangle, RotateCw, X } from "lucide-react";
2+
import { useTranslation } from "react-i18next";
23

34
import type { BridgeError, SidecarStatus } from "../rpc/contracts";
45
import styles from "./RuntimeNotice.module.css";
@@ -18,9 +19,10 @@ export function RuntimeNotice({
1819
onRestart,
1920
onDismissError,
2021
}: RuntimeNoticeProps) {
22+
const { t } = useTranslation();
2123
if (!error && runtime.phase !== "crashed" && runtime.phase !== "stopped") return null;
2224
const serviceOffline = runtime.phase === "crashed" || runtime.phase === "stopped";
23-
const message = error?.message ?? runtime.message ?? "The local App Server is unavailable.";
25+
const message = error?.message ?? runtime.message ?? t("runtime.offline", "The local App Server is unavailable.");
2426
return (
2527
<div className={styles.notice} role="alert">
2628
<AlertTriangle size={18} aria-hidden="true" />
@@ -42,7 +44,7 @@ export function RuntimeNotice({
4244
{serviceOffline ? (
4345
<button type="button" onClick={onRestart} disabled={busy}>
4446
<RotateCw size={14} />
45-
Restart service
47+
{t("runtime.restart", "Restart service")}
4648
</button>
4749
) : null}
4850
</div>

desktop/src/features/execution/ApprovalCard.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
TerminalSquare,
66
Wrench,
77
} from "lucide-react";
8+
import { useTranslation } from "react-i18next";
89

910
import type { Approval, ApprovalDecision } from "../../generated/app-server";
1011
import styles from "./ApprovalCard.module.css";
@@ -15,17 +16,17 @@ interface ApprovalCardProps {
1516
onRespond(approvalId: string, decision: ApprovalDecision): void;
1617
}
1718

18-
function requestDetail(approval: Approval): string {
19+
function requestDetail(approval: Approval, t: (key: string, defaultValue: string, options?: Record<string, string>) => string): string {
1920
const tool = approval.request.toolName;
2021
const reason = approval.request.reason;
2122
if (typeof reason === "string" && reason) return reason;
22-
if (typeof tool === "string" && tool) return `Allow ${tool} to continue?`;
23-
return "Review this operation before the agent continues.";
23+
if (typeof tool === "string" && tool) return t("approval.allowContinue", "Allow {{tool}} to continue?", { tool });
24+
return t("approval.reviewOperation", "Review this operation before the agent continues.");
2425
}
2526

26-
function requestTool(approval: Approval): string {
27+
function requestTool(approval: Approval, t: (key: string, defaultValue: string) => string): string {
2728
const tool = approval.request.toolName;
28-
return typeof tool === "string" && tool.trim() ? tool : "Sensitive operation";
29+
return typeof tool === "string" && tool.trim() ? tool : t("approval.sensitiveOperation", "Sensitive operation");
2930
}
3031

3132
function requestArguments(approval: Approval): string | null {
@@ -61,27 +62,28 @@ function CategoryIcon({ category }: { category: Approval["category"] }) {
6162
}
6263

6364
export function ApprovalCard({ approval, busy, onRespond }: ApprovalCardProps) {
65+
const { t } = useTranslation();
6466
if (approval.status !== "pending") {
6567
return (
6668
<p className={styles.resolution}>
67-
Decision: {approval.status.replaceAll("_", " ")}
69+
{t("approval.decision", "Decision: {{status}}", { status: approval.status.replaceAll("_", " ") })}
6870
</p>
6971
);
7072
}
7173
const argumentsPreview = requestArguments(approval);
7274
return (
73-
<div className={styles.card} aria-label="Approval required">
75+
<div className={styles.card} aria-label={t("approval.label", "Approval required")}>
7476
<div className={styles.operation}>
7577
<CategoryIcon category={approval.category} />
7678
<span>
77-
<strong>{requestTool(approval)}</strong>
79+
<strong>{requestTool(approval, t)}</strong>
7880
<small>{approval.category.replaceAll("_", " ")}</small>
7981
</span>
8082
</div>
81-
<p>{requestDetail(approval)}</p>
83+
<p>{requestDetail(approval, t)}</p>
8284
{argumentsPreview ? (
8385
<details className={styles.arguments}>
84-
<summary>Review arguments</summary>
86+
<summary>{t("approval.reviewArguments", "Review arguments")}</summary>
8587
<pre>{argumentsPreview}</pre>
8688
</details>
8789
) : null}
@@ -91,22 +93,22 @@ export function ApprovalCard({ approval, busy, onRespond }: ApprovalCardProps) {
9193
onClick={() => onRespond(approval.id, "approved_once")}
9294
disabled={busy}
9395
>
94-
Allow once
96+
{t("approval.allowOnce", "Allow once")}
9597
</button>
9698
<button
9799
type="button"
98100
onClick={() => onRespond(approval.id, "approved_session")}
99101
disabled={busy}
100102
>
101-
Allow for Session
103+
{t("approval.allowSession", "Allow for Session")}
102104
</button>
103105
<button
104106
className={styles.danger}
105107
type="button"
106108
onClick={() => onRespond(approval.id, "denied")}
107109
disabled={busy}
108110
>
109-
Deny
111+
{t("approval.deny", "Deny")}
110112
</button>
111113
</div>
112114
</div>

0 commit comments

Comments
 (0)