9 种语言
一本中文书翻完英文版只是开始。两天后我打开微信群,Qi 问了一句:
"剩下 9 种语言怎么办?"
按之前 Mustafa 那本书的清单:西、法、德、日、葡、俄、韩、阿,再加英文,正好 9 个。任务很清晰,工具也很成熟——同一份中文源、同一个 Master Translator、同一套 ContextWeave 流程。理论上把英文版那套跑 8 遍就行了。
我想得太简单了。
接下来三天,我把所有「想当然」的直觉一个一个推翻。最后跑完 225 个 chunk、9 个 Word 文件、4,464 等效页的时候,最大的收获不是产物本身,而是终于把一件事掰清楚了:翻译有「距离」。距离决定路径,路径决定质量。
直觉错觉:从中文直翻
第一站是西班牙语。
最自然的想法是:源是中文,目标是西班牙语,那就 ZH→ES 直翻。术语表里准备 125 个中→西的对照,跑 25 个 chunk,看起来天经地义。
跑完一看,所有 chunk 都标了 ✅,但有个数字让我皱眉:
chunk_002: 14,793 → 59,571 chars (膨胀比 4.0x)
chunk_017: 41,201 → 59,325 chars (膨胀比 1.4x ?)
中文 14K 字符,西班牙语 60K?这不对。同语系翻译(英→西、葡→西)的正常膨胀比是 1.1-1.2x,1.4x 已经偏高,4x 是异常信号。
打开译文细看——句子是对的,意思也准,但句式有股「翻译腔」:从句套从句、定语堆砌、动词拖在句尾。明显是 LLM 在用中文的句子骨架硬塞西班牙语词。
更糟的是术语。书里反复出现的「低空经济(Low-Altitude Economy, LAE)」,西班牙语应该用本地缩写 EBA(Economía de Baja Altitud),或者保留 LAE 不翻。LLM 选了第三条路:直接照搬「LAE」,但译文其他地方又用了不同的写法。一致性丢了。
我决定做个对照实验。
转折一:双跑对照
同一份中文源,跑两条路径:
- ZH→ES:直接从中文翻成西班牙语
- EN→ES:先用已有的英文版作为源,再翻成西班牙语(二跳)
抽 chunk_002(短)、chunk_010(中)、chunk_017(长,技术密集)三块对比:
| 维度 | ZH→ES(直翻) | EN→ES(二跳) |
|---|---|---|
| 术语本地化 | "LAE" 照搬 | "EBA" 西语本地缩写 |
| "操作系统"译法 | "Sistema Operativo" 全写 | "SO" 西语标准缩写 |
| 句式 | 中文句骨架,从句堆砌 | 紧凑、地道、动词在中段 |
| 膨胀比 | 14K → 59K(4.0x,异常) | 57K → 59K(1.04x,正常) |
| 技术准确性 | 准确 | 准确 |
EN→ES 在没有任何中国特色信息的情况下(英文版已经把所有中国政策术语译成英文),反而句式更地道、缩写更本地、膨胀比更合理。
为什么?
因为 LLM 的训练数据是不对称的。EN→ES 的平行语料是百亿级(西班牙语世界的大部分英译都通过 EN 中转),而 ZH→ES 的训练数据小好几个数量级。同一个 LLM,在两个方向上的「肌肉记忆」根本不在一个量级。
结论清晰:欧洲语系(西、法、德、葡、俄、阿)走 EN→target 二跳。日韩例外——CJK 语系内部距离近,且日韩对中文的政策术语有原生处理能力,走 ZH→target 直翻更优。
这是第一次把「翻译距离」这件事量化出来。
转折二:术语表的零命中
决定走二跳后,我以为只是改个源文件就行——把英文 chunks 喂给翻译脚本,目标语言改成西班牙语。
跑完一看,质量确实好了,但又有个细节让我警觉:日志里术语注入的提示,从单语言时的「📖 35 terms relevant to this chunk」变成了「📖 0 terms relevant」。
零命中。
我用的还是 terminology_zh_es.json——term 字段是中文:「低空经济」、「无人机」、「空域管理」。但源文本现在是英文:「Low-Altitude Economy」、「drone」、「airspace management」。中文 term 在英文文本里搜不到,整个术语表对二跳翻译完全失效。
这意味着前面跑出来的「质量更好」的 EN→ES 译文,其实是 LLM 完全自由发挥的结果——它根本没用到我精心整理的 125 个术语对照。
修复方案是术语表也要「二跳」——生成一个 terminology_en_es.json:
- term 字段:英文(匹配源文本)
- translation 字段:西班牙语(注入到 prompt 里)
我在 convert_terminology.py 加了个 --pivot 参数,一条命令同时输出两个版本:
python scripts/convert_terminology.py terminology_zh_en.json --target Spanish --pivot
# 输出:
# terminology_zh_es.json (中→西,CJK 路径用)
# terminology_en_es.json (英→西 pivot,二跳路径用)
带 pivot 术语表重跑 EN→ES,日志里的数字回来了:「📖 33 terms relevant」。这一轮的译文,在自由发挥的地道句式之上,又多了术语一致性这一层「钉子」。
教训:术语表的 term 字段必须匹配源文本的语言。二跳翻译时源是中转语言,术语表也要跟着「二跳」,否则术语注入沉默失效。
转折三:unknown 不是错误
接下来本以为只是把这套流程跑 8 遍。德语顺利、日语顺利、葡语顺利,每种语言 25 个 chunk,10 路并发,~15 分钟跑完。
到俄语开始出问题。
跑完一看,24/25 完美,chunk_019 显示 finish_reason: length——撞到 max_tokens 上限了。原来设的 16K,俄语膨胀比偏高(Cyrillic 字符 + 长形容词链),57K 英文膨胀到 67K 字符,超出了 ~56K chars 的 token 上限被截断。
调到 24K,重跑。OK。
接着是阿拉伯语。这次更怪:4 个 chunk 标了 finish_reason: unknown——既不是 stop 也不是 length。打开译文看,最后一段在某个词中间戛然而止,比如「ال」(阿语定冠词)后面什么都没有。
不是 token 上限。是 streaming 流被切断——CopilotX 后端代理对超长单次响应(~50K chars)有不可预测的超时。
降并发到 4 重跑,3 个修好了。剩下一个 chunk_003 死活补不齐——三次重试,每次都在「EH216-S ال」处截断。三方都同意这是后端的代理超时,不是脚本能解决的。
最后用了一个最朴素的办法:手动隔离尾部 1.8K 字符的英文段落,单独翻一次(这次很短,几十秒搞定,绝不会触发流断开),然后把这一小段拼回到原 chunk 的末尾。
# 隔离 chunk_003 末尾的两段英文
python -c "..." # 提取尾部到 chunk_003_tail.md
# 单独翻这 1.8K
python translate.py --input chunk_003_tail.md --output chunk_003_tail_ar.md \
--terminology terminology_en_ar.json
# 18.6 秒,1,824 → 1,691 chars,finish_reason: stop ✅
# 拼回去:从 "تبني منظومة المعايير التقنية" 开始截断旧译文,接上新尾部
python -c "..."
教训:当下游 API 不稳定时,缩小请求规模比加重试更有效。把一个 50K 的请求拆成 48K + 2K,超时概率会断崖式下降。
9 种语言,9 个膨胀比
最后所有 9 种语言全部交付,文件大小和膨胀比也讲了一个有意思的故事:
| 语种 | 路径 | MD 大小 | 膨胀比 | 备注 |
|---|---|---|---|---|
| English | ZH→EN | 1.10 MB | 3.6x | 中→英基线 |
| Spanish | EN→ES | 1.34 MB | 1.18x | 同语系 |
| French | EN→FR | 1.42 MB | 1.24x | 同语系,最长 |
| German | EN→DE | 1.28 MB | 1.12x | 同语系 |
| Portuguese | EN→PT | 1.29 MB | 1.13x | 同语系 |
| Russian | EN→RU | 2.33 MB | 1.10x | Cyrillic UTF-8 字节翻倍 |
| Korean | ZH→KO | 1.11 MB | 1.50x* | CJK,hangul 含敬语 |
| Japanese | ZH→JA | 1.17 MB | 1.36x* | CJK,汉字+假名混合 |
| Arabic | EN→AR | 1.71 MB | 0.82x | 阿语紧凑+RTL |
*相对中文源;其他语种相对英文中转。
跑完所有 9 种后再回头看,max_tokens 也成了一张「语言距离表」:欧洲语系 24K 够用,CJK 24K 偶尔擦边,俄/韩/阿 必须 32K。
单语言 vs 多语言的方法论分水岭
这次最大的认知转变,是发现单语言翻译和多语言翻译根本不是同一个问题。
单语言翻译的关键问题是「质量」:术语对不对、句式自然不自然、专业准不准。所有精力放在打磨术语表、调温度、加约束。
多语言翻译的关键问题是「路径选择」:
- 哪两个语言对之间的 LLM 训练数据足够丰富?
- 哪个中转语言能把术语「钉死」?
- 哪种语言会触发膨胀、哪种会触发流断开?
- 哪些情况能用术语表,哪些情况术语表会沉默失效?
这些问题在单语言场景里根本不存在。但当语言数量上 5 个、10 个、20 个时,路径选择的权重远大于翻译质量本身。
更进一步的洞察是:LLM 训练数据的不对称性是真实存在的、可量化的、能被工程化利用的。
这件事不只在翻译里成立。embedding 模型、retrieval 系统、模型路由——但凡牵涉「跨语言」或「跨模态」的环节,绕一道熟路 经常比直冲目的地更稳。pivot embedding、pivot retrieval、pivot routing,本质上都是同一个思路。
工具的进化也是分水岭
工具这边今天也长大了一截。三天里 Master Translator 加了 5 个能力:
--pivot术语表生成(一条命令出两个文件)--skip-existing模式(不依赖 state,看输出文件就跳过)max_tokens默认 16K → 24K(适配欧语膨胀)- 语言后缀过滤修复(之前会把
_en文件当新源翻一遍,输入加倍) load_config()统一到 utils.py(去重 + 修一个#误截 bug)
小龙虾的 Skill Review 从 8.5 升到 9.0。其中 4 个能力都是这次 9 语种翻译里踩出来的需求——不是预先设计的,是「这条路走不通」时被逼出来的。
每一本书都让工具长大一点。这一次不是一本书,是 9 个版本——所以工具的进化也比以往任何一次都密集。
回头再看那个微信群里的问题:「剩下 9 种语言怎么办?」
3 天前我以为答案是「跑 8 遍就行了」。3 天后答案变成了:
先决定路径,再谈翻译。
中文穿过英文,再走向阿拉伯语,看着像绕了远路,其实是抄了近道。

