首页
/ OpenCode 多语言翻译治理:Locale Glossary 目录规范与 translate:app 流水线详解

OpenCode 多语言翻译治理:Locale Glossary 目录规范与 translate:app 流水线详解

2026-09-05 12:42:30作者:廉彬冶Miranda

OpenCode 仓库中的 .opencode/glossary/ 目录定义了一套"按语言维护翻译术语与措辞偏好"的轻量治理机制。本篇基于该目录的 README 规范,结合 script/translate-app.ts.opencode/command/translate.md 的真实实现,讲清 Locale Glossary 的文件命名规则、内容结构、标准模板、贡献规范,以及术语表如何被翻译流水线实际消费——读完你可以为自己的项目建立一套可被 Agent 直接使用的多语言术语治理体系。

1. 定位:局部术语表,补充全局翻译指南

.opencode/glossary/README.md 开宗明义:

  • 该目录用于存放面向具体语言区域(locale)的翻译指引,是对全局翻译指南 .opencode/agent/translator.md 的补充;
  • 全局术语表(translator.md)仍然是共享"不可翻译"条目(命令、代码、路径、产品名等)的唯一事实来源(source of truth);
  • 各语言文件则沉淀社区在措辞与术语偏好上积累的经验(community learnings)。

文档同时说明了它引用的全局术语表位于 .opencode/agent/ 目录下;在当前仓库检出中,.opencode/agent/ 实际可见的是 triage.mdduplicate-pr.md 等 Agent 定义,而术语表的直接消费者是仓库中的翻译脚本与命令,后文第 5 节会用源码逐条印证。

这种"全局一份不可翻译清单 + 每语言一份偏好表"的分层设计,避免了把 60 多个语言的术语映射堆进同一个文件,也让每个语言的规则可以由熟悉该语言的社区成员独立演进。

2. 文件命名规范:一个 locale 一个文件

README 的 File Naming 章节给出了四条硬性约定,这些约定并非纸面文档,而是被 script/translate-app.tsglossaryFile() 函数直接执行:

规则 说明 示例
一个语言区域一个文件 目录内不混放多种语言 fr.md
使用小写 locale slug,尽量与文档站的 locale 对齐 区分地区变体 zh-cn.mdzh-tw.md
只有语言级(无地区区分)的指引时,直接用语言代码 不需要地区后缀 fr.md
允许非 BCP47 的别名 slug 以保持仓库一致性 需在文件内说明 br(巴西葡语,即 pt-BR)

第 4 条是理解本目录的关键。script/translate-app.ts 中的映射逻辑精确对应了这些规则:

export function glossaryFile(locale: Locale) {
  if (locale === "zh") return ".opencode/glossary/zh-cn.md"
  if (locale === "zht") return ".opencode/glossary/zh-tw.md"
  return `.opencode/glossary/${locale}.md`
}

即:简中 locale 代码 zh 实际读取 zh-cn.md,繁中 zht 读取 zh-tw.md,其余 locale(包括别名 br)一律按 ${locale}.md 定位。同文件中 L15-L77languages 映射表列出了全部受支持语言,其中 "br": "Brazilian Portuguese" 正是 README 所说"别名 slug"的落地形态。

对照实际目录,.opencode/glossary/ 当前包含 16 个语言文件:arbrbsdadeesfrjakonoplruthtrzh-cnzh-tw

3. 一个 Locale 文件里应该写什么

README 的 What To Put In A Locale File 章节定义了五类内容,其中 Avoid 为可选项:

  1. Sources:催生该指引的 PR / issue / 讨论;
  2. Do Not Translate (Locale Additions):该语言特有的不可翻译词或大小写决策;
  3. Preferred Terms:反复出现的 UI/文档词汇及其推荐译法;
  4. Guidance:语气、风格与一致性说明;若该语言使用别名 slug,必须在 Guidance 中记录这个别名(例如正文可写 pt-BR,而配置/示例用 br);
  5. Avoid(可选):应避免的字面直译或措辞。

3.1 实例一:成熟语言 zh-cn.md

