多语言、平台适配与反馈
本页说明 ZCode 如何以共享契约承载多语言、桌面/Web 平台差异与用户反馈相关状态,并解释这些边界如何影响 renderer、main、preload 之间的通信设计。
Purpose and Scope
本页覆盖三类彼此相关的基础能力:
- 多语言(i18n)约束:界面必须容纳翻译后的文本扩展,不能把英文短标签作为布局前提。
- 平台适配:通过共享的
IPlatformService/平台契约抽象 Desktop、Web、本地与远程环境差异,并覆盖 Windows、macOS、Linux 等平台差异。 - 反馈:平台通知协议中用于任务状态反馈的
feedback_update状态,以及设计规范中反馈颜色、编译反馈行和反馈截图预览的视觉约束。
本页不替代具体的 UI 组件、远程连接实现、浏览器 tab 生命周期或发布打包文档;这些实现仅在解释平台契约边界时被引用。源码中未在本页读取范围内提供完整的语言包加载器、反馈提交 API 或具体平台 service 实现,因此这些部分不作推断。
Overview
该能力的核心不是为每个平台复制一套业务逻辑,而是把跨端事实收敛为稳定的类型合同。仓库指导文件明确要求组件通过 packages/ui/src/hooks/ 访问服务,平台操作通过 IPlatformService(packages/shared/src/platform.ts)完成,而不是直接调用 window.zcode;同时,Desktop、Web、本地和远程环境的差异应通过依赖注入处理,并兼顾 Windows、macOS 与 Linux。
共享平台契约具有三个重要设计意图:
- 边界集中:renderer 只依赖共享类型和服务接口,不需要知道某个能力由 Electron main、Web API 或远程服务提供。
- 安全最小化:例如
createOpenInEditorRemoteTarget只转发打开编辑器所需的连接标识,不让 password 或 private-key passphrase 穿过 renderer/preload/main IPC。 - 可演进的兼容性:跨进程 payload 使用可选字段和明确的联合类型表达旧版本、不同平台或恢复路径,而不是依赖隐式约定。
Architecture
上图中的 Platform 是合同层,不是具体的 Electron 或 Web 实现。platform.ts 导入 AppSettings 与 Locale 类型,并同时声明任务通知、编辑器打开、文件保存、窗口 chrome、远程连接和嵌入式浏览器等跨端数据结构。这样做可以让上层组件以相同的数据形状处理平台能力;具体实现如何注册、注入和执行,必须在相应 sibling 页面中说明。
多语言边界与布局约束
仓库设计规范要求尊重 i18n 文本扩展,不能只为短英文标签设计固定宽度,也不能把紧密截断作为翻译后布局可用性的唯一保证。由此可以得到一个实现约束:组件应通过可伸缩布局、合理换行或可扩展容器承载 Locale 对应的文本,而不是在平台层硬编码某一种语言的长度假设。
platform.ts 将 Locale 作为共享 protocol.ts 类型导入,说明 locale 属于跨端协议的一部分,而不是某个 renderer 组件的私有状态。当前已读取的源码没有展示 locale 的枚举值、加载流程或 fallback 算法,因此本页只能确认类型依赖和布局约束,不能补充未验证的默认语言或切换 API。
平台适配的契约设计
通过联合类型表达平台差异
OpenInEditorRemoteTarget 将 SSH、WSL 与 Docker 的必要字段显式区分;ApplicationIconLocator 则把 macOS bundle id、Windows executable path 与 Windows AUMID 表达为不同的 kind。这种 discriminated union 让调用方在编译期处理平台分支,也避免把所有平台字段塞进一个含义模糊的对象。
1export type OpenInEditorRemoteTarget =
2 | Pick<SSHConnectOptions, "kind" | "host" | "port" | "username" | "sshConfigAlias">
3 | Pick<WSLConnectOptions, "kind" | "distro" | "user">
4 | Pick<DockerConnectOptions, "kind" | "container">;
5
6export type ApplicationIconLocator =
7 | { kind: "darwin-bundle-id"; value: string }
8 | { kind: "windows-executable-path"; value: string }
9 | { kind: "windows-aumid"; value: string };Source: platform.ts
Source: platform.ts
在跨进程边界前裁剪敏感字段
createOpenInEditorRemoteTarget 根据 target.kind 构造最小的编辑器目标。SSH 只保留 host、port、username 和可选 alias;WSL 只保留 distro 与非空 user;Docker 只保留 container。函数注释明确说明 password 和 private-key passphrase 不应继续穿过 renderer/preload/main IPC,这既是安全边界,也是降低跨端合同复杂度的手段。
1export function createOpenInEditorRemoteTarget(target: RemoteTarget): OpenInEditorRemoteTarget {
2 switch (target.kind) {
3 case "ssh":
4 return {
5 kind: "ssh",
6 host: target.host,
7 port: target.port,
8 username: target.username,
9 ...(target.sshConfigAlias?.trim() ? { sshConfigAlias: target.sshConfigAlias.trim() } : {}),
10 };
11 case "wsl": {
12 const user = target.user?.trim();
13 return {
14 kind: "wsl",
15 distro: target.distro,
16 ...(user ? { user } : {}),
17 };
18 }
19 case "docker":
20 return {
21 kind: "docker",
22 container: target.container,
23 };
24 }
25}Source: platform.ts
反馈在跨端通知中的表达
TaskNotificationPayload.status 是当前读取源码中最直接的反馈协议入口。它把任务状态建模为有限联合类型:completed、failed、permission_request、elicitation_request 和 feedback_update。通知同时携带 taskId、标题和正文,并允许用 requestId 关联需要继续交互的请求。
这意味着反馈不是一个无结构的字符串事件:消费者可以基于 status 做确定性的分支,避免把“任务完成”“失败”“需要权限”和“反馈更新”混为一类。源码没有展示通知的发送者、接收器或持久化策略,因此这里不声称其具体传输机制。
1export interface TaskNotificationPayload {
2 taskId: string;
3 status: "completed" | "failed" | "permission_request" | "elicitation_request" | "feedback_update";
4 requestId?: string;
5 title: string;
6 body: string;
7}Source: platform.ts
设计规范还规定了反馈视觉语义:语义反馈颜色应保持一致;编译失败、尚未执行的脚本使用 compile-feedback row 表达,并区分当前草稿与已有更新的状态;反馈截图预览对话框属于少数保留特定圆角 shell 的场景。这些规则属于视觉层,平台契约只负责传递状态和数据,不应在共享类型中嵌入 UI 样式。
Core Flow
这条流程强调的是类型边界而非未读取的具体调用链:调用方通过 service/hook 访问能力;renderer 使用共享 payload;运行时实现依据 Desktop、Web、本地或远程环境提供结果;反馈消费者按有限状态处理通知。若要追踪实际 IPC channel、DI 注册或 UI 状态存储,应转到对应实现页面。
Configuration Options
当前读取的相关源码没有发现可直接配置的语言 fallback、平台选择或反馈开关。可以确认的共享数据选项如下:
| 数据项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TaskNotificationPayload.requestId | string | undefined | 未提供 | 将通知关联到交互请求;源码只声明可选,不声明生成策略。 |
OpenInEditorOptions.remoteTarget | OpenInEditorRemoteTarget | undefined | 未提供 | 指定 SSH、WSL 或 Docker 目标。 |
OpenInEditorOptions.pathKind | "file" | "directory" | undefined | 未提供 | 区分打开文件或目录。 |
ApplicationIconLocator.kind | 联合字面量 | 必填 | 选择 macOS bundle id、Windows executable path 或 Windows AUMID。 |
API Reference
createOpenInEditorRemoteTarget(target: RemoteTarget): OpenInEditorRemoteTarget
将完整的 RemoteTarget 转换为打开编辑器所需的最小远程目标。函数按 target.kind 分支:SSH 保留连接定位字段并裁剪 alias 空白;WSL 裁剪可选 user 的空白并在为空时省略;Docker 仅保留 container。
参数:
target(RemoteTarget):SSH、WSL 或 Docker 远程目标。
返回值: OpenInEditorRemoteTarget,不包含密码或 private-key passphrase 等凭据字段。
异常与边界: 源码没有声明显式 throw;实现通过 switch 处理已定义的 kind 联合成员。调用方仍应保证传入值符合 RemoteTarget 类型。
buildLocalMediaPreviewUrl(path: string): string
为本地媒体预览构造 zcode-media://local/preview URL,并将路径写入 path 查询参数。该函数把 scheme 和参数编码集中在共享层,避免各端重复拼接 URL。
1export const LOCAL_MEDIA_PREVIEW_SCHEME = "zcode-media";
2
3export function buildLocalMediaPreviewUrl(path: string): string {
4 const url = new URL(`${LOCAL_MEDIA_PREVIEW_SCHEME}://local/preview`);
5 url.searchParams.set("path", path);
6 return url.toString();
7}Source: platform.ts
Failure Modes、边界与并发注意事项
- 通知定位不足:关闭 browser tab 的旧 payload 只有
tabId,在用户切换 workspace 后可能无法找到原 side pane;当前契约因此允许携带workspaceKey、remoteSessionId和sessionId,并保留缺省字段以兼容 recovery-orphan 路径。 - 跨端状态不一致:
BrowserTabResidencyState明确区分live-visible、live-background、suspend-pending、suspended和restoring,说明恢复与挂起不是一个布尔值可以表达的状态。相关状态机实现未在本次读取范围内展开。 - 截图握手超时:共享常量
BROWSER_SCREENSHOT_SURFACE_PREPARE_TIMEOUT_MS为 3,000 ms,payload 还可传递timeoutMs;注释说明这样做是为了避免 main 与 renderer 使用漂移的 deadline。 - 平台安全边界:编辑器目标转换会主动丢弃凭据;浏览器数据导入结果只返回数量、状态和受控错误类型,不跨进程返回 Cookie、LocalStorage 值或解密材料。
- 并发/代际一致性:browser payload 使用
browserGeneration、generation和sessionId等字段,拒绝结果可以返回residency-generation-mismatch;这表明消费者必须校验目标实例代际,不能仅凭 tab id 接受迟到消息。
Extension Points 与操作建议
扩展新的平台能力时,应优先在 packages/shared/src/platform.ts 增加可判别的 request/result/event 类型,再由 IPlatformService 和注入实现承载,而不是让 UI 直接访问 window.zcode。新增平台分支应使用联合类型和明确的 kind,并在跨进程前裁剪敏感字段。
新增反馈状态时,应评估它是否确实是任务生命周期中的新状态,而不是把任意 UI 文案塞入 body;消费者依赖有限联合类型进行分支,因此改变状态集合会影响所有实现。多语言 UI 扩展则应遵循设计规范的 i18n expansion 要求,并在 Desktop 与 mobile Web、不同平台和主题下验证布局。