天天看點

iOS項目的國際化

轉載自:http://www.jianshu.com/p/5ae148570b98

1.使用InfoPlist.string為你的應用名、權限提醒等配置資訊做國際化

大家都知道,我們使用

Localizable.strings

檔案為代碼中的字元串做國際化,但是實際上還有

InfoPlist.strings

。系統會自動識别

InfoPlist.strings

來對項目的一些配置資訊做國際化,例如:

CFBundleDisplayName 應用名稱

NSHumanReadableCopyright CopyRight

NSCameraUsageDescription 相機權限開啟時候彈框的提示文字

NSContactsUsageDescription 通訊錄權限開啟時候彈框的提示文字

NSLocationWhenInUseUsageDescription 定位權限開啟時候彈框的提示文字

NSMicrophoneUsageDescription 麥克風權限開啟時候彈框的提示文字

NSPhotoLibraryUsageDescription 相冊權限開啟時候彈框的提示文字

NSRemindersUsageDescription    推送權限開啟時候彈框的提示文字

......
           

諸如以上這些配置,無法直接在InfoPlist中直接配置(要做國際化),是以使用InfoPlist.strings來配置。

使用的方法很簡單,建立一個InfoPlist.strings檔案,并且在右側勾選對應的語言即可。

Tip:如果想要對應用名稱做國際化,需要在項目配置裡添加參數

Application has localized display name

YES

2.關于Base Internationlization選項的正确了解以及誤區

開啟項目的國際化選項會有一個開關

Use Base Internationlization

,在

Localizable.strings

中也會有

(Base)

檔案。一開始我很自以為是地以為Base的意思是如果項目并沒有對某個語言做國際化,那麼就使用Base語言,然而經過試驗,發現事實并非是這樣的,隻是自己自作聰明罷了。下面看一下官方對

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來服務的。

這也說明了一個問題,很多你原以為的東西事實上并不是一回事,時間久了你會自然而然地認為那是真理而不是當初的揣測,等真正暴露出為難了就很難發現了。

那麼iOS系統中語言選取的原則究竟是什麼呢?

我舉個例子:假設我的應用使用的開發語言是英語,同時對法語、日語、阿拉伯語做了國際化。然而我的系統語言設定的是中文,那麼打開應用會顯示什麼語言呢? 答案是根據你系統的

首選語言清單

而定,在系統設定-語言設定的界面,有一欄清單叫做

首選語言清單

,假設你目前系統語言是中文,但是之前一次選擇過阿拉伯文為系統語言,那麼此事打開你的應用,将會顯示阿拉伯文。

iOS對應用語言的判定遵循的原則是:

1 檢視應用是否針對系統語言做國際化,如有,選擇系統語言

2.周遊系統對首選語言,如果存在對首選語言有做國際化,選擇該首選語言

3.全部沒有對情況下使用development language

3.針對不同界面開發模式使用不同的國際化方案

純代碼手寫UI

沒什麼好說的,一個Localizable.strings解決所有問題。

使用

NSLocalizedString(@"KEY", nil)

的宏方法,大部分教程中并沒有提過這個方法的第二個參數是何含義,其實大部分情況下也不會用到,是以可以自己再定一個宏來除卻不必要的麻煩:

#define LOCAL_LANG(LangKey) NSLocalizedString(LangKey, nil)

當然這個第二個參數并非沒有用,

Tip4

來闡述這第二個參數的作用。

使用Storyboard

之前講Base Internationlization的時候就提過,它主要是為Storyboard和xib檔案服務的。開啟之後,在xib和storyboard右側有localize選項,相應的處理方法和strings檔案一樣,選擇國際化後會生成對應的strings檔案,Xcode會抽出xib和Storyboard中所用使用的字元,界面上顯示的内容會作為

base

key

value

,修改其他語言的.strings檔案就給Storyboard做了國際化。

使用Xib

Storyboard和Xib在做國際化中有一個很大的差別在于,一個Storyboard中可以包含多個視圖,多個視圖的字元串都可以集中在一個Storyboard.strings中,但是Xib往往是一個視圖。當然Xib也可以使用同樣的方法做國際化,但是如果一個項目中有30個xib,項目需要有8種語言,那就意味着需要維護30*8=240個strings檔案,然而很多情況下每個檔案裡隻有少數幾個字元,這樣的維護成本太大了。

針對Xib的國際化,可以使用如下方法:

直接在xib中用Localization.strings中的鍵作為值使用,然後對于需要國際化的view,統一調用如下方法:

