郵遞區號站的英文版是怎麼蓋的:官方對照檔、組合引擎與「沒有標準答案」的驗證

台灣郵遞區號查詢 上線後,站上累積了全台 368 個行政區、390 多頁的查詢內容。但後台搜尋字詞裡一直有一群查不到東西的人:他們要的不是 3+3 郵遞區號,而是英文地址。海外網購填收件地址、申請國外文件、報稅表格,都會遇到「請用英文填寫地址」這一關。
中華郵政其實有官方的「中文地址英譯」服務,但網頁查詢有圖形驗證碼,Web Service 版本則需要書面申請才能介接。也就是說,一般人每查一次就要打一次驗證碼,工具站也沒辦法直接幫使用者代查。這次的「英文版」功能地址中翻英,要補的就是這個縫隙。
這篇筆記記錄整條建置路線:資料從哪來、怎麼組、怎麼驗證,以及最後怎麼讓站內 390 多頁既有內容幫新功能導流。
原則先講清楚:地址翻譯不能用猜的
看到「中翻英」三個字,直覺的做法是找一套拼音函式庫把路名音譯出來。但台灣的路名英譯不是純拼音問題:
- 「羅斯福路」是 Roosevelt Rd.,不是 Luosifu Rd.,外來語路名要還原成原文。
- 「中正區」官方寫 Zhongzheng Dist.,但歷史上通用拼音、威妥瑪拼法混用,同一條路在不同來源可能有三種寫法。
- 郵差實際投遞看的是中華郵政系統裡的那一份譯名,寫得「像英文」不等於寄得到。
所以這個功能的第一原則是:譯名一律以中華郵政官方對照檔為準,音譯只是最後的 fallback。中華郵政公開了兩份關鍵資料:
| 資料 | 格式 | 規模 | 用途 |
|---|---|---|---|
| 路街中英對照檔 | Big5 CSV | 30,030 筆 | 路名 → 官方英譯(含 Roosevelt Rd. 這類特殊譯名) |
| 縣市鄉鎮中英對照檔 | XML | 全台縣市+行政區 | 縣市、行政區官方英譯 |
資料層:build 階段把 3 萬筆譯名 join 進站內
站本身是預渲染的靜態站,沒有後端。所有資料處理都放在 build-time 的資料管線,跑完產出靜態 JSON,線上零查詢成本。這次英譯資料的處理分三步:
1. 同步腳本抓官方最新版。 sync-roadname.ts 直接解析中華郵政下載頁,抓最新的路街對照 CSV(Big5 要先轉碼)與縣市鄉鎮 XML,並把版本與 sha256 記進 manifest,之後官方改版時,diff 一眼就看得出來源變了什麼。
2. 與郵遞區號資料 join,按行政區分片。 站上原本就有全台路名資料(郵遞區號查詢用),build 時把它跟官方英譯對照檔 join,產出每個行政區一份的 road-en/{縣市}/{行政區}.json,共 368 片。使用者查「大安區」的地址只會載入大安區那一片,並訂了 size budget:每片 gzip 後不得超過 10KB,超過就 build fail,避免資料悄悄長胖拖慢頁面。
3. 三層優先序決定每個路名的譯名。 合併時的規則是:
官方對照檔 > 人工修訂 patch CSV > pinyin-pro 音譯 fallback實際跑出來,站上 3 萬多個路名有 98% 直接命中官方對照檔;剩下 374 個查不到的(多半是對照檔改版後才出現的新路名)用拼音補足,但輸出時會標記 machine,介面上明確註記「非官方譯名」。寧可承認這筆是機器翻的,也不要讓使用者以為每筆都有官方背書。之後發現重要路名翻錯,補一行 patch CSV 重 build 就修正。
組合引擎:官方寫法其實是一套可規則化的文法
譯名解決了,第二個問題是組合。中華郵政的英文地址寫法有明確規範:順序跟中文完全相反(由小單位到大單位)、每個單位有固定縮寫、最後接 3+3 郵遞區號。例如:
台北市中正區重慶南路一段122號3樓
→ 3F., No. 122, Sec. 1, Chongqing S. Rd., Zhongzheng Dist., Taipei City 100407這段邏輯收在一個 183 行的純函式模組裡(runtime 與 build script 共用),規則本身不難,難的是台灣地址的「魔王格式」都要接得住:
- 巷弄:
羅斯福路四段119巷7弄→Aly. 7, Ln. 119, Sec. 4, Roosevelt Rd.,巷弄要從路名字串尾端拆出來,但「新立巷」這種文字巷名不能拆,整串是路名。 - 號之與樓之:
7之2號→No. 7-2;5樓之1→5F.-1;地下1樓→B1F.。 - 中文數字樓層:
五樓、十二樓也要吃得下來(實務上一堆人這樣打)。 - 方位詞:
建國北路→Jianguo N. Rd.,南北東西縮寫成 N./S./E./W.。
把這些規則寫成純函式的好處是可以被單元測試釘死:每個魔王格式一條測項,之後改 parser 不會默默弄壞某個角落。
沒有標準答案可以對,怎麼驗證?
這是整個功能最有趣的工程問題。3+3 郵遞區號那邊我做過 SOAP 逐筆比對(50 個地址跟官方系統對答案),但「中文地址英譯」官方網頁有驗證碼、Web Service 要書面申請,根本無法自動化逐筆比對。不過不能對答案,不代表不能驗證,最後設計了三層:
第一層:譯名層,來源即答案。 路名與行政區譯名直接取自官方對照檔,這一層跟官方「必然一致」,不需要比對,需要盯的只有那 374 筆標記為機器音譯的 fallback。
第二層:格式層,拿官方自己的輸出當樣本。 中華郵政英文版官網公布了各地郵局的英文局址,這就是官方英譯系統的實際輸出。挑了 10 筆台北市局址,把中文地址丟進站上翻譯,逐欄比對順序、縮寫、外來語譯名、3+3 郵遞區號,10 筆全數一致。比對過程還發現一筆官網寫 Zhongjheng(通用拼音殘留),對照檔上的標準寫法其實是 Zhongzheng,可見官方資料自己也有歷史包袱。
第三層:回歸層,共用 parser 一起考。 英譯工具跟郵遞區號查詢共用同一個地址 parser,所以原本的 SOAP 50 地址比對照跑,0 筆不一致,確保新功能沒有弄壞舊功能。
這層驗證還有意外收穫:比對局址時發現 新生北路一段7之2號 翻出來路名怪怪的:parser 把「7之」黏在路名尾巴,導致查無譯名、郵遞區號也查不到。修好「N之M號」的主號/副號拆解後,郵遞區號查詢本身也跟著受惠。最終全套回歸:unit 177 passed、E2E 6 passed。
讓 390 多頁舊內容幫新功能導流
功能做完只是一半,另一半是入口。工具站的老問題是新功能上了沒人知道,這次的做法是把它接進站內流量最大的路徑:
- 查詢結果卡直接接力:使用者在任何一頁查完郵遞區號,結果卡多一條「把這個地址中翻英」,帶著原始輸入
?q=跳到工具頁自動翻譯。首頁、22 個縣市頁、368 個行政區頁的查詢結果全部生效,等於 390 多頁內容都成了英譯工具的入口。 - 預填的小地雷:工具頁讀
?q=自動翻譯這件事只能在onMounted(瀏覽器端)做。因為站是 SSG 預渲染,build 出來的 HTML 沒有 query string,如果在 setup 階段就讀取,預渲染跟瀏覽器端的畫面會對不起來(hydration mismatch)。這條規則跟本站的顏文字 Logo 動畫踩過的雷一模一樣:任何非決定性的東西都留到 mounted 之後。 - 教學文與工具互相餵:同步上了一篇〈地址中翻英怎麼寫〉教學,文章解釋規則、工具負責動手,彼此內鏈。
小結
回頭看,「做英文版」聽起來像翻譯工作,實際上是一條完整的資料工程路線:
- 譯名不用猜的:官方對照檔為主、人工 patch 次之、音譯墊底且明確標示。
- 處理全部前移到 build-time:線上只讀分片 JSON,還能用 size budget 守住效能。
- 沒有標準答案就設計代理指標:來源一致性、官方輸出樣本、共用邏輯回歸,三層加起來的信心足夠上線。
- 新功能要接上舊流量:查詢結果卡一條內鏈,勝過任何「新功能上線」公告。
工具在這裡:地址中翻英(中文地址轉英文),免驗證碼、免申請,翻完一鍵複製。下一步打算把村里層級的譯名也補上(官方慣例是可省略,但有些文件就是要求填好填滿),到時候再回來更新這篇。