10 Tips For Internationalization and Localization

關(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 nameYES

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)容會作為basekeyvalue,修改其他語言的.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ù)為KEYVALUE,第二個參數(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ù)語,提起這個我突然想問個問題:有沒有人有過疑問,在AutoLayoutleading、trailingleftright到底有什么區(qū)別?明明設(shè)置的時候表現(xiàn)是一模一樣的。

在Xcode中,leadingtrailing是被推薦的,最好的證明就是即使一般人永遠都不會在意他們的區(qū)別,拖出來的布局永遠都不會出現(xiàn)leftright的字段,為什么呢?官方的解釋是這樣的:

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.

用中文講就是leadingtrailing會根據(jù)屏幕的布局方向做不同的表現(xiàn),在從左到右的布局中,它的效果會和leftright一樣,在從右到左的布局中(比如阿拉伯語、錫伯語)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)的布局方向做適配,那么你就把它的所有的leadingtrailing約束換成leftright,干脆的做法就是使用源碼方式打開xib文件,做全局替換,對應(yīng)的,很多界面還是根據(jù)系統(tǒng)自動適配方向?qū)τ诋?dāng)?shù)厝烁押?,那就繼續(xù)使用leadingleft,所以需要靈活使用。

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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容