Skip to content

插件国际化

本页说明插件如何提供多语言文本:哪些字段会被解析、资源文件放在哪里、宿主如何选择语言与回退、运行时消息如何格式化,以及如何用 Devkit 校验。

国际化只影响展示。请求参数、返回的歌曲元数据、歌词内容、写入音频的标签值和插件行为都不随语言变化。

文本值:@ 引用与字面量

Manifest 中显示给用户的文本(插件名称、描述、配置项标题、选项文本等)都写成普通字符串,用前缀区分两种含义:

含义界面显示
"@plugin.name"引用语言资源里的键 plugin.name当前语言的文本,例如“示例搜索源”
"@@price"@ 开头的普通文本去掉一个 @,显示 @price
"Example Source"普通文本原样显示,不去查资源

三条限制:

  • 只解析一层。资源文件里的值不会再被当成引用,所以 "tip": "@plugin.name" 会原样显示 @plugin.name
  • 引用不能为空,"@" 不合法。
  • 用到引用就必须声明 i18n,且 minHostApiVersion 不低于 4,否则安装会失败。

哪些字段会按上面的规则解析

位置解析说明
manifest.name插件名称
manifest.description插件描述
配置项 title配置项标题
配置项 summary标题下的说明
配置项 group界面按原始值归组,卡片标题用解析结果,见下方说明
选项 label、选项 summary下拉选项的文本
markdown 配置项的 defaultValue说明正文,通常把整段正文放在资源里
其他配置项的 defaultValue业务默认值,不能翻译,例如 "cn""true"
配置项 key、选项 valuedependency稳定标识,必须保持原样
identryiconauthorversionCodeversionNameapiVersioncapabilities不参与界面文本

group 既是分组身份也是卡片标题:界面按 group 的原始字符串把配置项归到同一张卡片,卡片标题显示它解析后的文本。所以同一组要写完全相同的 group 值(通常是同一个 @group.xxx 引用),不要每种语言写一个值。留空 group 的配置项归入应用自带的“基础”分组。

添加一种语言

以新增简体中文为例。

1. 在插件根目录建资源文件 locales/zh-Hans.json

json
{
  "plugin.description": "示例搜索源插件",
  "config.region.title": "地区",
  "config.region.summary": "接口使用的地区代码",
  "group.request": "请求",
  "config.region.option.cn": "中国大陆",
  "config.region.option.us": "美国"
}

2. 在 Manifest 中把要翻译的文本换成引用,并声明资源

json
{
  "id": "com.example.source",
  "name": "Example Source",
  "description": "@plugin.description",
  "versionCode": 2,
  "versionName": "1.1.0",
  "apiVersion": 4,
  "minHostApiVersion": 4,
  "configFields": [
    {
      "key": "region",
      "title": "@config.region.title",
      "summary": "@config.region.summary",
      "group": "@group.request",
      "type": "dropdown",
      "defaultValue": "cn",
      "options": [
        { "value": "cn", "label": "@config.region.option.cn" },
        { "value": "us", "label": "@config.region.option.us" }
      ]
    }
  ],
  "i18n": {
    "defaultLocale": "en",
    "resources": {
      "en": "locales/en.json",
      "zh-Hans": "locales/zh-Hans.json"
    }
  }
}

上面每个字段的值本身还是字符串:要翻译就写 @ 引用,不需要翻译就写字面量。引用写法不用再留一份原文,缺少某种语言的翻译时会自动用默认资源的文本;而品牌名、尺寸这类与语言无关的文本(例如 Example Source500 × 500)直接写字面量。

3. 建默认资源 locales/en.jsondefaultLocale 指向的这份文件必须包含所有被引用的键,其他语言找不到翻译时都用它:

json
{
  "plugin.description": "Example source plugin",
  "config.region.title": "Region",
  "config.region.summary": "Region code used by the API",
  "group.request": "Requests",
  "config.region.option.cn": "Mainland China",
  "config.region.option.us": "United States"
}

4. 校验

bash
node tools/plugin-devkit/src/cli.js validate ./my-plugin
node tools/plugin-devkit/src/cli.js inspect ./my-plugin --locales zh-CN

