關(guān)于iOS項目的國際化,之前有寫過一篇文章,不過不是很系統(tǒng),也有不少紕漏,還特地寫過一篇填坑文,但是因為太坑了,所以不了了事。這段時間又踩了不少坑,也啃了啃官方的文檔,特此整理10條Tip,有一些問題不會經(jīng)常遇到,有些細節(jié)也很容易被忽略,不過大部分應(yīng)該還是很有用的!
如果你之前對iOS的國際化一無所知,推薦看一下這篇文章,很精煉也很完整——《Internationalization Tutorial for iOS [2014 Edition]》
1.使用InfoPlist.string為你的應(yīng)用名、權(quán)限提醒等配置信息做國際化
大家都知道,我們使用Localizable.strings
文件為代碼中的字符串做國際化,但是實際上還有InfoPlist.strings
。系統(tǒng)會自動識別InfoPlist.strings
來對項目的一些配置信息做國際化,例如:
CFBundleDisplayName 應(yīng)用名稱
NSHumanReadableCopyright CopyRight
NSCameraUsageDescription 相機權(quán)限開啟時候彈框的提示文字
NSContactsUsageDescription 通訊錄權(quán)限開啟時候彈框的提示文字
NSLocationWhenInUseUsageDescription 定位權(quán)限開啟時候彈框的提示文字
NSMicrophoneUsageDescription 麥克風(fēng)權(quán)限開啟時候彈框的提示文字
NSPhotoLibraryUsageDescription 相冊權(quán)限開啟時候彈框的提示文字
NSRemindersUsageDescription 推送權(quán)限開啟時候彈框的提示文字
......
諸如以上這些配置,無法直接在InfoPlist中直接配置(要做國際化),所以使用InfoPlist.strings來配置。
使用的方法很簡單,新建一個InfoPlist.strings文件,并且在右側(cè)勾選對應(yīng)的語言即可。
Tip:如果想要對應(yīng)用名稱做國際化,需要在項目配置里添加參數(shù)Application has localized display name
為YES
2.關(guān)于Base Internationlization選項的正確理解以及誤區(qū)
開啟項目的國際化選項會有一個開關(guān)Use Base Internationlization
,在Localizable.strings
中也會有(Base)
文件。一開始我很自以為是地以為Base的意思是如果項目并沒有對某個語言做國際化,那么就使用Base語言,然而經(jīng)過試驗,發(fā)現(xiàn)事實并非是這樣的,只是自己自作聰明罷了。下面看一下官方對Base Internationlization
的解釋:
Base internationalization separates user-facing strings from .storyboard and .xib files. It relieves localizers of the need to modify .storyboard and .xib files in Interface Builder. Instead, an app has just one set of .storyboard and .xib files where strings are in the development language—the language that you used to create the .storyboard and .xib files. These .storyboard and .xib files are called the base internationalization. When you export localizations, the development language strings are the source that is translated into multiple languages. When you import localizations, Xcode generates language-specific strings files for each .storyboard and .xib file. The strings files don’t appear in the project navigator until you import localizations or add languages.
In Xcode 5 and later, base internationalization is enabled by default. If you have an older project, enable base internationalization before continuing, as described in Enabling Base Internationalization.
簡單的說,實際上Base Internationlization是為xib和storyboard來服務(wù)的。
這也說明了一個問題,很多你原以為的東西事實上并不是一回事,時間久了你會自然而然地認為那是真理而不是當(dāng)初的揣測,等真正暴露出為難了就很難發(fā)現(xiàn)了。
那么iOS系統(tǒng)中語言選取的原則究竟是什么呢?
我舉個例子:假設(shè)我的應(yīng)用使用的開發(fā)語言是英語,同時對法語、日語、阿拉伯語做了國際化。然而我的系統(tǒng)語言設(shè)置的是中文,那么打開應(yīng)用會顯示什么語言呢? 答案是根據(jù)你系統(tǒng)的首選語言列表
而定,在系統(tǒng)設(shè)置-語言設(shè)置的界面,有一欄列表叫做首選語言列表
,假設(shè)你當(dāng)前系統(tǒng)語言是中文,但是之前一次選擇過阿拉伯文為系統(tǒng)語言,那么此事打開你的應(yīng)用,將會顯示阿拉伯文。
iOS對應(yīng)用語言的判定遵循的原則是:
1 查看應(yīng)用是否針對系統(tǒng)語言做國際化,如有,選擇系統(tǒng)語言
2.遍歷系統(tǒng)對首選語言,如果存在對首選語言有做國際化,選擇該首選語言
3.全部沒有對情況下使用development language
3.針對不同界面開發(fā)模式使用不同的國際化方案
純代碼手寫UI
沒什么好說的,一個Localizable.strings解決所有問題。
使用NSLocalizedString(@"KEY", nil)
的宏方法,大部分教程中并沒有提過這個方法的第二個參數(shù)是何含義,其實大部分情況下也不會用到,所以可以自己再定一個宏來除卻不必要的麻煩:
#define LOCAL_LANG(LangKey) NSLocalizedString(LangKey, nil)
當(dāng)然這個第二個參數(shù)并非沒有用,Tip4
來闡述這第二個參數(shù)的作用。
使用Storyboard
之前講Base Internationlization的時候就提過,它主要是為Storyboard和xib文件服務(wù)的。開啟之后,在xib和storyboard右側(cè)有l(wèi)ocalize選項,相應(yīng)的處理方法和strings文件一樣,選擇國際化后會生成對應(yīng)的strings文件,Xcode會抽出xib和Storyboard中所用使用的字符,界面上顯示的內(nèi)容會作為base
的key
和value
,修改其他語言的.strings文件就給Storyboard做了國際化。
使用Xib
Storyboard和Xib在做國際化中有一個很大的區(qū)別在于,一個Storyboard中可以包含多個視圖,多個視圖的字符串都可以集中在一個Storyboard.strings中,但是Xib往往是一個視圖。當(dāng)然Xib也可以使用同樣的方法做國際化,但是如果一個項目中有30個xib,項目需要有8種語言,那就意味著需要維護30*8=240個strings文件,然而很多情況下每個文件里只有少數(shù)幾個字符,這樣的維護成本太大了。
針對Xib的國際化,可以使用如下方法:
直接在xib中用Localization.strings中的鍵作為值使用,然后對于需要國際化的view,統(tǒng)一調(diào)用如下方法:
+ (void)replaceTextWithLocalizedTextInSubviewsForView:(UIView*)targetView {
for (UIView* view in targetView.subviews) {
if (view.subviews.count > 0) {
[self replaceTextWithLocalizedTextInSubviewsForView:view];
}
if ([view isKindOfClass:[UILabel class]]) {
UILabel* label = (UILabel*)view;
label.text = LOCAL_LANG(label.text);
[label sizeToFit];
}
if ([view isKindOfClass:[UIButton class]]) {
UIButton* button = (UIButton*)view;
[button setTitle:LOCAL_LANG(button.titleLabel.text) forState:UIControlStateNormal];
}
if ([view isKindOfClass:[UITextField class]]) {
UITextField* textField = (UITextField*)view;
[textField setPlaceholder:LOCAL_LANG(textField.placeholder)];
}
}
}
這樣所有Xib中要翻譯的字符又都統(tǒng)一到了localizable.strings中了,然而這必然存在不少的弊端,譬如無法及時預(yù)覽。具體見Tip7
當(dāng)然,開發(fā)者遇到這樣的困難蘋果公司不可能沒有想到,之前使用這樣略黑科技的方法雖然解決了問題,但是其實蘋果官方有更徹底的解決方案。具體見Tip5
4.使用genstrings幫助你生成Localizable.strings
genstrings
是一個命令行工具,可以自動檢測國際化相關(guān)的宏,從而生成對應(yīng)的Localizable.strings文件,對應(yīng)的命令很簡單
find . -name *.m | xargs genstrings -o en.lproj
NSLocalizedString(@"KEY", nil)
的后一個參數(shù)實際上就是服務(wù)于genstrings
的,它的意義就是備注,使用genstrings工具可以按照第一個參數(shù)為KEY
和VALUE
,第二個參數(shù)為Comment
來生成對應(yīng)的鍵值對
比如NSLocalizedString(@"Hello",@"This is a comment")
就能生成
/* This is a comment */
"Hello" = "Hello";
具體的使用方法和效果可以參見視頻https://www.youtube.com/watch?v=1XxxmnDL_ls
關(guān)于代碼中字符串的國際化可以參考博客:字符串本地化,里面有不錯好的實踐
5.國際化資源的導(dǎo)出和導(dǎo)入
沒錯!Xcode自帶國際化資源的導(dǎo)入和導(dǎo)出功能!
不信?
點開Editor
瞧瞧!
有多少公司是直接把Localizable.strings文件拿出來給翻譯人員翻譯的?我司甚至為了方便翻譯寫了個自動抓取放到網(wǎng)頁上填表的工具,但是實際上Xcode已經(jīng)給我們做了很多。
使用Xcode導(dǎo)出的國際化資源是.xliff
格式的文件,很多第三方的翻譯軟件都是支持這個格式的文件的。它能把所有需要國際化的.strings
文件全部倒出成對應(yīng)語言的.xliff
文件,翻譯公司使用翻譯軟件生成其他語言的.xliff
文件,然后開發(fā)者可以使用Xcode自帶的導(dǎo)入功能將其導(dǎo)入。
不止如此!導(dǎo)入的時候系統(tǒng)還會提供工具檢驗是否存在沒有翻譯的字段,檢查diff。


