事情是這樣的。
我們有個 fork 自開源專案的 codebase,長期在上面開發客製化功能。每隔一段時間就要把 upstream 的新版本 merge 進來,保持跟主線的同步。流程做了很多次,已經算是熟門熟路。
然後有一次,merge 完、build 過、也部署了。結果測試的時候,某個新功能的介面文字全是英文。
「奇怪,我明明有加翻譯的⋯⋯」
你知道那種感覺。你確定你做了,但結果就是不對。
什麼是 i18n Drift?
Open source 專案通常有個 locales/ 或 i18n/ 資料夾,裡面放各語言的翻譯 JSON(或 yaml)。每次 upstream 新增功能,他們會先在英文(en.json)加 key,其他語言可能:
- 同步跟上了(理想情況)
- 留著等社群貢獻(常態)
- 根本沒人管(現實)
如果你的 fork 有自己維護的語言包(比如我們有維護繁體中文),upstream merge 之後,那些 upstream 新增的 key 就會在你的翻譯裡憑空消失——因為 upstream 的 zh-TW.json 根本沒有那些 key,而你 merge 進來的,是 upstream 的版本,不是你們自己維護的版本。
更精確地說:你的翻譯被 upstream 的不完整版本覆蓋了。
那次的事故還原
Merge upstream 的流程大概是:
1 | git fetch upstream |
解完衝突、跑 build、CI 過了就往下走。
問題是,衝突通常發生在業務邏輯的地方——Controller、Service、Migration 等等。翻譯檔因為格式單純(一個大 JSON),merge 工具幾乎都能自動解完,不會跳出來要你處理。
所以我完全沒注意到翻譯被動過。
直到上線才發現,某幾個新 UI 元件的文字回到英文。查了一下,發現 upstream 在這個版本新增了 30 幾個新的 translation key,而他們的 zh-CN.json 裡這些 key 根本不存在——git merge 就直接拿 upstream 的空殼覆蓋掉了。
翻譯同步的正確方式
這件事讓我確立了一個原則:merge upstream 時,翻譯檔要手動比對,不能只靠 git 自動 merge。
實際做法分兩步:
Step 1:找出新增的 keys
1 | # 看 upstream 這個版本新增了哪些 en.json 的 key |
或者更直接,merge 完之後看 en.json 的 diff:
1 | git diff HEAD~1 HEAD -- locales/en.json |
找出所有新增的 key 之後,去你們維護的語言包裡一個一個補上翻譯。
Step 2:驗證沒有遺漏
可以寫個簡單的腳本比對 en.json 和目標語言 json 之間的 key 差異:
1 | import json |
這個腳本跑一下,缺什麼一目瞭然。
(如果你的翻譯是巢狀結構,腳本要稍微複雜一點,但概念一樣。)
完整的 Upstream Merge Checklist
踩完那次坑之後,我把 upstream merge 的流程整理成一個 checklist。現在每次 merge 都照著跑,再也沒有翻譯突然消失的問題。
flowchart TD
A[git fetch upstream] --> B[建立 merge 分支]
B --> C[git merge upstream/main]
C --> D[解衝突]
D --> E[同步翻譯檔
補缺少的 keys]
E --> F[更新 .env 設定
有新的環境變數嗎?]
F --> G[dotnet build / npm build]
G --> H{Build OK?}
H -- No --> D
H -- Yes --> I[跑 swagger codegen / orval]
I --> J[前端 pnpm build]
J --> K{OK?}
K -- No --> D
K -- Yes --> L[commit & PR]
步驟解釋:
解衝突 — git 能自動 merge 的讓它跑,有衝突的手動解。重點是不要盲目選「ours」或「theirs」,要理解每個衝突的意義。
同步翻譯 — 這一步最容易被跳過,也是最容易踩坑的地方。用比對腳本驗證,不要靠感覺。
更新 .env — Upstream 新功能有時會帶新的環境變數。看一下 .env.example 的 diff,確認沒有漏掉必要設定。
Build — 這步很多人會做,但重點是要在所有程式碼修改完之後跑,包括翻譯和設定。中途 build 通不代表最後會通。
Swagger / Orval — 如果你的專案前後端是分離的,backend API spec 更新後要重新 generate 前端的 client code,否則前端拿到的型別定義會不對。
前端 Build — 前後端都跑完才是真的確認沒問題。
為什麼這步這麼容易被忽略?
我覺得問題在於「翻譯」這件事在技術人眼裡通常是低優先度的。它不是功能、不是邏輯、不是效能,它只是文字。
加上 git merge 通常能自動處理翻譯檔(因為它是純文字),不會跳出衝突提醒你,所以很自然就被跳過了。
但對使用者來說,介面文字回到英文這件事,跟「功能壞掉」沒什麼兩樣。
這件事教會我:checklist 的存在不是為了聰明人,而是為了人在疲倦或趕時間的時候,還能有最後一道防線。
最後
如果你也在維護一個 fork 並且需要定期 sync upstream,建議把「翻譯同步」這一步明確寫進你的 merge 流程裡。不用複雜,一個簡單的比對腳本 + 加進 merge checklist,就夠避掉大部分的問題。
如果這篇能幫你避開這個踩坑,那我的血淚就沒白流了 😂