實現(xiàn)模塊化應用的本地化
前言
我已經(jīng)有一段時間沒有從頭開始一個需要支持多種語言的新項目了。當然不是從頭開始,而是在代碼庫中通過使用 Swift 包將代碼分成不同模塊。
我想提醒自己記住許多在本地化實行中的過程,所以我認為最好寫一篇文章,以便下次開始同類型項目時可以參考。
開始吧!
讓我們看看代碼庫的一個簡化版本。它包含一個 Xcode 項目,一個單獨的 app target(即將運行的那個)和一個名為 Features 的 Swift 包。后者將包含 app 中所有頁面的代碼,每一頁將被定義為自己的產品:
Package.swift
// swift-tools-version: 5.6
import PackageDescription
let package = Package(
name: "Features",
products: [
.library(
name: "Home",
targets: ["Home"]),
.library(
name: "Detail",
targets: ["Detail"]
)
],
dependencies: [
],
targets: [
.target(
name: "Home"
),
.target(
name: "Detail"
)
]
)
這個 app target 將會作為 app 的組合層,其唯一的目的是導入每個功能,實例化它們并協(xié)調導航。所有的 UI ,演示和業(yè)務邏輯將留在各自的 "模塊" 中( Features Swift Package 中的一個 target)。這將允許每個功能獨立開發(fā)并完全的與其他功能隔離。
為了簡單起見,這個例子里僅有兩個功能:主頁和詳情,他們代表 app 中僅有的兩個頁面。
主頁有一個按鈕允許用戶導航到詳情頁面,還有一個標簽展示用戶當前所在區(qū)域的語言代碼。詳情頁只展示一個標簽,和主頁標簽展示的信息一致:
添加字符串!
看起來不錯,但是現(xiàn)在展示的信息是用英文通過硬編碼編寫的字符串。app 需要內容被翻譯成另外兩種語言:加泰羅尼亞語和西班牙語。
雖然有多種實現(xiàn)方式,我更傾向每個功能(或頁面)只包含它所需要的本地化字符串,這樣可以增加功能的可移植性和可重用性。
這可以在 Swift 包中完成,通過將所有必需的 .lproj 文件和所有需要本地化的內容(當前例子中只有 Localizable.strings 文件)放在目標文件夾下 - 我的習慣是放在父 Resources/ 文件夾下,并將這些資源定義為 Package.swift 的特定 target。
添加文件之后構建該功能將導致編譯器拋出如下錯誤:
這是因為 defaultLocalization 必須由 Package.swift 提供。所有功能的 target 來自一個包,所以只能有一個 defaultLocalization 。以下是 Package.swift 添加本地化內容之后的樣子:
Package.swift
// swift-tools-version: 5.6
import PackageDescription
let package = Package(
name: "Features",
defaultLocalization: "en",
platforms: [.iOS(.v15)],
products: [
.library(
name: "Home",
targets: ["Home"]),
.library(
name: "Detail",
targets: ["Detail"]
)
],
dependencies: [
],
targets: [
.target(
name: "Home",
dependencies: [],
resources: [.process("Resources/")]
),
.target(
name: "Detail",
resources: [.process("Resources/")]
)
]
)
注意:如果沒有為默認的本地化代碼提供本地化的內容,編譯器會顯示警告。這對于確保你不會發(fā)布包含基本本地化內容的軟件包版本非常有幫助。
Xcode warning shown when default localisation is missing。
支持本地化
可能與你的想法正好相反,把設備系統(tǒng)語言設置為加泰羅尼亞語或西班牙語并且運行 app 內容仍然用英文展示。原因是 Swift 包需要額外的信息去決定使用哪些本地化的內容,就目前來看,如果包里有目標內容,它們將只使用目標的基本本地化,否則使用包的默認本地化。
現(xiàn)在有兩種方式我們可以實現(xiàn)本地化:使新的本地化在 app target 中可用或啟用混合本地化。
在 app target 中添加新的本地化內容
在 Features Swift 包中啟用新的本地化的一種方式是將它們添加到導入功能的 Xcode 項目中。這可以通過進入 Xcode 項目,在項目設置中的 "Info" 一欄,添加本地化支持:
需要注意的是,本地化需要至少一個文件(例如一個空的 Localizable.strings 文件)。在本例中,因為 app target 是用 UIKit 構建的,并且在添加新的本地化時選擇了啟動 storyboard 進行本地化(如上視頻所示),所以已經(jīng)有一個本地化文件。
現(xiàn)在這將允許包從主包中獲取支持的本地化,并選擇相應的要使用的資源。
值得注意的是,如果設備有被 app 支持但是包不支持的語言,則后者將會回退到 Package.swift 中提供的 defaultLocalization .
同樣的,如果 app 不支持該語言,同樣會回退到相同的值。這也是為什么將 defaultLocalization 設置為與主目標基礎語言相同,以確保所有頁面上的一致性是非常重要的。這也是我更傾向于所有功能分組在一個 Swift 包之下的原因,這樣所有頁面上的 defaultLocalization 就有了單一真正的來源。
允許混合本地化
雖然采用 app target 的本地化是首選方法,因為他確保了所有頁面的一致性,并且只允許少數(shù)受支持的地方使用,但還有另一種方法允許包內容被本地化,而不必在主項目之外。
可以通過將 app 的 Info.plist 文件中的 CFBundleAllowMixedLocalizations 值設置為 YES 來實現(xiàn)。
這個設置將會告訴 app target 在不同的 target 或功能使用不同本地化是可以的,當添加新的本地化資源時, app 本地化會自動工作。
Enabling mixed localisations in the app targe
使用這種方法需要注意以下幾點:
1.不再需要將本地化添加到 app target,添加帶有本地化內容的 lproj 到包資源就可以了。當用戶修改區(qū)域時,如果你的資源包存在該語言包或默認提供 Package.swift ,軟件包也會展示該區(qū)域的語言內容。
2.支持多少個區(qū)域就會有多少個本地化資源。這意味著沒有一個單一的真實來源來確定整個 app 支持哪些本地化。這可能會導致一些問題,例如,某個功能有本地化資源內容,而該內容的本地化資源還未被應用。在本例中,除了刪除資源,沒有辦法隱藏它。
視頻鏈接:https://www.polpiella.dev/assets/posts/modularised-app-localisation/mixed-localisations.mp4。
第二點如上面的視頻中所示,當用戶把設備語言設置為法語。混合來源導致了不一致,因為主屏幕沒有 fr.lproj --因此它又回到了默認本地化資源,英語。另一方面,在詳情頁面,有可用的本地化內容,這是正確翻譯字符串的原因,正是這個原因,我喜歡將 app target 作為所有支持本地化的真實來源。
額外提示 - 自動化
我一直鼓勵盡可能地自動化檢索特定包的本地化字符串的流程。如果你的 app 有很多頁面,希望使添加本地化字符串的過程盡可能簡單和簡便。
我一直在使用的一款工具 SwiftGen,它可以為各種資源生成 Swift 接口,例如 Localizable.strings 文件。
創(chuàng)建一個利用這個可執(zhí)行文件的構建工具插件,可以使支持新本地化過程變得容易一點,并在各功能之間保持一致。