国际化
宿主语言、插件展示资源与页面 SDK 翻译。
Documentation content is currently available in Simplified Chinese.
插件通过清单声明语言资源,并使用 SDK 翻译接口显示文本。更新语言资源或翻译接口时,同步调整清单、词典和调用代码。
#资源归属
桌面程序、Web Client 和每个插件分别拥有业务词典。共享运行库只实现语言匹配、ICU 消息格式、格式化和校验。协议包的 diagnostics.en.ts 仅包含协议自身校验诊断,不包含应用界面及插件业务文案;对外不提供通用的应用 t() 接口。桌面端和 Web Client 分别通过自身 src/i18n/runtime.ts 与 src/i18n/locales 翻译界面。插件 SDK 不打包宿主或其他插件的词典;插件缺失消息时不能从宿主查找翻译。
插件清单中的 i18n 没有独立版本号:
{
"name": "My clock",
"nameKey": "plugin.name",
"i18n": {
"defaultLocale": "en-US",
"resources": {
"en-US": "locales/en-US.json",
"zh-CN": "locales/zh-CN.json",
"ja-JP": "locales/ja-JP.json"
}
}
}也可以用 messages 内联声明同样的词典;resources 与 messages 必须二选一。defaultLocale 必须对应一个存在的词典。开发者可以选择任意合法的规范 BCP 47 语言标签作为默认语言,插件支持的语言不受宿主界面语言数量限制。完全没有可翻译文案的插件可省略 i18n。
语言文件为扁平 JSON 字符串字典,推荐使用语义键:
{
"plugin.name": "My clock",
"items.count": "{count, plural, one {# item} other {# items}}"
}查找顺序为:匹配语言中的消息 → 插件默认语言中的消息 → 调用方提供的显示回退 → 原始键。语言匹配优先精确匹配,再按语言与文字脚本匹配,不跨简繁体自动匹配。空字符串是有效翻译。ICU 参数不会递归展开;标签形式的内容作为普通文本处理,不作为 HTML 执行。
#清单展示字段
nameKey、titleKey、descriptionKey、labelKey、placeholderKey、helpKey 显式引用当前插件词典。小组件的展示分类支持 categoryKey。未声明引用的原文字段原样保留。
本地化只遍历协议定义的展示容器。ID、动作类别 ID、参数键、value、enum、default、示例数据、用户保存内容和运行状态保持原值。默认语言中缺失清单引用会阻止打包及安装。
#后台 SDK
const { definePlugin, t, i18n } = require(process.env.FLYONDECK_SDK_PATH)
const label = t('items.count', { count: 3 })
const preferred = i18n.getRequestedLocale()
const resolved = i18n.getResolvedLocale()context.i18n 提供相同实例接口,包括 number、date、relativeTime 和 onLocaleChange。宿主通过保留环境变量 FLYONDECK_LOCALE 提供期望语言,插件不能用配置覆盖。运行中的默认实例接收宿主语言变更。每个异步命令通过 AsyncLocalStorage 保留发起请求的语言快照,t、格式化函数和 context.i18n 在 await 之后仍使用同一语言;跨插件服务继续传递这一语言。宿主切换语言只影响后续默认请求。动作和实例动作通过执行选项传递语言,在排队前固定;工作流通过 RunContext.locale 继续传递,不把语言写入业务参数。派生标签应在请求或渲染时生成。
getRequestedLocale() 保留用户期望语言;getResolvedLocale()/getLocale() 返回当前包实际使用的语言。例如宿主界面只能回退到英文时,日文插件仍会收到 ja-JP。
#页面 SDK
页面先加载 /api/plugin-sdk/i18n-v1.js,再加载对应的小组件或配置页 SDK。宿主在页面其他脚本执行前注入已验证的插件资源,页面无需注册或自行获取词典。
<script src="/api/plugin-sdk/i18n-v1.js"></script>
<script src="/api/plugin-sdk/widget-page-v1.js"></script>
<span data-i18n="plugin.name">My clock</span>
<script>
const label = FlyonDeckWidget.t('items.count', { count: 3 })
FlyonDeckWidget.i18n.onLocaleChange(render)
</script>静态文案支持 data-i18n,属性支持 data-i18n-title、data-i18n-aria-label、data-i18n-placeholder、data-i18n-alt。动态文本显式调用 t()。语言变化先更新静态标签,再通知动态渲染订阅者,避免初始标签覆盖实时状态。页面设置 lang 和 dir。
语言来自页面 URL 和通过父窗口及插件身份校验的消息,每个页面有独立实例。配置页接口对应 FlyonDeckWidgetConfig。Web Client 将期望语言与小组件数据请求一起签名,桌面 IPC 也单独传递语言。宿主校验语言后放入插件命令上下文,不把它混入插件业务输入或保存配置。天气插件的响应缓存按语言区分。用户输入、API 返回的文章和用户自定义标签不应作为翻译键。时区独立于语言配置。
#资源校验与发布
资源随插件发布,安装和运行不需要翻译服务联网。文件路径仅允许包内 JSON 相对路径,拒绝路径穿越、符号链接、重复 JSON 键、非法 UTF-8、无效 ICU 消息、跨语言参数不一致和超限资源。单个资源文件最多 256 KiB,总文件资源最多 2 MiB,每个词典最多 5000 条消息、最多 100 种语言。
Node-only 读取适配器通过 @flyondeck/plugin-contracts/node-i18n 使用,纯协议入口不导入文件系统。宿主页面资源快照转义 < 和 Unicode 行分隔符,响应使用 Cache-Control: no-store。
仓库内置扩展、官方插件和教学示例均使用自身 locales 目录与语义键。正常构建不从宿主生成插件词典,也不提供旧清单迁移脚本。
#SDK 诊断与共享工具
SDK 自身诊断保存在 plugin-sdk/diagnostics.json,使用稳定的 SDK_* 错误码和独立中英文词典,缺失语言回退到英文。插件 t() 不会查询 SDK 诊断,SDK 也不查询插件文案。错误码在进程间保留;业务逻辑使用错误码或明确的通知动作标识,不根据已翻译的文案判断。
宿主的 RequestLocale.ts 使用 Node AsyncLocalStorage,让宿主文案和纯协议诊断在异步等待、动作排队及工作流中保留调用方语言。主程序、Web Client 的日期时间显示遵循当前期望语言,时区仍然独立。
共享外观和布局校验通过 createAppearanceTools(t)/createDeviceGridTools(t) 显式接受调用方翻译函数。Web Client 的生产构建检查实际模块图,拒绝导入桌面业务词典。共享显示字段和权限说明在访问时翻译,浏览器偏好在其他模块生成显示元数据之前初始化。
#验证
npm test --prefix packages/plugin-contracts
npm --prefix packages run build
npm --prefix packages testbuild:i18n 自动执行 check:i18n,检查 19 个仓库插件的资源归属、清单引用、代码/HTML 消息键、翻译完整度,以及页面在 SDK 前加载 i18n 运行库。应用构建和安全测试通过 build:extensions 使用这道检查。市场发布检查器也读取包内词典并验证展示引用。
| 验收范围 | 可核对的实现与验证 |
|---|---|
| 任意默认语言、匹配与回退 | contracts 测试验证日文默认、区域与文字脚本匹配、包内回退、ICU、格式化、实例隔离;共 33 项通过 |
| 宿主/插件/SDK 资源解耦 | i18n-ownership.test.cjs 与 Web Client 实际构建模块图验证无跨包业务词典依赖,SDK 不包含旧中央词典 |
| 安全资源读取 | 重复键、非法 UTF-8、路径穿越、符号链接、缺失文件、超限资源与参数不一致均有拒绝验证;内联和文件词典都按 UTF-8 字节限额校验 |
| 标识及用户数据不变 | 清单展示遍历测试、设置输入保持测试、原生代码/调试器测试;业务 ID、枚举值、默认配置、用户文字和程序不进入翻译 |
| 请求语言隔离 | i18n-request-context.test.cjs 实际启动消费方与服务提供方,验证交错请求、宿主语言变化、跨插件服务、动态选项、已认证设备请求、排队动作及工作流 |
| 插件生命周期 | i18n-integration.test.cjs 验证离线打包、安装、执行、运行中替换词典和卸载;目录、后台动作和页面使用新资源,卸载后页面返回 404 |
| HTML 注入 | 资源在页面脚本前注入并转义脚本边界;省略可选 head 标签时保留 DOCTYPE;响应禁止缓存 |
| 页面语言切换 | i18n-pages.electron.cjs 从清单枚举全部 29 个页面入口/配置页,实际加载包内资源与 SDK,验证中英文切换、设置值和用户内容保持、无页面脚本异常 |
| 长文本、RTL 与异步旧响应 | 页面测试注入长阿拉伯语词典,检查 lang/dir 和窄窗口横向溢出并生成截图;延迟英文响应后切换中文,确认控制组件与天气/网络示例不会显示旧语言结果 |
| 宿主真实界面 | i18n.electron.cjs 验证语言选择器、偏好保存与重载,以及中英文通知时间和基于稳定动作标识的通知入口 |
| 桌面端回归 | test:security 包含原生资源构建、8 个内置插件构建、SDK 类型检查及 507 项测试:502 项通过;4 项 Windows 专用测试和 1 项无可调亮度显示器的测试在当前 macOS 环境跳过 |
| Web Client | 类型检查、生产构建及 42 项测试通过 |
| 可分发插件与示例 | 官方插件 25 项测试及打包通过;3 个官方插件和 8 个教学示例的完整包均通过市场检查;内置插件随宿主构建分发 |
| 文档与市场 | 文档字段索引、示例、链接、完整包检查等 19 项测试通过;市场 36 项服务测试、5 项 Web 测试及完整构建通过;文档与实现快照同步到市场 |
所有国际化调用方使用本文定义的资源格式和翻译接口。当前资源包含中文和英文;插件支持哪些语言、使用哪种默认语言由插件自己决定,不要求第三方插件采用中文。
#商店检查报告
商店的包检查器拥有独立诊断词库,持久化检查项目使用稳定 id,标题与详情保存 { code, values }。API 在响应时由相应资源所有者生成 message 和各支持语言的 messages。插件清单、包内语言资源及用户资料保持原文,不并入商店词库。商店页面、API、Worker、检查器分别维护资源;仅复用纯格式化机制。检查报告的生成方和读取方使用相同的数据结构。