** 原文發(fā)表在:https://www.xiaolei0808.com/2016/04/24/Localized-iOS/
https://blog.xiaolei.kim/ios--e5-9b-bd-e9-99-85-e5-8c-96-e5-bc-80-e5-8f-91/**
https://blog.xiaolei.kim/2016/04/25/iOSInternationalization.html
一個(gè)iOS應(yīng)用程序源织,如果想要在多個(gè)國(guó)家和地區(qū)的AppleStore上架,是很簡(jiǎn)單的一件事情讼载。如果想要每個(gè)國(guó)家和地區(qū)的用戶都能獲得良好的使用體驗(yàn),首先需要做的一件事情就是能夠讓用戶打開(kāi)App的第一時(shí)間,看到的是自己熟悉的語(yǔ)言。也就是說(shuō)App能夠根據(jù)用戶當(dāng)前所使用的語(yǔ)言或者用戶手動(dòng)選擇的語(yǔ)言,實(shí)時(shí)的改變App內(nèi)的語(yǔ)言狰贯。這就是iOS國(guó)際化所要實(shí)現(xiàn)的目的也搓。
國(guó)際化開(kāi)發(fā)的兩種情況
1.在App開(kāi)發(fā)之初赏廓,就已經(jīng)有了國(guó)際化開(kāi)發(fā)的Feature,這種情況下進(jìn)行國(guó)際化是很容易的傍妒,只要在開(kāi)發(fā)過(guò)程中把需要國(guó)際化的字符串進(jìn)行簡(jiǎn)單的處理即可幔摸。
2.已經(jīng)開(kāi)發(fā)完畢,開(kāi)發(fā)之初并沒(méi)有進(jìn)行國(guó)際化適配颤练,突然來(lái)需求說(shuō)需要進(jìn)行國(guó)際化既忆,此時(shí)面對(duì)項(xiàng)目中成百上千的字符串,內(nèi)心一定是崩潰的嗦玖。
接下來(lái)患雇,我會(huì)分別對(duì)這兩種情況來(lái)說(shuō)一下開(kāi)發(fā)過(guò)程。
注:演示所使用的Xcode版本為7.3宇挫。
新啟動(dòng)的工程
項(xiàng)目國(guó)際化配置
1.找到Project的Localizations選項(xiàng)苛吱,點(diǎn)擊加號(hào)(+),添加需要國(guó)際化的語(yǔ)言(一般工程中默認(rèn)支持英文器瘪,為了方便演示翠储,我只添加了中文簡(jiǎn)體支持)。此時(shí)會(huì)彈出一個(gè)選擇框橡疼,選擇你所要支持的Xib文件或StoryBoard文件援所。不需要支持Xib或StoryBoard文件則不勾選。
這里Use Base Internationalization開(kāi)啟狀態(tài)下欣除,每個(gè)國(guó)際化資源文件會(huì)有個(gè)Base選項(xiàng)住拭,主要針對(duì)String,Xib历帚,Storyboard作為一個(gè)基礎(chǔ)的模板滔岳。
2.創(chuàng)建多語(yǔ)言文件,一般命名為L(zhǎng)ocalizable.string抹缕。如果在開(kāi)發(fā)過(guò)程中不指定文件名澈蟆,系統(tǒng)會(huì)默認(rèn)在Bundle中尋找這個(gè)名稱的文件。當(dāng)然卓研,也可以任意命名趴俘,在開(kāi)發(fā)過(guò)程中手動(dòng)指定一下文件名即可睹簇。
3.找到并選中剛剛新建的Localizable.string文件,點(diǎn)擊Inspector下Localization選項(xiàng)下的Localize按鈕寥闪,任意選擇一個(gè)語(yǔ)言(我選擇的是English)太惠,然后點(diǎn)擊Localize按鈕,此時(shí)Localization選項(xiàng)會(huì)出現(xiàn)應(yīng)用支持的語(yǔ)言列表疲憋,選擇需要國(guó)際化的語(yǔ)言凿渊,Localizable.string文件下則會(huì)多出和所選擇語(yǔ)言對(duì)應(yīng)的子文件。
代碼中對(duì)國(guó)際化內(nèi)容的適配
1.我在ViewController中初始化了一個(gè)Label缚柳,并用NSLocalized宏對(duì)這個(gè)Label的字符串進(jìn)行國(guó)際化適配埃脏。
- (void)viewDidLoad {
[super viewDidLoad];
UILabel *localizationLabel = [[UILabel alloc] initWithFrame:CGRectMake(150, 200, 100, 20)];
[self.view addSubview:localizationLabel];
//利用NSLocalizedString宏對(duì)字符串進(jìn)行國(guó)際化適配
localizationLabel.text = NSLocalizedString(@"Hello", @"description for this key.");
}
既然用到了NSLocalizedString宏,我們就有必要了解一下這個(gè)宏到底干了什么秋忙。
NSLocalizedString宏探秘
點(diǎn)進(jìn)宏定義文件:
#define NSLocalizedString(key, comment) \
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
[bundle localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \
[bundle localizedStringForKey:(key) value:(val) table:(tbl)]
我們會(huì)發(fā)現(xiàn)國(guó)際化相關(guān)所定義的4個(gè)宏彩掐。
- NSLocalizedString(key, comment)
NSLocalizedString其實(shí)是從mainBundle中默認(rèn)讀取了Localizable.string中的key所對(duì)應(yīng)的value。comment參數(shù)則是對(duì)key的描述灰追,有利于翻譯人員理解這個(gè)key所適用的場(chǎng)景堵幽。
- NSLocalizedStringFromTable(key, tbl, comment)
NSLocalizedStringFromTable則是從mainBundle中讀取指定多語(yǔ)言文件中的key所對(duì)應(yīng)的value。tbl參數(shù)就是用于指定多語(yǔ)言文件名的參數(shù)弹澎。
- NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment)
NSLocalizedStringFromTableInBundle會(huì)讀取指定Bundle中所指定的多語(yǔ)言文件中的key所對(duì)應(yīng)的value朴下。bundle參數(shù)就是用于傳入所指定的Bundle。
- NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)
NSLocalizedStringWithDefaultValue則是從指定Bundle中讀取指定多語(yǔ)言文件中的key所對(duì)應(yīng)的value苦蒿,如果取不到對(duì)應(yīng)的value殴胧,允許指定一個(gè)默認(rèn)的value。val參數(shù)就是用于傳入默認(rèn)的value值刽肠。
2.了解了各個(gè)宏定義所代表的含義溃肪,我們繼續(xù)往下寫(xiě)。
在Localizable.strings(English)中加入如下鍵值對(duì):
"Hello" = "Hello";
在Localizable.strings(Chinese(Simplified))中加入如下鍵值對(duì):
"Hello" = "你好";
編譯運(yùn)行:
系統(tǒng)語(yǔ)言為英文時(shí)顯示效果:
系統(tǒng)語(yǔ)言為中文時(shí)顯示效果:
可見(jiàn)音五,文本會(huì)隨著系統(tǒng)語(yǔ)言的改變而改變惫撰。至此,一個(gè)簡(jiǎn)單的可以跟隨系統(tǒng)語(yǔ)言切換的Demo就完成了躺涝。
已開(kāi)發(fā)多個(gè)版本的工程
工程開(kāi)發(fā)了許久厨钻,代碼也寫(xiě)了幾萬(wàn)行了,這時(shí)Boss突然說(shuō)要進(jìn)行國(guó)際化適配坚嗜。
What the ***!!!
淡定夯膀,除了女朋友,還有什么能難道程序猿哥哥的事情呢苍蔬?
下面開(kāi)始诱建。
添加應(yīng)用需要國(guó)際化的語(yǔ)言
在Project的Localizations選項(xiàng),點(diǎn)擊加號(hào)(+)碟绑,添加需要國(guó)際化的語(yǔ)言(一般工程中默認(rèn)支持英文俺猿,為了方便演示茎匠,我只添加了中文簡(jiǎn)體支持)。
批量替換字符串
既然要在已有工程上進(jìn)行適配押袍,首先要做的就是給需要進(jìn)行國(guó)際化的字符串加上NSLocalizedString宏了诵冒。
- 2B程序猿
會(huì)一處一處的修改每個(gè)文件的每處字符串,直到天荒地老谊惭、浩觯枯石爛。
- 非主流程序猿
各種shell圈盔、Python腳本齊上陣豹芯。
- 文藝程序猿
Xcode大法好。正則大法好药磺。
我選擇第三種-告组。-
首先煤伟,我們需要一個(gè)正則癌佩,去匹配工程中所有需要替換的文本。如果你不太了解正則便锨,點(diǎn)我點(diǎn)我Nд蕖!放案。
下面這個(gè)正則可以匹配到所有符合OC字符串格式的包含有中文的字符串姚建。如果你用Swift,請(qǐng)去掉@
吱殉。
@"[^"]*[\u4E00-\u9FA5]+[^"\n]*?"
然后掸冤,重點(diǎn)來(lái)了。Command+Shift+F友雳,進(jìn)入全局搜索引擎稿湿,切換為Replace模式,并把匹配模式改為Regular Expression押赊。
在搜索條件里輸入(@"[^"]*[\u4E00-\u9FA5]+[^"\n]*?")
饺藤,在下面替換內(nèi)容里輸入NSLocalizedString($1, nil)
。此處正則表達(dá)式兩邊加括號(hào)的目的是為了能夠在替換時(shí)用$1
獲取原有字符串的值流礁,在替換時(shí)把原有值放入宏定義內(nèi)key的位置涕俗。然后,搜索神帅,可以看到搜索結(jié)果再姑,點(diǎn)擊Replace All
,即可完成替換找御。
生成多語(yǔ)言文件
替換完了原有字符串元镀,下面就是生成多語(yǔ)言文件了谜嫉。作為一個(gè)文藝程序猿,我是不可能一處一處的把項(xiàng)目中的字符串挑出來(lái)再寫(xiě)到Localizable.strings文件里的凹联。
好在Xcode自帶了一個(gè)命令行工具genstrings沐兰。我們可以利用這個(gè)工具生成所需的多語(yǔ)言文件。
首先蔽挠,我們需要先新建所需語(yǔ)言的文件夾住闯。
cd 工程目錄
mkdir en.lproj
mkdir zh-Hans.lproj
然后,遍歷所有.m
文件澳淑,根據(jù)每個(gè)文件內(nèi)的需要國(guó)際化的字符串生成key和value比原。
find . -name *.m | xargs genstrings -o en.lproj
find . -name *.m | xargs genstrings -o zh-Hans.lproj
此時(shí),en.lproj
和zh-Hans.lproj
文件夾中就應(yīng)該有了相應(yīng)的Localizable.string文件了杠巡。
我們把這兩個(gè)文件夾拖到工程里量窘,然后在相應(yīng)的Localizable.strings文件中,修改每個(gè)key所對(duì)應(yīng)的value值就行了(這個(gè)應(yīng)該是翻譯干的事兒)氢拥。
至此蚌铜,項(xiàng)目的國(guó)際化已經(jīng)適配完成。
應(yīng)用內(nèi)動(dòng)態(tài)更新語(yǔ)言
以上所做的國(guó)際化嫩海,只會(huì)跟隨系統(tǒng)語(yǔ)言進(jìn)行改變冬殃,并且需要Kill掉App重新打開(kāi)才會(huì)有效果。如果有一種需求叁怪,在應(yīng)用內(nèi)有一個(gè)語(yǔ)言列表审葬,只要選中其中一種語(yǔ)言,點(diǎn)擊確定后就能立刻更新應(yīng)用內(nèi)所有界面的語(yǔ)言奕谭,此時(shí)該怎么做呢涣觉?目前有好多大廠應(yīng)用,比如微信血柳、支付寶等都支持這種切換方式(微信和支付寶的實(shí)現(xiàn)方式不太一樣官册,咱們稍后再敘)。
替換系統(tǒng)國(guó)際化宏定義
由系統(tǒng)宏定義可知混驰,如果使用了系統(tǒng)的NSLocalizedString宏攀隔,它是默認(rèn)是從mainBundle中讀取Localizable.strings中的value的。使用其他的3個(gè)宏則可以指定文件名或是Bundle栖榨,但是使用起來(lái)也是不太靈活昆汹。為了使用起來(lái)更加方便靈活,我們可以自定義一個(gè)國(guó)際化的宏婴栽,實(shí)現(xiàn)其內(nèi)部的邏輯满粗。
下面,給出一種我自己的實(shí)現(xiàn)方式愚争。
//
// LELocalizedHelper.h
// Created by 金小白 on 16/4/23.
// Copyright ? 2016年 xiaolei.jin. All rights reserved.
// 國(guó)際化
#import <Foundation/Foundation.h>
#define LELocalizedString(key) [[LELocalizedHelper standardHelper] stringWithKey:key]
@interface LELocalizedHelper : NSObject
+ (instancetype)standardHelper;
- (NSBundle *)bundle;
- (NSString *)currentLanguage;
- (void)setUserLanguage:(NSString *)language;
- (NSString *)stringWithKey:(NSString *)key;
@end
//
// LELocalizedHelper.m
// Created by 金小白 on 16/4/23.
// Copyright ? 2016年 xiaolei.jin. All rights reserved.
// 國(guó)際化
#import "LELocalizedHelper.h"
static NSBundle *_bundle;
static NSString *const kUserLanguage = @"kUserLanguage";
@implementation LELocalizedHelper
+ (instancetype)standardHelper {
static LELocalizedHelper *helper;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
helper = [[LELocalizedHelper alloc] init];
});
return helper;
}
- (instancetype)init {
if (self = [super init]) {
if (!_bundle) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *userLanguage = [defaults valueForKey:kUserLanguage];
//用戶未手動(dòng)設(shè)置過(guò)語(yǔ)言
if (userLanguage.length == 0) {
NSArray *languages = [[NSBundle mainBundle] preferredLocalizations];
NSString *systemLanguage = languages.firstObject;
userLanguage = systemLanguage;
}
if ([userLanguage isEqualToString:@"zh-HK"] || [userLanguage isEqualToString:@"zh-TW"]) {
userLanguage = @"zh-Hant";
}
NSString *path = [[NSBundle mainBundle] pathForResource:userLanguage ofType:@"lproj"];
_bundle = [NSBundle bundleWithPath:path];
}
}
return self;
}
- (NSBundle *)bundle {
return _bundle;
}
- (NSString *)currentLanguage {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *userLanguage = [defaults valueForKey:kUserLanguage];
if (userLanguage.length == 0) {
NSArray *languages = [[NSBundle mainBundle] preferredLocalizations];
NSString *systemLanguage = languages.firstObject;
return systemLanguage;
}
return userLanguage;
}
- (void)setUserLanguage:(NSString *)language {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:@"lproj"];
_bundle = [NSBundle bundleWithPath:path];
[defaults setValue:language forKey:kUserLanguage];
[defaults synchronize];
}
- (NSString *)stringWithKey:(NSString *)key {
if (_bundle) {
return [_bundle localizedStringForKey:key value:nil table:@"Localizable"];
}else {
return NSLocalizedString(key, nil);
}
}
@end
使用的時(shí)候映皆,只需把原來(lái)NSLocalizedString(key, nil)的地方替換為L(zhǎng)ELocalizedString(key)即可挤聘。此時(shí)如果用戶沒(méi)有手動(dòng)更改過(guò)語(yǔ)言,默認(rèn)是跟隨系統(tǒng)語(yǔ)言變化的捅彻。
動(dòng)態(tài)刷新所有頁(yè)面
替換完了宏定義组去,下面就是全局刷新界面了。實(shí)現(xiàn)方式上來(lái)說(shuō)步淹,我目前共想出了3種从隆,實(shí)現(xiàn)方式上來(lái)說(shuō)各有優(yōu)劣。
重新載入rootViewController
這個(gè)方法應(yīng)該是編碼成本最低的方法了缭裆,只需要把原有的rootViewController移除并清空键闺,然后重新設(shè)置一遍rootViewController就行了。但是這種實(shí)現(xiàn)方式會(huì)重新加載已經(jīng)原來(lái)已經(jīng)加載好的所有界面澈驼。
語(yǔ)言改變發(fā)送通知
在用戶切換語(yǔ)言的時(shí)候辛燥,發(fā)送一個(gè)通知,然后在各個(gè)界面接收通知缝其,更新所有需要更新的文本即可挎塌。這種方法適合新建的項(xiàng)目,在代碼編寫(xiě)之初就預(yù)留好更新文本的方法氏淑,收到通知后調(diào)用此方法就行勃蜘。如果已經(jīng)是一個(gè)已上線項(xiàng)目,則改動(dòng)成本比較高假残,需要改動(dòng)的地方比較多。
.h暴露一個(gè)更新文字的方法
在用戶切換語(yǔ)言的時(shí)候炉擅,遍歷所有已經(jīng)加載的界面辉懒,調(diào)用更新文字的方法。這種實(shí)現(xiàn)也是比較適合新建的項(xiàng)目谍失,在代碼編寫(xiě)之初就預(yù)留好更新文本的方法眶俩。如果項(xiàng)目已上線,則改動(dòng)成本較高快鱼。
上文說(shuō)過(guò)颠印,微信和支付寶的實(shí)現(xiàn)方式不太一樣。根據(jù)我的觀察和判斷抹竹,微信使用的是上面的第一種方法线罕,也就是重新載入rootViewController的方法。因?yàn)槲⑿徘袚Q完語(yǔ)言之后窃判,迅速的切換到了根頁(yè)面钞楼,又迅速的push到了設(shè)置界面,有一個(gè)界面跳躍的過(guò)程袄琳,而且原來(lái)所有的已經(jīng)滑到底部的列表全部都重置到了最頂部询件。根據(jù)我的推斷燃乍,微信項(xiàng)目在編寫(xiě)之初也是沒(méi)有考慮到后期國(guó)際化的需求,迫于過(guò)大的改動(dòng)成本宛琅,只好使用第一種方法刻蟹。而支付寶切換過(guò)程就比較完美,實(shí)現(xiàn)了無(wú)縫切換嘿辟,和有可能是前期編碼過(guò)程中就預(yù)留了更新文本的方法座咆,后期直接調(diào)用就可以(不排除后期投入精力進(jìn)行重構(gòu)的可能)。
以上就是全部的iOS文字國(guó)際化的過(guò)程仓洼。
圖片國(guó)際化
網(wǎng)絡(luò)圖片國(guó)際化
很簡(jiǎn)單介陶,只要在請(qǐng)求的時(shí)候根據(jù)不同的語(yǔ)言環(huán)境調(diào)用不同的接口就可以。
本地圖片國(guó)際化
本地圖片的國(guó)際化也有兩種實(shí)現(xiàn)方式。
根據(jù)不同語(yǔ)言指定不同的圖片名稱
這種方法可以在初始化圖片名稱的時(shí)候使用國(guó)際化宏定義爆土,然后在對(duì)應(yīng)語(yǔ)言的多語(yǔ)言文件中沾鳄,根據(jù)圖片對(duì)應(yīng)的key指定相應(yīng)語(yǔ)言的value即可。
利用Xcode生成
這種方法不用對(duì)代碼進(jìn)行改動(dòng)某残,只需要在工程目錄中新建一個(gè)Group,放入所有需要國(guó)際化的原有圖片陵吸。然后選中圖片玻墅,點(diǎn)擊右側(cè)的Localization,選中需要支持的語(yǔ)言即可在原有圖片下生成選中語(yǔ)言所對(duì)應(yīng)的子文件壮虫。然后替換掉對(duì)應(yīng)語(yǔ)言的子圖片即可澳厢。
注:圖片名必須和原有名字一致
這樣,在更改系統(tǒng)語(yǔ)言的時(shí)候囚似,圖片也會(huì)隨之改變了剩拢。
以上就是我總結(jié)的所有的有關(guān)iOS國(guó)際化的操作了,如果有疏漏的地方饶唤,請(qǐng)各位看官多多指正徐伐。共勉。