Demo下载地址:https://github.com/StarHuiDream/Multilingual
前序:
国际化,多语言,App 本地化。是一个意思,就是让App 支持多种语言。国际化处理其实并不难,本文将讲解国际化的基本解决方案。对app 文案国际化的时候需要注意UI的适配,因为不同的语言环境下,不同的文案的长度是不一样的,所以这个处理起来很麻烦,而且很容易遗漏。
苹果关于 App 国际化的文档
本文的组成:
一、App 名字的国际化
二、申请使用一些系统权限提示语的国际化
三、App 里面文案的国际化
四、App 插件widget (today Extension)的国际化
一、App 名字的国际化
1、给项目添加本地语言包:
将App 要支持的国家或地区的语言在这里添加到工程里面。
添加完之后是这样的:
2、添加语言文件夹
(1)选中项目文件夹,选择New File
(2)选择String File,然后点击Next
(3)将文件夹名改为InfoPlist
3、将刚才导入的语言包导入到多语言文件夹
(1)选中 InfoPlist.strings 然后点击 localize
(2)点击localize之后会有一个弹框提示,直接点击localize就行。
(3)将刚才在第一步添加的多语言文件都打上对勾。如下图。
4、在对应的语言文件里面写上app 的名称
InfoPlist.strings(Chinese (Simplifled))中文简体
// app 的简体中文名字
CFBundleDisplayName = "简体";
InfoPlist.strings(Chinese (Traditional))中文繁体
// app 的繁体中文名字
CFBundleDisplayName = "繁體";
InfoPlist.strings(English)英文
// app 的繁体英文名字
CFBundleDisplayName = "English";
CFBundleDisplayName 是App 在手机桌面上展示的名称。
然后切换手机系统的语言,我们就可以看见在不同语言下App 的名称。
二、申请使用一些系统权限提示语的国际化
iOS 10 之后如果App 想要访问日历和提醒事项以及定位,相册,相机的等的使用权限的话,我们必须在 我们的app 的Info.plist文件中添加相应的关键字以及申请权限时候的提示语。
下面以申请使用定位权限为例:申请使用定位权限需要在工程的Info.plist文件中添加NSLocationWhenInUseUsageDescription ,然后在后面写上申请的理由。如下图:
然后app 在使用定位权限的时候会把我们写的申请理由弹框出来如下图:
但是这种只支持一种语言。如果想做成在不同语言环境下提示不同的语言,需要我们在InfoPlist.strings 里面添加相应的提示语。操作步骤和将app 名字国际化一样。
如下图:
NSLocationWhenInUseUsageDescription = "我们需要你的位置";
然后切换系统语言之后,我们查看权限提示如下图
三、App 里面文案的国际化
内容的国际化,包括页面标题,个个展示元素等等的国际化。
1、添加Localizable.strings文件夹
这个步骤语可以参照第一章,只是把文件夹名命名成Localizable就可以。然后将多语言文件导入文件夹,这一步也可以参照第一章。这里就不赘述了。添加完成之后是这样的。
2、页面内容的国际化主要包括以下三个方面
(1)将国际化文案预填进对应的语言文件
国际化文案由一个key 和对应的文案组成,例如上图,“生如夏花之绚烂,死如秋叶之静美”是key ,后面的“生如夏花之絢爛,死如秋葉之靜美”,是它在中文繁体下对应的文案。
//中文繁体
"生如夏花之绚烂,死如秋叶之静美"="生如夏花之絢爛,死如秋葉之靜美";
//中文简体
"生如夏花之绚烂,死如秋叶之静美"="生如夏花之绚烂,死如秋叶之静美";
//英文
"生如夏花之绚烂,死如秋叶之静美"="Let life be beautiful like summer flowers and death like autumn leaves";
注意:每个文案的key 在三个文件里面要保持一样。
(2)获取用户给app 设置的语言。
NSString * _Nonnull const languageFileKey = @"languageFileKey";
+ (NSString *)fetchLangFileName{
NSString *languageFileNameStr = [[NSUserDefaults standardUserDefaults] stringForKey:languageFileKey];
// (1)这里根据自己的需求处理,
// (2)我的的处理方式是:用户第一次使用app的时候,先获取用户系统的语言,如果用户手机的语言我们做了国际化,我们将用户的手机的当前语言设置成app的语言,如果用户手机系统的语言我们没有做相应的国际化处理,我直接将app的语言设置成英文,
// (3)注意:用这种方式获取的用户语言后面会打个-US(像这样“zh-Hant-US”),但是语言包名后面没这个所以需要处理一下
if (languageFileNameStr == nil || [languageFileNameStr isEqualToString:@""]) {
NSArray* languages = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"];
languageFileNameStr = [languages objectAtIndex:0];
if ([languageFileNameStr containsString:@"zh-Hant"]) {
languageFileNameStr = @"zh-Hant";
}else if ([languageFileNameStr containsString:@"zh-Hans"]){
languageFileNameStr = @"zh-Hans";
}else{
languageFileNameStr = @"en";
}
[STLanguageTool saveUserLocalLang:languageFileNameStr];
}
return languageFileNameStr;
}
+ (void)saveUserLocalLang:(NSString *_Nonnull)langFileName{
[[NSUserDefaults standardUserDefaults] setObject:langFileName forKey:languageFileKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
(3)通过NSLocalizedStringFromTableInBundle将相应的语言渲染出来。
这里我们将这个方法定义成一个宏,这样在开发的时候就方便了
#define STLANG(key) NSLocalizedStringFromTableInBundle(key, @"Localizable", [STLanguageTool localizedBundle], nil)
+ (NSBundle *_Nonnull)localizedBundle{
NSString *langFileNameStr = [STLanguageTool fetchLangFileName];
NSString *path = [[NSBundle mainBundle] pathForResource:langFileNameStr ofType:@"lproj"];
NSBundle *mBundle = [NSBundle bundleWithPath:path];
return mBundle;
}
使用如下:
self.title = STLANG(@"第二个页面");
self.textLabel.text = STLANG(@"生如夏花之绚烂,死如秋叶之静美");
做到这一步,我们已经实现了app 内容的国际化,但是我们会发现这样一个问题:每次切换语言的时候,当前页面的语言是没有发生变化的,原因是,当前切换语言的页面,是已经渲染过后的,所以每次切换语言的时候要对已经渲染过的页面要进行一些特殊处理。我这里采用发送通知的方式对这个问题进行处理。
+ (void)saveUserLocalLang:(NSString *_Nonnull)langFileName{
[[NSUserDefaults standardUserDefaults] setObject:langFileName forKey:languageFileKey];
[[NSUserDefaults standardUserDefaults] synchronize];
// 发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:LangChangeNotification object:nil];
}
// 在已经渲染过的页面注册通知
// 注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(langChange)
name:LangChangeNotification
object:nil];
// 在已经渲染过的页面接受通知
-(void)langChange{
self.title = STLANG(@"第一个页面");
self.contentLabel.text = STLANG(@"生如夏花之绚烂,死如秋叶之静美");
[self.nextBtn setTitle:STLANG(@"下一页") forState:UIControlStateNormal];
}
四、App 插件widget (today Extension)的国际化
1、widget(today Extension)
关于widget(today Extension)的开发可以参考我的另一篇文章 iOS 插件开发之 today widget
2、引入国际化文案
在项目中创建完插件之后,我们前面创建好的语言文件共享到today Extension,选中InfoPlist.string 然后在项目对应的widget 处打上对勾,如下图:
然后以同样的方法将Localizable.strings,引入到widget。
3、widget 名字的国际化
将widge 的 info.plist 中的Bundle display name 对应值修改成${PRODUCT_NAME}(这样就和app 的名称保持一致了)。如下图
${PRODUCT_NAME}
运行结果如下
4、widget 内的国际化
选中我们之前分装好的语言工具类的.m 文件将它引入widget。
引入语言工具之后,我们就可像处理app 的国际化一样对widget 进行国际化。
// 注意这里要根据自己的需求来处理,如果想让widget 语言与App 保持一致的话,需要将用户每次操作选择的语言保存到appGroup 这样,widget 才能访问,
// 我这里的处理是让widget 的语言和手机系统的保持一致。
self.textLabel.text = STSYSLANG(@"生如夏花之绚烂,死如秋叶之静美");