在開發 iOS 應用程式時,使用 Swift Package Manager (SPM) 來模組化程式碼已經成為主流做法。然而,當你在 Swift Package 中實作多國語言支援時,可能會遇到一個令人困惑的問題:Package 內的本地化在測試時運作正常,但整合到主專案後卻失效了。本文將深入探討這個問題的根源,以及如何透過 CFBundleLocalizations 和 CFBundleAllowMixedLocalizations 這兩個 Info.plist 設定來解決。
Swift Package 在多國語言會遇到什麼問題
問題現象
假設你正在開發一個 Swift Package,並且已經正確配置了多語言支援:
let package = Package(
name: "MyFeatureKit",
defaultLocalization: "en",
targets: [
.target(
name: "MyFeatureKit",
resources: [.process("Resources")]
)
]
)
Package 的資料夾結構如下:
MyFeatureKit/
└── Sources/
└── MyFeatureKit/
└── Resources/
├── en.lproj/Localizable.strings
├── es.lproj/Localizable.strings
├── ja.lproj/Localizable.strings
└── de.lproj/Localizable.strings
在 Package 內部測試時,你使用 Bundle.module 來載入本地化字串:
Text("welcome_message", bundle: .module)
一切運作正常。但當你將這個 Package 整合到主 App 中時,日文和德文的本地化突然失效了,系統只會回退到預設的英文。
問題根源
這個問題的核心在於 iOS 系統的本地化載入機制:系統預設只允許 Swift Package 使用主 App Bundle 中明確支援的語言。
換句話說:
- 主 App 透過
.lproj資料夾或CFBundleLocalizations宣告它支援哪些語言 - Swift Package 雖然有自己的 Bundle (
Bundle.module) 和本地化資源 - 但在執行時,系統會檢查主 App 支援的語言清單
- 如果 Package 嘗試使用主 App 未宣告支援的語言,該語言會被忽略
這個設計的原意是確保整個應用程式的本地化體驗一致,避免出現部分介面是語言 A,部分介面是語言 B 的混亂情況。然而,這也造成了 Swift Package 無法獨立提供更多語言支援的限制。
CFBundleLocalizations:明確宣告支援的語言
什麼是 CFBundleLocalizations
CFBundleLocalizations 是 Info.plist 中的一個鍵值,用於明確告訴系統你的應用程式支援哪些語言。它的資料型態是字串陣列,每個元素代表一種語言代碼。
解決 Swift Package 本地化問題
假設你的主 App 只有英文和西班牙文的本地化檔案(只有 en.lproj 和 es.lproj),但你的 Swift Package 支援英文、西班牙文、日文和德文。
不加 CFBundleLocalizations 的情況:
- 英文使用者:看到英文(正常)
- 西班牙文使用者:看到西班牙文(正常)
- 日文使用者:看到英文(Package 的日文被忽略)
- 德文使用者:看到英文(Package 的德文被忽略)
加入 CFBundleLocalizations 後:
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
<string>ja</string>
<string>de</string>
</array>
現在的結果:
- 英文使用者:看到英文
- 西班牙文使用者:看到西班牙文
- 日文使用者:看到日文(Package 的日文正常載入)
- 德文使用者:看到德文(Package 的德文正常載入)
重要特性與限制
優點:
- 即使主 App 沒有對應的
.lproj資料夾,只要在CFBundleLocalizations中宣告,Swift Package 就能使用該語言 - 實作簡單,只需修改 Info.plist
限制:
- App Store 會顯示你宣告的所有語言為「支援語言」
- 使用者可能會期待整個 App 都支援該語言,但實際上只有 Package 部分支援
- 容易造成使用者體驗不一致:主 App 介面是英文,但 Package 提供的功能是日文
適用情境:
- 你計劃在主 App 中也加入這些語言的支援,只是還沒完成
- 你的 App 結構簡單,大部分內容都在 Package 中
- 你願意接受 App Store 顯示更多支援語言
CFBundleAllowMixedLocalizations:允許混合本地化
什麼是 CFBundleAllowMixedLocalizations
CFBundleAllowMixedLocalizations 是一個布林值設定,用於明確告訴系統:「允許我的依賴項(包括 Swift Package、Framework 等)使用它們自己支援的任何語言,不受主 App 宣告語言的限制。」
解決 Swift Package 本地化問題
使用相同的情境:主 App 只有英文和西班牙文,Swift Package 支援英文、西班牙文、日文和德文。
加入 CFBundleAllowMixedLocalizations 後:
<key>CFBundleAllowMixedLocalizations</key>
<true/>
結果:
- 英文使用者:主 App 顯示英文,Package 顯示英文
- 西班牙文使用者:主 App 顯示西班牙文,Package 顯示西班牙文
- 日文使用者:主 App 顯示英文(或回退到基礎語言),Package 顯示日文
- 德文使用者:主 App 顯示英文(或回退到基礎語言),Package 顯示德文
重要特性與優勢
優點:
- App Store 只顯示主 App 實際支援的語言(英文、西班牙文)
- Swift Package 可以獨立提供更多語言支援
- 使用者不會對整體語言支援有錯誤期待
- 對於只使用特定 Package 功能的使用者,可以獲得更好的本地化體驗
限制:
- 可能造成介面語言不一致(主 App 和 Package 顯示不同語言)
- 需要確保這種混合語言的體驗在 UI/UX 上是可接受的
適用情境:
- 主 App 只支援少數語言,但整合的 Swift Package 提供更豐富的多語言支援
- Package 是功能型模組(如支付、地圖、社交分享),語言不一致不影響整體體驗
- 希望 App Store 只顯示主 App 實際完整支援的語言數量
實際案例:不同語言配置的影響
讓我們用一個具體的案例來說明這兩個設定的實際差異。
案例設定
主 App (Main Bundle):
- 實際本地化檔案:英文 (en)、西班牙文 (es)、繁體中文 (zh-Hant)
- 有完整的 UI 翻譯、App 名稱本地化等
Swift Package (第三方支付 SDK):
- 本地化檔案:英文 (en)、西班牙文 (es)、繁體中文 (zh-Hant)、日文 (ja)、德文 (de)
- 包含支付流程的 UI 和訊息
方案一:不做任何設定
Info.plist:
<!-- 空白,不加任何設定 -->
各語言使用者的體驗:
| 使用者語言 | 主 App 顯示 | Package 顯示 | 整體體驗 |
|---|---|---|---|
| 英文 | 英文 | 英文 | 完美一致 |
| 西班牙文 | 西班牙文 | 西班牙文 | 完美一致 |
| 繁體中文 | 繁體中文 | 繁體中文 | 完美一致 |
| 日文 | 英文(回退) | 英文(被限制) | 一致但未本地化 |
| 德文 | 英文(回退) | 英文(被限制) | 一致但未本地化 |
App Store 顯示: 支援 3 種語言
問題: 日文和德文使用者無法使用 Package 內建的本地化,即使 Package 已經準備好這些語言。
方案二:使用 CFBundleLocalizations
Info.plist:
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
<string>zh-Hant</string>
<string>ja</string>
<string>de</string>
</array>
各語言使用者的體驗:
| 使用者語言 | 主 App 顯示 | Package 顯示 | 整體體驗 |
|---|---|---|---|
| 英文 | 英文 | 英文 | 完美一致 |
| 西班牙文 | 西班牙文 | 西班牙文 | 完美一致 |
| 繁體中文 | 繁體中文 | 繁體中文 | 完美一致 |
| 日文 | 英文(回退) | 日文 | 不一致但部分本地化 |
| 德文 | 英文(回退) | 德文 | 不一致但部分本地化 |
App Store 顯示: 支援 5 種語言
問題:
- 日文和德文使用者在 App Store 看到「支援日文/德文」
- 下載後發現只有支付流程是日文/德文,主 App 介面仍是英文
- 可能造成使用者困惑或負評
方案三:使用 CFBundleAllowMixedLocalizations
Info.plist:
<key>CFBundleAllowMixedLocalizations</key>
<true/>
各語言使用者的體驗:
| 使用者語言 | 主 App 顯示 | Package 顯示 | 整體體驗 |
|---|---|---|---|
| 英文 | 英文 | 英文 | 完美一致 |
| 西班牙文 | 西班牙文 | 西班牙文 | 完美一致 |
| 繁體中文 | 繁體中文 | 繁體中文 | 完美一致 |
| 日文 | 英文(回退) | 日文 | 不一致但部分本地化 |
| 德文 | 英文(回退) | 德文 | 不一致但部分本地化 |
App Store 顯示: 支援 3 種語言
優勢:
- 日文和德文使用者在 App Store 知道 App 主要支援 3 種語言
- 下載後發現支付流程有日文/德文支援,這是額外的驚喜
- 使用者期待管理更合理
決策指南:何時使用哪一種設定
根據不同的開發情境,選擇合適的設定策略:
| 情境 | 推薦方案 | 理由 |
|---|---|---|
| Swift Package 語言 ⊆ 主 App 語言 | 不需要任何設定 | 系統預設行為已足夠 |
| 短期內會為主 App 加入 Package 支援的所有語言 | CFBundleLocalizations | 提前宣告,為完整本地化做準備 |
| Package 支援的額外語言是長期規劃,近期不會在主 App 實作 | CFBundleAllowMixedLocalizations | 避免誤導使用者,同時提供更好的 Package 體驗 |
| Package 是核心功能,語言支援是主要賣點 | CFBundleLocalizations + 盡快完成主 App 本地化 | 確保整體體驗一致性 |
| Package 是輔助功能(如第三方 SDK、工具庫) | CFBundleAllowMixedLocalizations | Package 語言不一致影響較小 |
| 有多個 Package,各自支援不同語言集合 | CFBundleAllowMixedLocalizations | 統一管理,避免 Info.plist 過於複雜 |
| 企業內部 App,使用者明確知道語言支援範圍 | CFBundleLocalizations | 可以接受混合語言體驗 |
| 面向大眾市場的 App | CFBundleAllowMixedLocalizations | 使用者體驗和期待管理更重要 |
特殊情境處理
情境 A:同時使用兩個設定
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
</array>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
結果: CFBundleAllowMixedLocalizations 會覆蓋 CFBundleLocalizations 的限制,Package 可以使用任何它支援的語言。此設定組合通常沒有必要。
情境 B:Package 的 defaultLocalization 與主 App 不同
// Package.swift
let package = Package(
defaultLocalization: "ja" // 主 App 的基礎語言是 "en"
)
問題: 當系統找不到合適的語言時,Package 會回退到日文,而主 App 會回退到英文,造成體驗不一致。
解決方案: 確保 Package 的 defaultLocalization 與主 App 的基礎語言(CFBundleDevelopmentRegion)一致。
實作檢查清單
在實作 Swift Package 多語言支援時,使用此檢查清單確保一切配置正確:
Swift Package 端
- Package.swift 中設定了
- 所有語言的資料夾使用正確的命名格式(如
en.lproj、zh-Hans.lproj -
Localizable.strings - 在程式碼中使用
Bundle.module而非
主 App 端
- 決定使用
CFBundleLocalizations或 - 如果使用
CFBundleLocalizations - Package 的
defaultLocalization
測試驗證
總結
Swift Package 的多語言支援問題源自於 iOS 系統對本地化資源載入的限制機制。理解 CFBundleLocalizations 和 CFBundleAllowMixedLocalizations 的差異,能幫助你根據專案需求做出正確的技術決策:
- CFBundleLocalizations:明確宣告支援的語言,適合計劃完整本地化的情境
- CFBundleAllowMixedLocalizations:允許 Package 獨立提供額外語言,適合大多數整合第三方 Package 的情境
對於多數開發者而言,CFBundleAllowMixedLocalizations 提供了更靈活且使用者友善的解決方案。它讓你能夠誠實地向使用者呈現 App 的實際語言支援情況,同時允許整合的 Swift Package 提供更豐富的多語言體驗。
參考資料
- Apple Developer Documentation - CFBundleLocalizations
- Apple Developer Documentation - CFBundleAllowMixedLocalizations
- Apple Developer Documentation - Localizing Package Resources