在開發 iOS 應用程式時,使用 Swift Package Manager (SPM) 來模組化程式碼已經成為主流做法。然而,當你在 Swift Package 中實作多國語言支援時,可能會遇到一個令人困惑的問題:Package 內的本地化在測試時運作正常,但整合到主專案後卻失效了。本文將深入探討這個問題的根源,以及如何透過 CFBundleLocalizationsCFBundleAllowMixedLocalizations 這兩個 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.lprojes.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.lprojzh-Hans.lproj
  • Localizable.strings
  • 在程式碼中使用 Bundle.module 而非

主 App 端

  • 決定使用 CFBundleLocalizations
  • 如果使用 CFBundleLocalizations
  • Package 的 defaultLocalization

測試驗證

總結

Swift Package 的多語言支援問題源自於 iOS 系統對本地化資源載入的限制機制。理解 CFBundleLocalizationsCFBundleAllowMixedLocalizations 的差異,能幫助你根據專案需求做出正確的技術決策:

  • CFBundleLocalizations:明確宣告支援的語言,適合計劃完整本地化的情境
  • CFBundleAllowMixedLocalizations:允許 Package 獨立提供額外語言,適合大多數整合第三方 Package 的情境

對於多數開發者而言,CFBundleAllowMixedLocalizations 提供了更靈活且使用者友善的解決方案。它讓你能夠誠實地向使用者呈現 App 的實際語言支援情況,同時允許整合的 Swift Package 提供更豐富的多語言體驗。

參考資料