opencode Web 应用开发规范深度解析:从本地调试流程到 SolidJS i18n 工程实践
本文围绕 packages/app/AGENTS.md 中定义的 opencode Web 应用(@opencode-ai/app)开发与维护规范展开,覆盖优先级决策原则、本地开发的双进程调试流程、SolidJS 状态管理与 60+ 语言本地化的完整工程约束。读完本文,你将掌握如何正确启动 opencode 的本地 UI 开发环境、理解其 i18n 类型化 API 的底层实现机制,以及在实际贡献代码时必须遵守的稳定性与本地化红线。
优先级原则:稳定性、简洁性、性能
packages/app/AGENTS.md 开宗明义地给出了 Web 应用包的工程优先级排序,按严格顺序为:
- stability(稳定性)
- simplicity(简洁性)
- performance(性能)
这一排序的实战含义是:当三者冲突时,先保稳定,再保简洁,最后才考虑性能优化。规范中特别指出了一条与性能相关的硬约束:
Before changing session or timeline code, record a production benchmark baseline and compare it after the change.
(修改 session 或 timeline 代码前,必须先记录生产环境基准线,改动后再对比。)
这条规则在 packages/app/package.json 的脚本中得到了落地支撑:包内维护了专门的基准与稳定性测试命令:
"test:stability": "bun test ./e2e/performance/unit/visual-stability.test.ts && playwright test --config e2e/performance/timeline-stability/playwright.config.ts",
"test:bench": "bun test ./e2e/performance/unit && playwright test --config e2e/performance/playwright.config.ts"
也就是说,"先记录 baseline 再对比"并非空泛口号——仓库在 packages/app/e2e/performance 目录下配套了 timeline 稳定性 Playwright 测试与视觉稳定性单测,为 session/timeline 代码的改动提供了可复跑的基准工具链。
调试红线:绝不重启应用或服务器进程
规范中 Debugging 一节只有一条,但措辞极其强烈:
NEVER try to restart the app, or the server process, EVER.
(永远、永远不要尝试重启应用或服务器进程。)
从源码结构看,opencode 的 Web 端是长连接架构:应用通过 WebSocket 与后端保持会话状态(依赖 packages/app/package.json 中的 @solid-primitives/websocket 等包维护前端连接),重启进程会丢失不可恢复的运行时会话上下文。因此遇到异常时的正确路径是排查代码本身,而不是用重启"掩盖"问题。
本地开发:双进程分离调试流程
这是本文档最具实操价值的部分。规范明确警告了一个常见误区:
opencode dev webproxieshttps://app.opencode.ai, so local UI/CSS changes will not show there.
opencode dev web 命令实际上代理到线上站点 https://app.opencode.ai,本地修改的 UI/CSS 在该页面上不会出现。要验证本地 UI 改动,必须分别独立启动后端与前端开发服务器:
1. 启动后端(在 packages/opencode 目录下)
bun run ./src/index.ts serve --port 4096
2. 启动前端 App(在 packages/app 目录下)
bun dev -- --port 4444
3. 打开 http://localhost:4444 验证 UI 改动
该页面会指向 http://localhost:4096 的后端。
从 packages/app/vite.config.ts 可以看到前端开发服务器的默认配置:
server: {
host: "0.0.0.0",
allowedHosts: true,
port: 3000,
},
默认端口为 3000,因此规范中 --port 4444 的显式指定是为了与后端 4096 端口配对、避免与其他本地服务冲突。构建链路上,vite 配置还挂载了 @sentry/vite-plugin(仅在设置了 SENTRY_AUTH_TOKEN / SENTRY_ORG / SENTRY_PROJECT 环境变量时启用 sourcemap 上传)以及来自 packages/app/vite.js 的 desktopPlugin,用于桌面端(Desktop)场景的额外处理。
SolidJS 状态管理约定:createStore 优先
规范在 SolidJS 一节给出一条简短而明确的框架使用约定:
Always prefer
createStoreover multiplecreateSignalcalls
(始终优先使用 createStore,而非多次 createSignal 调用。)
这条约定在 opencode 源码中被严格执行。以 i18n 语言上下文 packages/app/src/context/language.tsx 为例,整个语言状态就是用一个 createStore 承载的:
const [store, setStore, _, ready] = persisted(
Persist.global("language", ["language.v1"]),
createStore({
locale: initial,
}),
)
其核心优势在于:createStore 支持深层路径更新(如 setStore("locale", ...))与响应式嵌套结构,当状态天然是一个"对象"而非多个散装信号时,createStore 能避免信号爆炸和组件间隐式依赖。会话时间线组件(如 message-timeline.tsx)中大量使用 store 模式管理条目状态,正是这一约定的落地场景。
本地化(i18n)工程规范:从约束条款到源码实现
文档中最长、信息密度最高的章节是 Localization,它定义了一套完整的多语言工程红线。opencode Web 应用目前维护着 63 个语言字典文件(含英文基准),位于 packages/app/src/i18n 目录下(en.ts、zh.ts、ja.ts、ko.ts、ar.ts 等),完整语言列表由 packages/app/src/i18n/desktop-native.ts 中的 DESKTOP_NATIVE_LOCALES 常量统一定义。
核心规则逐条解读
1. 禁止硬编码用户可见英文文案。 规范原文:
NEVER hardcode user-visible English strings in production code. ALWAYS use an i18n key for visible copy, placeholders, accessible labels, tooltips, menus, dialogs, toasts, empty states, and displayed errors.
即所有可见文案、占位符、无障碍标签(accessible labels)、tooltip、菜单、对话框、toast、空状态与错误展示,全部必须走 i18n key。
2. 迁移文案时逐字节保留英文原文。 将现有文案迁移到 i18n 时,除非任务明确要求改文案,否则英文文本必须 byte-for-byte(逐字节)保持不变——英文是"设计师编写的源文案"(designer-written source copy),任何为翻译方便而改动英文原文或 key 的行为都被禁止。
3. 将语言复杂度封装在共享类型化 i18n API 背后。 这是整章最核心的一条架构约束。功能代码只允许使用两个 API:
language.t(...)—— 普通文案language.plural(baseKey, count, params)—— 数量敏感文案(复数处理)
产品代码不得:检查 locale 值、直接调用 Intl.PluralRules、自行构造或选择 .one / .other 等复数类目 key、或基于特定语言语法做分支判断。
这一约束在源码中完全兑现。packages/app/src/context/language.tsx 中 plural 的实现把复数逻辑全部收口在一处:
const plural = (key: PluralKey, count: number, params?) => {
const category = pluralCategory(intl(), count)
const current = (dict.loading ? base : (dict() ?? base)) as Record<string, string>
const candidate = `${key}.${category}`
const fallback = `${key}.other`
return i18n.resolveTemplate(current[candidate] ?? current[fallback] ?? fallback, { ...params, count })
}
可以看到复数类目由 pluralCategory(基于 CLDR 数据)解析,词典缺失时自动回退到 .other,插值交给 i18n.resolveTemplate 完成——调用方完全无需感知任何语言细节。真实调用示例散布于会话页面,如 message-timeline.tsx 中的 language.plural("ui.sessionTurn.diffs.changed", props.diffs.length)("N 个变更文件"标签),以及 session-revert-dock.tsx 和 session-followup-dock.tsx 中的 dock 汇总文案。
4. 优先使用完整翻译短语,禁止拼接语法碎片。 调用方不允许组装句子,占位符只保留"不可再分的动态值",如名称、路径、数量。这与 CLDR 复数语法的配合方式是:每个复数 key 下维护 .one / .other(以及特定语言需要的更多类目)完整短语,而不是用 {{count}} + 前后缀拼装。
5. 若现有 API 无法表达某翻译,加深共享语言模块而非在业务代码打补丁。 规范原文要求:扩展共享 language/UI i18n 模块,让一次类型化调用独占"locale 选择、复数解析、回退、插值"四件事,绝不把这些机制泄漏到产品代码。
术语考证:禁止凭模型知识翻译
规范对翻译质量给出了近乎苛刻的考证流程,这些条款直接决定了新增 locale 文件的工作方式:
- 多方语料交叉验证:术语与语法须对照 Unicode CLDR locale/plural 数据、Microsoft 本地化风格指南、Apple 本地化指南、Mozilla 本地化风格指南与 Pontoon / Firefox 本地化语料(
mozilla-l10n/firefox-l10n)核验。 - 开发者术语优先社区惯用词:对面向开发者的术语(如 session、prompt、agent、model、fork、shell、terminal、workspace、worktree 这类短标签),优先采用目标语言开发者社区已有的用词,对照 Firefox、KDE、VS Code 等已本地化的开发者产品;若存在至少两个独立语料则都要交叉核对。若惯例就是保留英文借词或缩写,则保留,不要自造译法。
- 按语境翻译完整短语:术语表命中只是证据,不是逐字翻译的许可。选择术语前,须确认该词在相同语法角色下的既有处理。
- locale 就绪前做一致性审计:对反复出现的概念审计是否翻译一致,逐一审查仍等于英文的 value,并将保留的英文分类为:产品名、provider/工具名、URL、代码 token、键盘图例、缩写、资源名、既定借词之一;无法归类的"遗留英文"必须翻译。
- 审查备注须注明所用语料:在翻译审查说明中写明使用了哪些语料库,并标注不确定或地域性强的术语,让母语审校者聚焦重点。
- 权威字典兜底:各 locale 还应参考对应语言权威机构(如 RAE/Fundéu、FranceTerme、Duden、TDK、Kotus、Språkrådet、Rada Języka Polskiego、俄/阿语言科学院、乌克兰正字法、台湾教育部辞典、泰国皇家学会等);英文词典作为语义真值来源,同时保留占位符、代码标识符、产品名与键盘标签。
源码级佐证:locale 懒加载、RTL 与持久化
packages/app/src/context/language.tsx 的实现印证了上述规范的工程落点:
- 英文基准内联、其余 locale 懒加载:
en字典在模块顶层直接flatten,其他 62 个语言通过loaders映射按需import(),加载后缓存进dictsMap,避免首屏加载全部语言包。 - RTL 方向处理:
RTL_LOCALES集合(ar、ur、pa、fa、dv)决定document.documentElement.dir的设置,方向切换同步写入 cookie(oc_locale=...; Max-Age=31536000; SameSite=Lax)供后端读取。 - locale 偏好持久化:通过
persisted(Persist.global("language", ["language.v1"]), ...)将用户选择写入 localStorage(key 为opencode.global.dat:language),下次启动时优先于navigator.languages检测生效。
自动化防线:parity 测试
规范中的"key 对齐"与"占位符保留"要求由 packages/app/src/i18n/parity.test.ts 做强制校验,覆盖 app、ui、desktop 三个字典域,核心断言包括:
- key 完整性:每个非英文 locale 必须拥有英文源词典的全部 key,且额外 key 只能是该语言所需的复数类目变体(如阿拉伯语的
many、few等); - 占位符一致性:翻译文本中的
{{...}}占位符必须与英文源逐一匹配(顺序与数量),防止翻译中丢失{{count}}等变量; - 复数类目存在性:CLDR 声明的每个复数类目都必须在各 locale 中提供对应
${key}.${category}变体; - 目标 key 已本地化:如
command.session.previous.unseen等 key 在所有 locale 中不得仍等于英文原文。
这意味着新增或修改一个 i18n key 的完整工作量是:改 en.ts + 改全部 62 个 locale 文件 + 保证 parity 测试通过——这正是规范强调"locale 复杂度收口在共享模块"的价值所在。
其他协作约定
文档剩余两条约定同样值得注意:
-
Tool Calling:
ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE—— 在给 Agent 的工具调用场景中,凡可并行的工具调用必须并行发起,以降低往返延迟(这与 opencode 作为 coding agent 的定位直接相关)。 -
Browser Automation:Web 自动化统一使用
agent-browser,核心工作流为四步:agent-browser open <url>—— 导航到页面agent-browser snapshot -i—— 获取带 ref 的交互元素(@e1、@e2)agent-browser click @e1/fill @e2 "text"—— 通过 ref 交互- 页面变化后重新 snapshot
所有命令可通过
agent-browser --help查看。
小结
packages/app/AGENTS.md 虽然篇幅不长,但它定义的是 opencode Web 应用的三条工程主轴:以稳定性为最高优先的性能管理(配套 e2e/performance 基准工具链)、双进程分离的本地调试纪律(4096/4444 端口约定),以及一套由 63 个语言字典、类型化 API 和 parity 测试共同保障的本地化体系。其中 i18n 部分从 language.tsx 的 t / plural 收口设计,到 desktop-native.ts 的 locale 清单,再到 parity.test.ts 的自动化校验,构成了一条可验证、可回归的完整链路——这也是向该项目贡献 UI 代码前最值得先读一遍的规范文件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00