一段英文交给模型,几秒钟就能得到中文。可把一本几百页的技术手册做成双语 PDF,问题往往出在翻译之后。
原文写着:
<p>Run<code>npm install</code>, then read<ahref="/guide">the guide</a>.</p>理想的结果是:“运行npm install,然后阅读指南。”命令保持原样,“指南”仍然可以点击。
如果把整个 HTML 节点直接交给模型,译文可能改掉命令、丢掉链接,或者生成一段看起来正确、却无法放回原节点的文本。PDF 还要面对双栏阅读顺序、表格、坐标和分页。模型翻对了,不代表双语文档就做对了。
图 1:从文档解析到双语写回的主要环节。
一、为什么“提取文字 → 翻译 → 拼回去”容易出错?
HTML 的一个段落里可能混有文字、代码、链接和公式。PDF 中看起来连续的一段话,底层可能是几十个独立绘制的文本片段。EPUB 的一章也可能分布在多个 XHTML 文件中。
直接按字符或固定 Token 数切分,容易遇到四种情况:
- **切错顺序:**PDF 双栏的左右两列被交替拼接,页眉或脚注混进正文。
- **丢失结构:**链接、代码或公式与普通文字一起翻译,写回时无法恢复原节点。
- **找错位置:**只凭原文字符串定位;相同句子在文档里出现多次,译文可能放错段落。
- **撑坏版面:**译文长度改变,固定文本框溢出,表格行高和分页随之变化。
所以第一步不是决定“每段多少 Token”,而是先识别:文档里哪些内容属于同一个语义块,哪些内容虽然出现在块内,却不能让模型改动。
二、先给原文块一个稳定身份
标题、段落、列表项、表格单元格、图注,可以分别成为语义块。每个块记录自己的blockId和来源位置,而不是仅靠它在数组里的序号。
一个简化的中间结构可以长这样:
{"blockId":"p-0286-04","sourceAnchor":{"page":286,"bbox":[72,180,510,232]},"sourceText":"Run npm install, then read the guide.","inlineTokens":["CODE_1","LINK_1"],"status":"segmented"}HTML 可以记录 DOM 路径,PDF 可以记录页码、坐标和阅读顺序,EPUB 可以记录 spine 与元素路径。这一层可称为Document IR(文档中间表示):不同格式的来源信息各自保留,翻译调度则统一按块管理。
这里还要分清两个概念:**语义块是最终写回和检查的单位;翻译单元是一次送给模型的输入。**为了让模型理解上下文,可以把相邻的几个块放在一次请求中;返回后仍要按blockId拆回原块。
三、代码和链接先保护,再翻译
回到开头的例子。如果希望链接文字“the guide”可以翻译,但链接地址和 HTML 节点不被改动,可以把输入整理为:
Run ⟪PH_001:CODE⟫, then read ⟪PH_002:LINK_START⟫the guide⟪PH_003:LINK_END⟫.npm install的原始代码节点,以及链接的href,由系统单独保存。模型只处理可翻译的文字,并保留占位符。
写回前至少要检查:占位符有没有缺失、重复或被改名;链接的开始和结束标记是否仍然成对。检查通过,再恢复代码与链接节点。公式、URL、产品型号、{name}和%s这类变量,也可以按各自规则保护。
这一步并不能保证译文质量,但能防止模型在翻译时顺手改掉文档结构。
四、翻译完成后,怎么找到原来的位置?
假设第 286 页有两个几乎一样的提示段落。只用原文内容搜索,两个位置都可能匹配。即使保存了 PDF 坐标,重新解析或排版后,坐标也可能发生变化。
比较稳妥的做法是把三个信号放在一起核对:
- **结构位置:**原来属于哪一页、哪个节点或 EPUB 文件。
- **邻居特征:**前后分别是什么块,阅读顺序是否一致。
- **内容校验:**原文摘要或关键特征是否仍然匹配。
先按原始位置查找;对不上时,只在附近范围内重定位。如果仍有多个候选位置,就标记为待复核,而不是选一个“最像的”自动写入。
图 2:blockId关联原文和译文,多个定位信号确认最终写回位置。
最隐蔽的错误不是没翻出来,而是译文读起来没问题,却出现在错误的原文下面。
五、写回 PDF,位置正确还不够
HTML 可以在原文段落后插入译文节点;EPUB 可以修改对应 XHTML,再检查章节导航和内部链接。PDF 更难:译文通常不能直接塞回原来的文本框。
例如原文只有两行,译文变成四行。如果仍按原坐标绘制,可能压住下一个段落。表格单元格里的译文变长,还会影响行高、跨页和图注位置。
因此,双语 PDF 通常要在“原文块与译文块保持对应”的前提下重新安排版面,并在生成后检查溢出、阅读顺序和链接。准确写回包含两件事:找对内容位置,以及让新页面仍然可读。
网页实时翻译又是另一种写回场景。页面可能被 React、Vue 等框架重新渲染,插件插入的译文也会触发 DOM 变化监听。因此它还需要节点标记、重复翻译检测和视口调度。两种场景可以共用分块与占位符的思路,但不能直接共用写回实现。
六、翻到第 286 块失败,前面的结果怎么办?
几百页的文档不可能假设一次处理永远成功。接口超时、某个块解析失败、人工审核暂停,都可能打断任务。
如果每个块都保存状态和结果,流程就可以从未完成的块继续:
segmented → translating → translated → approved → rendered这里要区分第 286 页和第 286 块:一页可能包含多个块,真正用于续跑和局部复核的是块级状态。
术语更新也类似。比如一份手册里API Gateway已确认译为“API 网关”,后续块应尽量遵循同一译法。若术语表变更,可以找出命中该术语的块,逐块检查或重译,而不必默认重跑整本书。
七、怎么检查双语成品有没有做好?
只看“翻译是否通顺”还不够。更能暴露系统问题的是下面几类测试:
- 代码与变量:
npm install、{name}、%s是否保持原样? - **链接:**链接文字翻译后,点击是否仍指向原地址?
- **重复段落:**两处相同原文的译文,是否写回各自位置?
- **复杂页面:**双栏、表格、脚注和图注的阅读顺序是否正确?
- **长译文:**文本框、表格和分页有没有溢出或遮挡?
- **中断恢复:**某一块失败后,已确认的块是否可以保留?
占位符完整性、数字与单位异常、疑似漏译、异常长度等,可以先由程序筛查;语义准确性和有歧义的写回位置,仍需要复核。自动检查是质量门槛,不等于质量保证。
最后:模型翻对一句话,只是开始
文档翻译真正难的是模型前后的工程:识别结构、保护内联对象、保持块与位置的关联、适配新版面,以及在失败后继续处理。
翻译 API 可以返回一段不错的中文。但要把几百页的原文变成可阅读、可核对的双语文档,每一段译文还得知道自己从哪里来、该回哪里去。
双语文档最怕的不是某段没有译文,而是译文看着正确,却放错了位置。
如果想进一步了解这些能力在产品里的实际应用,可以体验:
有谷大脑:https://brain.yogu.pro