opencode RTL 支持开发指南:从方向感知布局到 Electron 标题栏的完整实践
opencode RTL 支持开发指南从方向感知布局到 Electron 标题栏的完整实践【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencodeOpenCode 的 Web 应用与桌面端需要同时服务 LTR左到右与 RTL右到左用户仓库中的rtl-aware-developmentSkill.opencode/skills/rtl-aware-development/SKILL.md把这一需求沉淀为一套可复用的开发准则。本文以该 Skill 为主体逐条拆解其核心规范并结合packages/app、packages/desktop、packages/ui中的真实源码说明 opencode 是如何落地“方向与语言解耦、逻辑属性优先、物理坐标显式处理”的完整 RTL 支持方案的。读完后你可以在任意 opencode 前端模块中正确地实现或评审 RTL/LTR 行为。核心原则方向独立于语言Skill 的开篇即给出总纲Treat direction as independent from language——把书写方向视为独立于语言的选择并且测试时必须覆盖“英文 LTR”“英文 强制 RTL”以及真实 RTL 语言与混合书写的组合。这一点直接决定了实现方式不能靠“换一个 RTL 语言包”来验证方向行为因为真实用户的界面语言与阅读方向是可以解耦的。opencode 源码正是这么做的——在 language.tsx 中方向由两部分推导语言本身的默认方向RTL_LOCALES集合ar、ur、pa、fa、dv命中则rtl否则ltr见 language.tsx#L23-L27用户可显式覆盖setLayout(direction, ...)提供方向覆盖且当覆盖值与语言默认方向一致时会自动归一为undefined即回到“跟随语言”见 language.tsx#L237-L239。因此“选择阿拉伯语”不等于“强制 RTL”反之亦然。这也正是 Skill 所强调的“Do not change the selected locale merely to force RTL”。在文档上设置 lang 与 dir并传播到弹层组件Skill 第一条准则在 document 上设置lang和dir并把方向传播到承载 Portal 化菜单和弹层的组件 Provider 中。opencode 的落地分两层。第一层是文档级LanguageProvider中有一个副作用持续把语言与方向同步到html元素并写入 Cookie用于 SSR/首屏见 language.tsx#L207-L213createEffect(() { if (typeof document ! object) return const value locale() document.documentElement.lang intl() document.documentElement.dir direction() document.cookie cookie(value) })第二层是组件级。Solid 的 Portal 化组件下拉菜单、Popover脱离文档 DOM 位置后仍需要知道方向opencode 通过一个专门的layoutLocale推导值解决const layoutLocale createMemo(() { if (!layout.direction) return intl() // Kobalte derives menu direction from locale rather than accepting a direction override. return layout.direction rtl ? ar : en })注释直说了原因Kobalte 的 I18n Provider 从 locale 推导菜单方向而不是接受方向覆盖因此用ar/en这两个代理 locale 把方向语义“翻译”给 UI 库。该值经由 app.tsx#L237-L241 注入I18nProviderI18nProvider value{{ locale: language.intl, layoutLocale: language.layoutLocale, t: language.t, plural: language.plural }} 最终在 packages/ui/src/context/i18n.tsx#L64 中layoutLocale优先于locale生效菜单等 Portal 组件的方向由此与主文档保持一致。保持语义化的 DOM 与焦点顺序Skill 指出Flexbox 和 Grid 本身会遵循dir不要为了镜像布局而添加row-reverse、CSSorder或颠倒的 DOM 结构以保持 DOM 顺序与焦点顺序的语义一致性。这是一条约束类准则而非实现类它的价值在于评审检查项——当某段代码出现flex-row-reverse或order: -1时应优先怀疑它是在硬编码镜像 LTR 布局而不是响应dir。在 opencode 的组件树中例如 titlebar-tab-strip.tsx 这类横向条带组件布局均依赖标准 flex 方向方向切换由dir属性自动完成无需额外反向样式。优先使用逻辑属性物理值只留给真正物理的场景Skill 中给出了最典型的 CSS 对照示例这是 RTL 改造的高频操作点/* Avoid */ padding-left: 12px; right: 0; border-right: 1px solid; text-align: left; /* Prefer */ padding-inline-start: 12px; inset-inline-end: 0; border-inline-end: 1px solid; text-align: start;逻辑属性padding-inline-start、inset-inline-end等随dir自动翻转一份样式同时服务两种方向。Skill 同时划定了物理坐标的合法保留场景指针位置、Canvas 几何、原生窗口控件等确实与屏幕物理位置绑定的部分。opencode 的标题栏实现正是这个边界的范例见下文 Electron 章节。隔离混合方向文本Skill 要求未知方向的文本用dirauto或bdi隔离代码、URL、ID、文件系统路径必须保持 LTR但不要为此把外层组件整体强制为 LTR。原文档给出的示例span classfile-rowbdi dirautoREADME.md/bdi/span bdi dirltrcodeC:\src\app.ts/code/bdi这在 opencode 这类编码代理产品里尤为关键会话面板、文件树、终端面板会同时渲染用户输入的任意语言文本与代码/路径。典型风险是 RTL 段落中嵌入C:\src\app.ts这类路径时Unicode 双向算法会把反斜杠位置排错用bdi dirltr或unicode-bidi: isolate把路径作为整体隔离即可保证路径字符序稳定同时不影响外层 RTL 布局。镜像“方向语义”而不是“所有图像”Skill 明确区分了该镜像与不该镜像的元素可以/需要镜像返回/前进、上一页/下一页、折叠展开指示器、缩进方向、方向性进度条不能镜像品牌 Logo、时钟、媒体控制按钮、图表、文本本身需要显式反向物理方向的渐变、translateX、SVG transform、动画增量delta。最后一类最容易被遗漏translateX(20px)在 LTR 下表示“向右移动”但在语义上若表示“进入”RTL 下应为负值。评审时可以把“代码里所有translateX、gradient(to right, ...)、SVGtransform”作为检查清单逐条确认它表达的是物理方向还是语义方向。交互映射clientX 永远是物理的Skill 指出的核心矛盾clientX等指针坐标是物理的而“逻辑边”的 resize 需要方向感知的增量换算——在 LTR 下拖动右边缘delta clientX - startX直接就是宽度增量在 RTL 下同一边缘在视觉上位于左侧增量需要取反。此外语义化的“上一个/下一个”键盘控件在 RTL 下可能交换 ArrowLeft/ArrowRight具体应遵循对应的 WAI-ARIA 组件模式如 Window splitter 模式。这条准则指导 resize 手柄、splitter、横向滚动条等组件的实现事件坐标不做转换但“把物理 delta 映射到逻辑尺寸变化”的那一层必须引入方向分支。不要假设 LTR 的滚动行为Skill 提示RTL 页面的scrollLeft可以从0开始并向负值增长滚动起点在右侧跨方向代码不能假设scrollLeft 0或“滚到尽头时scrollLeft scrollWidth - clientWidth”。推荐做法是用scrollIntoView({ inline: nearest })或经过双向验证的方向归一化辅助函数。这一条对应 Skill 测试矩阵中的“both LTR and RTL scroll endpoints”——验证时必须检查两个方向的滚动端点行为而不是只看 LTR。Electron 标题栏物理与逻辑的边界Skill 对 Electron 标题栏的要求非常具体优先使用原生 caption 控件用titleBarOverlayenv(titlebar-area-*)定义安全内容矩形Windows/macOS 的原生控件避让区和trafficLightPosition保持物理坐标矩形内部的应用导航则使用逻辑布局标题栏内可交互子元素标记app-region: no-drag。opencode 桌面端的主进程实现与之完全对应见 windows.ts#L186-L198...(process.platform darwin ? { titleBarStyle: hidden as const, trafficLightPosition: { x: 14, y: 14 }, // 物理坐标红绿灯按钮位置 } : {}), ...(process.platform win32 ? { frame: false, titleBarStyle: hidden as const, titleBarOverlay: overlay({ mode }), // 原生 caption 控件覆盖层 } : {}),macOS隐藏标题栏trafficLightPosition用{ x: 14, y: 14 }这一物理坐标避让红绿灯按钮Windows无边框窗口叠加原生titleBarOverlay把最小化/最大化/关闭交给系统绘制从而天然获得正确方向与高 DPI 行为。渲染进程侧则用 CSS 环境变量读取系统给出的安全矩形。titlebar.tsx#L184-L185 中width: windows() ? env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()})) : undefined,env(titlebar-area-width, fallback)优先取 Electron 注入的实际可用宽度取不到时回退到按控件宽度估算的值——安全矩形内部的应用导航随后按逻辑属性布局方向翻转时自动适配。而可交互的标题栏元素则显式声明不参与窗口拖拽如 titlebar-tab-strip.tsx#L291 的[app-region:no-drag]。验证看行为而不只是截图Skill 的收尾准则验证行为而非仅截图——需要检查计算样式computed styles、伪元素几何、命中区域、焦点顺序、键盘行为、子菜单方向、缩放/分级、以及 LTR 与 RTL 双方向的滚动端点。Skill 给出的测试矩阵是验收清单组合覆盖点英文 LTR基线行为英文 强制 RTL方向与语言解耦真实 RTL 语言 RTL真实脚本含标点/数字混合 RTL/LTR 内容、长标签、数字、代码、路径双向文本与 LTR 豁免内容键盘、指针 resize、滚动、菜单/子菜单、Electron 标题栏控件双方向交互与物理/逻辑映射仓库内有一个现成的验证入口调试栏debug-bar.tsx#L566-L573提供一个方向切换开关点击即在 LTR/RTL 之间翻转调用上文提到的language.setDirection对应测试矩阵中“英文 强制 RTL”这一组合无需切换界面语言即可全量走查上述检查项。小结这套 Skill 的核心可归纳为四句话方向与语言解耦dir独立可覆盖Portal 组件经layoutLocale感知方向布局用逻辑属性、保持语义化 DOM物理量指针、控件避让、trafficLightPosition显式保留语义量镜像图标、resize 增量、键盘左右键显式映射验证看行为矩阵而非截图。以上每条准则都能在当前仓库中找到对应实现或验证入口可直接作为 opencode 前端 RTL 改造与 Code Review 的检查标准。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考