因为个人在开发英语相关的App,所以为了以后更好的用户体验,准备加入语言切换功能,以及App国际化。实现应用内切换语言后无需退出应用即可生效,新安装App时根据用户的手机语言显示对应的App名字以及App内的语言。现在就这一需求进行实现。
关于 NSBundle
Bundle 是一个目录,其中包含了在程序会使用到的资源,包含了如图像、声音、程序中需要用到的文件,甚至是编译好的代码等等。而在实现软件内配置语言的时候就是通过 Bundle 的路径去获取配置文件,根据这个配置文件取出对应的字体渲染到 View 上。
当然,配置程序语言只是 Bundle 的一种用途。还可以用 Bundle 去获取工程中 info.plist 的详细信息,比如
NSString *shortVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; NSString *bundleIdentifier = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]; NSString *bundleDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; NSString *path = [[NSBundle mainBundle] bundlePath]; NSString *resPath = [[NSBundle mainBundle] resourcePath];
|
App名称国际化
配置 Project
点击 PROJECT -> info -> Localizations
这里默认只有 English
点击下方的加号可以添加你想要的语言。其中,zh-Hans
是简体中文, zh-Hant
是繁体中文。
如图所示:
新建 .strings 文件
在与 info.plist
文件同级目录下创建 .strings
文件,Command + N
新建 Strings File
文件,命令为 InfoPlist.strings
。
如图所示:
配置 .strings 文件
创建成功后选中 InfoPlist.strings
文件,点击 Localize...
按钮,左侧弹框中选择语言。
如图所示:
选中后再次勾选上你想要的其他语言
如图所示:
勾选成功后在左侧 InfoPlist.strings
中创建了几个文件,如图:
分别在各自的文件内写入如下代码,其中 iWords
是我的App在英语环境下显示的名称,你只需要在对应的文件内写入对应的名称即可,这样当手机语言变化时,就会显示这些对应文件内的 App 名称。
CFBundleDisplayName = "iWords";
|
到此为止,App 名称的国际化完成。切记创建 .strings
文件时一定要命名为 InfoPlist.strings
。
App 内部实现语言切换
新建并配置 .strings 文件
内部实现语言切换和 App 名称国际化基本一致,首先创建 .strings
配置文件,这里我命名为:DHLocalizable.strings
。
创建成功后依然点击右侧的 Localize...
按钮,并手动勾选其他语言,勾选完成后左侧依旧生成对于的.strings
文件,如图:
创建多语言切换工具类
创建继承于 NSObject
的工具类,命名为:DHLanguageTool
其中 DHLanguageTool.h
中声明基本的方法,包括初始化App语言,当前的语言,设置语言等。
#define DHLocalizedString(key) [[DHLanguageTool bundle] localizedStringForKey:(key) value:@"" table:@"DHLocalizable"] #import <Foundation/Foundation.h> #define DHLanguageKey @"userLanguage" #define DHSimplifiedChinese @"zh-Hans" #define DHTraditionalChinese @"zh-Hant" #define DHEnglish @"en" @interface DHLanguageTool : NSObject * 获取当前资源文件 */ + (NSBundle *)bundle; * 初始化语言文件 */ + (void)initUserLanguage; * 获取应用当前语言 */ + (NSString *)userLanguage; * 设置当前语言 */ + (void)setUserlanguage:(NSString *)language;
|
DHLanguageTool.m
文件中对其进行实现,我这里一共可以设置3种语言,其中包括简体中文,繁体中文以及英文。
#import "DHLanguageTool.h" #import "MainTabBar.h" static DHLanguageTool *currentLanguage; @implementation DHLanguageTool static NSBundle *bundle = nil; + (NSBundle *)bundle{ return bundle; } + (void)initUserLanguage{ NSString *languageString = [[NSUserDefaults standardUserDefaults] valueForKey:DHLanguageKey]; if(languageString.length == 0){ NSArray *languagesArray = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"]; languageString = languagesArray.firstObject; [[NSUserDefaults standardUserDefaults] setValue:languageString forKey:@"userLanguage"]; [[NSUserDefaults standardUserDefaults] synchronize]; } if ([[DHLanguageTool SimplifiedChinese] containsObject:languageString]) { languageString = [[DHLanguageTool SimplifiedChinese] firstObject]; } else if ([[DHLanguageTool english] containsObject:languageString]) { languageString = [[DHLanguageTool english] firstObject]; }else if ([[DHLanguageTool TraditionalChinese] containsObject:languageString]) { languageString = [[DHLanguageTool TraditionalChinese] firstObject]; } else { languageString = [[DHLanguageTool SimplifiedChinese] firstObject]; } NSString *path = [[NSBundle mainBundle] pathForResource:languageString ofType:@"lproj"]; bundle = [NSBundle bundleWithPath:path]; } + (NSArray *)english { return @[@"en"]; } + (NSArray *)SimplifiedChinese{ return @[@"zh-Hans"]; } + (NSArray *)TraditionalChinese{ return @[@"zh-Hant"]; } + (NSString *)userLanguage { NSString *languageString = [[NSUserDefaults standardUserDefaults] valueForKey:DHLanguageKey]; return languageString; } + (void)setUserlanguage:(NSString *)language { if([[self userLanguage] isEqualToString:language]) return; NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]; bundle = [NSBundle bundleWithPath:path]; [[NSUserDefaults standardUserDefaults] setValue:language forKey:DHLanguageKey]; [[NSUserDefaults standardUserDefaults] synchronize]; [UIApplication sharedApplication].keyWindow.rootViewController = nil; if ([UIApplication sharedApplication].keyWindow.rootViewController == nil) { MainTabBar *tabBar = [[MainTabBar alloc] init]; tabBar.selectedIndex = 2; [UIApplication sharedApplication].keyWindow.rootViewController = tabBar; setToast(@"语言切换成功"); } }
|
工具类定义完成后在预编译文件内导入该工具类的头文件。
配置多语言切换 .strings 文件
在刚才创建的 DHLocalizable.strings
下展开并在对应的语言文件内填写需要进行语言切换的字段。如:我的英文文件和中文文件内填写如下:
"查单词" = "Search"; "记单词" = "Write"; "我" = "Me"; "邀请好友" = "Invite friends"; "给我好评" = "To evaluate"; "使用帮助" = "Help center";
|
"查单词" = "查单词"; "记单词" = "记单词"; "我" = "我"; "邀请好友" = "邀请好友"; "给我好评" = "给我好评";
|
注意:其中前边对应 键(key) ,后边对各个语言的值(value).
开始使用并初始化
self.title = DHLocalizedString(@"语言切换");
|
因为我们的工具类中已经定义了宏,所以在需要进行语言切换的控件进行这样的书写,书写的内容为各个语言文件中的 key。
设置完成所有字段后,在 AppDelegate
的 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法里先初始化语言。
[DHLanguageTool initUserLanguage];
|
当用户手机设置了简体中文、繁体中文或者英文后,点击App进入后会显示对应的语言,但如果用户设置了其他语言的话,就默认显示中文了。这里并不是唯一的,具体要适配哪种语言,可以根据实际情况进行设定。
更改其他语言
在切换语言的点击事件内调用以下方法:
[DHLanguageTool setUserlanguage:DHSimplifiedChinese]; [DHLanguageTool setUserlanguage:DHTraditionalChinese]; [DHLanguageTool setUserlanguage:DHEnglish];
|
多语言切换的坑
更改语言后页面的加载逻辑
1、重新载入rootViewController
这个方法应该是编码成本最低的方法了,只需要把原有的rootViewController移除并清空,然后重新设置一遍rootViewController就行了。但是这种实现方式会重新加载已经原来已经加载好的所有界面。
2、语言改变发送通知
在用户切换语言的时候,发送一个通知,然后在各个界面接收通知,更新所有需要更新的文本即可。这种方法适合新建的项目,在代码编写之初就预留好更新文本的方法,收到通知后调用此方法就行。如果已经是一个已上线项目,则改动成本比较高,需要改动的地方比较多。
3、.h暴露一个更新文字的方法
在用户切换语言的时候,遍历所有已经加载的界面,调用更新文字的方法。这种实现也是比较适合新建的项目,在代码编写之初就预留好更新文本的方法。如果项目已上线,则改动成本较高。
注:为了方便,我使用的方法1。
关于本地化语言的宏定义 DHLocalizedString(<#key#>)
系统自带的方法是:NSLocalizedString(<#key#>, <#comment#>)
,这也是一份宏定义:
#define NSLocalizedString(key, comment) \ [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]
|
能看到它调用的是 NSBundle.mainBundle
,而我们在更改语言的工具类里的 bundle
已经更改了。
所以系统的 NSLocalizedString(<#key#>, <#comment#>)
已经失效,必须重写一份宏定义:
#define DHLocalizedString(key) [[DHLanguageTool bundle] localizedStringForKey:(key) value:@"" table:@"DHLocalizable"]
|
1、必须使用自己的类名来调用类方法 [DHLocalizableController bundle]
以获取自己的 bundle
。
2、table 后的参数为 .strings 文件的文件名,若你创建的文件名为 Localizable.strings
,则该参数可为 nil ,系统默认按 Localizable.strings
查找。否则必须配置文件名,且只是文件名,不加 .stringd 后缀。
关于初始化语言 [DHLocalizableController initUserLanguage]
在 initUserLanguage
方法中有这样一段代码来做判断
if ([[DHLanguageTool SimplifiedChinese] containsObject:languageString]) { languageString = [[DHLanguageTool SimplifiedChinese] firstObject]; } else if ([[DHLanguageTool english] containsObject:languageString]) { languageString = [[DHLanguageTool english] firstObject]; }else if ([[DHLanguageTool TraditionalChinese] containsObject:languageString]) { languageString = [[DHLanguageTool TraditionalChinese] firstObject]; } else { languageString = [[DHLanguageTool SimplifiedChinese] firstObject]; }
|
各位可能会对这个判断比较疑惑,在这之前已经有判断了:先获取用户设置的语言,有则使用用户设置的语言,没有则使用系统语言。
然而因为某些原因用户设置过的语言(如:zh-Hans)会在另一个相同工程运行之后将该语言更改为zh-Hans-CZ;或者用户将系统语言设置为日本语或其他语言。
出现以上情况时 DHLocalizedString(<#key#>) 这个方法从 .strings 配置文件里是去不到对应的字体,就会返回空。
后果轻则页面一片空白了,重则直接 crash ,如:
NSArray *arr = @[DHLocalizedString(@"语言切换"),DHLocalizedString(@"意见反馈"),DHLocalizedString(@"加入群组")];
|
1、有一种极端情况,比如:软件需要配置多国语言,很多很多的那一种。在 .strings 文件里配置了许多国家的语言。然而在软件内部只提供中文、英文等某几种语言,其他语言根据系统语言自适应。不想在 initUserLanguage
方法里做一大堆的乱七八糟的判断。只要在 initUserLanguage
的判断方法 else
里使用系统语言:
} else { languageString = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"][0]; }
|
2、比如:就想使用 NSLocalizedString(<#key#>, <#comment#>)
方法。
总归还是有方法使用 NSLocalizedString(<#key#>, <#comment#>)
的。
使用 Category
为 NSBundle
类扩展一个设置语言的方法,并且使用 runtime
为 NSBundle
动态添加一个关于 bundle
的属性,重载 NSBundle.mainBundle
的 localizedStringForKey
方法。目的就是将更改的字体传给 NSLocalizedString(<#key#>, <#comment#>)
映射的 localizedStringForKey
方法返回的 bundle
,使得更改的字体应用到系统上。
好吧,show you the code:
#import "NSBundle+DHLanguage.h" #import <objc/runtime.h> static const NSString *DHBundleKey = @"DHLanguageKey"; @interface BundleEx : NSBundle @end @implementation BundleEx - (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName { NSBundle *bundle = objc_getAssociatedObject(self, &RDBundleKey); if (bundle) { return [bundle localizedStringForKey:key value:value table:tableName]; } else { return [super localizedStringForKey:key value:value table:tableName]; } } @end @implementation NSBundle (DHLanguage) + (void)setLanguage:(NSString *)language { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ object_setClass([NSBundle mainBundle], [BundleEx class]); }); id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil; objc_setAssociatedObject([NSBundle mainBundle], &RDBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
|
1、objc_getAssociatedObject
和 objc_setAssociatedObject
是一对 getter
、setter
方法,目的是为了给 NSBundle
类动态添加一个属性。
2、object_setClass
在 BundleEx
里实现一个 localizedStringForKey
方法,然后将 BundleEx
这个类设置给 [NSBundle mainBundle]
。目的就是相当于重载 [NSBundle mainBundle]
的 localizedStringForKey
方法。
再说本篇文章,该类别新增方法的使用:
在 DHLocalizableController
类的 + (void)setUserlanguage:(NSString *)language
方法里,本地化存储语言之后调用如下方法:
[NSBundle setLanguage:language];
|
之后,关于 DHLocalizableController
类里边关于 bundle
的操作就可以舍弃了。
注意:使用这种方法要确保你的 .strings
的文件名为 Localizable.strings
否则还是要重新设置宏定义。
后记
参考链接1
参考链接2
微信扫一扫,向我赞赏
支付宝扫一扫,向我赞赏