所以如果使用了Xcode自帶的導(dǎo)入導(dǎo)出工具,那么之前提到的Xib情況下生成的.strings文件維護困難的問題也隨之解決了。
6.修改xcscheme來幫助你對特定對國家語言和環(huán)境做調(diào)試
不少同學(xué)可能會有這樣的體驗,假設(shè)你開發(fā)的默認語言是英文,但是想測試下中文環(huán)境下的情況,但是即使系統(tǒng)語言是中文,你使用開發(fā)證書編譯調(diào)試的時候依舊會顯示英文的界面,這是為什么呢?一張圖就能解釋這個問題:

調(diào)試的時候選擇Edit Scheme
可以手動選擇需要調(diào)試的語言和地區(qū)環(huán)境,這樣針對特定語言做調(diào)試的時候你再也不需要手動修改本地語言環(huán)境了。
7.使用Pseudolocalizations提前發(fā)現(xiàn)界面適配問題(國際化適配測試)
之前提過,如果使用Tip3所提過的xib全局替換的黑科技會帶來一定的弊端,其中之一就是無法實時預(yù)覽。那么就來講一講如何對不同的國家語言的界面做實時預(yù)覽。步驟如下
1.選擇對應(yīng)的已經(jīng)做了國際化的.xib或者.storyboard文件
2.View -> Assistant Editor -> Show Assistant Editor
3.點擊Assistant Editor的左上角按鈕點擊選擇最后一個選項preview
4.此時可以看到該界面文件的預(yù)覽圖,右下角會有語言選擇,同時會有一個選項:Double-Length Pseudo-Language檢驗如果是雙倍長度的
能否完整顯示
5.當(dāng)然這個功能還可以預(yù)覽對不同尺寸屏幕的適配
8.保證你的UILabel和UITextField等完美適應(yīng)不同語言的不同長度
利用好
AutoLayout
讓你的UILabel
根據(jù)內(nèi)容自適應(yīng)長度如果對
UILabel
或者UITextField
有硬性的MaxWidth
設(shè)定,達到了最大寬度但是無法顯示全,可以采用AutoShrink
的方案。
針對UILabel可以在xib或者storyboard中設(shè)置AutoShrink屬性,該屬性的作用是如果Label內(nèi)容顯示不下,使用何種策略來適應(yīng),
iOS提供了多種選項,包括按比例縮放、最小字體、減小字間距。當(dāng)然在代碼中可以統(tǒng)一設(shè)置UILabel的adjustsFontSizeToFitWidth、
minimumScaleFactor、allowsDefaultTighteningForTruncation等屬性,這些屬性默認都是NO。
針對UITextField,也有一個Min Font Size的屬性可以調(diào)整
配合Pseudolocalizations提早發(fā)現(xiàn)界面溢出的問題
特別需要注意的地方可以提前跟翻譯人員說明,控制字符長度
9.讓你的應(yīng)用適配不同的布局方向
事先聲明,這是個大坑
獲取大部分開發(fā)者都不會遇到過布局方向
這個術(shù)語,提起這個我突然想問個問題:有沒有人有過疑問,在AutoLayout
中leading
、trailing
和left
和right
到底有什么區(qū)別?明明設(shè)置的時候表現(xiàn)是一模一樣的。
在Xcode中,leading
和trailing
是被推薦的,最好的證明就是即使一般人永遠都不會在意他們的區(qū)別,拖出來的布局永遠都不會出現(xiàn)left
和right
的字段,為什么呢?官方的解釋是這樣的:
When adding constraints, use the attributes leading and trailing for horizontal constraints.
For left-to-right languages, such as English, the attributes leading and trailing are
equivalent to left and right. For right-to-left language, such as Hebrew or Arabic, leading
and trailing are equivalent to right and left. The leading and trailing attributes are the
default values for horizontal constraints.
用中文講就是leading
和trailing
會根據(jù)屏幕的布局方向做不同的表現(xiàn),在從左到右的布局中,它的效果會和left
和right
一樣,在從右到左的布局中(比如阿拉伯語、錫伯語)leading
相當(dāng)于right
,trailing
相當(dāng)于left
。
想看看具體效果如何,可以把系統(tǒng)語言設(shè)為阿拉伯文看看效果。
值得注意的是:iOS9在原先基礎(chǔ)上對從右到左的布局做了更徹底的適配,這會是的使用iOS8的SDK編譯的包在iOS9阿拉伯環(huán)境下會有奇怪的表現(xiàn),最簡單的解決方法就是使用iOS9的SDK重新編譯。
然而,在開發(fā)中,你會遇到各種各樣的情況,下面我列舉幾種容易遇到的問題:
1.不是所有的界面你都希望它要根據(jù)系統(tǒng)的屏幕方向做適配
舉個簡單的例子,如果你的主頁是一個內(nèi)容寬度為屏幕的2.5倍的scrollView,那么在從右到左的的環(huán)境中,整個界面就完全亂了套了(你可以想象一下)。
最簡單的解決方案,如果這個界面你不希望它根據(jù)系統(tǒng)的布局方向做適配,那么你就把它的所有的leading
和trailing
約束換成left
和right
,干脆的做法就是使用源碼方式打開xib文件,做全局替換,對應(yīng)的,很多界面還是根據(jù)系統(tǒng)自動適配方向?qū)τ诋?dāng)?shù)厝烁押?,那就繼續(xù)使用leading
和left
,所以需要靈活使用。
2.系統(tǒng)的界面適配并不會自動給你的UILabel和UITextField做Alignment的適配
當(dāng)然,我不確定這個玩意兒能否可以全局設(shè)置,之前花了一點時間還是沒有找到最佳的方案。的確,坑爹的就是即使界面布局左右顛倒了,但是界面元素中的內(nèi)容缺沒有做更深層次的適配,這點很多時候不得不手動適配。所以你需要手動獲取當(dāng)前系統(tǒng)語言,并做相應(yīng)處理。可以使用AOP做全局的替換,也可以手動調(diào)用Category中的方法去做fix,具體我就不貼代碼了
3.參數(shù)的位置
假設(shè)一段字符串需要顯示:"Hello %@",name
,然而在其他語言中可能是%@,balabalala,name
,怎么辦!?
一個處理方案就是在localizable.strings
做參數(shù)話而不是直接在代碼中,這是一個好的習(xí)慣。當(dāng)然在阿拉伯環(huán)境中還是會遇到一個坑爹的情況,因為阿拉伯文是從右到左的閱讀順序,而中文、英文不是。舉個最坑爹的你無法相信的例子:
"一段阿拉伯文 %@",name"
和 "%@ 一段阿拉伯文",name"
在界面上顯示的結(jié)果是一樣的,name永遠在最右邊,然而實際的要求就是name需要在最左邊。
后來為了解決這個參數(shù)的問題,我使用了一個很黑很黑的方法:
- (void)fixArgumentPositionIfNeed {
//如果不是阿拉伯文
return;
NSString *replacedStr = [NSString stringWithFormat:@"?%@",self.text];
NSMutableAttributedString *attributeReplacedStr = [[NSMutableAttributedString alloc] initWithString:replacedStr attributes:nil];
[attributeReplacedStr addAttributes:@{NSForegroundColorAttributeName:[UIColor clearColor]} range:NSMakeRange(0, 1)];
[self setAttributedText:attributeReplacedStr];
}
思路就是在字符串的前面再加入一個阿拉伯文字符,然后再把它設(shè)為隱藏。。。。。
10.手動獲取系統(tǒng)語言和地區(qū)環(huán)境
盡管iOS中很多國際化的問題都是直接項目適配并且系統(tǒng)幫忙處理的,但是還是會有不少情況需要手動獲取系統(tǒng)當(dāng)前的語言環(huán)境,并針對當(dāng)前語言環(huán)境做判斷做相應(yīng)的處理。具體獲取語言的代碼如下:
+ (NSString *)systemLanguage {
NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSArray* languages = [defs objectForKey:@"AppleLanguages"];
NSString* preferredLang = [languages objectAtIndex:0];
return preferredLang;
}
其中languages
是一個數(shù)組,數(shù)組中成員就是你的首選語言
,其中第一個就是系統(tǒng)當(dāng)前語言
不過不知大家是否還記得,iOS9出來后微博、微信等應(yīng)用(這些應(yīng)用都有一個共同的特點:可以在應(yīng)用內(nèi)手動設(shè)置系統(tǒng)語言)都出現(xiàn)了語言錯亂的問題,問題的根源就是iOS9系統(tǒng)中這個方法取出來的值變了。
在iOS9以前,如果當(dāng)前語言版本是英文,那就是en
,阿拉伯文就是ar
,中文就是zh-Hans
,然而在iOS9中,假設(shè)我當(dāng)前的語言是簡體中文,當(dāng)前的地區(qū)是埃及,取出來的值就是:zh-Hans-EG
,沒錯,在iOS9新的API中,這個方法返回的參數(shù)會帶上地區(qū)的后綴。所以原先對語言的判斷就不能單單的isEquasToString
了,起碼要使用hasPrefix
方法或者做一下split
。
那么問題來了!對于很多App來說系統(tǒng)語言是一個很重要的東西,為什么iOS9的這次升級蘋果官方會在這么一個地方做改動呢。我猜呀,蘋果官方肯定是不支持App直接使用系統(tǒng)語言的這個參數(shù)來控制本地化,更希望App的本地化工作能夠遵循系統(tǒng)。
總結(jié)
國際化這個工作雖然邊緣但是不得不做,而且如果做的不夠?qū)I(yè)很容易花費很多的人力,也達不到最好的效果。-.- 表示我們現(xiàn)在就在填之前埋下的坑。。所以還是趁此機會跟大家分享一下。
最后,很抱歉因為涉及到的東西比較多,所以不能做到圖文并茂,之后我會專門寫一個小的項目盡量把我之前提到過的Tips盡量都濃縮在里面,盡量提供一個最佳時間的Demo。