如何翻譯 JSON 語言檔而不破壞鍵值或佔位符
了解 en.json 檔案中哪些值會被翻譯、檔案翻譯工具會改寫什麼(註解、鍵值順序、大型 ID),以及如何用免費腳本和測試檔案檢查佔位符、ICU 複數形式和鍵值。

要翻譯 en.json 這類 JSON 語言檔,請把整個檔案交給看得懂 JSON 的翻譯工具。把原始文字貼進聊天視窗或網頁翻譯器,是風險很高的做法。看得懂 JSON 的翻譯工具會讀取檔案、只翻譯文字值,再把同樣的鍵值寫回去,所以你的應用程式還是能載入這個檔案。複製貼上可能把鍵值也翻掉、改掉引號,或漏掉大括號。以上任何一種都會讓檔案壞掉。
保住鍵值算是簡單的部分。麻煩大多在後頭:{name} 佔位符回來變成 {nom}、ICU 複數形式丟掉 other 分支、網址被當成一般文字送去翻譯,還有翻譯工具對檔案本身做的幾處改動。這些就是本指南要涵蓋的內容。
我們做了一個測試檔案,把上述每一種情況都放進去,並寫了一支免費檢查腳本,用來比對原始檔和譯文。我們也把測試檔案跑過 BookTranslator 所用 JSON 解析與寫入程式碼的本機副本,並用標記代替模型,讓你能確切看到哪些值被送出,以及檔案回來變成什麼樣子。
快速解答:檔案各部位會發生什麼事
| 檔案部位 | 我們測試中的範例 | BookTranslator 的處理方式 | 該檢查什麼 |
|---|---|---|---|
| 鍵值 | "greeting", "error" | 原樣保留 | 無 |
| 純文字值 | "Reading list" | 翻譯 | 語意、螢幕上的長度 |
| 含佔位符的值 | "Welcome back, {name}!", "Saved %s of %d chapters" | 翻譯,佔位符會包含在送出的文字中 | 佔位符名稱與數量(檢查腳本會處理這項) |
| ICU 複數訊息 | "{count, plural, one {# book} other {# books}}" | 以單一字串送出 | other 分支,以及目標語言的複數類別 |
| 行內 HTML | "accept the <b>Terms</b>." | 翻譯;翻譯後會檢查標籤數量 | 標籤名稱與數量(檢查工具會處理) |
| 以文字形式儲存的 URL | "https://example.com/help" | 送出翻譯 | 確認它原樣返回 |
| 不含字母的值 | "2.4.1", "—", "" | 保持原樣 | 無 |
數字、true/false、null | 20, true | 保持原樣 | 極大的 ID(見下文) |
| 字串陣列 | ["Open", "Save", "Export as EPUB"] | 在翻譯檔案中翻譯;在雙語檔案中不翻譯 | 雙語檔案(若你使用) |
| 註解(JSONC) | // Shown on the home screen | 已移除。若值中也包含 //,檔案可能無法載入 | 上傳前移除註解 |
本指南其餘部分說明每一列的來源及因應方式。
步驟 1:上傳前檢查原始檔案
對你打算翻譯的檔案執行檢查工具。它只需要 Python 3,別無其他需求:
python3 check_i18n_json.py en.json
在我們的測試檔案上,它印出:
WARN limits.projectId: 9007199254740993 is above 2^53 and can change in a JavaScript round trip; store IDs as strings
WARN error.network: a flat key with dots and a nested key resolve to the same dot path; tools that address strings by dot path can mix them up
INFO (root): 3 keys look like integers; JavaScript sorts those first and in numeric order when it rewrites the object
INFO source: 17 strings, 2 numbers
這兩個警告和關於類整數鍵的提示,各自對應我們在測試檔案經過翻譯器時看到的變更。若你的檔案完全沒有這些情況,可以直接跳到步驟 3。
檢查工具也會拒絕非嚴格 JSON 的檔案,例如含有註解或尾隨逗號的檔案,因為那是下一個問題。
我們的測試檔案傳回了什麼
測試於 2026 年 11 月 2 日執行,針對 BookTranslator 後端目前的 JSON 處理方式。測試並未呼叫模型,而是將每個會送出翻譯的值替換為 [ZH] 加上原始文字。這讓結果可預測:前面有 [ZH] 的就是有送出,沒有的就是沒有送出。兩份輸出均已發布:翻譯檔案 和 雙語檔案。
翻譯後的檔案
每個鍵都原封不動地回傳,檔案也仍可解析。含有字母的值都已送出,包括 {name}、%s、ICU 訊息、<b> 標籤,以及說明文件的 URL。版本字串 "2.4.1"、破折號和空字串則沒有送出,因為它們不含任何字母。數字和布林值也都保持原樣。
另外還有四件事發生了變化:
- 有一個字串被放進了錯誤的鍵。 在我們的測試中,檔案裡有一個扁平鍵
"error.network",其文字為「Check your connection.」,同時還有一個巢狀的"error": { "network": "Network error" }。翻譯工具會用各層級之間以點分隔的路徑來辨識每個字串,因此這兩個字串擁有相同的位址error.network。在輸出中,兩個鍵都存放了「Network error」的翻譯。「Check your connection.」則消失了。這需要同一個檔案中同時存在這兩種寫法,雖然罕見,但會無聲無息地失敗。檢查工具會在你上傳前提出警告。 - 類整數的鍵被重新排序了。
"steps": { "10": ..., "2": ..., "1": ... }回傳後變成"1"、"2"、"10"。JavaScript 在重建物件時,總是會先以數字順序列出類整數的鍵。鍵和值仍然互相對應,所以你的應用程式不會有問題,但與原始檔案進行差異比對時會顯得雜亂。 - 一個大型 ID 失去了精確度。
9007199254740993回傳後變成9007199254740992。JavaScript 以 64 位元浮點數儲存數字,而超過 2^53 的整數無法全部精確表示(MDN 關於Number.MAX_SAFE_INTEGER)。請將長 ID 儲存為字串,或將它們排除在語言檔案之外。 - 格式被重寫了。 輸出使用兩個空格縮排,並以換行符號結尾。如果你的原始檔案使用定位字元或四個空格,差異比對中預期會出現空白字元的變更。
我們的測試檔案沒有重複的鍵,但這點值得注意:JavaScript 的 JSON 解析器對於重複的鍵只會保留最後一個值,因此較早的文字永遠不會被翻譯。你的應用程式會載入相同的最後一個值,所以表面上不會有任何問題,但檢查工具會列出重複的鍵,讓你可以清理它們。
雙語檔案
雙語檔案會保留每個原始值,並在結尾加上一個帶有 _translated 的兄弟鍵:
"greeting": "Welcome back, {name}!",
"greeting_translated": "[ZH] Welcome back, {name}!",
這對於審閱來說很方便,因為原始文字和翻譯並排顯示。但這不是你的應用程式可以載入的檔案,因為每個鍵現在都有一個雙胞胎。請在你的應用程式中使用翻譯後的檔案。
有一個缺口:陣列內的字串不會有 _translated 兄弟鍵,因為陣列項目沒有鍵可以附加。在我們的測試中,"menu": ["Open", "Save", "Export as EPUB"] 在雙語檔案中回傳為英文,但在翻譯後的檔案中則已翻譯。如果你從雙語檔案進行審閱,請檢查翻譯後檔案中的陣列。
含有註解的檔案
許多專案會在語言檔案中保留註解(JSONC),而嚴格的 JSON 並不允許這麼做。翻譯工具會先嘗試嚴格的 JSON。如果失敗,它會移除註解和尾隨逗號,然後再試一次。移除註解意味著刪除一行中 // 之後的所有內容,而這也包括 https:// 中的 //。
我們的含有註解和 URL 的檔案完全無法載入;解析在第一個註解處就失敗了。一個含有註解但任何值內都沒有 // 的檔案則載入正常,且註解會從輸出中移除。
所以:如果你的檔案含有註解,請在上傳前移除它們,或者保留一份含有註解的副本,並接受翻譯後不會有這些註解。
步驟 2:準備檔案
你可以在翻譯前修正大部分這些問題:
- 使其成為嚴格的 JSON。 移除註解和尾隨逗號。如果你的建置工具會讀取 JSONC,請將含有註解的檔案保留為來源,並匯出一份乾淨的副本用於翻譯。
- 使用單一的鍵風格。 要嘛巢狀鍵,要嘛用點扁平化,不要在一個檔案中同時使用兩者。如果你使用 i18next,它的
keySeparator選項會控制鍵中的點如何被解讀,所以在扁平化任何東西之前請先檢查該設定。 - 將非文字資料移出。 ID、URL、功能旗標和限制應放在設定中,而不是語言檔案裡。如果某個 URL 必須保留,就保留它,但預期在翻譯後需要檢查。我們的不翻譯指南涵蓋了如何決定還有什麼應該保留原文。
- 在各地以相同方式撰寫佔位符。 單一風格,例如
{name},比混合{name}、{{name}}和%s更容易檢查。
步驟 3:翻譯整個檔案
將清理後的檔案上傳到 BookTranslator 的文件翻譯工具,它會將 .json 與其他可接受的本地化和結構化文字格式一起列出。價格會在結帳前顯示。任務完成後,你的歷史記錄頁面會提供翻譯後的檔案和雙語檔案作為個別下載。
文字翻譯的方式有兩點會影響結果:
- 模型看不到你的鍵。 每個值都會以編號區段的形式送出,不包含鍵名。如果
"button.open"和"status.open"都寫著「Open」,模型無從得知其中一個是動詞、另一個是形容詞。簡短且語意不明的字串,就是需要人工檢查的對象。 - 每個值都是獨立翻譯的。 沒有任何機制把兩個鍵綁在一起,因此同一個詞出現在兩個地方,可能譯成兩種不同的說法;而需要兩種不同譯法的詞,也可能譯成同一種。BookTranslator 的選用自動詞彙表會收集人名、地名和專業術語並維持一致,但你無法為它指定自己的固定譯法。對於必須以某個確切譯法翻譯的術語,請維護一份術語清單,並在翻譯後檢查這些字串;我們的詞彙表指南說明了如何規劃。
行內 HTML 標籤會在翻譯後計數,標籤對不上的區段會重新嘗試。大括號佔位符、%s 樣式的佔位符和 ICU 語法會隨文字一起送出,但不會以同樣的方式檢查。請對每一份譯文執行步驟 4。
如果你偏好用聊天模型或本機模型來翻譯,請只送出值,絕不要送出原始檔案,並用腳本依鍵放回。以下檢查兩種方式都適用。
步驟 4:檢查譯文檔案
將譯文與原文比對:
python3 check_i18n_json.py en.json fr.json
為了展示它能抓到什麼,我們做了一份植入錯誤的法文檔案。它是手工製作的,不是模型輸出。在與步驟 1 相同的原文警告之後,檢查器印出:
ERROR app.greeting: placeholders differ; lost ['name'] extra ['nom']
ERROR app.help_url: urls differ; lost ['https://example.com/help'] extra ['https://example.com/aide']
ERROR app.items: ICU message has no 'other' case
ERROR app.legacy: placeholders differ; lost ['%d'] extra []
ERROR limits.maxFiles: type changed from int to str
ERROR limits.projectId: non-text value changed: 9007199254740993 -> 9007199254740992
INFO menu[1]: identical to source: 'Save'
INFO compared 20 source values, 1 unchanged strings
上述每一項都會在應用程式中顯示為錯誤:空的名稱、壞掉的連結、複數渲染時崩潰或顯示原始訊息、缺少章節數、以文字比較的上限。INFO 那行列出傳回後未變動的字串。有些沒問題,例如品牌名稱;法文選單裡的「Save」則不然。
檢查器會比對鍵、值類型、非文字值、佔位符({name}、{{name}}、%s、%1$s、%@)、ICU 引數名稱、HTML 標籤和 URL。它不會評判譯文本身。
複數類別本來就會改變
ICU 複數會為每個複數類別列出一則訊息。英文有兩個類別,one 和 other,但許多語言的類別組合不同。我們 items 訊息的良好俄文譯文通常會用到 one、few、many 和 other,日文譯文則只有 other。因此這裡的不一致未必是錯誤。
這些類別來自 Unicode CLDR 複數規則,而 other 是每種語言都有的類別。以下是 Node.js 中 Intl.PluralRules 搭配 CLDR 48 所回報的基數類別:
| 語言 | 複數類別 |
|---|---|
| 英文、德文 | one、other |
| 法文、西班牙文、葡萄牙文 | one、many、other |
| 俄文、波蘭文 | one、few、many、other |
| 阿拉伯文 | zero、one、two、few、many、other |
| 日文、中文、韓文 | 其他 |
法文、西班牙文和葡萄牙文只在非常大的整數(例如一百萬)時使用 many,因此小數量的訊息通常可以省略它。檢查工具會將缺少 other 回報為錯誤,而其他類別變更則會回報為警告,供您與此表對照。您可以自行列印任何語言的清單:
node -e 'console.log(new Intl.PluralRules("ru").resolvedOptions().pluralCategories)'
翻譯工具會收到英文的類別,因此請檢查回傳的結果。如果俄文或阿拉伯文的訊息仍然只有 one 和 other,請手動補上缺少的類別,或請翻譯人員處理。
然後在應用程式中載入它
檢查工具證明的是檔案可以安全載入,而不是讀起來通順。將應用程式切換到新語言並檢視各個畫面:德文或芬蘭文的長單字是否超出按鈕、阿拉伯文或希伯來文的由右至左文字,以及您的程式碼與語言檔案分開格式化的日期或數字。我們的翻譯與本地化指南涵蓋了除了文字之外,不同市場之間還有哪些變動。
常見問題
翻譯後的檔案無法在我的應用程式中載入。 請對它執行檢查工具。如果檢查工具無法解析它,表示檔案在翻譯後被編輯過,或原本就無效。如果解析成功,請尋找關於鍵或型別的 ERROR 行。
字串顯示為原始 ICU 訊息,例如 {count, plural, ...}。 ICU 語法在翻譯過程中損壞了,通常是缺少大括號或缺少 other 類別。請從來源複製結構,只翻譯每個類別內的文字。
我的檔案是 YAML、.po 檔或 Markdown。 BookTranslator 也接受這些格式,但每種格式都有自己的規則。如需文件說明,請參閱我們的 Markdown 翻譯指南;如需網頁說明,請參閱 HTML 檔案指南。
我可以只翻譯新的鍵嗎? 無法在單次上傳中做到;整個檔案都會被翻譯。請將新的鍵放在另一個檔案中,翻譯它,然後按鍵合併結果。檢查工具會確認合併後的檔案包含每一個鍵。
我應該翻譯鍵嗎? 不應該。鍵是您的程式碼用來查詢的識別碼。只有值才是供人閱讀的文字。





