問題背景
目前 Radarr 的翻譯社群已提供 zh_TW.json 繁體中文翻譯檔,然而使用者在 UI 設定頁面的語言下拉選單中,只能看到「Chinese」一個選項,且對應到的翻譯是簡體中文 (zh_CN)。
Sonarr、Lidarr 也有同樣的問題。

UI 語言系統架構
要理解問題,必須先了解整個語言選單的資料流向:
Language.All (Language.cs)
│
▼
LanguageController.GetAll() ← GET /api/v3/language
│ 將 Language.All 序列化
▼
Redux Store: state.settings.languages
│
▼
createLanguagesSelector.ts ← 過濾掉 "Any"
│
▼
UISettingsConnector.js ← 再過濾掉 "Any", "Unknown", "Original"
│
▼
UISettings.js ← 渲染下拉選單
UI 語言選單的內容完全由 Language.cs 的 Language.All 靜態列表決定。
翻譯檔載入流程
UILanguage (int, 存於資料庫)
│
▼
IsoLanguages.Get((Language)UILanguage)
│ 透過 Language 物件找到對應的 IsoLanguage
▼
組合識別符:TwoLetterCode + "-" + CountryCode
│ 例如 "zh" + "-" + "CN" → "zh-CN"
▼
GetLanguageFileName()
│ "zh-CN" → replace("-","_") → "zh_cn"
▼
GetResourceFilename()
│ "zh_cn" → "zh_CN.json"
▼
載入翻譯文件
現行程式碼的問題點
-
Language.cs
目前僅有單一Chinese常數,未區分繁簡體:public static Language Chinese => new Language(10, "Chinese"); -
IsoLanguages.cs
目前僅定義簡體中文的對應國碼:new IsoLanguage("zh", "cn", "zho", "Chinese", Language.Chinese), -
// 正規表達式解析 (line 31) (?<chinese>\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕) ... // 標題字串解析 (line 101) if (lowerTitle.Contains("mandarin") || lowerTitle.Contains("cantonese") || lowerTitle.Contains("chinese")) { languages.Add(Language.Chinese); }CHT、BIG5、繁等專屬繁體標記,與CHS、GB、简等簡體標記,全部被歸類為同一個Language.Chinese,繁簡體通通視為同一語言,無法區分。 -
UiConfigController.cs
UILanguage存儲的是整數Language ID,且驗證邏輯明確要求此 ID 必須存在於Language.All中。這意味著在不修改 Language.cs 的情況下,UI 無法新增一個獨立的繁體中文語言選項。SharedValidator.RuleFor(c => c.UILanguage) .Must(value => Language.All.Any(o => o.Id == value)) .WithMessage("Invalid UI Language ID");
若僅新增繁體中文的 IsoLanguage
new IsoLanguage("zh", "tw", "zho", "Transition Chinese", Language.Chinese),若讓繁體中文與簡體中文同時指向 Language.Chinese (id=10),
IsoLanguages.Get() 的實作是:
public static IsoLanguage Get(Language language)
{
return All.FirstOrDefault(l => l.Language == language);
}由於底層是 HashSet,迭代順序不保證,Get(Language.Chinese) 只會回傳第一個找到的條目。換言之,選擇 Chinese 後,載入的翻譯究竟是 zh_CN.json 還是 zh_TW.json,結果是不確定的。
此外,IsoLanguages.Find() 的三字母碼查找邏輯亦不支援國碼細分:
else if (langCode.Length == 3)
{
// 只比對 ThreeLetterCode,完全忽略 CountryCode
return All.FirstOrDefault(l => l.ThreeLetterCode == langCode);
}zh-CN 與 zh-TW 的 ThreeLetterCode 均為 zho。當外部來源(例如字幕檔名 movie.zho.srt)傳入三字母碼時,系統無法區分繁簡體,結果取決於 HashSet 的內部順序,屬於不確定行為。
雖然兩字母碼查找具備 CountryCode 對應邏輯,但由於兩個條目都映射至同一個 Language.Chinese,UI 選單中仍只會顯示單一 Chinese 選項。
解決方案評估
方案A: Language.cs 新增 ChineseTraditional
優點
不引入任何新架構概念。Language.cs 的 Lookup 字典以 ID 作為 Key,只要 ID 唯一,靜態初始化不會有任何問題,整個媒體功能鏈完整支援繁體中文。
缺點
Language枚舉身兼兩職造成語義混淆
Language 同時被用於「UI 翻譯語言」和「媒體音軌/字幕語言標籤」兩個不同領域。新增ChineseTraditional後,它在兩個領域都生效。對大多數人這是好事,但若社群有「繁體字幕、普通話音軌」這類精確表達需求,現有架構本身即無法處理,這是既有設計限制,並非本方案所引入的新問題。IsoLanguages.cs三字母碼衝突必須謹慎處理
zh-CN 與 zh-TW 都屬於 ISO 639-2 的zho,新增條目時必須妥善處理查找優先順序,避免不確定行為。
方案B:新增 UILocale 字串欄位
讓 UI 翻譯語言選擇完全脫離 Language 枚舉系統,改以字串形式(如 zh-TW)儲存。
優點
- UI 翻譯語言與媒體語言枚舉完全解耦
翻譯語言的選擇不再依賴Language.All,只要存在對應的 JSON 檔案,即可自動支援;新增翻譯語言無需修改任何 C# 程式碼。 - 未來擴展性更高
任何語言變體(如zh-HK粵語、pt-BR巴西葡萄牙語作為 UI 語言)都可以在不修改後端程式碼的情況下直接支援,只需要添加翻譯 JSON 檔案。
缺點
-
引入平行設定欄位,架構雙重複雜
系統中同時存在兩個語言設定:UILanguage: int → 控制媒體語言偏好 (Language.Chinese id=10) UILocale: string → 控制 UI 翻譯語言 ("zh-TW")兩者責任界線模糊,容易造成維護人員混淆。
-
媒體功能完全無法使用繁體中文
由於沒有新增Language枚舉值,以下功能完全不受益:- Quality Profile 無法設定「繁體中文」
- Custom Format 無法條件判斷繁體中文音軌
- MediaInfo 音軌 zh-TW 仍然被辨識為
Language.Chinese - 片名翻譯查詢無法指定繁體中文
-
修改量是方案 A 的兩倍以上