国际化
插件本地化分两个互相独立的层,各有职责:
- manifest 字符串(
name、description、AI 工具的description)由宿主根据manifest.json中可选的i18n块翻译。这些字符串驱动插件市场卡片、工具网格、详情抽屉以及 Agent 工具面板——也就是宿主渲染的所有界面。见下文 manifest 字符串本地化。 - 插件 UI 字符串(iframe 内部的一切)是插件自己的职责。宿主通过 SDK 推送当前 locale;插件读取它并用自带的消息表翻译。见下文 插件 UI 本地化。
无论哪一层,locale 都由宿主统一持有;插件永远不要自带语言切换器。
读取 locale
FengYuClient.ready() 会以一个 Environment 完成,其 locale 字段持有当前活动的 locale 标签(例如 en、zh-CN):
import { fengyu } from './sdk.js'
const env = await fengyu.ready()
console.log(env.locale) // → 'zh-CN'ready() 还会把 locale 应用到文档——document.documentElement.lang 会被设为 env.locale——因此任何 locale 感知的 CSS 或属性选择器都无需额外接线即可工作。
响应变化
如果用户在插件打开期间切换语言,宿主会发出一个 environment 事件。用 client.on('environment', handler) 订阅:
const off = fengyu.on('environment', (env) => {
applyLocale(env.locale)
})
// ...稍后,在销毁时
off()处理器收到的是完整的、更新后的 Environment,因此重新读取 env.locale(以及 env.theme)并重新渲染即可。
不要在握手之后才订阅
Vue 插件应优先使用 mountFengYuApp;它会先建立环境事件订阅,再等待 ready 握手。如果需要 自行编写启动逻辑,必须保持这个顺序:
const off = client.on('environment', applyEnvironment)
const initial = await client.ready()
applyEnvironment(initial)宿主可能在 iframe 刚加载时就发送第一条环境事件。如果直到 await client.ready() 之后才注册 监听器,就会产生竞态:初始事件可能丢失,插件会一直停留在兜底的深色/英文界面,即使宿主已经 是浅色/中文。环境事件可能只包含部分字段,因此应先与最近一次状态合并,再更新 Vuetify 和插件 自己的消息表。
manifest 字符串本地化
市场卡片、工具网格、详情抽屉以及 Agent 工具面板所展示的字符串,都是宿主从 manifest.json 读取的。要本地化它们,在 manifest 里加一个可选的 i18n 块,按短 locale 标签(en、zh)索引。顶层字段保持英文作为默认值;每个 locale 覆盖字段都各自可选——不翻译的字段直接省略:
{
"id": "fan.summer.excel",
"name": "Excel Splitter",
"description": "Split Excel workbooks by sheet, column value, or complex rules",
"rpc": {
"methods": {
"excel_analyze": {
"description": "Analyze the granted Excel workbook…",
"inputSchema": { "type": "object", "properties": { "filePath": { "type": "string" } }, "required": ["filePath"] }
}
}
},
"aiTools": [
{ "name": "excel_analyze", "description": "Analyze the granted Excel workbook…", "method": "excel_analyze", "effect": "read" }
],
"i18n": {
"zh": {
"name": "Excel 拆分器",
"description": "按工作表、列值或复杂规则拆分 Excel 工作簿",
"aiTools": {
"excel_analyze": { "description": "分析已授权的 Excel 工作簿……" }
}
}
}
}对于 locale 为 zh-CN 的请求,查找顺序为:
i18n["zh-CN"]—— 精确标签i18n["zh"]—— 语言族- 顶层默认值(英文)
任一级别都可以缺失——宿主会依次回退到下一级,因此插件可以只翻译关心的字段,永远不会出现空白。宿主根据请求的 Accept-Language 头选择 locale(前端会自动发送当前 UI 语言)。
此处不本地化的内容
author—— 品牌标识,不翻译。inputSchema/outputSchema—— 其内嵌的 JSON Schematitle/description保持英文(它们向 LLM 描述工具参数)。- AI 工具
description的覆盖仅用于前端展示。发给 LLM 的字符串始终是顶层英文原文,因此翻译不会影响工具选择质量。
插件 UI 本地化
宿主不翻译插件 iframe UI 内部的字符串。请自带消息表并使用共享响应式运行时,使回退、 插值和 locale 归一化在所有插件中保持一致:
import { createFengYuI18n, mountFengYuApp } from '@infinia/plugin-ui'
const messages = createFengYuI18n({
en: { title: 'Split complete', pick: 'Choose a file' },
zh: { title: '拆分完成', pick: '选择文件' },
})
await mountFengYuApp({ root: App, client, onEnvironment: messages.applyEnvironment })
messages.t('title')当活动 locale 没有翻译时回退到一个默认 locale(通常是 en)。由于宿主设置了 document.documentElement.lang,你随时也可以从 DOM 读取 locale 作为兜底:
const locale = document.documentElement.lang // 'en' | 'zh-CN' | ...WARNING
不要在你的插件 UI 中添加语言切换器。宿主是 locale 的唯一事实来源;插件级别的切换器会与 App 其余部分失去同步。