昨天讓同一份 manifest 產出了中英文兩份手冊,但那只是「出一份英文版」。手冊是會一直改的,真正麻煩的是之後的維護:中文正文改了一段,英文版怎麼知道要跟著改?
今天就來處理翻譯同步。
相信大家第一直覺肯定是就就是每次中文改版,就把整份英文重翻一次。
但是,這樣其實很浪費 token,而且更大的問題是,已經審過的譯文會被丟掉。英文版可能已經請母語人士看過、修過用詞,整份重翻就要全部重審一次。
用過 AI Agent 的人,應該馬上會想到另一個做法:把中文的 git diff 跟英文正文一起交給 agent,請它「只翻有改的地方,其他不要動」。
大部分時候,這樣確實會成功。而且在「找出英文對應的是哪一段」這件事上,agent 比今天要做的工具還強。它看得懂意思,就算英文把一段拆成兩段,也對得起來。
但只靠 agent,還少了三樣東西:
所以今天要做的,不是取代 agent,而是替它補上這三樣東西:翻譯交給 agent (或人),工具負責記住基準、列出要翻的段落,並在翻完之後驗證其他段落沒有被動到。
做法是把正文用空行切成段落,每段算一個 hash:
const text = buf.join('\n')
const hash = crypto.createHash('sha256').update(text).digest('hex').slice(0, 8)
算 hash 之前,會先統一換行符號、去掉行尾空白,避免只是排版不同,就被當成改了內容。
英文正文旁邊放一份對照紀錄 docs/en/50-camera-add.sync.json,記下「中文這段的 hash ↔ 英文那段的 hash」:
{
"source": "docs/50-camera-add.md",
"blocks": [
{ "source": "0b98ba91", "target": "78b6b4e5" },
{ "source": "72c25f6e", "target": "357f26f9" },
...
]
}
檢查時,拿現在每段中文的 hash 去紀錄裡找:
重點是用 hash 比對,不用位置。如果用「第幾段」來對,中文在中間插入一段,後面所有段落都會錯位,全部變成「需要翻譯」。用 hash 比對的話,沒改的段落不管移到哪裡都認得出來。
另外,只有 {{screenshot:*}} 的段落不需要翻譯,中英文寫法本來就一樣,所以不會出現在「需要翻譯」的清單裡。
範例專案 auto-manual-gen 的 chore/day23 分支,新增了一個 sync 指令:
npm run sync -- --locale en # 列出每一章的同步狀態
npm run sync -- --locale en --accept camera-add # 記下這一章目前的對照
--accept 負責寫入對照紀錄:第一次使用時,先確認現有的英文是對的,執行一次建立基準;之後每次翻好、審完,再執行一次更新紀錄。
另外,只要有任何一章沒有同步,sync 就會以非 0 的狀態結束,所以可以直接放進 CI,不用靠人記得去檢查。
先對每一章執行 --accept 建立基準,接著在中文版「新增攝影機」做了三個改動:
$ npm run sync -- --locale en
✔ overview 9 段都已同步
✔ live-monitor 11 段都已同步
✔ layout-preset 12 段都已同步
✔ settings 11 段都已同步
✖ camera-add
需要翻譯 第 27 行:新增的攝影機會出現在「攝影機清單」的最下方。
需要翻譯 第 33 行:> 注意:「{{legend.name}}」或「{{legend.source}…
需要確認 原文第 31 行對應的譯文被改過:「{{legend.zone}}」預設為「大門」,「{{legend.enabl…
舊譯文 docs/en/50-camera-add.md:29:**{{legend.zone}}** defaults to **Main G…
舊譯文 docs/en/50-camera-add.md:31:> Note: While **{{legend.name}}** is emp…
幾個觀察:
範例專案的
tools/samples/放了這三個改動的檔案,README 有重現步驟,執行紀錄在tools/logs/day23-sync.txt。
這份清單就是交給 AI Agent 的範圍:只把「需要翻譯」的段落,連同對應的舊譯文、App 的 en.json 一起給它,翻出來的東西範圍小、好審。翻完之後再跑一次 sync,如果 agent 動到了其他段落,會出現在「需要確認」。
英文版補好這兩段、確認沒有動到其他段落,再執行 --accept,狀態就回到全部同步。
hash 只能告訴我們「中文哪一段變了」,但沒辦法告訴我們「英文哪一段是它的翻譯」。這個對應關係,只能在 --accept 那一刻,靠「第 1 段對第 1 段、第 2 段對第 2 段」建立。
所以譯文的段落結構必須跟原文一模一樣,段落數對不上時,--accept 會拒絕記錄。這代表翻譯時不能把一段拆成兩段,也不能把兩段併成一段,而英文的句構偏偏常常需要這樣做。
這個限制對手冊來說還算可以接受:步驟本來就是一步一段,結構很固定。但段落的粒度也因此比較粗,像一整串編號步驟中間沒有空行,就算一段,改其中一個步驟,整串都要重翻。
另一種做法,是在英文正文的每一段前面加上
<!-- src:0b98ba91 -->這樣的註解,直接標明它是哪一段中文的翻譯。對應關係跟內容綁在一起,就不需要一對一,也不會有「被報兩次」的問題。代價是正文變得很雜亂,翻譯的人 (或 AI) 也必須記得保留這些註解。我最後選擇讓正文保持乾淨。
今天處理了多語言手冊的長期維護:
--accept 建立基準,之後每次翻好、審完再更新一次,對照紀錄跟正文一起進版控。多語言的部分就到這裡告一段落,明天開始會開始新的主題~