简体中文术语表展示了完整的成熟形态:

  • Sources:PR #13942(该 PR 的讨论直接催生了表内规则);
  • Do Not Translate (Locale Additions):OpenCode(正文中保持大写;仅当 opencode 出现在命令、包名、路径或代码中时保留小写)、OpenCode ZenOpenCode CLICLI/TUI/MCP/OAuth,以及首次引入 MCP 时优先给出英文全称 Model Context Protocol;
  • Preferred Terms(PR 支撑、可演进):
English Preferred Notes
prompt 提示词 flag/代码中保持 --prompt 不变
session 会话
provider 提供商
share link / shared URL 分享链接 面向用户的分享动作优先用 分享
headless (server) 无界面 文档措辞
authentication 认证 auth/OAuth 语境优先
cache 缓存
keybind / shortcut 快捷键 面向用户的文档措辞
workflow 工作流 例如 GitHub Actions workflow
  • Guidance:自然简洁优于逐字直译;语气直接且友好(PR #13942 持续朝这个方向修正措辞);命令、flag、代码、行内代码、URL、文件路径、模型 ID 等技术产物原样保留;枚举式字面量(如 defaultjson)保持英文;术语一旦选定,跨页面保持一致;
  • Avoid:正文指代产品名时避免用小写 opencode(应用 OpenCode);避免在已有推荐译法的概念上混用多个译名。

3.2 实例二:别名语言 br.md

巴西葡语术语表是"别名 slug"规则的示范:仓库代码与配置中该语言的 locale 代码是 br,而标准 BCP47 写法是 pt-BR。该文件在 Preferred Terms 中明确记录了三条映射关系——正文语言名用 pt-BR、仓库 slug 用 br、浏览器 locale 检测时 pt / pt-br / pt-BR 一律归一到 br——并在 GuidanceAvoid 中反复强调:代码片段、路径、配置示例中不得把 br 改成 pt-br,也不得混用不同葡语变体。

3.3 实例三:起步阶段 fr.md

法语术语表展示了"还没有 PR 级术语修正"时的合法状态:其 Preferred Terms 一节直接写明"No PR-backed term mappings yet"(暂无 PR 支撑的术语映射),仅保留 Guidance 与 Avoid 两条通用指引。这正对应 README 贡献规范中"若无术语级修正,先用通用指引起步"的要求,说明术语表允许渐进积累,但每条规则最终都要有出处

4. 标准模板

README 给出了新语言文件的完整模板(此处将模板示例中的外部 PR 链接省略为"对应 PR 链接"占位,实际填写时附上具体 PR):

# <locale> Glossary

## Sources

- PR #12345(附对应 PR 链接)

## Do Not Translate (Locale Additions)

- `OpenCode` (preserve casing)

## Preferred Terms

| English | Preferred | Notes     |
| ------- | --------- | --------- |
| prompt  | ...       | preferred |
| session | ...       | preferred |

## Guidance

- Prefer natural phrasing over literal translation

## Avoid

- Avoid ... when ...

结构上就是第 3 节五类内容的一一落地:Sources 必须非空且随规则同步更新;Preferred Terms 用三列表格(英文原词 / 推荐译法 / 备注)承载可演进的映射;GuidanceAvoid 承载风格与负面清单。

5. 入库标准:什么样的指引才值得写

README 给出三条筛选标准,用于控制术语表的信息质量:

  • Repeated:该用法在多个文档/屏幕中反复出现,而非一次性偶发;
  • Easy to apply consistently:规则能被一致地、机械地执行;
  • Backed by a community contribution or review discussion:有社区贡献或评审讨论作为依据。

对应的 Contribution Notes 进一步规定:

  • 可能演进的条目要标注为 "preferred"(推荐,而非强制);
  • 示例保持简短;
  • 每次新增规则必须同步更新 Sources 一节;
  • 优先收录 PR 支撑的指引,而不是凭空发明的术语映射;没有术语级修正时,先写通用指引。

这四条实际上把术语表当作"活文档"管理:规则有出处、有状态(preferred/fixed)、可演进,与 zh-cn.md 中"These are preferred terms ... and may evolve"的声明相互印证。

6. 源码印证:glossary 如何被翻译流水线消费

README 只定义了"文件长什么样",而"文件怎么被用"要看 script/translate-app.tsscript/translate-app.md。这条流水线把英文字典视为只读事实来源,把各语言字典与英文对齐,术语表在其中是注入给翻译 Agent 的上下文。

6.1 翻译请求的组装

translate() 函数(script/translate-app.ts)对每个待翻译 locale 执行:

const glossary = glossaryFile(plan.locale)
const glossaryContent = (await Bun.file(path.join(root, glossary)).exists())
  ? await Bun.file(path.join(root, glossary)).text()
  : undefined
const prompt = template.replaceAll("$1", plan.locale).replaceAll(
  "$ARGUMENTS",
  JSON.stringify({
    locale: plan.locale,
    language: plan.language,
    glossary: glossaryContent ? { file: glossary, content: glossaryContent } : undefined,
    domains: plan.domains.map((domain) => ({
      source: domain.source,
      target: domain.target,
      ...domain.drift,   // missing / extra / placeholders
    })),
  }, null, 2),
)

要点有三:

  • 术语表文件整体以 { file, content } 形式内嵌进翻译请求的 JSON;文件不存在时该字段直接省略,流水线可继续运行——这就是"每个 locale 一个文件、按需存在"命名规范的价值;
  • 请求同时携带三个 domains 的漂移清单(missing / extra / placeholders)。每个 domain 的源与目标字典由 targetFiles() 确定:packages/app/src/i18n/<locale>.tspackages/ui/src/i18n/<locale>.ts,desktop 原生语言还包含 packages/desktop/src/renderer/i18n/<locale>.ts,源文件一律是对应的 en.ts;
  • 模板文本来自 script/translate-app.md,其中明确要求 "Apply the locale glossary included in the request"(第 21 行),并要求对 session、prompt、agent、model、provider、fork、shell、terminal、workspace、worktree、context、permission、tool、server 等高频概念做一致的上下文化翻译,而不是逐词翻译。

文档站与 UI 文案的另一条翻译入口 .opencode/command/translate.md 也在 Requirements 中写明:"Also apply locale-specific guidance from .opencode/glossary/<locale>.md when available (for example, zh-cn.md)"(第 13 行)。两条入口共享同一套术语表。

6.2 权限收敛:术语表之外的护栏

translationConfig()(script/translate-app.ts)为翻译 Agent 生成的内联配置把权限压到最小:默认 deny,仅放开 readglobgrepwebfetchwebsearch,而 edit 只允许作用于本次请求列出的目标文件。这与 script/translate-app.md 中 "Edit only the target files listed in the request" 的文字约束形成双重保障——术语表提供"怎么译",权限配置保证"只能改该改的文件"。

6.3 漂移检测与验证

findDrift()(script/translate-app.ts)按 key 逐一对比英文源字典与目标字典,产出三类漂移:

  • missing:英文有而目标语言缺失的 key(含按 locale 复数类别展开的变体);
  • extra:目标语言多出的 key;
  • placeholders:同一 key 下 {{token}} 占位符集合不一致的条目(例如 {{count}} 丢失)。

bun run translate:app -- <locale|all> 支持 --dry-run(仅报告漂移)与 --check(存在漂移时以非零码退出,可接入 CI);翻译完成后还会用 worktreeSnapshot() 做前后快照比对,确认没有目标文件之外的任何改动被写入。此外 packages/app/AGENTS.md 对 Agent 补充了使用姿势:"A glossary hit is evidence, not permission to translate word-by-word"——命中术语表是证据,不是逐词翻译的许可,整句要在产品语境中翻译。

7. 实践要点小结

  • 分层:共享的不可翻译清单放全局指南,语言特有的大小写决策、推荐译法、语气规范放 .opencode/glossary/<locale>.md,两者互补、职责不重叠;
  • 命名即接口:slug 规则被 glossaryFile() 硬编码执行,新增语言文件前先看 script/translate-app.ts 的映射;别名 slug(如 br)必须在文件 Guidance 中显式声明;
  • 结构固定:Sources / Do Not Translate / Preferred Terms / Guidance / Avoid 五段式模板保证任何语言的文件都可被流水线与读者一致地解读;
  • 规则要可追溯:每条 Preferred Term 都标注 preferred(可演进)状态,每次加规则必更新 Sources,宁缺毋造;
  • 与流水线闭环:术语表内容会被原样注入翻译请求,配合漂移检测(missing/extra/placeholders)与最小化编辑权限,构成"规范 → 上下文注入 → 自动验证"的完整治理回路。
登录后查看全文
热门项目推荐
相关项目推荐