Bob 1.21.0 插件翻译结果支持 Markdown
Bob 1.21.0 的翻译结果支持按 Markdown 渲染,内置的 AI 翻译服务已经默认启用。插件通过新的 content 字段声明译文格式,也能让译文以 Markdown 展示。本文介绍 content 字段的用法、Markdown 里引用插件自带图片的方式,以及对已有插件的兼容说明。
背景
此前插件回传译文只有 toParagraphs 一种方式:一个 string 数组,Bob 展示时在元素之间插入空行。AI 类插件为了保留模型输出的格式,通常只给数组设置一个元素,把完整文本原样回传。但模型输出的 Markdown 语法(标题、列表、代码块、表格等)会原样显示成 #、*、| 这些符号,可读性很差。
1.21.0 的结果卡片可以直接渲染 Markdown,支持标题、列表、引用、代码块(带语法高亮)、表格、链接、图片、数学公式、Mermaid 图表和分割线,流式输出过程中也能边收边渲染。Markdown 渲染需要 macOS 13 及以上,macOS 12 上译文按纯文本显示,Markdown 标记原样保留。如果渲染出现异常,用户可以在「偏好设置 - 翻译 - Markdown」中关闭渲染,回到纯文本显示。
借这次改动,译文的回传结构也做了收敛:新增 content 对象,用 format 告诉 Bob 应该怎么理解 text,不再靠数组元素个数暗示格式。
content 对象
translate result 新增 content 属性,存在时 Bob 以它为准,忽略 toParagraphs。
| 属性 | 类型 | 说明 |
|---|---|---|
| format | string | 译文格式,决定 Bob 怎么理解 text。可选值 plain、markdown、lines,不传按 plain 处理。 |
| text | string | 译文文本。 |
三种格式的用法如下。
plain:纯文本整篇显示
text 原样按纯文本显示,不拆合段落、不裁剪,换行就是换行。
query.onCompletion({
result: {
from: "en",
to: "zh-Hans",
content: {
format: "plain",
text: "第一行译文\n第二行译文"
}
}
});markdown:整篇按 Markdown 渲染
text 原样按 Markdown 渲染。AI 类插件让模型输出 Markdown 后直接透传即可,不需要做任何处理。
query.onCompletion({
result: {
from: "en",
to: "zh-Hans",
content: {
format: "markdown",
text: "## 摘要\n\n- 第一点\n- 第二点\n\n| 列 A | 列 B |\n| --- | --- |\n| 1 | 2 |"
}
}
});译文里的软换行(单个换行符)会显示成段内换行,不会像通用 Markdown 渲染器那样合并成空格。翻译场景下换行结构来自用户原文(字幕、歌词、代码注释),保留换行更符合预期。
lines:逐行译文,由 Bob 按原文结构拼回
传统机器翻译服务(DeepL、Google、百度这类)用这个格式。text 里每一行是一行译文,Bob 会按原文的换行结构把译文拼回去:原文两行之间是单换行,译文也是单换行;原文是空行,译文也是空行。
接口的返回形态不同,处理方式也不同:
- 接口把整段译文作为一个字符串返回,原封不动放进
text即可,不需要做任何处理 - 接口按数组逐行返回,用换行把数组连成一个字符串再放进
text
// 接口整段返回
content: { format: "lines", text: resp.translatedText }
// 接口按数组返回
content: { format: "lines", text: resp.lines.join("\n") }拼回的工作由 Bob 完成,插件不需要自己对齐原文。有几点需要知道:
- Bob 按「第 i 行译文对应原文第 i 个非空行」拼回,只含空白的行会被忽略,所以译文里不要用空行占位
- 每行内容原样保留,Bob 不做 trim,插件也不需要
- 译文行数与原文对不上时,Bob 用单换行拼接、不会加空行,不会报错
lines按纯文本显示,不解析 Markdown
认不出的 format
如果 format 是 Bob 当前版本不认识的值,Bob 不会猜、也不会当纯文本显示,而是在正文位置提示「当前版本不支持该译文格式」。这样以后再新增格式时,还没升级的用户能知道需要升级,而不是看到一堆原始标记。
流式输出
onStream 回调同样传 content,text 传累计的全文。同一次 translate 调用内,某一帧没带 format 时会沿用上一帧的值,避免卡片在纯文本和 Markdown 之间来回切换。
与 thinkInfo 的配合
thinkInfo.splitThinkTag 设为 true 时,Bob 会从 content.text 里拆出 <think></think> 标签的内容作为思考过程,只对 plain 和 markdown 生效。lines 不支持拆标签,思考内容请放到 thinkInfo.content。
Markdown 中引用插件自带的图片
Markdown 译文里的图片支持 http(s) 远程地址、data: 内联和本地文件。为了让插件能引用自己包内的图片,新增了 bob-plugin:// 地址:
bob-plugin://<插件 identifier>/<路径>- 路径以
$sandbox/开头时指向插件沙盒目录,与 $file 的约定一致 - 其余路径指向插件安装目录,相对于插件根目录

这个地址只在图片位置有意义,放在链接位置点击不会有任何动作。插件未安装、文件不存在或路径越出插件目录时,图片位置会显示 alt 文本。
兼容说明
toParagraphs 仍然可用,但已废弃
toParagraphs 继续可用,Bob 会把它归到 content 的两种格式里:
- 丢掉空字符串元素后只有一个元素,按
format: plain整篇原样显示 - 多个元素,用换行连起来按
format: lines处理
新插件请直接使用 content。
多段译文的显示方式变了
这是对已有插件影响最大的一条。此前 toParagraphs 有多个元素时,Bob 在元素之间插入空行;1.21.0 起改为按原文的换行结构拼回,原文是单换行的地方译文也是单换行。已发布的插件不改代码,显示效果也会跟着变,这是有意为之:译文的换行结构应该跟原文一致,而不是一律空一行。
query.text 保留原文的换行结构
与上一条配套,Bob 的文本预处理现在保留原文的换行结构:单换行保持单换行,空行保持空行,连续多个空行压成一个。此前所有换行都会被处理成空行。如果插件对 query.text 按 \n\n 拆段,需要留意这个变化。
同时兼容新老版本 Bob
content 和 toParagraphs 可以同时传:1.21.0 及以上版本以 content 为准,更早的版本不认识 content,会读取 toParagraphs。
query.onCompletion({
result: {
content: { format: "markdown", text: markdownText },
toParagraphs: [markdownText]
}
});如果不准备兼容老版本,请将 info.json 的 minBobVersion 提升到 1.21.0,appcast.json 的 minBobVersion 也需同步修改。

