Demo同步更新到Swift2.3
本文地址: http://mokai.me/iOS-i18n.html
在真正將國(guó)際化實(shí)踐前,只知道通過(guò)NSLocalizedString
方法將相應(yīng)語(yǔ)言的字符串加載進(jìn)來(lái)即可。但項(xiàng)目的新需求增加英文版本,并支持應(yīng)用內(nèi)無(wú)死角切換~,這才跳過(guò)各種坑實(shí)現(xiàn)了應(yīng)用內(nèi)切換語(yǔ)言,并記錄至此。
環(huán)境
系統(tǒng)環(huán)境: iOS7 or later
開(kāi)發(fā)環(huán)境: Swift2.3 & Xcode7.3.1
DEMO: LocalDemo
這個(gè)Demo的功能主要是切換語(yǔ)言后相應(yīng)的界面文字&圖片以及搜索引擎都會(huì)隨語(yǔ)言變化。我們會(huì)圍繞這個(gè)DEMO進(jìn)行講解,讀者可以先下載這個(gè)Demo運(yùn)行看下效果再往下
iOS國(guó)際化原理分析
國(guó)際化其實(shí)都大同小異,其核心思想就是為每種語(yǔ)言單獨(dú)定義一份資源
。
iOS就是通過(guò)xxx.lproj
目錄來(lái)定義每個(gè)語(yǔ)言的資源,這里的資源可以是圖片,文本,Storyboard,Xib等。我們可以看看LocalDemo源代碼的物理目錄結(jié)構(gòu)
Base,暫時(shí)無(wú)需理會(huì)
English
中文
每種語(yǔ)言都有自己的 語(yǔ)言代碼.lproj文件夾,加載資源時(shí)只需要加載相應(yīng)語(yǔ)言文件夾下的資源就OK,這步可以系統(tǒng)為我們完成,也可以手動(dòng)去做。
項(xiàng)目源代碼中如果有多個(gè)不同目錄的國(guó)際化資源,則會(huì)有產(chǎn)生多個(gè)xxx.lproj,但在編譯打包后,會(huì)集中放在app的根目錄中的xxx.lproj中,不信你看~
開(kāi)始國(guó)際化
首先點(diǎn)擊項(xiàng)目->PROJECT->Info->Localizations中添加要支持的語(yǔ)言
此處Use Base Internationalization開(kāi)啟狀態(tài)下,每個(gè)國(guó)際化資源文件會(huì)有個(gè)Base選項(xiàng),主要針對(duì)String,Storyboard,Xib作為一個(gè)基礎(chǔ)的模板,像后述storyboard國(guó)際化中方案二就是基于Base StoryBoard進(jìn)行改動(dòng)。
在點(diǎn)擊+
添加相應(yīng)語(yǔ)言時(shí)會(huì)彈出以下對(duì)話框,意思是為現(xiàn)有的資源添加語(yǔ)言文件,我們點(diǎn)擊Finish
就行了
文本的國(guó)際化
主要針對(duì)代碼中的字符串進(jìn)行國(guó)際化,比如說(shuō)一些消息,UI標(biāo)題等。
我們通過(guò)一個(gè)Localizable.strings
文件來(lái)存儲(chǔ)每個(gè)語(yǔ)言的文本,它是iOS默認(rèn)加載的文件,如果想用自定義名稱命名,在使用NSLocalizedString
方法時(shí)指定tableName為自定義名稱就好了,但你的應(yīng)用規(guī)模不是很大就不要分模塊搞特殊了。
每個(gè)資源文件如果想為一種語(yǔ)言添加支持,通過(guò)其屬性面板中的Localization
添加相應(yīng)語(yǔ)言就行了,此時(shí)Localizable.strings
處于可展開(kāi)狀態(tài),子級(jí)有著相應(yīng)語(yǔ)言的副本。我們把相應(yīng)語(yǔ)言的文本放在副本里面就行了
此處Base與前面提過(guò)到的
開(kāi)啟Use Base Internationalization
是有關(guān)聯(lián)的,只有開(kāi)啟了全局Use Base Internationalization
此處才會(huì)顯示。那為什么這里沒(méi)有勾選Base?Base做為一個(gè)基礎(chǔ)模板,作用于Strings文件是沒(méi)有太大意義的,另外去掉Base意義著在Base.lproj中少了一個(gè)strings文件,APP大小也所有下降,這點(diǎn)對(duì)于圖片的Base更是如此
在上圖可以看到其實(shí)就是為每一套語(yǔ)言新建一份strings,其內(nèi)容采用"key" = "value";
的格式,注意有;
號(hào)
我們?cè)诖a中這樣寫就行了
NSLocalizedString("首頁(yè)",comment: "")
NSLocalizedString("好友",comment: "")
NSLocalizedString("我",comment: "")
另外中文strings【Localizable.strings(Simplified)】可以不要的(可以理解為中文為APP的默認(rèn)語(yǔ)言),因?yàn)閗ey就是value,當(dāng)找不到相應(yīng)的語(yǔ)言strings或value時(shí)會(huì)直接返回key。nice!這樣一來(lái)我們做文本的國(guó)際化就只要維護(hù)一個(gè)英文副本strings就O了
圖片的國(guó)際化
二種方案,通過(guò)原生支持與自定義命名
注意,新版Xcode中Images.xcassets不支持國(guó)際化(屬性頁(yè)面中沒(méi)有
Localization
),Xcode5以前是支持的
-
方案一:自定義文本命名
利用文本國(guó)際化的方式,在代碼中調(diào)用
UIImage(named: NSLocalizedString("search_logo",comment: ""))
不推薦,一是因?yàn)樽龇ㄌ玪ow了,工作量明顯加大。二是不能在Storyboard或XIB中使用
-
方案二:原生支持
同上,Base副本去掉。另外需要注意的是,使用這種方式,在XIB或Storyboard中引用圖片時(shí)如果只使用名稱是實(shí)時(shí)顯示不了的,一定要加上后綴名。如avater.png
使用方式不變,iOS會(huì)自動(dòng)找相應(yīng)語(yǔ)言(xxx.lproj)下的圖片
```
UIImage(named: "avater")
```
對(duì)于圖片的放置,正確姿態(tài)應(yīng)該是`需要國(guó)際化的圖片放在自定義Group里面,不需要國(guó)際化的圖片放在Images.xcassets`
Storyboard&XIB的國(guó)際化
前面的兩種資源國(guó)際化比較簡(jiǎn)單,但Storyboard國(guó)際化就稍微麻煩了點(diǎn)。同樣它也有二種方案
-
方案一:每種語(yǔ)言定制一套Storyboard
在上圖我們可以看到,每種語(yǔ)言都可以切換為strings或Storyboard(默認(rèn)為strings)。如果選用
Interface Builder Storyboard
方案,那么每種語(yǔ)言都有一套相應(yīng)的Storyboard,各個(gè)語(yǔ)言Storyboard間的界面改動(dòng)不關(guān)聯(lián) -
方案二:基于基礎(chǔ)的
Base StoryBoard
以及每種語(yǔ)言一套strings <a id='storyboard_2'></a>基于一個(gè)基礎(chǔ)的Storyboard,可以看作是一個(gè)基礎(chǔ)的模板,Storyboard里面所有的文本類資源(如UILabel的text)都會(huì)被放在相應(yīng)語(yǔ)言的strings里面。此時(shí)我們?yōu)镾toryboard里的字符類資源作國(guó)際化只需要編輯相應(yīng)語(yǔ)言的strings就行了
首選方案二。因?yàn)椴捎梅桨敢唬饬x著你每改動(dòng)一個(gè)界面元素就得去相應(yīng)語(yǔ)言Storyboard一一改動(dòng),那跟為每個(gè)語(yǔ)言新起一個(gè)項(xiàng)目是一樣的道理。但是采用方案二,我們只需改動(dòng)Base Storyboard就行了
注意,方案二中相應(yīng)語(yǔ)言的strings一旦生成后,Base Storyboard有任何編輯都不會(huì)影響到strings,這就意味著如果我們刪除或添加了一個(gè)UILabel的text,strings也不能同步改動(dòng)
還好,Xcode為我們提供了ibtool
工具來(lái)生成Storyboard的strings文件。
ibtool Main.storyboard --generate-strings-file ./NewTemp.string
但是ibtool生成的strings文件是BaseStoryboard的strings(默認(rèn)語(yǔ)言的strings),且會(huì)把我們?cè)瓉?lái)的strings替換掉。所以我們要做的就是把新生成的strings與舊的strings進(jìn)行沖突處理(新的附加上,刪除掉的注釋掉),這一切可以用這個(gè)pythoy腳本來(lái)實(shí)現(xiàn),見(jiàn)AutoGenStrings.py。然后我們將借助Xcode 中 Run Script
來(lái)運(yùn)行這段腳本。這樣每次Build時(shí)都會(huì)保證語(yǔ)言strings與Base Storyboard保持一致
應(yīng)用內(nèi)切換語(yǔ)言
應(yīng)用啟動(dòng)時(shí),首先會(huì)讀取NSUserDefaults中的key為AppleLanguages
的內(nèi)容,該key返回一個(gè)String數(shù)組,存儲(chǔ)著APP支持的語(yǔ)言列表,數(shù)組的第一項(xiàng)為APP當(dāng)前默認(rèn)的語(yǔ)言。
在安裝后第一次打開(kāi)APP時(shí),會(huì)自動(dòng)初始化該key為當(dāng)前系統(tǒng)的語(yǔ)言編碼,如簡(jiǎn)體中文就是zh-Hans。
//獲取APP當(dāng)前語(yǔ)言
(NSUserDefaults.standardUserDefaults().valueForKey("AppleLanguages") as! Array<String>)[0]
那么我們要實(shí)現(xiàn)語(yǔ)言切換改變AppleLanguages
的值即可,但是這里有一個(gè)坑,因?yàn)樘O果沒(méi)提供給我們直接修改APP默認(rèn)語(yǔ)言的API,我們只能通過(guò)NSUserDefaults手動(dòng)去操作,且AppleLanguages
的值改變后APP得重新啟動(dòng)后才會(huì)生效(才會(huì)讀取相應(yīng)語(yǔ)言的lproj中的資源,意義著就算你改了,資源還是加載的APP啟動(dòng)時(shí)lproj中的資源),猜測(cè)應(yīng)該是框架層在第一次加載時(shí)對(duì)AppleLanguages
的值進(jìn)行了內(nèi)存緩沖
//設(shè)置APP當(dāng)前語(yǔ)言
var def = NSUserDefaults.standardUserDefaults()
def.setValue([“zh-Hans”], forKey:"AppleLanguages")
def.synchronize()
那么問(wèn)題來(lái)了,如何做到改變AppleLanguages
的值就加載相應(yīng)語(yǔ)言的lproj資源?
其實(shí),APP中的資源加載(Storyboard、圖片、字符串)都是在NSBundle.mainBundle()
上操作的,那么我們只要在語(yǔ)言切換后把NSBundle.mainBundle()
替換成當(dāng)前語(yǔ)言的bundle就行了,這樣系統(tǒng)通過(guò)NSBundle.mainBundle()
去加載資源時(shí)實(shí)則是加載的當(dāng)前語(yǔ)言bundle中的資源
lproj目錄可以用一個(gè)NSBundle表示
import Foundation
/**
* 當(dāng)調(diào)用onLanguage后替換掉mainBundle為當(dāng)前語(yǔ)言的bundle
*/
private let _bundle:UnsafePointer<Void> = unsafeBitCast(0,UnsafePointer<Void>.self)
class BundleEx: NSBundle {
override func localizedStringForKey(key: String, value: String?, table tableName: String?) -> String {
if let bundle = languageBundle() {
return bundle.localizedStringForKey(key, value: value, table: tableName)
}else{
return super.localizedStringForKey(key, value: value, table: tableName)
}
}
}
extension NSBundle {
private struct Static {
static var onceToken : dispatch_once_t = 0
}
func onLanguage(){
//替換NSBundle.mainBundle()為自定義的BundleEx
dispatch_once(&Static.onceToken) {
object_setClass(NSBundle.mainBundle(), BundleEx.self)
}
}
//當(dāng)前語(yǔ)言的bundle
func languageBundle()->NSBundle?{
return Languager.standardLanguager().currentLanguageBundle
}
}
其他
-
設(shè)置運(yùn)行語(yǔ)言環(huán)境
有時(shí)我們第一次安裝APP時(shí)不想默認(rèn)跟隨系統(tǒng),那么可以通過(guò)Xcode的scheme來(lái)指定特定語(yǔ)言 -
Storyboard實(shí)時(shí)預(yù)覽
直接上圖~
-
IB中UIImageView國(guó)際化無(wú)效
解決辦法就是為UIImageView
擴(kuò)展一個(gè)方法,然后通過(guò)IB中的User Defined Runtime Attributes
把imageName傳進(jìn)去extension UIImageView{ var local: String { get{ return "" } set(newlocal) { self.image = localizedImage(newlocal) } } }
-
IB中UITextView國(guó)際化無(wú)效
解決辦法和UIImageView類似,擴(kuò)展一個(gè)方法,然后把self.text做為key去strings文件中拿相應(yīng)語(yǔ)言的valueextension UITextView { var local: Bool { get{ return true } set(newlocale) { self.text = localized(self.text) } } }
- LaunchScreen.xib的國(guó)際化
很遺憾,到目前為止,還不支持LaunchScreen.xib的國(guó)際化,我們只能通過(guò)自定義一個(gè)LaunchViewController來(lái)完成此需求,但也有些不足,就是應(yīng)用啟動(dòng)時(shí)會(huì)黑屏一段時(shí)間,所以建議啟動(dòng)頁(yè)面不要弄國(guó)際化
參考
- iOS國(guó)際化——通過(guò)腳本使storyboard翻譯自增
- Working with Localization
- How to force NSLocalizedString to use a specific language
小小廣告
本人目前是一名自由職業(yè)者,接受移動(dòng)兩端的項(xiàng)目開(kāi)發(fā),如果你有需求或者有資源請(qǐng)速與我聯(lián)系吧,QQ865425695