關(guān)于iOS開發(fā)中的國際化(也可稱為多語言)在網(wǎng)上的文章多如牛毛半夷,不過總結(jié)起來就那么一回事婆廊,不是說他們寫的不好我寫的多好,而是說過于零散巫橄。
現(xiàn)在淘邻,我將結(jié)合實(shí)際場(chǎng)景需求進(jìn)行國際化做法詳解∠婊唬可以肯定的是宾舅,Android的國際化做法大同小異,無非也就是各個(gè)語言版本的文件替換彩倚,我們先來分析下真實(shí)的需求是怎么一回事筹我。
國際化需求:
- 只提供English和Chinese Simplified兩種語言;
- App名稱跟隨系統(tǒng)語言變化帆离;
- 用戶首次打開app時(shí)蔬蕊,app的語言與系統(tǒng)語言保持一致(系統(tǒng)語言為非簡體中文,默認(rèn)app都是英文)用戶手動(dòng)更改語言之后哥谷,之后都記憶用戶選擇的語言岸夯;
- 用戶在App內(nèi)切換語言后,App本身所有文本信息全部替換成對(duì)應(yīng)語言们妥。
根據(jù)需求猜扮,我比較糾結(jié)的地方是,App的靜態(tài)文本數(shù)據(jù)可以存兩份在本地监婶,也就是English一份Chinese Simplified一份旅赢,但請(qǐng)求的API是同時(shí)返回兩份中英文數(shù)據(jù)or分中英文兩個(gè)接口齿桃?如果是要一個(gè)接口同時(shí)返回了中英文兩份數(shù)據(jù),顯然會(huì)加大數(shù)據(jù)包的大小煮盼,其次用戶很有可能從安裝App的那天開始就不再切換App語言源譬,甚至平均幾個(gè)星期才換一次,同時(shí)返回兩份數(shù)據(jù)是否多余孕似,但是這么做幾乎可以達(dá)到“無感知”數(shù)據(jù)源切換踩娘,相當(dāng)于是說,一旦用戶選擇好了要切換語言喉祭,“啪嗒”點(diǎn)了完成养渴,立馬pop掉當(dāng)前頁面,然后整個(gè)App的數(shù)據(jù)源中英文切換可以幾乎用“瞬間完成”來形容泛烙。
如果是分中英文兩個(gè)接口理卑,實(shí)際上就會(huì)出現(xiàn)微信在進(jìn)行語言切換時(shí)的loading菊花,因?yàn)橐匦吕∮⑽陌鏀?shù)據(jù)蔽氨,不過好處是可以減少上一種做法的數(shù)據(jù)包整體大小藐唠。這兩種做法我都有實(shí)踐過,如果你的App是非常固定鹉究,不會(huì)頻繁出現(xiàn)語言切換的需求宇立,那么可以使用第二種;如果App有一天之內(nèi)可能會(huì)頻繁切換多次語言的情況自赔,第一種無疑妈嘹。
經(jīng)過一番探討,雖然要供給拉美绍妨、北美和歐洲的同學(xué)使用润脸,但是不會(huì)出現(xiàn)頻繁切換語言的情況,所以他去,最終我們選擇了第二種解決方案毙驯。先來看一張最終成果gif圖,
從上圖中可以看到其實(shí)并沒有對(duì)數(shù)據(jù)源進(jìn)行切換灾测,因?yàn)楸邸!P惺T始帷:笈_(tái)沒寫完??。
不過也不影響我們的講解蛾号,首先稠项,明確一個(gè)概念,我們能夠做的國際化語言支持iOS系統(tǒng)中自帶的所有語言鲜结,只要你能在系統(tǒng)設(shè)置中找到的語言展运,就能夠?qū)δ愕腁pp做對(duì)應(yīng)版本的國際化適配活逆;其次,每對(duì)App適配一種語言拗胜,就要單創(chuàng)建出一個(gè)語言文件(要不然會(huì)引起沖突)蔗候。OK,我們正式進(jìn)入講解埂软。
首先創(chuàng)建一個(gè)工程
我起名為languageTest
锈遥。
工程初始化
// AppDelegate.m
#import "navOneViewController.h"
#import "navTwoViewController.h"
@interface AppDelegate ()
@property (nonatomic, strong) UITabBarController *rootTabBar;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.rootTabBar = [[UITabBarController alloc]init];
self.rootTabBar.delegate = (id)self;
self.window.rootViewController = self.rootTabBar;
[[UITabBar appearance] setBarTintColor:[UIColor whiteColor]];
navOneViewController *navOneController = [navOneViewController new];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:navOneController];
nav1.title = @"首頁";
navTwoViewController *navTwoController = [navTwoViewController new];
UINavigationController *nav2 = [[UINavigationController alloc] initWithRootViewController:navTwoController];
nav2.title = @"發(fā)現(xiàn)";
self.rootTabBar.viewControllers = @[nav1, nav2];
[self.window makeKeyAndVisible];
return YES;
}
在Appdelegate
中,我們創(chuàng)建一個(gè)具備基本展示功能的tabBar及掛載在其之上的VC勘畔,
// navOneViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self.view addSubview:label];
label.font = [UIFont systemFontOfSize:25];
label.textColor = [UIColor whiteColor];
label.text = @"這是首頁";
}
// navTwoViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self.view addSubview:label];
label.font = [UIFont systemFontOfSize:25];
label.textColor = [UIColor whiteColor];
label.text = @"這是發(fā)現(xiàn)";
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 100, 100)];
[self.view addSubview:button];
[button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor blueColor];
[button setTitle:@"改變語言" forState:UIControlStateNormal];
}
- (void)buttonClick {
}
在各自對(duì)應(yīng)的VC中寫下相關(guān)UI所灸,并預(yù)留相關(guān)Button點(diǎn)擊事件即可。
創(chuàng)建語言文件
創(chuàng)建路徑: file -> new -> file... -> String File炫七,文件名嚴(yán)格命名為——“Localizable”爬立,創(chuàng)建好該文件后,點(diǎn)擊該文件万哪,并打開Xcode的右邊功能區(qū)(不知道應(yīng)該叫啥)侠驯,在Localization功能區(qū)勾選語言版本,如果此時(shí)你并未看到或者只有English可選奕巍,我們需要到PROJECT -> info -> Localization吟策,添加需要的語言。
添加完成后伍绳,會(huì)在之前創(chuàng)建的Localization.string文件下看到多出來的語言文件踊挠,我選擇了English和Chinese Simplified≌Ч穑現(xiàn)在冲杀,我們已經(jīng)可以在對(duì)應(yīng)生成的語言文件中進(jìn)行需要多語言替換的字段編寫了。
// Localizable.string/English文件
"home" = "home";
"homeString" = "I'm home";
"discover" = "discover";
"discoverString" = "I'm discover";
"change" = "change";
// Localizable.string/Chinese(Simplified)文件
"home" = "首頁";
"homeString" = "我是首頁";
"discover" = "發(fā)現(xiàn)";
"discoverString" = "我是發(fā)現(xiàn)";
"change" = "改變語言";
并新建一個(gè)pch文件睹酌,pch文件同樣也是頭文件权谁,不過這是一個(gè)特殊頭文件,是一個(gè)預(yù)編譯文件憋沿,位于該文件中的所有內(nèi)容旺芽,能夠被其他所有源文件共享和訪問,相信你也看出來了辐啄,如果在pch文件中寫了大量的不是必須文件采章,則會(huì)延長編譯期時(shí)間,我們可以在.pch文件中放:
- 全局宏壶辜;
- 整個(gè)工程中都能用上的頭文件悯舟;
- 動(dòng)態(tài)更加當(dāng)前App運(yùn)行的環(huán)境切換相關(guān)宏(debug or release)。
因此砸民,我們需要?jiǎng)?chuàng)建一個(gè)pch文件去存放接下來要在整個(gè)工程中都要用到的判斷語言環(huán)境的中英文宏抵怎。創(chuàng)建一個(gè)pch文件的方式為奋救,file -> new -> file... -> 搜“pch”關(guān)鍵字,創(chuàng)建它反惕。
進(jìn)入工程配置 -> TARGET -> Build Settings -> 搜pch關(guān)鍵詞 -> 在“Apple LLVM 9.0 - Language”下的Prefix Header中尝艘,雙擊輸入你的.pch文件路徑,我寫的是$(SRCROOT)/PrefixHeader.pch
姿染,填寫完畢背亥,回車,會(huì)看到生成的絕對(duì)路徑悬赏,確定pch文件路徑是否正確隘梨。一切都沒問題后,編譯通過即可舷嗡。
在pch文件中轴猎,寫入以下宏定義,
```objc
#define AppLanguage @"appLanguage"
#define PJLocalString(key) \
[[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@",[[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"]] ofType:@"lproj"]] localizedStringForKey:(key) value:@"" table:nil]
首先定義了一個(gè)AppLanguage
宏进萄,推薦大家的命名更加多樣化一些捻脖,因?yàn)镺C并沒有namespace,如果我們的命名過于簡單中鼠,就會(huì)導(dǎo)致和Apple本身自定義的NSUserDefaults默認(rèn)值產(chǎn)生沖突可婶。
PJLocalString(key)
這個(gè)宏“定義”了一個(gè)更長的方法,我們也都明確了一個(gè)概念援雇,在iOS中的每個(gè)國際化語言矛渴,就對(duì)應(yīng)著一個(gè)文件,這個(gè)文件就保存在App沙盒的根目錄中惫搏,我們要做的就是在某個(gè)時(shí)機(jī)替換系統(tǒng)所采用的語言文件即可具温,而PJLocalString(key)
這個(gè)宏所做的事情,就是替換筐赔!先從NSUserDefaults中取出對(duì)應(yīng)的語言key(en還是zh-Hans)铣猩,根據(jù)語言key去索引到對(duì)應(yīng)的.lproj文件,最后把要替換的關(guān)鍵詞傳入茴丰,拋出找到的對(duì)應(yīng)值(我覺得找的這個(gè)過程用的結(jié)構(gòu)應(yīng)該不是hashmap达皿,真的很快。??)
多語言文件有了贿肩,宏也有了峦椰,那怎么用呢?舉個(gè)例子汰规!
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self.view addSubview:label];
label.font = [UIFont systemFontOfSize:25];
label.textColor = [UIColor whiteColor];
label.text = PJLocalString(@"homeString");
}
只需要在多語言文字的地方調(diào)用PJLocalString()
宏汤功,傳入對(duì)應(yīng)key即可。但是此時(shí)運(yùn)行工程控轿,會(huì)發(fā)現(xiàn)啥都沒了冤竹,是因?yàn)槲覀儾⑽磳?duì)NSUserDefaults中做當(dāng)前語言的設(shè)置拂封,這就導(dǎo)致了取出的值為nil。所以鹦蠕,還需要在AppDelegate
文件中設(shè)置初始語言冒签,
if(![[NSUserDefaults standardUserDefaults] objectForKey:AppLanguage]){
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:AppLanguage];
[[NSUserDefaults standardUserDefaults] synchronize];
}
這樣,我們即可完成第一次進(jìn)入App時(shí)初始化基礎(chǔ)語言钟病,如果我們想要實(shí)時(shí)更改呢萧恕?這就需要用到了通知,使用通知機(jī)制去給監(jiān)聽語言設(shè)置改變的監(jiān)聽者進(jìn)行相應(yīng)的處理肠阱,
// 給Appdelegate.m更新以下方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if(![[NSUserDefaults standardUserDefaults] objectForKey:AppLanguage]){
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:AppLanguage];
[[NSUserDefaults standardUserDefaults] synchronize];
}
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.rootTabBar = [[UITabBarController alloc]init];
self.rootTabBar.delegate = (id)self;
self.window.rootViewController = self.rootTabBar;
[[UITabBar appearance] setBarTintColor:[UIColor whiteColor]];
navOneViewController *navOneController = [navOneViewController new];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:navOneController];
nav1.title = PJLocalString(@"home");
navTwoViewController *navTwoController = [navTwoViewController new];
UINavigationController *nav2 = [[UINavigationController alloc] initWithRootViewController:navTwoController];
nav2.title = PJLocalString(@"discover");
self.rootTabBar.viewControllers = @[nav1, nav2];
// 新增監(jiān)聽方法
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeLanguage:) name:@"changeLanguage" object:nil];
[self.window makeKeyAndVisible];
return YES;
}
- (void)changeLanguage:(NSNotification *)notify {
self.rootTabBar.viewControllers[0].tabBarItem.title = PJLocalString(@"home");
self.rootTabBar.viewControllers[1].tabBarItem.title = PJLocalString(@"discover");
}
// navOneViewController.m更新以下方法
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blueColor];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self.view addSubview:label];
label.font = [UIFont systemFontOfSize:25];
label.textColor = [UIColor whiteColor];
label.text = PJLocalString(@"homeString");
// 新增監(jiān)聽方法
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeLanguage:) name:@"changeLanguage" object:nil];
}
- (void)changeLanguage:(NSNotification *)notify {
self.label.text = PJLocalString(@"discoverString");
}
// navTwoViewController.m更新以下方法
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
self.label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 20)];
[self.view addSubview:self.label];
self.label.font = [UIFont systemFontOfSize:25];
self.label.textColor = [UIColor whiteColor];
self.label.text = PJLocalString(@"discoverString");
self.button = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 100, 100)];
[self.view addSubview:self.button];
[self.button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
self.button.backgroundColor = [UIColor blueColor];
[self.button setTitle:PJLocalString(@"change") forState:UIControlStateNormal];
// 新增監(jiān)聽方法
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeLanguage:) name:@"changeLanguage" object:nil];
}
- (void)changeLanguage:(NSNotification *)notify {
self.label.text = PJLocalString(@"discoverString");
[self.button setTitle:PJLocalString(@"change") forState:UIControlStateNormal];
}
- (void)buttonClick {
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:AppLanguage];
[[NSUserDefaults standardUserDefaults] synchronize];
// 同步完NSUserDefault后票唆,發(fā)送語言更改通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"changeLanguage" object:nil];
}
編譯運(yùn)行吧,見證奇跡的時(shí)刻到了~點(diǎn)擊“更改語言”button屹徘,怎么樣走趋,是不是瞬間全都改過了。噪伊。??
但是現(xiàn)在只完成了第一和第四個(gè)需求簿煌,我們接著來完成第三個(gè)需求,“用戶首次打開app時(shí)鉴吹,app的語言與系統(tǒng)語言保持一致(系統(tǒng)語言為非簡體中文姨伟,默認(rèn)app都是英文)用戶手動(dòng)更改語言之后,之后都記憶用戶選擇的語言”豆励。
分析一下夺荒,該需求的重點(diǎn)在于用戶第一次打開App時(shí)整體App語言設(shè)置跟隨系統(tǒng)語言設(shè)置,非簡體中文之外的語言良蒸,都設(shè)置成英文技扼,因此,我們需要對(duì)AppDelegate.m
文件進(jìn)行改造诚啃,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// App第一次啟動(dòng)跟隨系統(tǒng)語言設(shè)置
if(![[NSUserDefaults standardUserDefaults] boolForKey:@"firstLaunch"]){
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"firstLaunch"];
NSArray *allLanguages = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"];
NSString *preferredLanguage = allLanguages[0];
if([preferredLanguage rangeOfString:@"zh-Hans"].location != NSNotFound) {
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:AppLanguage];
} else {
[[NSUserDefaults standardUserDefaults] setObject:@"en" forKey:AppLanguage];
}
}
........
接下來完成最后一個(gè)需求淮摔,把App的名稱也做國際化適配,如果你之前有過在info.plist
文件中修改過App的名字始赎,我們現(xiàn)在要做的事情同樣也是改名字,而且是針對(duì)info.plist
整個(gè)文件做國際化仔燕,同樣新建一個(gè)string file
文件造垛,命名嚴(yán)格填寫為infoPlist.strings
,并且在Xcode的右邊拓展欄中選擇Localizable
晰搀,點(diǎn)擊生成English和Chinese Simplified多語言文件
// 在English中寫下
CFBundleName = "your english name";
CFBundleDisplayName = "your english name";
// 在Chinese Simplified中寫下
CFBundleName = "你的中文名";
CFBundleDisplayName = "你的中文名";
OK五辽,以上就是本篇文章所要表達(dá)的所有內(nèi)容,當(dāng)然這些都是demo級(jí)別的code外恕,如果此文對(duì)你有幫助杆逗,記得對(duì)其進(jìn)行多多改造乡翅!
demo地址:
https://github.com/windstormeye/iOSMorePractices/tree/master/languageTest
原文鏈接:pjhubs.com