+ (void)replaceTextWithLocalizedTextInSubviewsForView:(UIView*)targetView {
    for (UIView* view in targetView.subviews) {
        if (view.subviews.count > ) {
            [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中要翻譯的字元又都統一到了localizable.strings中了,然而這必然存在不少的弊端,譬如無法及時預覽。具體見

Tip7

當然,開發者遇到這樣的困難蘋果公司不可能沒有想到,之前使用這樣略黑科技的方法雖然解決了問題,但是其實蘋果官方有更徹底的解決方案。具體見Tip5

4.使用genstrings幫助你生成Localizable.strings

genstrings

是一個指令行工具,可以自動檢測國際化相關的宏,進而生成對應的Localizable.strings檔案,對應的指令很簡單

find . -name *.m | xargs genstrings -o en.lproj

NSLocalizedString(@"KEY", nil)

的後一個參數實際上就是服務于

genstrings

的,它的意義就是備注,使用genstrings工具可以按照第一個參數為

KEY

VALUE

,第二個參數為

Comment

來生成對應的鍵值對

比如

NSLocalizedString(@"Hello",@"This is a comment")

就能生成

/ This is a comment /

"Hello" = "Hello";

具體的使用方法和效果可以參見視訊https://www.youtube.com/watch?v=1XxxmnDL_ls

關于代碼中字元串的國際化可以參考部落格:字元串本地化,裡面有不錯好的實踐

5.國際化資源的導出和導入

沒錯!Xcode自帶國際化資源的導入和導出功能!

不信?

點開

Editor

瞧瞧!

有多少公司是直接把Localizable.strings檔案拿出來給翻譯人員翻譯的?我司甚至為了友善翻譯寫了個自動抓取放到網頁上填表的工具,但是實際上Xcode已經給我們做了很多。

使用Xcode導出的國際化資源是

.xliff

格式的檔案,很多第三方的翻譯軟體都是支援這個格式的檔案的。它能把所有需要國際化的

.strings

檔案全部倒出成對應語言的

.xliff

檔案,翻譯公司使用翻譯軟體生成其他語言的

.xliff

檔案,然後開發者可以使用Xcode自帶的導入功能将其導入。

不止如此!導入的時候系統還會提供工具檢驗是否存在沒有翻譯的字段,檢查diff。

iOS項目的國際化
iOS項目的國際化

是以如果使用了Xcode自帶的導入導出工具,那麼之前提到的Xib情況下生成的.strings檔案維護困難的問題也随之解決了。

6.修改xcscheme來幫助你對特定對國家語言和環境做調試

不少同學可能會有這樣的體驗,假設你開發的預設語言是英文,但是想測試下中文環境下的情況,但是即使系統語言是中文,你使用開發證書編譯調試的時候依舊會顯示英文的界面,這是為什麼呢?一張圖就能解釋這個問題:

iOS項目的國際化

調試的時候選擇

Edit Scheme

可以手動選擇需要調試的語言和地區環境,這樣針對特定語言做調試的時候你再也不需要手動修改本地語言環境了。

7.使用Pseudolocalizations提前發現界面适配問題(國際化适配測試)

之前提過,如果使用Tip3所提過的xib全局替換的黑科技會帶來一定的弊端,其中之一就是無法實時預覽。那麼就來講一講如何對不同的國家語言的界面做實時預覽。步驟如下

1.選擇對應的已經做了國際化的.xib或者.storyboard檔案

2.View -> Assistant Editor -> Show Assistant Editor

3.點選Assistant Editor的左上角按鈕點選選擇最後一個選項preview

4.此時可以看到該界面檔案的預覽圖,右下角會有語言選擇,同時會有一個選項:Double-Length Pseudo-Language檢驗如果是雙倍長度的

能否完整顯示

5.當然這個功能還可以預覽對不同尺寸螢幕的适配

8.保證你的UILabel和UITextField等完美适應不同語言的不同長度

  • 利用好

    AutoLayout

    讓你的

    UILabel

    根據内容自适應長度
  • 如果對

    UILabel

    或者

    UITextField

    有硬性的

    MaxWidth

    設定,達到了最大寬度但是無法顯示全,可以采用

    AutoShrink

    的方案。

針對UILabel可以在xib或者storyboard中設定AutoShrink屬性,該屬性的作用是如果Label内容顯示不下,使用何種政策來适應,

iOS提供了多種選項,包括按比例縮放、最小字型、減小字間距。當然在代碼中可以統一設定UILabel的adjustsFontSizeToFitWidth、

minimumScaleFactor、allowsDefaultTighteningForTruncation等屬性,這些屬性預設都是NO。

針對UITextField,也有一個Min Font Size的屬性可以調整

  • 配合Pseudolocalizations提早發現界面溢出的問題
  • 特别需要注意的地方可以提前跟翻譯人員說明,控制字元長度

9.讓你的應用适配不同的布局方向

事先聲明,這是個大坑

擷取大部分開發者都不會遇到過

布局方向

這個術語,提起這個我突然想問個問題:有沒有人有過疑問,在

AutoLayout

leading

trailing

left

right

到底有什麼差別?明明設定的時候表現是一模一樣的。

在Xcode中,

leading

trailing

是被推薦的,最好的證明就是即使一般人永遠都不會在意他們的差別,拖出來的布局永遠都不會出現

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

會根據螢幕的布局方向做不同的表現,在從左到右的布局中,它的效果會和

left

right

一樣,在從右到左的布局中(比如阿拉伯語、錫伯語)

leading

相當于

right

trailing

相當于

left

想看看具體效果如何,可以把系統語言設為阿拉伯文看看效果。

值得注意的是:iOS9在原先基礎上對從右到左的布局做了更徹底的适配,這會是的使用iOS8的SDK編譯的包在iOS9阿拉伯環境下會有奇怪的表現,最簡單的解決方法就是使用iOS9的SDK重新編譯。

然而,在開發中,你會遇到各種各樣的情況,下面我列舉幾種容易遇到的問題:

1.不是所有的界面你都希望它要根據系統的螢幕方向做适配

舉個簡單的例子,如果你的首頁是一個内容寬度為螢幕的2.5倍的scrollView,那麼在從右到左的的環境中,整個界面就完全亂了套了(你可以想象一下)。

最簡單的解決方案,如果這個界面你不希望它根據系統的布局方向做适配,那麼你就把它的所有的

leading

trailing

限制換成

left

right

,幹脆的做法就是使用源碼方式打開xib檔案,做全局替換,對應的,很多界面還是根據系統自動适配方向對于當地人更友好,那就繼續使用

leading

left

,是以需要靈活使用。

2.系統的界面适配并不會自動給你的UILabel和UITextField做Alignment的适配

當然,我不确定這個玩意兒能否可以全局設定,之前花了一點時間還是沒有找到最佳的方案。的确,坑爹的就是即使界面布局左右颠倒了,但是界面元素中的内容缺沒有做更深層次的适配,這點很多時候不得不手動适配。是以你需要手動擷取目前系統語言,并做相應處理。可以使用AOP做全局的替換,也可以手動調用Category中的方法去做fix,具體我就不貼代碼了

3.參數的位置

假設一段字元串需要顯示:

"Hello %@",name

,然而在其他語言中可能是

%@,balabalala,name

,怎麼辦!?

一個處理方案就是在

localizable.strings

做參數話而不是直接在代碼中,這是一個好的習慣。當然在阿拉伯環境中還是會遇到一個坑爹的情況,因為阿拉伯文是從右到左的閱讀順序,而中文、英文不是。舉個最坑爹的你無法相信的例子:

"一段阿拉伯文 %@",name"

 和 

"%@ 一段阿拉伯文",name"

在界面上顯示的結果是一樣的,name永遠在最右邊,然而實際的要求就是name需要在最左邊。

後來為了解決這個參數的問題,我使用了一個很黑很黑的方法:

- (void)fixArgumentPositionIfNeed {
    //如果不是阿拉伯文
    return;
    NSString *replacedStr = [NSString stringWithFormat:@"ل%@",self.text];
    NSMutableAttributedString *attributeReplacedStr = [[NSMutableAttributedString alloc] initWithString:replacedStr     attributes:nil];
    [attributeReplacedStr addAttributes:@{NSForegroundColorAttributeName:[UIColor clearColor]} range:NSMakeRange(, )];
    [self setAttributedText:attributeReplacedStr];
}
           

思路就是在字元串的前面再加入一個阿拉伯文字元,然後再把它設為隐藏。。。。。

10.手動擷取系統語言和地區環境

盡管iOS中很多國際化的問題都是直接項目适配并且系統幫忙處理的,但是還是會有不少情況需要手動擷取系統目前的語言環境,并針對目前語言環境做判斷做相應的處理。具體擷取語言的代碼如下:

+ (NSString *)systemLanguage {
    NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
    NSArray* languages = [defs objectForKey:@"AppleLanguages"];
    NSString* preferredLang = [languages objectAtIndex:];
    return preferredLang;
}
           

其中

languages

是一個數組,數組中成員就是你的

首選語言

,其中第一個就是系統目前語言

不過不知大家是否還記得,iOS9出來後微網誌、微信等應用(這些應用都有一個共同的特點:可以在應用内手動設定系統語言)都出現了語言錯亂的問題,問題的根源就是iOS9系統中這個方法取出來的值變了。

在iOS9以前,如果目前語言版本是英文,那就是

en

,阿拉伯文就是

ar

,中文就是

zh-Hans

,然而在iOS9中,假設我目前的語言是簡體中文,目前的地區是埃及,取出來的值就是:

zh-Hans-EG

,沒錯,在iOS9新的API中,這個方法傳回的參數會帶上地區的字尾。是以原先對語言的判斷就不能單單的

isEquasToString

了,起碼要使用

hasPrefix

方法或者做一下

split

繼續閱讀