模組五|重構期的品質與節奏(Day 22–26)
我在一個團隊裡,把一套後台管理系統從 Vue 2 升級到 Vue 3:舊系統當時還在線上服務使用者,新系統是我們按業務功能一塊一塊搬出來的那一版。兩套都要支援多國語言。
舊系統有四份語言檔。我把它們的行數加起來:
15,606 行。
四個純物件字面量的檔案(本質上就是一份「代號對文案」的清單,只是寫成 .js 程式碼),一萬五千行。而每新增一句文案,你要手動同步四個檔案。
新系統的語系從 4 個變成 11 個,但維護成本下降了。今天講這件事怎麼做到的,以及一個我們沒修掉、反而變嚴重的問題。
先講翻譯是怎麼進到那四個檔案裡的。
專案根目錄有一支 322 行的腳本,它做的事是這樣:
.js 檔改成 .mjs
然後把那個 Excel 寄給翻譯人員,等他們填完寄回來,再手動貼回程式碼。
我第一次讀懂這支腳本的時候的感覺是:這不是工程,這是儀式。
每一步都有它的理由:因為語言檔是 .js 而不是資料格式,所以要執行才拿得到內容;因為要執行,所以要轉成模組格式;因為翻譯人員不會用 git,所以要 Excel。
每一個「因為」都合理,加起來就變成一條沒有人想維護的流水線。
而且它有個致命的性質:它是單向的。你可以把程式碼變成 Excel,但 Excel 回來之後,要靠人貼回去。貼漏一個,那個語系就永遠少一句。
新系統的翻譯腳本大約 670 行,串接翻譯服務。但真正讓它可用的不是「會自動翻譯」,是這三件事:
腳本靠一個參數決定這次要處理哪些內容:
--mode=missing # 只補「有中文、但某個語系還沒有」的
--mode=bootstrap # 全新語系,整包翻一次
--mode=changed # 只處理中文有變動的
第三個最重要。因為日常開發的情況幾乎都是「改了幾句中文」,而不是「新增一個語系」。
如果只有全量模式,那每次都要重翻一萬多句:慢、貴,而且會蓋掉人工修過的翻譯。
任何一種模式後面都可以再加一個參數,讓腳本只印出它打算做什麼、不真的動到檔案:
--mode=changed --dry-run
先看會動到哪些 key(key 就是每一句文案在程式裡的代號,程式碼引用的是代號,實際顯示哪個字由語系檔決定),確認沒問題再真的跑。
這件事看起來很基本,但它決定了大家敢不敢用。一個會直接改動十一個語系檔、又不能預覽的腳本,沒有人會在下班前跑它。

這是我覺得最關鍵的設計。專案裡有一份記錄「哪些 key 在哪個語系翻到什麼程度」的狀態檔。
有了它,腳本才知道:
沒有狀態,你就只能全量重跑。而全量重跑會蓋掉人工修正,那正是大家不敢用自動化的原因。
舊流程和新流程的差別,不在「有沒有用 AI 翻譯」,在於:
舊的是「人執行一套流程」,新的是「腳本執行,人做決定」。
而讓這個轉變成立的是那三樣東西:可以只做增量、可以預演、有狀態可以記憶。
缺任何一個,自動化都會退回人工,因為不敢用。
我覺得這可以推廣成一條判準:
會被人工重複執行的流程,遲早會出錯。而把它自動化的前提,是它必須「可增量、可預演、有記憶」。
順帶一提,語系從 4 個變成 11 個之後,人反而不是流程中的瓶頸了。這才是真正的收益:不是省下打字時間,是新增一個語系從「一個專案」變成「一個指令」。
講完成功的部分,講失敗的。
舊系統的 i18n key(i18n 是 internationalization「國際化」的縮寫,把中間 18 個字母省略成數字,講的就是「同一套介面切換多國語言」這件事)有一個很嚴重的反模式:把整句中文的拼音當成 key。
大概是這種形狀(我改寫成中性的例子):
{
zuiGaoLeiJiJinEBuNengXiaoYuZuiDiLeiJiJinE: '最高累計金額不能小於最低累計金額',
quSheZhiQiShuHouSanQiZuiGaoJinEZuoWeiJiZhun: '取設定期數後三期最高金額作為基準',
}
我數了舊系統:超過 30 個字元的 key 有 123 個。
這種命名的問題很明顯:
zuiGao... 是哪一頁的?不知道正確的做法應該是描述「位置與用途」,例如 orderSearch.validation.amountRange。

457 個。
不是 123。是 457。
而且新系統的語系檔總行數比舊系統的單一語系檔還少,也就是說,這個反模式的密度變高了三倍以上。
我看到這個數字的時候,比看到任何舊系統的問題都更不舒服。因為這代表:
我們把舊系統最糟糕的一個習慣,原封不動地搬進了新系統,而且做得更徹底。
我事後回想,原因大概有三個,而且每一個都很好理解:
一、搬遷的時候,key 是跟著頁面一起搬的。 你在重寫一個頁面,順手把它的 i18n key 複製過來。這是阻力最小的路徑。重新命名代表你要決定新名字、要確認沒有衝突、要處理其他頁面也在用的情況。
二、沒有人負責 i18n 的命名規範。 API 命名有人管(Day 15 那份對照表,一份明文規定每支 API 該叫什麼名字的文件),元件命名有人管,但 i18n key 沒有。它被當成「文案的附屬品」,而不是程式碼。
三、它不會痛。 這是最關鍵的。爛的 key 不會導致 bug、不會讓建置失敗、不會在 review 時被抓到,它只會在三年後讓某個人多花二十分鐘。
而 Day 20 講過:能落地的規則,特徵是「違反時一眼看得出來」。 i18n key 剛好相反。
一、自動翻譯的品質需要人工複查。 機器翻譯在後台系統的專業術語上會出錯,而錯的翻譯不會有人回報,因為使用那個語系的人,通常不會來跟你說「這個詞怪怪的」。我們的做法是關鍵頁面人工確認,其餘接受。
二、狀態檔本身變成一個要維護的東西。 它如果跟實際檔案不同步(例如有人手動改了語系檔沒跑腳本),腳本的判斷就會錯。
三、那 457 個 key 現在更難改了。 因為它們散在 11 個語系裡,改一個 key 要同步 11 個檔案。我們把一個 4 倍的問題,變成了 11 倍。
明天 Day 23 講一個看起來很難看的數字:重構期間,四成的改動是在修 bug。我會論證為什麼這不是品質問題——以及什麼情況下你才真的該擔心。