diff --git a/CHANGELOG.md b/CHANGELOG.md
index 909ff2a4..5e0769f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
selection or a file. `lich sessions --json` and `whoami --json` now also carry
each session's `path` and `projectPath`, and `send --project` now takes a
directory, as `lich --help` already said it did.
+- **Simplified Chinese (简体中文) joins the language settings.** Both Settings ›
+ Appearance pickers, the *Interface language* and the *Prompt language*, now
+ offer Simplified Chinese next to English, Brazilian Portuguese and
+ Spanish. A browser set to any Chinese variant picks it by default for the
+ interface.
- **Two language settings, English and Brazilian Portuguese.** Settings ›
Appearance now has an *Interface language* for lich's own screens and a
diff --git a/docs/hooks/README.md b/docs/hooks/README.md
index cd5700e3..54c55226 100644
--- a/docs/hooks/README.md
+++ b/docs/hooks/README.md
@@ -90,7 +90,7 @@ release before it did.
`LICH_PROMPT_LANG` is the seventh, set on every session with a transport: the
language the user picked for the text lich hands agents (Settings, "Prompt
-language"), as a BCP 47 tag: `en`, `pt-BR` or `es` today. Anything that composes text
+language"), as a BCP 47 tag: `en`, `pt-BR`, `es` or `zh-CN` today. Anything that composes text
an agent reads from inside the session (the `lich` CLI, its MCP server, a hook)
writes it in this language. An absent or unknown tag means English, so a plugin
older or newer than the lich it runs under keeps working. It is read at spawn:
diff --git a/frontend/src/components/common/Trans.test.tsx b/frontend/src/components/common/Trans.test.tsx
index fffb5300..398ab943 100644
--- a/frontend/src/components/common/Trans.test.tsx
+++ b/frontend/src/components/common/Trans.test.tsx
@@ -1,6 +1,7 @@
import { renderToStaticMarkup } from "react-dom/server"
import { afterEach, describe, expect, it, vi } from "vitest"
import { resetLocale, setLocale } from "@/lib/i18n/i18n"
+import { zhCN } from "@/lib/i18n/locales/zh-cn"
import { Trans } from "./Trans"
afterEach(() => {
@@ -29,4 +30,11 @@ describe("Trans", () => {
const html = renderToStaticMarkup()
expect(html).toBe("2 archivos")
})
+
+ it("renders Simplified Chinese", () => {
+ vi.stubGlobal("localStorage", { setItem: () => {}, getItem: () => null })
+ setLocale("zh-CN")
+ const html = renderToStaticMarkup()
+ expect(html).toBe(zhCN.common.count.file.other.replace("{count}", "2"))
+ })
})
diff --git a/frontend/src/lib/i18n/i18n.test.ts b/frontend/src/lib/i18n/i18n.test.ts
index ded80b88..c57b84eb 100644
--- a/frontend/src/lib/i18n/i18n.test.ts
+++ b/frontend/src/lib/i18n/i18n.test.ts
@@ -12,6 +12,7 @@ import {
import { en } from "./locales/en"
import { es } from "./locales/es"
import { ptBR } from "./locales/pt-br"
+import { zhCN } from "./locales/zh-cn"
function stubStorage(initial: Record = {}) {
const data = new Map(Object.entries(initial))
@@ -102,6 +103,23 @@ describe("t", () => {
expect(t("common.count.file", { count: 2 })).toBe("2 arquivos")
})
+ it("renders Simplified Chinese with its single plural form", () => {
+ stubStorage()
+ setLocale("zh-CN")
+ const forms = zhCN.common.count.file
+ expect(forms.one).toBe(forms.other)
+ expect(t("common.count.file", { count: 1 })).toBe(forms.other.replace("{count}", "1"))
+ expect(t("common.count.file", { count: 0 })).toBe(forms.other.replace("{count}", "0"))
+ expect(t("common.action.cancel")).toBe(zhCN.common.action.cancel)
+ expect(t("common.action.cancel")).not.toBe("Cancel")
+ })
+
+ it("matches any Chinese browser language to zh-CN", () => {
+ expect(matchLocale("zh-CN")).toBe("zh-CN")
+ expect(matchLocale("zh")).toBe("zh-CN")
+ expect(matchLocale("zh-Hans-CN")).toBe("zh-CN")
+ })
+
it("rejects misuse at compile time", () => {
// @ts-expect-error a key that does not exist
expect(() => t("sidebar.nothing.here")).toThrow()
@@ -135,7 +153,7 @@ const placeholders = (text: string) => [...text.matchAll(/\{(\w+)\}/g)].map((m)
// translation that renames, drops or invents a placeholder.
describe("every locale against English", () => {
const source = leaves(en as unknown as Tree)
- const locales = { "pt-BR": ptBR, es }
+ const locales = { "pt-BR": ptBR, es, "zh-CN": zhCN }
it("covers every locale the picker offers", () => {
expect(Object.keys(locales)).toEqual(LOCALES.filter((locale) => locale !== "en"))
diff --git a/frontend/src/lib/i18n/i18n.ts b/frontend/src/lib/i18n/i18n.ts
index 0d03fc54..aac83fe0 100644
--- a/frontend/src/lib/i18n/i18n.ts
+++ b/frontend/src/lib/i18n/i18n.ts
@@ -4,6 +4,7 @@ import type { At, ParamsArg, PluralForms, Shape } from "./catalog"
import { en, type MessageKey, type Messages } from "./locales/en"
import { es } from "./locales/es"
import { ptBR } from "./locales/pt-br"
+import { zhCN } from "./locales/zh-cn"
// The interface language. It is a page preference, not a workspace setting:
// it changes nothing the backend does, so it lives in localStorage like the
@@ -11,7 +12,7 @@ import { ptBR } from "./locales/pt-br"
// setting on purpose (prompt-language-store.ts, internal/prompt): people often
// run the interface in one language and talk to their agents in another.
-export const LOCALES = ["en", "pt-BR", "es"] as const
+export const LOCALES = ["en", "pt-BR", "es", "zh-CN"] as const
export type Locale = (typeof LOCALES)[number]
/** Each language named in itself, which is how someone who cannot read the
@@ -20,9 +21,10 @@ export const LOCALE_NAMES: Record = {
en: "English",
"pt-BR": "Português (Brasil)",
es: "Español",
+ "zh-CN": "简体中文",
}
-const CATALOGS: Record> = { en, "pt-BR": ptBR, es }
+const CATALOGS: Record> = { en, "pt-BR": ptBR, es, "zh-CN": zhCN }
export const UI_LANGUAGE_PREF = "lich.appearance.language"
diff --git a/frontend/src/lib/i18n/lib-messages.test.ts b/frontend/src/lib/i18n/lib-messages.test.ts
index 5257c21b..759672ef 100644
--- a/frontend/src/lib/i18n/lib-messages.test.ts
+++ b/frontend/src/lib/i18n/lib-messages.test.ts
@@ -63,4 +63,13 @@ describe("messages built in plain modules follow the language", () => {
setLocale("pt-BR")
expect(formatAge(5 * 60_000)).toBe("5 min")
})
+
+ it("formats units and ages for Simplified Chinese", () => {
+ setLocale("zh-CN")
+ expect(COST_MISS_REASON["mixed-models"]).toMatch(/[\u4e00-\u9fff]/)
+ expect(formatAge(5 * 60_000)).toMatch(/^5\s?(分|min)/)
+ const now = new Date(2026, 0, 5, 12, 0, 0)
+ const at = Math.floor(now.getTime() / 1000) + 45 * 60
+ expect(timeUntil(at, now)).toContain("45")
+ })
})
diff --git a/frontend/src/lib/i18n/locales/zh-cn/common.ts b/frontend/src/lib/i18n/locales/zh-cn/common.ts
new file mode 100644
index 00000000..0272457e
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/common.ts
@@ -0,0 +1,43 @@
+import type { Shape } from "../../catalog"
+import type { common as en } from "../en/common"
+
+export const common = {
+ count: {
+ file: { one: "{count} 个文件", other: "{count} 个文件" },
+ commit: { one: "{count} 个提交", other: "{count} 个提交" },
+ },
+ time: {
+ justNow: "刚刚",
+ },
+ action: {
+ cancel: "取消",
+ save: "保存",
+ create: "创建",
+ },
+ checkAgainButton: {
+ check: "重新检查",
+ checking: "正在检查…",
+ },
+ errorBoundary: {
+ stoppedRendering: "{label} 已停止渲染",
+ reload: "重新加载窗口",
+ retry: "重试",
+ },
+ stepper: {
+ default: "默认",
+ },
+ toolMissing: {
+ notInstalled: "未安装 {label}",
+ install: "安装 {bin}",
+ },
+ pickerDialog: {
+ navigate: "导航",
+ filter: "筛选",
+ close: "关闭",
+ },
+ ui: {
+ dialog: {
+ close: "关闭",
+ },
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/diff.ts b/frontend/src/lib/i18n/locales/zh-cn/diff.ts
new file mode 100644
index 00000000..b2a3b7de
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/diff.ts
@@ -0,0 +1,88 @@
+import type { Shape } from "../../catalog"
+import type { diff as en } from "../en/diff"
+
+export const diff = {
+ binaryPreview: {
+ before: "之前",
+ after: "之后",
+ added: "已添加",
+ deleted: "已删除",
+ binary: "二进制文件",
+ binaryNoPreview: "二进制文件 · 此文件类型无法预览",
+ loading: "正在加载…",
+ missing: "不在此修订中",
+ pdfSide: "选择显示 PDF 的哪一侧",
+ },
+ commentBatch: {
+ openSession: "打开一个会话,将这些评论发送给它",
+ removeComment: "移除评论",
+ notePlaceholder: "关于整个更改的备注…",
+ noteLabel: "关于整个更改的备注",
+ count: { one: "{count} 条评论", other: "{count} 条评论" },
+ clear: "清空",
+ send: "发送",
+ },
+ diffBulk: {
+ collapseAll: "折叠所有文件",
+ expandAll: "展开所有文件",
+ showWhitespace: "显示空白字符更改",
+ hideWhitespace: "隐藏空白字符更改",
+ },
+ discardDialog: {
+ title: "放弃更改",
+ description: "要还原 {path} 的所有未提交更改吗?此操作无法撤销。",
+ descriptionAdded: "要还原 {path} 的所有未提交更改吗?该文件将从磁盘中删除。此操作无法撤销。",
+ confirm: "放弃更改",
+ },
+ injectMenu: {
+ injectFile: "注入文件",
+ injectLines: "注入行",
+ injectLinesRange: "注入行 {lines}",
+ sessionComment: "给会话的评论…",
+ sessionCommentRange: "给会话的评论 {lines}…",
+ reviewComment: "对拉取请求的评论…",
+ reviewCommentRange: "对拉取请求的评论 {lines}…",
+ revertSelected: "还原所选行",
+ revert: { one: "还原 {count} 行更改", other: "还原 {count} 行更改" },
+ },
+ fileDiff: {
+ readFailed: "无法读取 {path}",
+ whitespaceOnly: "仅空白字符",
+ onlyWhitespaceChanged: "仅空白字符发生了更改",
+ addAsContext: "将文件添加为上下文",
+ discard: "放弃更改",
+ viewed: "已查看",
+ },
+ reviewPanel: {
+ lastTurnHint: "上一轮运行期间磁盘上发生的更改,包括格式化工具、编辑器或你自己所做的编辑。",
+ discardFailed: "放弃更改失败:{error}",
+ reverted: "已还原 {name} 中的 +{added} -{deleted}",
+ undo: "撤销",
+ undoFailed: "无法撤销对 {name} 的还原",
+ revertFailed: "无法还原 {name} 中的行",
+ said: "说",
+ sourceLabel: "选择要显示的更改",
+ workingTree: "工作区",
+ lastTurn: "上一轮",
+ ended: "{age}前结束",
+ turnReadFailed: "无法读取上一轮",
+ notRepository: "不是 git 仓库",
+ loading: "正在加载…",
+ emptyTitle: "此时间窗口内没有任何更改。",
+ emptyBody: "上一轮开始到结束期间,磁盘上没有文件发生变化。",
+ unrecordedTitle: "没有记录上一轮。",
+ unrecordedBody: "一轮在此结束后,这里会显示内容。",
+ lostTitle: "这一轮的记录已丢失。",
+ lostBody: "一轮在此结束,但 lich 无法为它保存检出的快照。下一轮会照常记录。",
+ noChanges: "没有未提交的更改",
+ squeezed: "并排视图需要更宽的面板",
+ },
+ reviewSlots: {
+ forSession: "给会话 · {lines}",
+ onPullRequest: "对拉取请求 · {lines}",
+ addToBatch: "添加到批次",
+ addToReview: "添加到审查",
+ sessionPlaceholder: "这里应该改什么?它会连同其他内容一起发送给会话。",
+ reviewPlaceholder: "留下评论。提交审查时一并发送。",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/dock.ts b/frontend/src/lib/i18n/locales/zh-cn/dock.ts
new file mode 100644
index 00000000..3d551b5c
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/dock.ts
@@ -0,0 +1,37 @@
+import type { Shape } from "../../catalog"
+import type { dock as en } from "../en/dock"
+
+export const dock = {
+ rightDock: {
+ fileBrowser: "文件浏览器",
+ reviewChanges: "审查更改",
+ tabCode: "代码",
+ tabReview: "审查",
+ exitFullScreen: "退出全屏",
+ fullScreen: "全屏",
+ closePanel: "关闭面板",
+ fileTree: "文件树",
+ reviewPanel: "审查面板",
+ resizePanel: "调整面板大小",
+ },
+ searchResults: {
+ prompt: "搜索此检出中每个文件的文本",
+ searching: "正在搜索…",
+ noMatch: "没有文件包含“{query}”",
+ },
+ filesPanel: {
+ searchPlaceholder: "在文件中搜索",
+ filterPlaceholder: "按名称筛选",
+ searchLabel: "在文件中搜索",
+ filterLabel: "按名称筛选文件",
+ matchesLabel: "输入框匹配的内容",
+ byName: "名称",
+ byText: "文本",
+ readFailed: "无法读取此文件夹",
+ loading: "正在加载…",
+ noMatch: "没有匹配的文件",
+ empty: "这里没有文件",
+ backToTree: "返回文件树",
+ readOnly: "只读",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/env.ts b/frontend/src/lib/i18n/locales/zh-cn/env.ts
new file mode 100644
index 00000000..99e87d7f
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/env.ts
@@ -0,0 +1,58 @@
+import type { Shape } from "../../catalog"
+import type { env as en } from "../en/env"
+
+export const env = {
+ providerSummary: {
+ installed: "已安装",
+ installedCustomPath: "已安装 · 自定义路径",
+ signedOut: "已登出",
+ windowPercent: "{length} 窗口的 {percent}%",
+ percentUsed: "已用 {percent}%",
+ },
+ quota: {
+ tokenLogin: "令牌登录",
+ weekShort: "周",
+ },
+ paths: {
+ cwdUnknown: "工作目录未知 · 位于 {host} 内",
+ },
+ sandbox: {
+ windowsReason: "lich 在 Windows 上没有沙箱后端",
+ windowsAdvice: "无需安装任何东西,每个会话都直接在本机上运行。",
+ macReason: "sandbox-exec 不可用",
+ macAdvice:
+ "macOS 自带 /usr/bin/sandbox-exec,所以缺少可用的 sandbox-exec 意味着系统已损坏,lich 无法修复。每个会话都直接在本机上运行。",
+ linuxReason: "未安装 bubblewrap",
+ linuxAdvice: "请安装 bubblewrap 并重新打开 lich。在此之前,每个会话都直接在本机上运行。",
+ confinedMeans:
+ "一个只包含智能体自身状态的空主目录,本机只读,只能写入其检出目录内部。网络保持开启。",
+ },
+ vcsTools: {
+ gitWithout: "没有它,分支、差异和工作树都将为空。",
+ ghWithout: "没有它,无法使用拉取请求、检查和 PR 检出。",
+ },
+ binary: {
+ parkedProject: "{name} 覆盖已关闭",
+ parkedProjectFallback: "项目",
+ parkedGlobal: "全局覆盖已关闭",
+ executable: "可执行",
+ noSuchFile: "文件不存在",
+ notOnPath: "不在 $PATH 中",
+ notExecutable: "不可执行",
+ homeNotExpanded: "~ 不会被展开",
+ relativePath: "相对路径",
+ detailHomeShortcut:
+ "lich 直接启动该二进制文件,因此 ~ 按字面处理而不会被展开。请使用完整路径。",
+ detailRelative:
+ "相对路径会按每个会话自己的工作目录解析,因此每个会话指向的二进制文件各不相同。请使用完整路径。",
+ detailBroken: "问题解决之前,会话无法启动。你也可以关闭或清除覆盖设置,回退到下一层的配置。",
+ },
+ commitIdentity: {
+ noneLead: "此检出中没有 git 身份。",
+ noneNote: "在设置 user.email 之前,提交将被拒绝。",
+ landAsNamed: "提交将以 {name} 的身份记录",
+ landAs: "提交将以以下身份记录",
+ setLocal: ",在此仓库中设置,覆盖你的全局配置。",
+ setGlobal: ",来自 git user.email,而非此账户。",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/footer.ts b/frontend/src/lib/i18n/locales/zh-cn/footer.ts
new file mode 100644
index 00000000..c75dcd5a
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/footer.ts
@@ -0,0 +1,19 @@
+import type { Shape } from "../../catalog"
+import type { footer as en } from "../en/footer"
+
+export const footer = {
+ item: {
+ attach: { label: "附加文件", example: "附加文件" },
+ files: { label: "文件资源管理器", example: "文件" },
+ changes: { label: "更改" },
+ pr: { label: "拉取请求" },
+ checkout: { label: "分支" },
+ path: { label: "工作目录" },
+ model: { label: "模型", example: "模型" },
+ context: { label: "上下文窗口" },
+ plan: { label: "套餐用量" },
+ cost: { label: "费用" },
+ handsOn: { label: "实际操作时间" },
+ clock: { label: "日期和时间" },
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/git.ts b/frontend/src/lib/i18n/locales/zh-cn/git.ts
new file mode 100644
index 00000000..819a0c58
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/git.ts
@@ -0,0 +1,40 @@
+import type { Shape } from "../../catalog"
+import type { git as en } from "../en/git"
+
+export const git = {
+ baseStatus: {
+ behind: {
+ one: "落后 {base} {count} 个提交",
+ other: "落后 {base} {count} 个提交",
+ },
+ conflict: { one: "{count} 个文件有冲突", other: "{count} 个文件有冲突" },
+ },
+ fileSearch: {
+ cutMatches: "{count}+ 处匹配",
+ summary: "{matches}({files})",
+ matches: { one: "{count} 处匹配", other: "{count} 处匹配" },
+ cutNote: "仅显示前 {count} 个匹配行。请缩小搜索范围。",
+ tooLarge: {
+ one: "{count} 个超过 1 MB 的文件未被搜索。",
+ other: "{count} 个超过 1 MB 的文件未被搜索。",
+ },
+ },
+ fileTree: {
+ hidden: "已隐藏:{names}。",
+ cut: "此文件夹中的文件数超出了文件树能列出的数量。",
+ },
+ lastTurn: {
+ saidPreviousTurn: "来自上一轮",
+ noTurnWindow: "{name} 既不报告一轮的开始也不报告其结束,因此没有可划定的时间窗口。",
+ },
+ carry: {
+ reused: "{name} 已存在,因此按现状检出,未带入未提交的工作。",
+ failed: "无法带入未提交的工作:{error}",
+ },
+ codemirror: {
+ expand: { one: "展开 {count} 行未更改内容", other: "展开 {count} 行未更改内容" },
+ showLines: "显示第 {from}–{to} 行",
+ revertTitle: "还原此更改",
+ revert: "还原",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/hotkeys.ts b/frontend/src/lib/i18n/locales/zh-cn/hotkeys.ts
new file mode 100644
index 00000000..07379f9e
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/hotkeys.ts
@@ -0,0 +1,60 @@
+import type { Shape } from "../../catalog"
+import type { hotkeys as en } from "../en/hotkeys"
+
+export const hotkeys = {
+ group: {
+ sessions: "会话",
+ view: "视图",
+ app: "应用",
+ },
+ action: {
+ commandPalette: "命令面板",
+ newSession: "新建会话",
+ newWorktree: "新建工作树会话",
+ renameSession: "重命名当前会话",
+ closeSession: "关闭当前会话",
+ togglePin: "固定或取消固定当前会话",
+ openTerminal: "在会话目录中打开终端",
+ delegate: "委派给另一个会话",
+ nextSession: "下一个会话",
+ prevSession: "上一个会话",
+ nextWaitingSession: "下一个等待输入的会话",
+ focusTerminal: "聚焦会话终端",
+ nextProject: "下一个项目",
+ prevProject: "上一个项目",
+ toggleSidebar: "切换会话侧边栏",
+ toggleDock: "切换右侧停靠栏",
+ searchInFiles: "在文件中搜索",
+ splitBeside: "在此会话旁显示另一个会话",
+ otherPane: "聚焦下一个窗格",
+ settings: "设置",
+ pulls: "拉取请求",
+ shortcuts: "键盘快捷键",
+ },
+ terminalCost: {
+ a: "Ctrl+{key} 是 shell 的移到行首操作,会话将不再收到它。",
+ c: "Ctrl+{key} 是 shell 的中断操作,会话将不再收到它。",
+ d: "Ctrl+{key} 是 shell 的输入结束操作,会话将不再收到它。",
+ e: "Ctrl+{key} 是 shell 的移到行尾操作,会话将不再收到它。",
+ k: "Ctrl+{key} 是 shell 的删除到行尾操作,会话将不再收到它。",
+ l: "Ctrl+{key} 是 shell 的清屏操作,会话将不再收到它。",
+ q: "Ctrl+{key} 是 shell 的恢复输出操作,会话将不再收到它。",
+ r: "Ctrl+{key} 是 shell 的历史搜索,会话将不再收到它。",
+ s: "Ctrl+{key} 是 shell 的暂停输出操作,会话将不再收到它。",
+ u: "Ctrl+{key} 是 shell 的删除到行首操作,会话将不再收到它。",
+ w: "Ctrl+{key} 是 shell 的删除单词操作,会话将不再收到它。",
+ z: "Ctrl+{key} 是 shell 的挂起操作,会话将不再收到它。",
+ f: "Ctrl+{key} 是会话终端自带的搜索,会话将不再收到它。",
+ v: "Ctrl+{key} 是 lich 发送给智能体的图片粘贴操作,会话将不再收到它。",
+ Backspace: "Ctrl+{key} 是 lich 发送给智能体的删除单词操作,会话将不再收到它。",
+ },
+ unassigned: "未分配",
+ shortcuts: {
+ terminalTitle: "终端",
+ passthroughTitle: "传递给智能体",
+ searchOutput: "搜索会话的输出",
+ attachImage: "从剪贴板附加图片",
+ newline: "插入换行而不发送",
+ eraseWord: "删除前一个单词",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/index.ts b/frontend/src/lib/i18n/locales/zh-cn/index.ts
new file mode 100644
index 00000000..85431d2d
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/index.ts
@@ -0,0 +1,39 @@
+import type { Shape } from "../../catalog"
+import type { Messages } from "../en"
+import { common } from "./common"
+import { diff } from "./diff"
+import { dock } from "./dock"
+import { env } from "./env"
+import { footer } from "./footer"
+import { git } from "./git"
+import { hotkeys } from "./hotkeys"
+import { palette } from "./palette"
+import { prompts } from "./prompts"
+import { pulls } from "./pulls"
+import { session } from "./session"
+import { settings } from "./settings"
+import { shell } from "./shell"
+import { sidebar } from "./sidebar"
+import { tabs } from "./tabs"
+import { terminal } from "./terminal"
+import { update } from "./update"
+
+export const zhCN = {
+ common,
+ diff,
+ dock,
+ env,
+ footer,
+ git,
+ hotkeys,
+ palette,
+ prompts,
+ pulls,
+ session,
+ settings,
+ shell,
+ sidebar,
+ tabs,
+ terminal,
+ update,
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/palette.ts b/frontend/src/lib/i18n/locales/zh-cn/palette.ts
new file mode 100644
index 00000000..9ab0c052
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/palette.ts
@@ -0,0 +1,32 @@
+import type { Shape } from "../../catalog"
+import type { palette as en } from "../en/palette"
+
+export const palette = {
+ commandPalette: {
+ title: "命令面板",
+ placeholder: "跳转到会话、项目或某条消息,或输入 > 执行操作…",
+ searchLabel: "搜索会话和项目",
+ resultsLabel: "结果",
+ filterLabel: "筛选结果",
+ forgetFailed: "无法忘记 {label}:{error}",
+ resumeFailed: "无法恢复 {label}:{error}",
+ actionUnavailable: "{label} 在此处不可用",
+ nothingClosed: "还没有关闭的内容",
+ nothingClosedHint: "关闭的会话会保留在这里,包括它的分支、智能体和对话,直到它的工作树被移除。",
+ noMatches: "没有与 {query} 匹配的结果",
+ noMatchesInTab: "在{tab}中没有与 {query} 匹配的结果",
+ shownOfTotal: "{shown} / {total}",
+ tab: {
+ All: "全部",
+ Sessions: "会话",
+ Projects: "项目",
+ Messages: "消息",
+ History: "历史",
+ },
+ checkoutGone: "检出已不存在",
+ matches: { one: "{count} 处匹配", other: "{count} 处匹配" },
+ sessions: { one: "{count} 个会话", other: "{count} 个会话" },
+ relocate: "重新定位",
+ reopen: "重新打开",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/prompts.ts b/frontend/src/lib/i18n/locales/zh-cn/prompts.ts
new file mode 100644
index 00000000..150aede7
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/prompts.ts
@@ -0,0 +1,32 @@
+import type { Shape } from "../../catalog"
+import type { prompts as en } from "../en/prompts"
+
+export const prompts = {
+ pullRequest: {
+ conflicts: "拉取请求 #{number}({branch})与 {base} 存在合并冲突。请解决这些冲突。",
+ checksUnnamed: {
+ one: "拉取请求 #{number}({branch})的 CI 失败了。有 {count} 项检查未通过,请找出是哪些并修复。",
+ other:
+ "拉取请求 #{number}({branch})的 CI 失败了。有 {count} 项检查未通过,请找出是哪些并修复。",
+ },
+ checksListed: "拉取请求 #{number}({branch})的 CI 失败了。请修复以下检查:\n\n{checks}",
+ checksOmitted: {
+ one: "\n\n…另有 {count} 项失败的检查未列出。",
+ other: "\n\n…另有 {count} 项失败的检查未列出。",
+ },
+ create:
+ "分支 {branch} 还没有拉取请求。请用 `gh pr create` 创建一个:如果分支尚未推送到远程,先推送;根据提交和与基础分支的差异撰写标题和正文;如果仓库有拉取请求模板,请遵循该模板。",
+ },
+ delegate: {
+ session: "委派给“{target}”会话:",
+ newWorktree: "委派给一个新的工作树会话:",
+ newWorktreeWithCommand:
+ "委派给一个新的工作树会话(运行 `lich open --worktree --prompt `):",
+ },
+ reviewComments: {
+ header: "审查评论:",
+ },
+ issue: {
+ head: "GitHub issue #{number},{title}\n{url}",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/pulls.ts b/frontend/src/lib/i18n/locales/zh-cn/pulls.ts
new file mode 100644
index 00000000..4f14b900
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/pulls.ts
@@ -0,0 +1,317 @@
+import type { Shape } from "../../catalog"
+import type { pulls as en } from "../en/pulls"
+
+export const pulls = {
+ prHandoff: {
+ resolveConflicts: "解决冲突",
+ fixChecks: "修复 CI 错误",
+ },
+ byline: {
+ someone: "某人",
+ },
+ commentBox: {
+ sending: "正在发送…",
+ },
+ verdict: {
+ approved: "已批准",
+ changesRequested: "请求了更改",
+ dismissed: "评审已驳回",
+ commented: "已评论",
+ requested: "已请求评审",
+ },
+ mergeMessageDialog: {
+ squashAndMerge: "压缩并合并",
+ createMergeCommit: "创建合并提交",
+ description: "编辑提交消息,然后合并。",
+ subject: "提交消息",
+ body: "详细描述",
+ optional: "可选",
+ merging: "正在合并…",
+ merge: "合并",
+ },
+ mergeGate: {
+ changesRequested: "评审者请求了更改",
+ reviewRequired: "GitHub 要求先完成评审,但尚未有人评审",
+ merged: "拉取请求已合并",
+ closed: "拉取请求已关闭",
+ draft: "拉取请求仍是草稿",
+ conflicts: "与 {base} 存在冲突",
+ behind: "基础分支已更新,请先更新此分支",
+ pattern: {
+ match: "{target}必须匹配 {pattern}",
+ notMatch: "{target}不得匹配 {pattern}",
+ startWith: "{target}必须以 {pattern} 开头",
+ notStartWith: "{target}不得以 {pattern} 开头",
+ endWith: "{target}必须以 {pattern} 结尾",
+ notEndWith: "{target}不得以 {pattern} 结尾",
+ contain: "{target}必须包含 {pattern}",
+ notContain: "{target}不得包含 {pattern}",
+ },
+ target: {
+ message: "消息",
+ authorEmail: "作者邮箱",
+ committerEmail: "提交者邮箱",
+ },
+ ruleNote: "规则集约束着 {base}:对每个提交,{clauses}。",
+ },
+ commitAuthors: {
+ committed: "{who} 于 {date} 提交",
+ committedOn: "提交于 {date}",
+ noAccount: "未关联 GitHub 账号",
+ committedByNoAccount: "由未关联 GitHub 账号的地址提交",
+ strays: {
+ one: "有 {count} 个提交归在 {logins} 名下,而不是 @{author}。",
+ other: "有 {count} 个提交归在 {logins} 名下,而不是 @{author}。",
+ },
+ orphans: {
+ one: "有 {count} 个提交未关联 GitHub 账号。",
+ other: "有 {count} 个提交未关联 GitHub 账号。",
+ },
+ orphansWithEmails: {
+ one: "有 {count} 个提交未关联 GitHub 账号:{emails}。",
+ other: "有 {count} 个提交未关联 GitHub 账号:{emails}。",
+ },
+ andMore: "{shown} 等另外 {hidden} 项",
+ },
+ pullsChecks: {
+ none: "没有检查结果。",
+ },
+ pullsCommits: {
+ none: "没有提交。",
+ },
+ pullsConflicts: {
+ loading: "正在判断哪些文件存在冲突…",
+ failed: "无法判断哪些文件存在冲突:{error}",
+ stale: "目前没有文件冲突,GitHub 可能尚未同步。",
+ count: {
+ one: "{count} 个冲突文件",
+ other: "{count} 个冲突文件",
+ },
+ showFewer: "收起",
+ more: "另有 {hidden} 个",
+ },
+ pullsEmptyState: {
+ openFailed: "无法打开拉取请求:{error}",
+ noneFor: "{branch} 没有打开的拉取请求。",
+ none: "没有打开的拉取请求。",
+ openingSession: "正在打开会话…",
+ createWithAgent: "让智能体创建",
+ opening: "正在打开…",
+ openOnGitHub: "在 GitHub 上打开",
+ },
+ submitReviewDialog: {
+ verdict: {
+ approve: {
+ title: "批准",
+ action: "批准",
+ description: "批准此拉取请求。GitHub 不允许批准自己的拉取请求。",
+ },
+ comment: {
+ title: "评论",
+ action: "发送评审",
+ description: "只发送评论,不给出结论。",
+ },
+ request_changes: {
+ title: "请求更改",
+ action: "请求更改",
+ description: "要求完成下面的更改后才能合入。",
+ },
+ },
+ summaryPlaceholder: "摘要(可选)",
+ summaryAlone: "没有行评论,这次只发送摘要。",
+ lineComments: {
+ one: "随附 {count} 条行评论。",
+ other: "随附 {count} 条行评论。",
+ },
+ },
+ pullsConversation: {
+ commentFailed: "评论失败:{error}",
+ loading: "正在读取对话…",
+ empty: "还没有任何发言。在这里发表的评论属于拉取请求本身;针对某一行的评论会出现在差异中。",
+ resolved: {
+ one: "{count} 个已解决的讨论串",
+ other: "{count} 个已解决的讨论串",
+ },
+ commentOn: "评论此拉取请求",
+ comment: "评论",
+ commentPlaceholder: "说点关于整个拉取请求的内容",
+ dismissFailed: "驳回失败:{error}",
+ dismiss: "驳回",
+ why: "此评审不再适用的原因",
+ dismissReview: "驳回评审",
+ whyPlaceholder: "评审写下之后有什么变化",
+ },
+ pullsOverview: {
+ saveFailed: "无法保存描述:{error}",
+ openedBy: "由 {author} 打开",
+ description: "描述",
+ edit: "编辑",
+ noDescription: "没有描述。",
+ descriptionPlaceholder: "描述此拉取请求",
+ saving: "正在保存…",
+ candidatesFailed: "无法读取可评审的人员:{error}",
+ requestFailed: "无法向 {login} 请求评审:{error}",
+ withdrawFailed: "无法撤回对 {login} 的评审请求:{error}",
+ reviewers: "评审者",
+ nobodyYet: "暂无",
+ requestReview: "请求评审",
+ candidatesLoading: "正在读取可评审的人员…",
+ nobodyElse: "没有其他人可以评审",
+ },
+ pullsStats: {
+ checksFailing: "{total} 项检查中有 {failed} 项失败",
+ checksRunning: "{total} 项检查中有 {pending} 项正在运行",
+ checksPassed: {
+ one: "{count} 项检查通过",
+ other: "{count} 项检查通过",
+ },
+ merged: "已合并",
+ closed: "已关闭",
+ draft: "草稿",
+ open: "打开",
+ approved: "已批准",
+ changesRequested: "已请求更改",
+ reviewRequired: "需要评审",
+ threadsUnresolved: "已请求更改 · {total} 个讨论串中有 {open} 个未解决",
+ threadsResolved: "已请求更改 · 所有讨论串均已解决",
+ conflictsWith: "与 {base} 存在冲突",
+ mergeable: "可合并",
+ checkingMergeability: "正在检查是否可合并…",
+ },
+ pullsList: {
+ filter: {
+ all: "全部",
+ ready: "就绪",
+ drafts: "草稿",
+ failing: "失败",
+ },
+ sort: {
+ updated: "最近更新",
+ oldest: "最早更新",
+ failing: "失败的优先",
+ number: "编号最大",
+ },
+ qualifierWords: "关键词会匹配编号、标题、作者和分支。",
+ show: "显示拉取请求列表",
+ hide: "隐藏拉取请求列表",
+ filterPlaceholder: "筛选,例如 is:merged",
+ filterLabel: "筛选拉取请求",
+ filterGroup: "拉取请求筛选",
+ state: {
+ open: "打开",
+ closed: "已关闭",
+ merged: "已合并",
+ all: "全部",
+ },
+ cappedHint: "只加载了最近更新的 {limit} 个,筛选只在这些中搜索。",
+ capped: "前 {limit} 个",
+ none: {
+ open: "没有打开的拉取请求。",
+ closed: "没有已关闭的拉取请求。",
+ merged: "没有已合并的拉取请求。",
+ all: "没有拉取请求。",
+ },
+ noMatch: "没有符合该筛选条件的内容。",
+ review: {
+ approved: "已批准",
+ changesRequested: "已请求更改",
+ },
+ draft: "草稿",
+ fork: "分叉",
+ checkedOut: "已检出",
+ },
+ pullsFiles: {
+ loadFailed: "无法加载差异:{error}",
+ none: "没有文件更改",
+ resizeTree: "调整文件树大小",
+ hideTree: "隐藏文件树",
+ showTree: "显示文件树",
+ viewed: "已查看 {viewed}/{total}",
+ },
+ reviewThread: {
+ replyFailed: "回复失败:{error}",
+ resolveFailed: "标记为已解决失败:{error}",
+ reopenFailed: "重新打开失败:{error}",
+ line: "第 {line} 行",
+ resolved: "已解决",
+ outdated: "· 已过时",
+ comments: {
+ one: "{count} 条评论",
+ other: "{count} 条评论",
+ },
+ reopen: "重新打开",
+ resolve: "标记为已解决",
+ reply: "回复",
+ replyPlaceholder: "回复此讨论串",
+ pending: "待处理,提交评审时发送",
+ edit: "编辑此评论",
+ discard: "丢弃此评论",
+ },
+ pulls: {
+ alreadyRemoved: "{name} 已被移除",
+ pinnedSession: "该工作树有已固定的会话,请先取消固定。",
+ notCreatedByLich: "此工作树不是 lich 创建的,请在侧边栏中移除。",
+ dirty: "该工作树有未提交的更改,请在侧边栏中移除。",
+ removed: "已移除 {name}",
+ removeFailed: "移除工作树失败:{error}",
+ merged: "已将 #{number} 合并到 {base}",
+ announceFailed: "无法通知 {name} 中的会话:{error}",
+ removeWorktree: "移除工作树",
+ openSessionFailed: "无法打开会话:{error}",
+ handOffFailed: "无法交给该会话:{error}",
+ goToSession: "前往会话",
+ openInSession: "在会话中打开",
+ forkBlocked: "头部分支位于不允许维护者编辑的分叉上,无法把提交推回去",
+ noRepository: "没有仓库",
+ loadFailed: "无法加载拉取请求:{error}",
+ runningRemoval: {
+ title: "会话仍在运行",
+ description:
+ "{labels} 正在 {name} 中执行一轮。移除工作树会让它当场停止,进行中的这一轮将丢失。",
+ removeAnyway: "仍然移除",
+ },
+ noSessionToHandOff: "此检出上没有可以交给的会话",
+ headNotOnBranch: "HEAD 不在任何分支上",
+ },
+ pullRequestView: {
+ rebaseAndMerge: "变基并合并",
+ failingChecks: {
+ one: "有 {count} 项检查失败,GitHub 仍会合并",
+ other: "有 {count} 项检查失败,GitHub 仍会合并",
+ },
+ mergeFailed: "合并失败:{error}",
+ approvedToast: "已批准 #{number}",
+ reviewSent: "已在 #{number} 上发送评审",
+ reviewSentWithComments: {
+ one: "已在 #{number} 上发送评审,含 {count} 条评论",
+ other: "已在 #{number} 上发送评审,含 {count} 条评论",
+ },
+ reviewFailed: "评审失败:{error}",
+ opening: "正在打开…",
+ openingSession: "正在打开会话…",
+ approving: "正在批准…",
+ approved: "已批准",
+ approve: "批准",
+ submitReview: "提交评审",
+ comment: "评论",
+ requestChanges: "请求更改",
+ discardPending: {
+ one: "丢弃 {count} 条待处理评论",
+ other: "丢弃 {count} 条待处理评论",
+ },
+ merging: "正在合并…",
+ merge: "合并",
+ methodBypass: "{method},绕过规则",
+ editMessage: "{method},编辑消息…",
+ editMessageBypass: "{method},编辑消息…(绕过规则)",
+ refresh: "刷新",
+ open: "打开",
+ tab: {
+ overview: "概览",
+ commits: "提交",
+ files: "文件更改",
+ conversation: "对话",
+ checks: "检查",
+ },
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/session.ts b/frontend/src/lib/i18n/locales/zh-cn/session.ts
new file mode 100644
index 00000000..747f59e5
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/session.ts
@@ -0,0 +1,60 @@
+import type { Shape } from "../../catalog"
+import type { session as en } from "../en/session"
+
+export const session = {
+ schedule: {
+ choice15Minutes: "15 分钟",
+ choice1Hour: "1 小时",
+ choice4Hours: "4 小时",
+ choiceTomorrow: "明天",
+ countdown: "{amount}后",
+ today: "今天 {clock}",
+ weekdayAt: "{weekday} {clock}",
+ },
+ limitLine: {
+ sessionWindow: "会话上限",
+ weeklyWindow: "每周上限",
+ usageWindow: "用量上限",
+ },
+ filter: {
+ phase: {
+ waiting: "等待中",
+ running: "运行中",
+ unread: "未读",
+ idle: "空闲",
+ },
+ phaseJoiner: "或",
+ noMatch: "没有会话。当前会话保持显示。",
+ noMatchQuery: "没有与“{query}”匹配的会话。当前会话保持显示。",
+ noMatchPhases: "没有{phases}的会话。当前会话保持显示。",
+ noMatchPhasesQuery: "没有与“{query}”匹配的{phases}会话。当前会话保持显示。",
+ },
+ cost: {
+ mixedModels: "此对话切换过模型,因此 lich 无法计价。",
+ unpricedModel: "此模型暂无价格,lich 需要联网获取。",
+ },
+ handsOn: {
+ detailTurn:
+ "此会话被实际操作的时长,包括输入、汇报或运行一轮的时间。超过 15 分钟的间隔计为离开。",
+ detailTool:
+ "此会话被实际操作的时长,包括输入或汇报工具调用的时间。超过 15 分钟的间隔计为离开。",
+ },
+ spawnGate: {
+ checkoutGone: "此会话的工作树已不存在,因此会话已关闭。请重新创建工作树以继续。",
+ conversationGone: "之前的对话已不可用,正在启动新会话。",
+ },
+ fork: {
+ unavailable: "{name} 不支持分叉,只能恢复。",
+ },
+ palette: {
+ untitledConversation: "未命名对话",
+ groupSessions: "会话",
+ groupProjects: "项目",
+ groupMessages: "消息",
+ groupOpen: "已打开",
+ groupClosed: "已关闭",
+ groupClosedSessions: "已关闭的会话",
+ groupOutsideLich: "lich 之外",
+ indexing: { one: "正在索引 {count} 个会话", other: "正在索引 {count} 个会话" },
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/settings.ts b/frontend/src/lib/i18n/locales/zh-cn/settings.ts
new file mode 100644
index 00000000..db8b2fc8
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/settings.ts
@@ -0,0 +1,404 @@
+import type { Shape } from "../../catalog"
+import type { settings as en } from "../en/settings"
+
+export const settings = {
+ language: {
+ uiTitle: "界面语言",
+ uiDescription: "lich 自身界面所用的语言。",
+ promptTitle: "提示词语言",
+ promptDescription:
+ "lich 交给智能体的文本所用的语言:简报、转发的消息和通知。此后启动的会话在所有场景下都使用该语言;已打开的会话仅在转发的消息中使用。",
+ uiSearchWords: "语言 区域 翻译 英语 葡萄牙语 简体中文",
+ promptSearchWords: "语言 区域 翻译 智能体 简报 转发 英语 葡萄牙语 简体中文",
+ promptSaveFailed: "无法保存提示词语言:{error}",
+ },
+ settings: {
+ search: "搜索设置",
+ resultCount: { one: "{count} 项设置", other: "{count} 项设置" },
+ noMatch: "没有与 {query} 匹配的内容。搜索只匹配每项设置的名称,不匹配其值。",
+ section: {
+ appearance: "外观",
+ notifications: "通知",
+ hotkeys: "快捷键",
+ providers: "提供商",
+ sandbox: "沙箱",
+ versionControl: "版本控制",
+ updates: "更新",
+ help: "帮助",
+ },
+ },
+ appearanceSettings: {
+ importPickTitle: "导入主题",
+ templatePickTitle: "保存主题模板",
+ imported: "已导入主题:{name}",
+ importFailed: "主题导入失败:{error}",
+ installed: {
+ one: "已从 {pack} v{version} 安装 {count} 个主题",
+ other: "已从 {pack} v{version} 安装 {count} 个主题",
+ },
+ installFailed: "主题安装失败:{error}",
+ upToDate: "{pack} 已是 v{version}",
+ updated: "已将 {pack} 更新到 v{version}",
+ updateFailed: "主题更新失败:{error}",
+ removed: "已移除主题:{name}",
+ removeFailed: "主题移除失败:{error}",
+ templateSaved: "已将主题模板保存到 {path}",
+ templateFailed: "主题模板生成失败:{error}",
+ zoomTitle: "缩放",
+ zoomOut: "缩小",
+ zoomIn: "放大",
+ resetZoom: "重置缩放",
+ resetTextSize: "重置终端文字大小",
+ textSizeTitle: "终端文字大小",
+ textSmaller: "减小终端文字",
+ textLarger: "增大终端文字",
+ replaceThemesTitle: "替换已导入的主题?",
+ replaceThemesDescription:
+ "该仓库包含已安装的主题:{themes}。仍要安装吗?这将永久删除之前的版本。",
+ replaceThemes: "替换主题",
+ removeThemeTitle: "移除已导入的主题?",
+ removeThemeDescription: "删除 {name}?这将从 lich 中移除已导入的主题文件。",
+ removeTheme: "移除主题",
+ replaceThemeTitle: "替换已导入的主题?",
+ replaceThemeDescription: "已存在 id 为 {id} 的主题。仍要导入吗?这将永久删除之前的主题。",
+ replaceTheme: "替换主题",
+ },
+ themePicker: {
+ title: "主题",
+ searchWords: "颜色 深色 浅色 终端 调色板",
+ description: "为界面和终端着色。",
+ choose: "选择主题",
+ system: "系统",
+ followsOs: "跟随操作系统",
+ inUse: "使用中",
+ bundled: "内置",
+ count: { one: "{count} 个主题", other: "{count} 个主题" },
+ import: "导入",
+ updateNamed: "更新 {name}",
+ updateHint: "检查仓库中是否有更新的版本",
+ removeNamed: "移除 {name}",
+ },
+ brokenThemeList: {
+ label: "无法加载的主题",
+ heading: { one: "{count} 个主题无法加载", other: "{count} 个主题无法加载" },
+ removeNamed: "移除 {name}",
+ remove: "移除",
+ },
+ importThemeDialog: {
+ title: "导入主题",
+ description: "从 git 仓库安装可保留主题的版本信息,也可以选择单个主题文件。",
+ repositoryUrl: "仓库 URL",
+ install: "安装",
+ installing: "正在安装…",
+ repositoryHint: "包含 {file} 和一个或多个主题文件的仓库。更新时,lich 比较的是其清单中的版本。",
+ or: "或",
+ chooseFile: "选择文件…",
+ fileHint: "单个主题文件。没有版本,也没有更新。",
+ downloadTemplate: "下载模板",
+ },
+ fontSetting: {
+ title: "终端字体",
+ searchWords: "字体 等宽",
+ sample: "the quick brown fox 0O1lI",
+ search: "搜索字体",
+ empty: "没有匹配的字体。",
+ },
+ footerSettings: {
+ title: "页脚",
+ searchWords: "状态栏 布局 排列",
+ loading: "正在加载已保存的选择…",
+ saving: "正在保存…",
+ description: "在两侧之间拖动项目,或拖出以隐藏。对所有项目生效。",
+ restoreDefault: "恢复默认",
+ saveFailed: "无法保存页脚:{error}",
+ ceilingTitle: "花费上限",
+ ceilingSearchWords: "费用 预算 美元 usd",
+ ceilingDescription:
+ "会话的 API 费用接近此金额时发出警告:80% 为琥珀色,95% 为红色。留空表示不设上限。",
+ ceilingPlaceholder: "无上限",
+ ceilingLabel: "会话花费上限(美元)",
+ },
+ footerLayoutEditor: {
+ zone: { available: "可用", left: "左侧", right: "右侧" },
+ allPlaced: "所有项目都已在页脚中",
+ dragHere: "将项目拖到此处",
+ moveNamed: "移动 {name}",
+ previewLabel: "页脚预览",
+ previewHint: "预览 · 示例值 · 滚动查看所有项目",
+ previewScrollLabel: "可滚动的页脚预览",
+ previewEmpty: "未选择页脚项目",
+ },
+ diffLayoutSetting: {
+ title: "差异布局",
+ searchWords: "并排 拆分 统一 评审",
+ description:
+ "审查标签页和拉取请求中变更的显示方式。面板过窄而放不下两列时,并排视图会显示为统一视图。",
+ layout: { unified: "统一", split: "并排" },
+ },
+ notificationsSettings: {
+ needsInputTitle: "会话需要输入时通知我",
+ needsInputDescription:
+ "当会话被你阻塞(例如权限提示或提问),且你当前不在 lich 窗口中时,发出桌面通知。你在 lich 中时不会有变化:由卡片上的提醒铃和提示消息来告知。",
+ needsInputLabel: "会话需要输入时发出桌面通知",
+ finishedTitle: "会话完成工作时通知我",
+ finishedDescription:
+ "当会话结束一轮、没有需要询问你的内容,且你当前不在 lich 窗口中时,发出桌面通知。与上面的设置相互独立:即使你不想被每个提示打扰,也值得知道自己离开时的长时间运行已经结束。不会附带提示消息:在 lich 中,卡片及其项目标签页已经会告知。",
+ finishedLabel: "会话完成工作时发出桌面通知",
+ },
+ hotkeysSettings: {
+ alsoBound: "同时绑定到 {actions}",
+ pressKeys: "请按下按键…",
+ unassignNamed: "取消 {name} 的快捷键",
+ unassignHint: "不为此操作绑定快捷键",
+ resetNamed: "重置 {name} 的快捷键",
+ resetHint: "恢复默认快捷键",
+ terminalNote:
+ "这是 lich 自有的固定快捷键:它会覆盖浏览器的加速键,而浏览器只响应原本的组合键,不响应重新绑定后的按键。",
+ passthroughNote:
+ "lich 会在这些按键送往智能体终端的途中改写它们,因此它们是固定的,不可重新绑定。",
+ },
+ providersPane: {
+ detecting: "正在检测提供商…",
+ heading: "提供商",
+ addProvider: "添加提供商",
+ moreDetected: "另检测到 {count} 个",
+ defaultTitle: "默认提供商",
+ defaultSearchWords: "智能体 新建会话",
+ defaultDescription:
+ "隐式的会话操作所启动的提供商:新建会话快捷键、空项目中的“新建会话”按钮,以及新创建的工作树。",
+ allProjects: "所有项目",
+ defaultForAll: "所有项目的默认提供商",
+ defaultForProject: "{name} 的默认提供商",
+ clear: "清除",
+ clearFallsBack: "清除后回退到 {name}",
+ following: "跟随所有项目:{name}",
+ openSettings: "打开 {name} 的设置",
+ defaultHere: "此处默认",
+ customPath: "自定义路径",
+ skipWorktrees: "工作树",
+ skipEverywhere: "无提示",
+ enableNamed: "启用 {name}",
+ },
+ providersSettings: {
+ defaultDescription:
+ "隐式的会话操作所使用的提供商,除非你所在的项目在 设置 › 提供商 中另行指定。",
+ defaultLabel: "全局默认提供商",
+ detected: "已在 PATH 中检测到",
+ },
+ providerDetail: {
+ back: "所有提供商",
+ enabled: "已启用",
+ rightNowTitle: "当前状态",
+ rightNowSearchWords: "打开的会话 文档",
+ openSessions: { one: "{count} 个会话已打开", other: "{count} 个会话已打开" },
+ documentation: "文档",
+ },
+ providerToggleRow: {
+ notFound: "在 PATH 中未找到",
+ },
+ providerDocsLink: {
+ howToInstall: "安装方法",
+ },
+ providerBinary: {
+ title: "可执行文件",
+ searchWords: "路径 可执行 命令 binary",
+ description: "会话启动所用的可执行文件。",
+ descriptionProject: "{name} 中的会话启动所用的可执行文件。",
+ pickTitle: "选择 {provider} 的可执行文件",
+ pickerFailed: "无法打开文件选择器",
+ projectOnly: "仅 {name}",
+ allProjects: "所有项目",
+ layersHint: "来源:启用的第一层优先生效",
+ done: "完成",
+ useLayer: "使用为{layer}设置的 {provider} 可执行文件",
+ layerPath: "{layer}的 {provider} 可执行文件",
+ pickLayer: "选择{layer}的 {provider} 可执行文件",
+ browse: "浏览",
+ useDifferent: "使用其他可执行文件",
+ sourceProjectNamed: "为 {name} 设置",
+ sourceProject: "为此项目设置",
+ sourceGlobal: "为所有项目设置",
+ sourcePath: "来自 $PATH",
+ },
+ providerBinSettings: {
+ skipTitle: "跳过权限提示",
+ skipSearchWords: "yolo 危险 批准 确认",
+ skipDescription:
+ "{provider} 可以在不询问的情况下运行到什么程度:它会不经确认地编辑文件、运行命令和安装内容,lich 使用 {flag} 启动它。",
+ skipLabel: "{provider} 可以在不询问的情况下运行到什么程度",
+ skip: {
+ never: {
+ label: "从不",
+ consequence: "在每个检出中,每次编辑和每条命令都会等待你确认。",
+ },
+ worktrees: {
+ label: "仅工作树",
+ consequence: "项目目录中的会话仍会询问。",
+ },
+ everywhere: {
+ label: "所有位置",
+ consequence: "包括你工作的目录树。不会再询问任何内容。",
+ },
+ },
+ confirmTitle: "跳过权限提示:{level}",
+ confirmDescription:
+ "{provider} 将不经确认地编辑文件、运行命令和安装内容,并使用 {flag} 启动。{consequence}",
+ confirm: "跳过提示",
+ },
+ planUsageSetting: {
+ title: "套餐用量",
+ searchWords: "配额 订阅 上限 用量",
+ description:
+ "你的订阅在每个时间窗口内已用掉多少,从你的账户读取,lich 打开期间每 5 分钟刷新一次。",
+ signedOut: "已登出。",
+ signedOutRun: "已登出。运行 {command} 以读取套餐用量。",
+ readFailed: "无法读取套餐用量。提供商的用量端点没有文档,可能已经变更。",
+ used: "已用",
+ resets: "重置于",
+ },
+ restoreSetting: {
+ title: "恢复的会话",
+ searchWords: "恢复 继续 对话 新建 重启 询问",
+ description: "在 lich 重启之前运行过 {provider} 的卡片,在你第一次打开时的行为。",
+ label: "已恢复的 {provider} 卡片的行为",
+ choice: {
+ ask: { label: "询问", consequence: "每张已恢复的卡片在打开前都会询问。" },
+ resume: {
+ label: "继续",
+ consequence: "已恢复的卡片在打开时继续其对话。如需空白会话,请新建会话。",
+ },
+ fresh: {
+ label: "重新开始",
+ consequence: "已恢复的卡片以空白状态打开,不会接续旧的对话。",
+ },
+ },
+ },
+ subagentCardsSetting: {
+ title: "子智能体作为 lich 会话",
+ searchWords: "智能体 子智能体 卡片 工作树 委派 后台",
+ description:
+ "通用子智能体会打开自己的卡片和工作树,而不是隐藏在 {provider} 内部运行。对更改后打开的会话生效。",
+ label: "将 {provider} 子智能体作为 lich 会话运行",
+ },
+ ultracodeSetting: {
+ title: "Ultracode",
+ searchWords: "工作流 编排 多智能体 强度",
+ description:
+ "每个 {provider} 会话启动时都开启 ultracode,在其运行的任意强度下编排多智能体工作流。会话重启或恢复后仍保持开启。",
+ label: "以 ultracode 启动 {provider} 会话",
+ },
+ sandboxSettings: {
+ rung: { off: "关闭", ask: "询问", worktrees: "工作树", everywhere: "所有位置" },
+ exposes: {
+ off: "每个会话都在本机上运行。",
+ ask: "你取消勾选其选项的会话会在本机上运行。",
+ worktrees: "项目目录中的会话在本机上运行。",
+ },
+ summary: "空的主目录,本机只读,仅在检出目录中可写。网络保持开启。",
+ confinedTitle: "哪些会话在受限环境中运行",
+ confinedSearchWords: "隔离 bwrap seatbelt",
+ confinedHint:
+ "“询问”会在“新建会话”菜单和“新建工作树”对话框中提出这个问题。在无人可以回答的地方打开的会话(由其他会话或通过 MCP 工具打开)会受限运行。终端永远不受限;沙箱是为无人值守的智能体准备的。",
+ carryTitle: "受限会话可以带入什么",
+ sshTitle: "SSH 代理",
+ sshSearchWords: "推送 密钥",
+ sshDescription:
+ "git push 可在其中使用。会话运行期间,使用你的代理中的每个身份,对任意主机签名。",
+ sshLoaded: "已加载:{keys}",
+ sshNone: "未加载任何密钥。",
+ ghTitle: "GitHub 令牌",
+ ghSearchWords: "gh 凭据",
+ ghDescription: "gh 可在其中使用。智能体可以从其环境中读出该令牌,并在此仓库之外使用它。",
+ ghAccount: "以 {login} 身份。",
+ ghActiveAccount: "以 gh 的活动账户身份。",
+ rungLabel: "哪些 {provider} 会话在受限环境中运行",
+ confirmTitle: "减少受限的 {provider} 会话",
+ confirmDescription: "{rung}:{exposes}",
+ confirm: "不再限制",
+ canConfine: "此机器可以限制会话",
+ cannotConfine: "此机器无法限制会话",
+ statusDetail: ":{detail}",
+ },
+ vcsToolsSetting: {
+ title: "命令行工具",
+ searchWords: "git gh 安装",
+ description: "lich 通过 git 和 GitHub CLI 工作。此界面上的一切操作都经由它们执行。",
+ notOnPath: "不在 $PATH 中",
+ install: "安装",
+ },
+ versionControlSettings: {
+ account: "GitHub 账户",
+ accountFor: "{name} 的 GitHub 账户",
+ accountSearchWords: "登录 gh 切换",
+ openProject: "打开一个项目以配置其版本控制。",
+ accountDescription:
+ "lich 为此项目以哪个账户运行 gh:用于拉取请求、检查和 PR 检出。gh 每个主机只保留一个活动账户,因此只有其他账户可见的仓库会显示为未找到。企业主机上的账户会连同主机一起列出。",
+ activeAccount: "gh 的活动账户",
+ selectAccount: "选择账户",
+ listFailed: "无法列出 GitHub 账户:{error}",
+ noAccounts: "gh 尚未登录任何账户。请在终端中运行 `gh auth login`。",
+ },
+ updatesSettings: {
+ applicationTitle: "应用程序",
+ applicationSearchWords: "版本 升级",
+ applicationDescription: "lich {version},启动时以及每小时检查一次更新。",
+ checkForUpdates: "检查更新",
+ available: "lich {version} 可用,请按提示操作。",
+ upToDate: "你使用的已是最新版本。",
+ checkFailed: "检查失败,是否已联网?",
+ whatsNewTitle: "新增内容",
+ whatsNewSearchWords: "更新日志 发行说明",
+ patchNotesFor: "v{version} 的更新说明。",
+ noPatchNotes: "此构建没有更新说明。",
+ viewPatchNotes: "查看更新说明",
+ },
+ pluginSetting: {
+ title: "lich 插件",
+ searchWords: "钩子 安装",
+ description: "在支持运行它的提供商 CLI 中提供会话状态、标题、git 刷新和恢复。",
+ installing: "正在为 {name} 安装 lich 插件…",
+ installed: "插件已安装。重启会话以生效。",
+ installFailed: "安装失败",
+ updating: "正在为 {name} 更新 lich 插件…",
+ updated: "插件已更新。重启会话以生效。",
+ updateFailed: "更新失败",
+ checked: "已检查。",
+ checkFailed: "检查失败,是否已联网?",
+ cliMissing: "未安装 CLI",
+ notInstalled: "未安装插件",
+ unsupported: "v{version},此 lich 不支持",
+ install: "安装",
+ updateTo: "更新到 v{version}",
+ installVersion: "安装 v{version}",
+ codexHint: "Codex:在 Codex 会话中运行 /hooks 以信任该插件的钩子。",
+ crushHint:
+ "Crush:上报其会话 id 并刷新 git 状态。它没有回合结束事件,因此其卡片不显示状态,并保留自己的名称。",
+ ompHint:
+ "oh-my-pi:上报其状态、名称和 git 变更。它没有可观测的批准事件,因此等待你授权的会话显示旋转图标,而不是提醒铃。",
+ antigravityHint:
+ "Antigravity:上报其状态、名称和 git 变更。它没有可观测的批准事件,因此等待你授权的会话显示旋转图标,而不是提醒铃。",
+ cursorHint:
+ "Cursor CLI:运行安装在 Claude Code 中的插件,因此它上报该版本所上报的内容,并随之更新。lich 在此只添加自己的工具。与上面两者一样,它不会触发批准事件,因此等待你授权的会话显示旋转图标,而不是提醒铃。",
+ checkForUpdates: "检查更新",
+ },
+ helpSettings: {
+ bugTitle: "报告问题",
+ bugSearchWords: "issue github 缺陷 bug",
+ bugDescription:
+ "在浏览器中打开问题表单,并已填好版本和平台。lich 不会替你提交问题,需要你自己填写。在终端中运行 `lich rage`,可将日志、版本以及 lich 在此机器上发现的信息打包成一个归档文件以便附上。",
+ openBugReport: "打开问题报告",
+ logTitle: "日志文件",
+ logSearchWords: "调试 日志",
+ logDescription:
+ "日志包含绝对路径、项目和分支名称以及你的 gh 登录名,但绝不包含会话令牌。附到任何地方之前请先阅读。",
+ openingLogFolder: "正在打开日志文件夹…",
+ logFolderOpened: "已打开日志文件夹。",
+ openLogFolderFailed: "无法打开日志文件夹",
+ openLogFolder: "打开日志文件夹",
+ noLogFile: "仅输出到 stderr,没有日志文件。",
+ aboutTitle: "关于",
+ aboutSearchWords: "版本 许可证",
+ aboutVersion: "lich {version},{platform}。",
+ repository: "仓库",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/shell.ts b/frontend/src/lib/i18n/locales/zh-cn/shell.ts
new file mode 100644
index 00000000..5bed149e
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/shell.ts
@@ -0,0 +1,180 @@
+import type { Shape } from "../../catalog"
+import type { shell as en } from "../en/shell"
+
+export const shell = {
+ agentPluginGate: {
+ updateAvailable: "lich 插件 {version} 可用",
+ update: "更新",
+ later: "稍后",
+ installVersion: "安装 v{version}",
+ updating: "正在更新 lich 插件…",
+ updated: "插件已更新。重启会话以应用。",
+ updateFailed: "更新失败",
+ installed: "插件已安装。重启会话以应用。",
+ codexTrust: "Codex:在 Codex 会话中运行 /hooks 以信任该插件的钩子。",
+ installFailed: "安装失败:{error}",
+ title: "启用智能体集成",
+ description:
+ "lich 插件会向应用报告会话的状态和标题,刷新其 git 状态,并让已恢复的会话继续之前的对话。请将它安装到你使用的 CLI 中。",
+ needsHooks: "需要通过 /hooks 信任",
+ dontAskAgain: "不再询问",
+ notNow: "暂不",
+ installing: "正在安装…",
+ install: "安装",
+ },
+ appUpdateGate: {
+ available: "lich {version} 可用",
+ updateAndInstall: "更新并安装",
+ later: "稍后",
+ updated: "lich 已更新到 {version}",
+ retry: "重试",
+ restart: "重启",
+ restartFailed: "重启失败:{error}",
+ install: "安装",
+ viewRelease: "查看发布说明",
+ },
+ confirmDialog: {
+ cancel: "取消",
+ },
+ emptySessions: {
+ title: "没有打开的会话",
+ description: "打开一个会话,开始在此项目中工作。",
+ noAgent: "在 PATH 中未找到智能体,因此这里会打开终端。请在设置 › 提供商中配置。",
+ newTerminal: "新建终端",
+ newSession: "新建会话",
+ },
+ fileTree: {
+ openInEditor: "在编辑器中打开",
+ expandAll: "全部展开",
+ collapseAll: "全部折叠",
+ },
+ footerBar: {
+ attachFailed: "无法附加文件",
+ attachFile: "附加文件",
+ browseCode: "浏览代码",
+ reviewChanges: "审查更改",
+ viewPullRequest: "查看拉取请求",
+ pullRequest: "PR #{number}",
+ leftFooter: "左侧页脚",
+ rightFooter: "右侧页脚",
+ },
+ footerSession: {
+ contextWindow: "上下文窗口",
+ contextTokens: "{tokens} / {window} 个 token",
+ costUnavailable: "费用不可用",
+ sessionCost: "会话费用",
+ costOfBudget: "预算 {budget} 中的 {cost}",
+ costNote: "此会话各对话的 API 费用。订阅收费可能不同。",
+ handsOnTime: "亲自操作时长",
+ handsOnFailed: "无法读取亲自操作时长。",
+ },
+ gitMissingGate: {
+ title: "未安装 git",
+ description:
+ "lich 通过 git 读取每个分支、差异和工作树。没有 git 时会话仍可运行,但版本控制相关界面会保持为空。",
+ notNow: "暂不",
+ installGit: "安装 git",
+ },
+ home: {
+ title: "没有打开的项目",
+ description: "打开一个文件夹,开始终端会话。",
+ openProject: "打开项目",
+ },
+ notificationsOptIn: {
+ title: "会话需要你时通知你?",
+ description:
+ "刚才有会话在你离开时请求了你的输入。发生这种情况时,lich 可以弹出桌面通知,注明会话及其项目,这样你就可以启动任务后离开窗口。你正在查看的会话不会发送通知。你可以随时在设置 › 通知中更改。",
+ noThanks: "不用了",
+ notifyMe: "通知我",
+ },
+ paneSeams: {
+ resizeColumns: "调整列宽",
+ resizeRows: "调整行高",
+ },
+ patchNotesDialog: {
+ title: "lich {version} 的新功能",
+ viewChangelog: "查看完整更新日志",
+ gotIt: "知道了",
+ },
+ planQuota: {
+ planUsage: "套餐用量",
+ locked: "已锁定",
+ },
+ providerSetupDialog: {
+ chooseTitle: "选择你的智能体",
+ noneTitle: "未找到编码智能体",
+ chooseDescription:
+ "lich 在你的机器上找到了这些。打开你使用的,其余的不会出现在新建会话菜单中。",
+ noneDescription: "lich 运行你机器上已安装的智能体,但一个也没找到。请安装一个,然后再检查。",
+ changeLater: "你可以随时在设置 › 提供商中更改。",
+ pointToBinary: "已经安装了?在设置 › 提供商中让 lich 指向它的可执行文件。",
+ continue: "继续",
+ },
+ quotaGauge: {
+ aheadOfPace: "超前于进度",
+ aheadTooltip: "消耗速度超前于此窗口的进度。",
+ locked: "已锁定",
+ },
+ resumeSessionDialog: {
+ title: "恢复之前的会话?",
+ description:
+ "{label} 留下了一段对话({id})。选择恢复可从中断处继续,或选择新建以开始空白对话。",
+ alwaysFor: "对 {provider} 始终如此",
+ changeInSettings: "· 可在设置 › 提供商中更改",
+ thisProvider: "此提供商",
+ startNew: "新建",
+ resume: "恢复",
+ },
+ shortcutsOverlay: {
+ title: "键盘快捷键",
+ rebind: "在设置 › 快捷键中重新绑定",
+ close: "关闭",
+ },
+ uncleanExitGate: {
+ title: "lich 上次运行意外结束",
+ description: "你的会话已恢复,但它们正在运行的内容已随之停止。",
+ openLogFolder: "打开日志文件夹",
+ opening: "正在打开日志文件夹…",
+ opened: "已打开日志文件夹。",
+ openFailed: "无法打开日志文件夹",
+ dismiss: "忽略",
+ },
+ updateProgressToast: {
+ downloading: "正在下载 lich {version}",
+ installing: "正在安装 lich {version}…",
+ reopens: "lich 会自行关闭并重新打开。",
+ },
+ app: {
+ stage: "舞台",
+ reloadStage: "重新加载舞台",
+ screen: "此界面",
+ panel: "此面板",
+ },
+ projects: {
+ relocated: "{name} 现在打开 {path}",
+ relocateFailed: "重新定位失败:{error}",
+ noLongerAvailable: "{label} 已无法恢复",
+ bringBackFailed: "无法恢复 {label}",
+ closed: "已关闭 {label}",
+ undo: "撤销",
+ entrypointSet: "已设置入口命令",
+ entrypointSetDescription: "将在此终端下次启动时运行。",
+ entrypointCleared: "已清除入口命令",
+ entrypointClearedDescription: "此终端将再次启动普通 shell。",
+ entrypointFailed: "无法保存入口命令:{error}",
+ scheduled: "已定时 {when}",
+ scheduledNow: "已立即定时",
+ scheduleCleared: "已清除定时",
+ scheduleFailed: "无法设置定时:{error}",
+ },
+ projectEvents: {
+ unlabeledSession: "一个会话",
+ scheduleLost: "定时提示词随 {label} 一同丢失",
+ scheduleDue: "{when} 到期:{prompt}",
+ relayAnswered: "{target} 已在其自己的会话中回复",
+ relayDetail: "它结束时没有通过 lich 回复,请打开它查看答案。",
+ open: "打开",
+ needsInput: "{label} 需要你的输入",
+ finished: "{label} 已完成工作",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/sidebar.ts b/frontend/src/lib/i18n/locales/zh-cn/sidebar.ts
new file mode 100644
index 00000000..a65312b7
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/sidebar.ts
@@ -0,0 +1,271 @@
+import type { Shape } from "../../catalog"
+import type { sidebar as en } from "../en/sidebar"
+
+export const sidebar = {
+ cardColor: {
+ menu: "颜色",
+ current: "{color}(当前)",
+ color: {
+ theme: "主题色",
+ red: "红色",
+ orange: "橙色",
+ amber: "琥珀色",
+ green: "绿色",
+ teal: "青色",
+ blue: "蓝色",
+ violet: "紫罗兰色",
+ pink: "粉色",
+ },
+ },
+ entrypointDialog: {
+ title: "入口命令",
+ description: "每次启动此终端时运行。退出后会回到 shell。",
+ command: "命令",
+ hint: "在 {cwd} 中运行,不会加载你的 shell rc 文件,因此别名不能作为命令。留空则启动普通 shell。",
+ },
+ newFolderDialog: {
+ title: "新建文件夹",
+ description: {
+ one: "将此会话归入你自己命名的文件夹。会话保持打开,其检出不会改变。",
+ other: "将这 {count} 个会话归入你自己命名的文件夹。会话保持打开,它们的检出不会改变。",
+ },
+ name: "名称",
+ placeholder: "设计系统",
+ exists: "{name} 已存在,因此会归入其中。",
+ moveHere: "移到此处",
+ },
+ sessionCard: {
+ schedule: "定时发送提示词…",
+ scheduledIn: "已定时 {when}…",
+ scheduledDue: "已定时…",
+ handOffFailed: "无法交给该会话:{error}",
+ openCheckoutFailed: "无法打开检出:{error}",
+ commandCopied: "已复制:{command}",
+ copyCommandFailed: "无法复制命令:{error}",
+ openFolderFailed: "无法打开文件夹:{error}",
+ sandboxed: "沙箱中",
+ sandboxedDrift: "沙箱中,设置已变更",
+ unsandboxedDrift: "不在沙箱中,设置已变更",
+ viewPullRequest: "查看拉取请求 #{number}",
+ askForPullRequest: "让智能体创建拉取请求",
+ pullRequestShort: "PR",
+ pinNamed: "置顶 {name}",
+ unpinNamed: "取消置顶 {name}",
+ closeNamed: "关闭 {name}",
+ rename: "重命名",
+ pin: "置顶",
+ unpin: "取消置顶",
+ moveToFolder: "移到文件夹",
+ takeOutOf: "从 {folder} 中移出",
+ newFolder: "新建文件夹…",
+ showBeside: "并排显示",
+ stopShowing: "停止显示",
+ showBesideDelegates: {
+ one: "并排显示其 {count} 个被委派会话",
+ other: "并排显示其 {count} 个被委派会话",
+ },
+ delegate: "委派给会话…",
+ copySendCommand: "复制发送命令",
+ openIn: "打开方式",
+ terminal: "终端",
+ editor: "编辑器",
+ fileManager: "文件管理器",
+ pullRequest: "拉取请求",
+ closeSession: "关闭会话",
+ },
+ sessionEntrypointItem: {
+ label: "入口命令…",
+ shellOnly: "入口命令仅适用于终端会话。",
+ },
+ sessionForkItem: {
+ label: "分叉到工作树…",
+ },
+ settingsCard: {
+ label: "设置",
+ close: "关闭设置",
+ },
+ pullRequestCard: {
+ label: "拉取请求",
+ close: "关闭拉取请求",
+ open: "打开",
+ },
+ sessionSidebar: {
+ newSession: "新建会话",
+ newSessionButton: "新建会话",
+ filter: "筛选会话",
+ collapse: "折叠侧边栏",
+ resize: "调整侧边栏大小",
+ pullRequests: "拉取请求",
+ closePullRequests: "关闭拉取请求",
+ delegatesSkipped: {
+ one: "{count} 个被委派会话无法加入分屏",
+ other: "{count} 个被委派会话无法加入分屏",
+ },
+ issueHandoffFailed: "无法将该议题交给会话:{error}",
+ forkPruned: "{name} 已没有可分叉的对话:{provider} 已将其清理。",
+ moveTitle: "将 {name} 移到此分屏?",
+ showBesideTitle: "在此会话旁显示 {name}?",
+ moveEndsGroup:
+ "它是 {group} 中仅有的两个会话之一,因此它离开后该分组会结束。无论如何都不会关闭任何会话。",
+ moveLeavesSplit:
+ "它已在 {group} 中,而一个会话同一时间只能在一个分屏中,因此它会离开原分屏。无论如何都不会关闭任何会话。",
+ moveConfirm: "移动",
+ },
+ sidebarRail: {
+ expand: "展开侧边栏",
+ newSession: "新建会话",
+ },
+ sessionTargetPicker: {
+ title: "委派给会话",
+ placeholder: "搜索要委派的会话…",
+ search: "搜索要委派的会话",
+ results: "此会话可委派的会话",
+ pick: "选择",
+ noMatch: "没有匹配 {query} 的会话",
+ newWorktree: "新建工作树会话…",
+ },
+ sessionPhaseChips: {
+ group: "按状态筛选",
+ all: "全部",
+ phase: {
+ waiting: "等待中",
+ running: "运行中",
+ unread: "未读",
+ idle: "空闲",
+ },
+ },
+ sessionStatusRung: {
+ commandLine: "命令行",
+ waitingOnYou: "等待你操作",
+ compacting: "正在压缩…",
+ handoffHeld: "有内容正在等待此提示词",
+ inbox: { one: "{count} 个结果已就绪", other: "{count} 个结果已就绪" },
+ todo: "已完成 {progress}",
+ todoProgress: "{done}/{total}",
+ scheduled: "已定时 {when}",
+ waitingForPrompt: "等待提示词",
+ origin: "来自 {origin}",
+ },
+ sessionGroup: {
+ pinned: "已置顶",
+ },
+ sessionGroupHeader: {
+ expand: "展开 {name}",
+ collapse: "折叠 {name}",
+ waiting: "此处有会话在等待你操作",
+ unread: "此处有未读的回合",
+ groupName: "分组名称",
+ newSessionIn: "在 {name} 中新建会话",
+ optionsFor: "{name} 的选项",
+ renameFolder: "重命名文件夹",
+ renameGroup: "重命名分组",
+ ungroup: "取消分组",
+ moveToFolder: "将分组移到文件夹",
+ newFolder: "新建文件夹…",
+ },
+ sessionLaunchMenuItems: {
+ terminal: "终端",
+ newTerminal: "新建终端",
+ run: "运行",
+ goToRunCard: "前往运行卡片",
+ runNotOnWindows: "运行脚本是 sh 脚本,不会在 Windows 上运行。",
+ runConfined: "受限运行",
+ openSession: "打开会话",
+ back: "返回",
+ worktree: "工作树",
+ },
+ folderLaunchMenuItems: {
+ newSessionIn: "在 {folder} 中新建会话",
+ newSessionInOn: "在 {folder} 中新建会话,位于 {checkout}",
+ },
+ schedulePromptDialog: {
+ title: "定时发送提示词",
+ description: "到时间后会输入到 {label},效果与你亲自输入相同。",
+ placeholder: "执行发布检查清单",
+ scheduledFor: "定时于 {when}",
+ cancel: "取消定时",
+ },
+ worktreeDialog: {
+ title: "新建工作树",
+ forkTitle: "将 {label} 分叉到新工作树",
+ description: "选择基础分支,并可选填新工作树的名称。",
+ carriesConversation: "带上对话",
+ carriesHistory: "新会话会基于 {label} 的历史副本打开,并从那里继续。原卡片不受影响。",
+ name: "工作树名称",
+ namePlaceholder: "分支名、议题(#128)或用文字描述的任务",
+ opensSelected: "打开所选工作树",
+ lookingUpIssue: "正在查找 #{number}…",
+ branch: "分支:{branch}",
+ autoBranch: "<自动生成>",
+ baseBranch: "基础分支",
+ loadingBranches: "正在加载分支…",
+ searchPlaceholder: "搜索分支…",
+ searchLabel: "搜索基础分支",
+ thisSession: "此会话",
+ thisCheckout: "此检出",
+ workingTree: "{branch} · 工作区",
+ sessionUncommitted: {
+ one: "同一提交,另加此会话尚未提交的 {count} 个文件。",
+ other: "同一提交,另加此会话尚未提交的 {count} 个文件。",
+ },
+ checkoutUncommitted: {
+ one: "同一提交,另加此检出尚未提交的 {count} 个文件。",
+ other: "同一提交,另加此检出尚未提交的 {count} 个文件。",
+ },
+ worktrees: "工作树",
+ localBranches: "本地分支",
+ remoteBranches: "远程分支",
+ noMatch: "没有匹配 {filter} 的分支",
+ noBranches: "未找到分支",
+ creating: "正在创建…",
+ open: "打开工作树",
+ fork: "分叉",
+ create: "创建工作树",
+ },
+ worktreeCloseDialogs: {
+ runningTitle: "会话仍在运行",
+ runningDescription: "{label} 正在执行一个回合。关闭会让进程就地停止,进行中的回合将丢失。",
+ closeAnyway: "仍然关闭",
+ closeTitle: "关闭工作树会话",
+ adoptedDescription:
+ "位于 {path} 的工作树不是 lich 创建的。保留它,还是删除该检出?删除会移除该目录,但保留其分支。",
+ createdDescription: "保留还是删除位于 {path} 的工作树?删除会移除检出,但保留其分支。",
+ keep: "保留工作树",
+ remove: "删除工作树",
+ dirtyTitle: "工作树有未提交的更改",
+ dirtyDescription:
+ "位于 {path} 的工作树包含未提交的更改。删除后这些更改将被永久丢弃。分支会保留。",
+ discardAndRemove: "丢弃更改并删除",
+ },
+ worktreeScriptRows: {
+ setupEditorLabel: "设置脚本,保存到 {file}",
+ runEditorLabel: "运行命令,保存到 {file}",
+ discard: "丢弃",
+ detected: "检测到 {detected}。在新工作树中运行 {command}?",
+ use: "使用",
+ edit: "编辑",
+ noRunCommand: "没有运行命令,因此该检出保留的端口保持未使用。",
+ set: "设置",
+ },
+ worktreeSandboxRow: {
+ runConfined: "受限运行",
+ },
+ useWorktreeClose: {
+ removeFailed: "删除工作树失败:{error}",
+ },
+ sessionTooltip: {
+ waitingOn: "正在等待 {peer}",
+ answering: "正在回复 {peer}",
+ commandLine: "命令行",
+ ticket: "工单 {ticket}",
+ scheduledFor: "定时于 {when}",
+ sandboxed: "沙箱中",
+ notSandboxed: "不在沙箱中",
+ confinedMeans: "主目录为空,机器只读,仅可写入此检出。",
+ unconfinedMeans: "此会话直接运行在本机上。",
+ settingMoved: "在沙箱设置变更前打开;重新打开会话以应用。",
+ setAtOpen: "会话打开时确定;重新打开以更改。",
+ notMounted: "未挂载(符号链接):{paths}",
+ morePaths: "另外 {count} 项",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/tabs.ts b/frontend/src/lib/i18n/locales/zh-cn/tabs.ts
new file mode 100644
index 00000000..32001077
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/tabs.ts
@@ -0,0 +1,38 @@
+import type { Shape } from "../../catalog"
+import type { tabs as en } from "../en/tabs"
+
+export const tabs = {
+ homeTab: {
+ home: "主页",
+ },
+ notificationsButton: {
+ title: "通知",
+ titlePending: "通知,{count} 条待处理",
+ caughtUp: "你已处理完所有通知",
+ dismiss: "忽略 {name}",
+ },
+ openProjectMenu: {
+ open: "打开项目",
+ recent: "最近的项目",
+ relocate: "重新定位",
+ openFolder: "打开文件夹…",
+ moreClosed: {
+ one: "还有 {count} 个已关闭的项目,请在命令面板中搜索",
+ other: "还有 {count} 个已关闭的项目,请在命令面板中搜索",
+ },
+ },
+ projectTab: {
+ close: "关闭 {name}",
+ },
+ projectTabs: {
+ pullRequests: "拉取请求",
+ settings: "设置",
+ closeTitle: "会话仍在运行",
+ closeBody: {
+ one: "关闭 {name} 会中断一个正在进行一轮的会话。重新打开项目时可选择从每个会话中断处恢复,进行中的那一轮会丢失。",
+ other:
+ "关闭 {name} 会中断 {count} 个正在进行一轮的会话。重新打开项目时可选择从每个会话中断处恢复,进行中的那一轮会丢失。",
+ },
+ closeAnyway: "仍然关闭",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/terminal.ts b/frontend/src/lib/i18n/locales/zh-cn/terminal.ts
new file mode 100644
index 00000000..11603587
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/terminal.ts
@@ -0,0 +1,47 @@
+import type { Shape } from "../../catalog"
+import type { terminal as en } from "../en/terminal"
+
+export const terminal = {
+ dropHint: {
+ attachTo: "附加到 {label}",
+ confined: "位于检出目录之外时,会以副本形式传入",
+ pasted: "其路径会粘贴到提示符处",
+ },
+ exitBanner: {
+ restart: "重新启动",
+ close: "关闭",
+ },
+ searchBar: {
+ placeholder: "查找",
+ label: "搜索终端",
+ previous: "上一个匹配项",
+ next: "下一个匹配项",
+ close: "关闭搜索",
+ },
+ drop: {
+ notAttached: "未附加:{files}",
+ },
+ host: {
+ stopShowing: "不再显示 {name}",
+ paneName: "{name} 窗格",
+ rememberFailed: "无法记住该选择:{error}",
+ },
+ view: {
+ restartFailed: "会话重新启动失败:{error}",
+ startFailed: "会话启动失败:{error}",
+ },
+ sessionExit: {
+ ended: "会话已结束",
+ endedWithCode: "会话已结束,退出码 {code}",
+ },
+ dropFiles: {
+ folderConfined: "无法移交沙箱会话检出目录之外的文件夹,请拖入文件",
+ folderMissing: "在此会话或你的主目录下找不到该文件夹,请拖入文件",
+ },
+ copyToast: {
+ copied: {
+ one: "已复制 {count} 个字符到剪贴板",
+ other: "已复制 {count} 个字符到剪贴板",
+ },
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/i18n/locales/zh-cn/update.ts b/frontend/src/lib/i18n/locales/zh-cn/update.ts
new file mode 100644
index 00000000..f13e493e
--- /dev/null
+++ b/frontend/src/lib/i18n/locales/zh-cn/update.ts
@@ -0,0 +1,18 @@
+import type { Shape } from "../../catalog"
+import type { update as en } from "../en/update"
+
+export const update = {
+ plugin: {
+ incompatibleInstall:
+ "{installs} 中的 lich 插件不受此 lich 支持。请安装 v{version},即此 lich 支持的版本。",
+ incompatibleUpdate:
+ "{installs} 中的 lich 插件不受此 lich 支持。请更新 lich,或确认已联网以查找此 lich 支持的版本。",
+ },
+ progress: {
+ updateFailed: "更新失败:{error}",
+ installFailed: "安装失败:{error}",
+ downloadFailed: "下载失败:{error}",
+ downloadFailedAt: "下载在 {percent}% 时失败:{error}",
+ bytesOf: "{received} / {total}",
+ },
+} satisfies Shape
diff --git a/frontend/src/lib/prompt-language-store.ts b/frontend/src/lib/prompt-language-store.ts
index 7f5ea0f1..56101ec9 100644
--- a/frontend/src/lib/prompt-language-store.ts
+++ b/frontend/src/lib/prompt-language-store.ts
@@ -7,7 +7,7 @@ import { Store } from "@/lib/rpc"
// every message, a spawn reads it for the briefing and LICH_PROMPT_LANG.
// This store is the page's copy, loaded once and written through.
//
-// The tags are the interface's own (en, pt-BR): both sides ship the same set.
+// The tags are the interface's own (en, pt-BR, zh-CN): both sides ship the same set.
// SETTING_KEY and GLOBAL_SCOPE mirror the Go side's promptLanguageKey and its
// global (project-less) scope.
diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go
index be2693a5..0159882b 100644
--- a/internal/prompt/prompt.go
+++ b/internal/prompt/prompt.go
@@ -20,10 +20,11 @@ const (
English Lang = "en"
PortugueseBR Lang = "pt-BR"
Spanish Lang = "es"
+ ChineseCN Lang = "zh-CN"
)
// Langs is every language a catalog exists for, English first as the default.
-var Langs = []Lang{English, PortugueseBR, Spanish}
+var Langs = []Lang{English, PortugueseBR, Spanish, ChineseCN}
// EnvVar is the variable lich exports into every session PTY with the prompt
// language at spawn time. Text composed outside the lich process (the lich CLI,
@@ -47,6 +48,7 @@ var catalogs = map[Lang]*Catalog{
English: &en,
PortugueseBR: &ptBR,
Spanish: &es,
+ ChineseCN: &zhCN,
}
// For is the catalog of a language. A Lang that came through Parse always has
@@ -76,6 +78,8 @@ func (c *Catalog) Pick(n int, p Plural) string {
// isOneEnglish, isOnePortuguese and isOneSpanish are the CLDR "one" rules for
// integers: Portuguese counts zero as singular, English and Spanish do not.
+// Chinese has no plural: isOneChinese is never true, so Pick always returns Other.
func isOneEnglish(n int) bool { return n == 1 }
func isOnePortuguese(n int) bool { return n == 0 || n == 1 }
func isOneSpanish(n int) bool { return n == 1 }
+func isOneChinese(int) bool { return false }
diff --git a/internal/prompt/prompt_test.go b/internal/prompt/prompt_test.go
index b8c0679f..f7c1400c 100644
--- a/internal/prompt/prompt_test.go
+++ b/internal/prompt/prompt_test.go
@@ -70,7 +70,7 @@ func verbs(format string) []string {
func TestParse(t *testing.T) {
for tag, want := range map[string]Lang{
- "en": English, "pt-BR": PortugueseBR, "": English, "fr": English, "pt-br": English,
+ "en": English, "pt-BR": PortugueseBR, "": English, "fr": English, "pt-br": English, "zh-CN": ChineseCN,
} {
if got := Parse(tag); got != want {
t.Errorf("Parse(%q) = %q, want %q", tag, got, want)
@@ -96,6 +96,7 @@ func TestPickFollowsEachLanguagesPluralRule(t *testing.T) {
}{
{English, 0, "other"}, {English, 1, "one"}, {English, 2, "other"},
{PortugueseBR, 0, "one"}, {PortugueseBR, 1, "one"}, {PortugueseBR, 2, "other"},
+ {ChineseCN, 0, "other"}, {ChineseCN, 1, "other"}, {ChineseCN, 2, "other"},
}
for _, tc := range cases {
if got := For(tc.lang).Pick(tc.n, forms); got != tc.want {
diff --git a/internal/prompt/zh_cn.go b/internal/prompt/zh_cn.go
new file mode 100644
index 00000000..1afa74c3
--- /dev/null
+++ b/internal/prompt/zh_cn.go
@@ -0,0 +1,122 @@
+package prompt
+
+var zhCN = Catalog{
+ isOne: isOneChinese,
+
+ BriefingIntro: "你正运行在 lich 中。lich 并排运行多个编码智能体会话,并且可以在这个会话旁再打开更多会话:" +
+ "每个会话都是一张卡片,用户可以随时查看,也可以在任务进行到一半时接管;" +
+ "当工作需要独立的检出时,卡片会运行在各自的 git worktree 中。",
+ BriefingCards: "在这里,你自己的 Agent 工具会打开它们:general-purpose 子智能体会作为其中一张卡片运行," +
+ "除非你要求 isolation 'worktree',否则就在当前检出中后台运行,它的报告会自动回到你这里。" +
+ "请用它来分派工作。只有子智能体做不到的事才自己打开会话:另一种智能体、" +
+ "用户指定的分支,或者需要比当前会话存活更久的工作。",
+ BriefingNative: "这个会话本身就是由另一个会话打开的子智能体卡片,在这里你自己的 Agent 工具会在本会话内部运行子智能体," +
+ "而不是作为卡片运行。请用它来分派工作,不要新开 lich 会话:" +
+ "你的最终消息就是打开你的那个会话正在等待的报告。",
+ BriefingSessions: "当工作需要分派时(同时进行多个任务,每个分支或检出一个),应当打开的是这些会话," +
+ "而不是你自己的宿主工具运行的子智能体:子智能体没有检出,没有卡片," +
+ "用户也无法引导或恢复它。",
+ BriefingCommandTools: "你工具列表里的 lich 工具可以打开一个会话并把任务交给它。",
+ BriefingCommandCLI: "用 `lich open --worktree BRANCH --prompt '任务内容'` 打开一个会话," +
+ "这条命令会打开会话并同时把任务交给它。",
+
+ RelayMessage: "[lich] %[1]s,并非来自你自己的提示词。\n\n%[2]s\n\n%[3]s",
+ OriginCLI: "由 lich 命令行转发的消息",
+ OriginSession: "来自会话 %q 的消息",
+ ReplyCommand: " \"$LICH_BIN\" reply %s \"<你的回答>\"",
+ ReplyRouteCLI: "有了答案后,运行以下命令发回:\n%s",
+ ReplyRouteTools: "有了答案后,用 lich 工具 `%[1]s`(工单 %[2]s)发回," +
+ "或运行:\n%[3]s",
+ ReplyOnlyWayBack: "这张工单是唯一的回路:提问的一方正阻塞在它上面,不会读取任何其他内容。" +
+ "不要通过给其他会话发消息来回答,用其他任何方式发送的答案都会丢失。" +
+ "回答请写成简明的报告,说明做了什么、在哪里做的、还剩什么,不要贴完整记录:" +
+ "发送方要为读到的每个字节付费,而细节本来就在你的提交和文件里。",
+ WorkerTask: "[lich] 来自会话 %[1]q 的任务,该会话把本会话作为它的子智能体打开。" +
+ "你完成后的最终消息会作为报告发回给它。\n\n%[2]s",
+ PickTicketNudge: "[lich] 你的这一轮结束时没有发送任何答案,因此 %[1]d 个请求已作为未回答退回给了各自的发送方:\n" +
+ "%[2]s\n会话之外没有任何办法判断那一轮对应其中哪一个,所以无法替你回答其中任何一个。" +
+ "你仍在处理的那一个依然可以回答,并且此后的每个答案都必须指明它的工单:" +
+ "lich reply \"\"。",
+ NudgeResultsReady: Plural{
+ One: "你发出的任务 %[2]s 的结果已就绪",
+ Other: "你发出的 %[1]d 个任务的结果已就绪(%[2]s)",
+ },
+ NudgeRouteCLI: "运行:\n \"$LICH_BIN\" wait",
+ NudgeRouteTools: "不带工单调用 lich 工具 `%s`,或运行:\n \"$LICH_BIN\" wait",
+ NudgeNotice: "[lich] %[1]s。要一次性取回全部结果,请%[2]s",
+
+ SubagentReport: "[lich] 会话 %[1]q%[2]s 已完成你交给它的任务(工单 %[3]s)。" +
+ "它的报告:\n\n%[4]s",
+ SubagentReportBranch: ",分支 %s",
+ BlockedNotice: "[lich] 会话 %q 是你打开的子智能体,正在它的卡片中等待权限确认。" +
+ "它的任务仍然保持打开:请打开该卡片来回答。",
+ ReportSummary: Plural{
+ One: "lich 会话 %s 已完成",
+ Other: "lich 会话 %s 已完成",
+ },
+ BlockedSummary: "lich 会话 %q 正在等待权限确认",
+ MergeNotice: "[lich] 拉取请求 #%[1]d %[2]q(分支 %[3]s)已通过 lich 合并到 %[4]s。" +
+ "这是一条通知,不是任务:请更新你所记录的这项工作的状态,不要做其他任何事。",
+ MergeSummary: "拉取请求 #%[1]d 已合并到 %[2]s",
+ LateNotice: "[lich] 原定于 %[1]s 发送,延迟 %[2]s 送达。\n\n",
+ ResumePrompt: "[lich] 你的用量上限已重置。请继续之前进行的任务。",
+
+ CopyNotice: "[lich] %s 的副本;原文件无法从本会话访问,修改只会保留在副本中。",
+
+ MCPInstructions: `lich 在同一个窗口中并排运行编码智能体会话;这些工具就是本会话与其他会话协作的方式。
+
+并行委派:对于可以与你并行进行的工作,在它自己的 git worktree 中打开一个工作会话(open_session 并指定 worktree),让它拥有自己的检出,然后用 send_to_session 把任务交给它。先检查 list_worktrees:已经检出的分支会被直接打开,而不是新建。工作会话是显示在用户屏幕上的完整会话:可见、可引导,工作完成后由你负责关闭(close_session)。这正是它与你自己的宿主工具所运行的子智能体的区别,也是用户说要把工作分派到各个分支或 worktree 时所要求的:子智能体没有自己的检出,也没有用户可以打开、阅读或在任务中途接管的卡片。子智能体只适合用来读取信息然后丢弃,不适合用来运行别人要求亲眼看到的实现。例外是 lich-plugin 把 general-purpose 子智能体变成这类卡片的 Claude Code 会话:在那里你的 Agent 工具会在后台打开工作会话,它的报告会自动回来,所以用它来分派,而把在这里打开会话留给子智能体做不到的事。
+
+不要轮询结果。超过等待时间的发送会返回一个工单,你继续做自己的工作;结果就绪时,会有一条简短的 [lich] 通知出现在你的提示处,用 wait_for_answer(不带工单)一次性取回全部。该通知只会在你的两轮之间送达,所以在你自己的长时间一轮运行期间,请在结果会改变你下一步行动的节点(继续委派之前、长时间验证之后、最终汇总之前)用 wait_for_answer 加 no_wait 查看:它会返回已就绪的内容以及谁还欠一个结果,而不会等待。每个决策点只看一次:没有就绪的就继续做自己的事。循环反复调用就是轮询,会耗费这套设计本来要节省的 token。
+
+在本会话内部运行的子智能体或工作流步骤不是它自己的智能体,lich 无法区分它们:它以 private 发送和打开,然后等待它自己的工单。[lich] 通知和不带工单的 wait_for_answer 属于本会话自己的智能体。
+
+当一条带工单的 [lich] 消息出现在你的提示处时,你就是工作者:完成任务,然后用 %s 回答,内容是简明的报告(做了什么、在哪里、还剩什么),不要贴完整记录。`,
+ HandOverFailed: "任务没有送达:%[1]v。会话已打开,等上述问题处理完后," +
+ "请用 %[2]s 把任务交给它。",
+ SendToSessionPrivate: "带 private 的 send_to_session",
+ OutcomeUnread: "会话 %[1]q 一直没有接手任务:任务已键入该提示处,但没有任何东西读取它," +
+ "说明该终端被别的东西占用了,比如提供商仍在启动,或屏幕上有它自己的提问" +
+ "(Claude Code 第一次在某个目录运行时会询问是否信任该目录)。" +
+ "没有任何内容排队,也没有任何回答。请告诉用户打开 %[1]q 卡片并处理上面的内容;" +
+ "之后需要重新发送任务。",
+ OutcomeUndelivered: "任务没有送达会话 %[1]q:因为该终端不在提示处,任务被扣留了" +
+ "(worktree 的设置脚本仍在运行,或提供商始终没有启动),而会话已经结束," +
+ "或这种状态持续了太久。不再有任何内容排队,也没有任何回答。" +
+ "请告诉用户打开 %[1]q 卡片查看那里发生了什么;之后需要重新发送任务。",
+ OutcomeUnanswered: "会话 %[1]q 结束了这一轮,但没有通过 lich 回答。" +
+ "它产生的内容都在该会话中,请告诉用户打开 %[1]q 卡片阅读。" +
+ "如果它仍在后台工作,它的答案在一小时内仍可能送达这张工单。",
+ OutcomeStopped: "会话 %q 在回答之前被关闭,因此任务已停止,不会再有答案。",
+ OutcomeExpired: "会话 %[1]q 没有在任务保持打开的一小时内回答,因此任务已过期,不会再有答案。" +
+ "请打开 %[1]q 卡片查看它的进展。",
+ StillWorkingPrivate: "%[1]s 仍在工作。这个任务是私有的:不会有通知宣告它的结果," +
+ "不带工单的 wait_for_answer 也永远不会返回它。请用工单 %[2]q 调用 wait_for_answer " +
+ "来等待它,等待你自己的工单不算轮询。",
+ StillWorkingOpen: "%[1]s 仍在工作。这个任务是开放的:如果该会话还没到提示处,任务会被扣留," +
+ "等它就绪后再送入。结果就绪时,会有一条简短的通知出现在你自己的提示处," +
+ "所以请继续做自己的事,无需轮询。用 wait_for_answer 取回" +
+ "(工单 %[2]q,或不带工单一次性取回全部)。",
+ StillWorkingPrivateCLI: "%[1]s 仍在工作。这个任务是私有的:发送会话的提示处不会键入任何通知," +
+ "不带工单的 `lich wait` 也不会返回它。" +
+ "请用以下命令等待它:\n lich wait %[2]s\n",
+ StillWorkingOpenCLI: "%[1]s 仍在工作。这个任务是开放的:该会话尚未就绪时收到的消息会被扣留到就绪为止," +
+ "结果就绪时会在发送会话的提示处键入一条通知。" +
+ "如果想改为等待它:\n" +
+ " lich wait %[2]s\n",
+ CollectedNothing: "没有可取回的内容:没有等待中的结果,你也没有打开的任务。",
+ CollectedAnswer: "来自 %[1]q 的答案(工单 %[2]s):\n%[3]s",
+ CollectedStillWorking: "仍在工作:%s。它们的结果会自行出现在你的提示处。",
+ OpenedProject: "项目 %q",
+ OpenedWorktree: "%[1]s,位于 worktree %[2]s",
+ OpenedConfined: " 它在受限环境中运行:一个空的主目录,只包含它的智能体自己的状态," +
+ "整台机器只读,只能写入它自己的检出。",
+ OpenedSession: "已打开会话 %[1]q(%[2]s),位于 %[3]s。%[4]s\n" +
+ "它可以用 %[1]q 和 %[5]q 称呼。它的智能体可能仍在启动(新的 " +
+ "worktree 会先运行项目的设置脚本),所以你发给它的任务会被扣留," +
+ "等智能体就绪后再送入,不会丢失。\n",
+ ClosedRemoved: "已关闭 %[1]q,并移除了它的 worktree %[2]s。\n",
+ ClosedKept: "已关闭 %[1]q。它的 worktree %[2]s 仍然保留,会话已被搁置:" +
+ "再次在该分支上打开会话,会接续它之前的对话。\n",
+ ClosedPlain: "已关闭 %q。\n",
+}