当中文穿过英文,再走向阿拉伯语

当中文穿过英文,再走向阿拉伯语

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 大小膨胀比备注
EnglishZH→EN1.10 MB3.6x中→英基线
SpanishEN→ES1.34 MB1.18x同语系
FrenchEN→FR1.42 MB1.24x同语系,最长
GermanEN→DE1.28 MB1.12x同语系
PortugueseEN→PT1.29 MB1.13x同语系
RussianEN→RU2.33 MB1.10xCyrillic UTF-8 字节翻倍
KoreanZH→KO1.11 MB1.50x*CJK,hangul 含敬语
JapaneseZH→JA1.17 MB1.36x*CJK,汉字+假名混合
ArabicEN→AR1.71 MB0.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 天后答案变成了:

先决定路径,再谈翻译。

中文穿过英文,再走向阿拉伯语,看着像绕了远路,其实是抄了近道。

Comments