inspect --locales 会按给定语言偏好显示解析后的插件名称;加 --json 还能看到描述与完整的配置项文本,可以直接确认翻译是否生效。

资源文件约定

text
<plugin-root>/
├── manifest.json
├── source.js
├── locales/
│   ├── en.json
│   ├── zh-Hans.json
│   └── zh-Hant.json
└── icon.png
  • 一种语言一个 UTF-8 JSON 文件,内容是「资源键 → 字符串」的字典,值必须是字符串。
  • 语言标签用规范的 BCP 47 写法,例如 enjapt-BRzh-Hanszh-Hant;它就是 resources 的键。
  • 每个文件不超过 512 KiB;每个插件 1 到 64 种语言。
  • 路径必须是插件目录内的相对 .json 路径,不能用绝对路径、\..
  • 资源随插件一起打包,不要写进 includeDirs:它不是脚本。

键名没有强制格式,按引用位置分组便于维护:

前缀用途
plugin.*名称与描述
config.<配置项 key>.*配置项标题、说明、选项(建议 config.<key>.option.<value>、正文用 config.<key>.content
group.<分组标识>分组标题
error.*warn.*status.*运行时消息,见下文

键的规则:

  • 默认资源要包含两类键:所有被 @ 引用的键,以及任一语言资源里出现过的键。
  • 翻译资源可以少写键,缺少时去父级资源和默认资源里找;但不能多出默认资源里没有的键。
  • 带占位符的运行时消息,各语言中 %s%d 的编号和类型必须一致。
  • 界面文本里的 %%s%20 只是普通字符,不参与占位符校验。

语言匹配与回退

宿主按应用当前的语言偏好列表依次尝试:完整标签匹配 → 同语言同文字体系的资源 → 同文字体系地区资源 → defaultLocale

以只提供 enzh-Hanszh-Hant 的插件为例:

应用语言选中的资源原因
zh-CNzh-Hans同语言、同文字体系
zh-TWzh-HKzh-Hant同语言、同文字体系
en-USen-GBen完整标签或同语言匹配
de-DEja-JPen没有匹配,回退到默认资源

文字体系(简体 Hans、繁体 Hant 等)由系统 ICU 数据补全,匹配结果只取决于资源的语言标签;少见语言在不同系统上可能有差异。

选中语言后如果缺键,依次查找:

  1. 选中语言的资源
  2. 父级资源:例如选中的是 zh-Hant-TW,就用 zh-Hant
  3. 默认资源

界面在展示时才解析引用,数据库里保存的仍是 Manifest 原始文本,所以切换语言不需要重新安装或迁移插件。

运行时消息

脚本里需要展示给用户的消息同样走资源,通过 Platform.i18n 读取:

函数说明
Platform.i18n.getLocale()返回当前选中的语言标签,例如 "zh-Hans";插件没有 i18n 时返回 "und"
Platform.i18n.t(key, ...args)取回 key 的文本;传入参数时按占位符格式化,不传参数时原样返回
javascript
// locales/en.json:      "error.candidateFailed": "Candidate %1$s failed: %2$s"
// locales/zh-Hans.json: "error.candidateFailed": "候选 %1$s 获取失败:%2$s"
try {
  // ...
} catch (e) {
  Platform.log.warn("Example", Platform.i18n.t(
    "error.candidateFailed",
    String(song.title || song.id || ""),
    String(e && e.message ? e.message : e)
  ));
}

占位符规则:

  • 支持 %s%d%1$s%2$d%%,最多 64 个参数。
  • 只有一个参数时可以省略编号;多个参数必须写从 1 开始连续的位置编号,不能混用带编号与不带编号的写法。
  • %s 只接受字符串;%d 只接受 JavaScript 安全范围内的整数,小数、数字字符串、空值都会抛错。
  • 参数按可变参数传入,不传数组;数量必须与资源中声明的位置数一致。
  • 译文可以换序或重复使用同一个编号,但不能增删参数、不能改变类型。
  • 不支持浮点、宽度、日期格式和复数规则。

调用注意:

  • 键在选中语言、父级资源和默认资源里都不存在时抛出 Unknown plugin string: <key>;参数数量或类型不符也会抛错,插件可以自行捕获。
  • 运行时键只在脚本里引用,Manifest 无法检查它们,因此要保证脚本中的键名与资源一致。任一语言声明了某个键,默认资源里必须也有这个键;带占位符的键还要求各语言签名一致。
  • 运行时不参与业务判断:不要用 getLocale() 决定请求参数(例如接口的语言参数)。这类需求应做成独立的配置项,否则用户切换界面语言会改变搜索结果与缓存键。
  • Platform.i18n 属于宿主 API 4,调用它的插件应声明 minHostApiVersion: 4;版本字段的区别见 API 版本沿革

用 Devkit 校验

在插件仓库根目录执行:

bash
node tools/plugin-devkit/src/cli.js validate ./my-plugin
node tools/plugin-devkit/src/cli.js inspect ./my-plugin --locales zh-CN,en
node tools/plugin-devkit/src/cli.js test ./my-plugin searchSongs --keyword "晴天" --locales zh-CN,en
node tools/plugin-devkit/src/cli.js pack ./my-plugin
命令会检查什么
validate资源路径、语言标签规范、defaultLocale 是否存在、默认资源是否覆盖全部引用键、翻译是否多出默认资源没有的键、运行时键的占位符签名是否一致、引用是否满足 minHostApiVersion >= 4
inspect --locales按语言偏好显示解析后的插件名称;加 --json 可以看到解析后的描述与完整配置项文本
test --locales用真实脚本运行,确认运行时消息在目标语言下的输出
packlocales/ 一起打进安装包

Devkit 的完整命令与限制见本地调试插件

发布前检查

  1. 需要翻译的文本字段都换成了 @ 引用,不需要翻译的保持字面量。
  2. 默认资源覆盖全部引用键,且没有多余的、默认资源未声明的键。
  3. 同组配置项使用完全相同的 group 值。
  4. 运行时消息键在每种语言中的占位符编号与类型一致。
  5. 脚本里用到的运行时键与资源文件中的键一一对应。
  6. 使用引用或 Platform.i18n 的插件已声明 minHostApiVersion: 4
  7. validate 通过,并用 inspect --localestest --locales 检查过目标语言。

常见问题

Q:界面还是显示 @config.region.title

说明这个键没有解析成功。检查 i18n 是否声明、defaultLocale 是否在 resources 里,以及默认资源是否有这个键——正常情况下默认资源缺键会在安装阶段就报错,页面上不会漏出 @。如果字符串本来就该以 @ 开头,请写成 @@...

Q:为什么有几个字段不能翻译?

key、选项 valuedependency 和普通配置项的 defaultValue 都是稳定标识或业务值,宿主不会解析它们。需要多语言的默认值请改成下拉选项,用 label 提供文本。

Q:markdown 配置项怎么翻译?

把正文当作普通资源:"defaultValue": "@config.help.content",然后在各语言资源里给 config.help.content 写正文。正文里的 #-、链接等 Markdown 语法保留在资源值里。

Q:翻译可以缺键吗?

可以。缺键会依次回退到父级资源、默认资源。但不能新增默认资源里没有的键——这通常是键名拼写错误,validate 会直接报出来。

Q:粘贴带 % 的文本会不会报错?

界面文本不会:只有含 %s/%d 这类占位符的运行时资源才做签名校验,100%%20 会被当成普通字符。运行时消息里如果要显示字面百分号,请写 %%

Q:怎么调试“某个键没生效”?

先跑 validate,它会给出具体的键名与原因;再用 inspect --locales <标签> 看解析结果,最后用 test --locales 看运行时消息。日志里出现 Unknown plugin string: <key> 表示脚本引用的键在资源里不存在。

Q:用户改了插件名称,翻译会被覆盖吗?

用户自定义名称优先于翻译,其他文本仍按语言解析。

Q:旧插件没有 i18n 会怎样?

继续使用 Manifest 中的原始文本,行为不变。只有当字段里出现 @ 引用时,才必须声明 i18n