关于 iOS 开发中的国际化(也可称为多语言)在网上的文章多如牛毛,不过总结起来就那么一回事,不是说他们写的不好我写的多好,而是说过于零散。
现在,我将结合实际场景需求进行国际化做法详解。可以肯定的是,Android 的国际化做法大同小异,无非也就是各个语言版本的文件替换,我们先来分析下真实的需求是怎么一回事。
根据需求,我比较纠结的地方是,App 的静态文本数据可以存两份在本地,也就是 English 一份 Chinese Simplified 一份,但请求的 API 是同时返回两份中英文数据 or 分中英文两个接口?如果是要一个接口同时返回了中英文两份数据,显然会加大数据包的大小,其次用户很有可能从安装 App 的那天开始就不再切换 App 语言,甚至平均几个星期才换一次,同时返回两份数据是否多余,但是这么做几乎可以达到“无感知”数据源切换,相当于是说,一旦用户选择好了要切换语言,“啪嗒”点了完成,立马 pop 掉当前页面,然后整个 App 的数据源中英文切换可以几乎用“瞬间完成”来形容。
如果是分中英文两个接口,实际上就会出现微信在进行语言切换时的 loading 菊花,因为要重新拉取英文版数据,不过好处是可以减少上一种做法的数据包整体大小。这两种做法我都有实践过,如果你的 App 是非常固定,不会频繁出现语言切换的需求,那么可以使用第二种;如果 App 有一天之内可能会频繁切换多次语言的情况,第一种无疑。
经过一番探讨,虽然要供给拉美、北美和欧洲的同学使用,但是不会出现频繁切换语言的情况,所以,最终我们选择了第二种解决方案。先来看一张最终成果 gif 图,
从上图中可以看到其实并没有对数据源进行切换,因为。。。。后台没写完😓。
不过也不影响我们的讲解,首先,明确一个概念,我们能够做的国际化语言支持 iOS 系统中自带的所有语言,只要你能在系统设置中找到的语言,就能够对你的 App 做对应版本的国际化适配;其次,每对 App 适配一种语言,就要单创建出一个语言文件(要不然会引起冲突)。OK,我们正式进入讲解。
我起名为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 = @"发现";
self.rootTabBar.viewControllers = @[nav1, nav2];
[self.window makeKeyAndVisible];
return YES;
}
在Appdelegate
中,我们创建一个具备基本展示功能的 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 = @"这是发现";
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 {
}
在各自对应的 VC 中写下相关 UI,并预留相关 Button 点击事件即可。
创建路径:file -> new -> file... -> String File,文件名严格命名为——“ Localizable ”,创建好该文件后,点击该文件,并打开 Xcode 的右边功能区(不知道应该叫啥),在 Localization 功能区勾选语言版本,如果此时你并未看到或者只有 English 可选,我们需要到 PROJECT -> info -> Localization,添加需要的语言。
添加完成后,会在之前创建的 Localization.string 文件下看到多出来的语言文件,我选择了 English 和 Chinese Simplified。现在,我们已经可以在对应生成的语言文件中进行需要多语言替换的字段编写了。
// Localizable.string/English 文件
"home" = "home";
"homeString" = "I'm home";
"discover" = "discover";
"discoverString" = "I'm discover";
"change" = "change";
// Localizable.string/Chinese(Simplified)文件
"home" = "首页";
"homeString" = "我是首页";
"discover" = "发现";
"discoverString" = "我是发现";
"change" = "改变语言";
并新建一个 pch 文件,pch 文件同样也是头文件,不过这是一个特殊头文件,是一个预编译文件,位于该文件中的所有内容,能够被其他所有源文件共享和访问,相信你也看出来了,如果在 pch 文件中写了大量的不是必须文件,则会延长编译期时间,我们可以在.pch 文件中放:
因此,我们需要创建一个 pch 文件去存放接下来要在整个工程中都要用到的判断语言环境的中英文宏。创建一个 pch 文件的方式为,file -> new -> file... -> 搜“ pch ”关键字,创建它。
进入工程配置 -> TARGET -> Build Settings -> 搜 pch 关键词 -> 在“ Apple LLVM 9.0 - Language ”下的 Prefix Header 中,双击输入你的.pch 文件路径,我写的是$(SRCROOT)/PrefixHeader.pch
,填写完毕,回车,会看到生成的绝对路径,确定 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]
首先定义了一个AppLanguage
宏,推荐大家的命名更加多样化一些,因为 OC 并没有 namespace,如果我们的命名过于简单,就会导致和 Apple 本身自定义的 NSUserDefaults 默认值产生冲突。
PJLocalString(key)
这个宏“定义”了一个更长的方法,我们也都明确了一个概念,在 iOS 中的每个国际化语言,就对应着一个文件,这个文件就保存在 App 沙盒的根目录中,我们要做的就是在某个时机替换系统所采用的语言文件即可,而PJLocalString(key)
这个宏所做的事情,就是替换!先从 NSUserDefaults 中取出对应的语言 key ( en 还是 zh-Hans ),根据语言 key 去索引到对应的.lproj 文件,最后把要替换的关键词传入,抛出找到的对应值(我觉得找的这个过程用的结构应该不是 hashmap,真的很快。😨)
多语言文件有了,宏也有了,那怎么用呢?举个例子!
- (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");
}
只需要在多语言文字的地方调用PJLocalString()
宏,传入对应 key 即可。但是此时运行工程,会发现啥都没了,是因为我们并未对 NSUserDefaults 中做当前语言的设置,这就导致了取出的值为 nil。所以,还需要在AppDelegate
文件中设置初始语言,
if(![[NSUserDefaults standardUserDefaults] objectForKey:AppLanguage]){
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:AppLanguage];
[[NSUserDefaults standardUserDefaults] synchronize];
}
这样,我们即可完成第一次进入 App 时初始化基础语言,如果我们想要实时更改呢?这就需要用到了通知,使用通知机制去给监听语言设置改变的监听者进行相应的处理,
// 给 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];
// 新增监听方法
[[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");
// 新增监听方法
[[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];
// 新增监听方法
[[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 后,发送语言更改通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"changeLanguage" object:nil];
}
编译运行吧,见证奇迹的时刻到了~点击“更改语言” button,怎么样,是不是瞬间全都改过了。。😝
但是现在只完成了第一和第四个需求,我们接着来完成第三个需求,“用户首次打开 app 时,app 的语言与系统语言保持一致(系统语言为非简体中文,默认 app 都是英文)用户手动更改语言之后,之后都记忆用户选择的语言”。
分析一下,该需求的重点在于用户第一次打开 App 时整体 App 语言设置跟随系统语言设置,非简体中文之外的语言,都设置成英文,因此,我们需要对AppDelegate.m
文件进行改造,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// App 第一次启动跟随系统语言设置
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];
}
}
........
接下来完成最后一个需求,把 App 的名称也做国际化适配,如果你之前有过在info.plist
文件中修改过 App 的名字,我们现在要做的事情同样也是改名字,而且是针对info.plist
整个文件做国际化,同样新建一个string file
文件,命名严格填写为infoPlist.strings
,并且在 Xcode 的右边拓展栏中选择Localizable
,点击生成 English 和 Chinese Simplified 多语言文件
// 在 English 中写下
CFBundleName = "your english name";
CFBundleDisplayName = "your english name";
// 在 Chinese Simplified 中写下
CFBundleName = "你的中文名";
CFBundleDisplayName = "你的中文名";
OK,以上就是本篇文章所要表达的所有内容,当然这些都是 demo 级别的 code,如果此文对你有帮助,记得对其进行多多改造!
demo 地址: https://github.com/windstormeye/iOSMorePractices/tree/master/languageTest
原文链接:pjhubs.com
最近自己在总结从大一到现在 iOS 开发经验内容,目前正在编写 UITableView、Objective-C 语言使用、iOS 与 OpenCV 结合、iOS 与 Cocos2D-X 结合等内容,因为目前只有我自己一个人在总结,后续会一块拉小伙伴们持续输出高质量总结文章哒~希望大家能够喜欢,能够帮助到大家!
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.