runtime结合block实现国际化的GIT地址:https://github.com/AlanZhangQ/runtime-block-Nationality.git
配置需要国际化的语言
配置需要国际化的语言,这也是国际化之前的准备工作,无论我们是国际化App名称、代码中的字符串、图片、还是storyboard和xib,都需要进行这一步的准备工作(一个项目中需要且仅需要配置一次)。
- 选中project->Info->Localizations,然后点击"+",添加需要国际化/本地化的语言,如下图(默认需要勾选Use Base Internationalization):
-
此处以添加法语为例,如下图:
-
弹出如下对话框,直接点击finish,如下图:
-
同理,添加简体中文、繁体中文,最终结果如下图:
备注:
“zh-Hans”和“zh-Hant”是简体中文和繁体中文的缩写。这是标准的缩写。H可大写也可小写。"en"是英语的缩写。其他语言请百度各国语言缩写即可查询。
(一)应用名称本地化/国际化
应用名称本地化,是指同一个App的名称,在不同的语言环境下(也就是手机设备的语言设置)显示不同的名称。比如,微信在简体中文环境下App名称显示为“微信”,在英语环境下显示为“weChat”。下面就开始进行应用名称本地化。
- 选中Info.plist,按下键盘上的command + N,选择Strings File(iOS->Resource->Strings File)
- 文件名字命名为InfoPlist,且必须是这个名字(因每个人电脑设置差异,此处本人电脑没有显示strings后缀名):
- 点击create后,Xcode左侧导航列表就会出现名为InfoPlist.strings的文件,如下图:
- 选中InfoPlist.strings,在Xcode的File inspection(Xcode右侧文件检查器)中点击Localize,目的是选择我们需要本地化的语言,如下图:
注意:
在点击Localize之前,一定要保证我们已经添加了需要本地化的语言,也就是上面配置需要国际化的语言那一步(步骤:project->Info->Localizations,然后点击"+",添加需要国际化/本地化的语言)。
点击Localize后,会弹出一个对话框,展开对话框列表,发现下拉列表所展示的语言正是我们在上面配置的需要国际化的语言,选择我们需要本地化的语言,然后点击对话框的Localize按钮,如下图:
注意:
如果我们没有在 PROJECT 中配置需要国际化的语言(project->Info->Localizations,然后点击"+"),上图下拉列表中将只会出现"Base"和"English"选项,English语言是系统默认的语言,其他需要国际化的语言(例如中文简体、繁体)必须通过上面的配置本地化语言那一步手动添加。
- 然后我们发现Xcode右侧的File inspection变成了下图的样式:
- 接下来,勾选Chinese(zh-Hans)、Chinese(zh-Hant),如下图:
- 此时,Xcode左侧的InfoPlist.stirings左侧多了一个箭头,点击箭头可以展开,如下图所示:
从上图可以看出,InfoPlist.strings文件下包含了English、Chinese(Simplified)、Chinese(Traditional)这五种语言的文件。
原理:程序启动时,会根据操作系统设置的语言,自动加载InfoPlist.strings文件下对应的语言文件,然后显示应用程序的名字。
- 接下来,我们分别用不同的语言给InfoPlist.strings下的文件设置对应的名字。
(1)在InfoPlist.strings(english)中加入如下代码:
// runtime Block Localizable是App在英语环境环境下显示的名称
CFBundleDisplayName = "runtime Block Localizable";
备注:
CFBundleDisplayName可以使用双引号,也可以不使用双引号!
(3)在InfoPlist.strings(Chinese(Simplified))中加入如下代码:
CFBundleDisplayName = "运行时_块_国际化";
(4)在InfoPlist.strings(Chinese(Traditional))中加入如下代码:
CFBundleDisplayName = "運行時_塊_國際化";
修改模拟器语言环境为English。App名称如下图:
修改模拟器语言环境为Chinese(Simplified)。App名称如下图:
修改模拟器语言环境为Chinese(Traditional)。App名称如下图:
备注:
过去本地化App名称,需要在Info.plist文件中增加一个名为“Application has localized display name”的BOOL类型的Key,并且需要将其值设置为YES(如下图)。目的是让App支持本地化App名称。但现在可以忽略这一步。
至此,本地化App名称已经演示完毕,其步骤就是:
- 在Project的设置中通过点击"+"添加需要本地化的语言。
- 然后在Xcode右侧的File inspection中点击Localize,选中需要本地化App名称的语言。
- 最后在每个语言对应的文件中以key = value(CFBundleDisplayName = "App名称";);的形式设置App的名称。
(三)应用内切换语言(结合了runtime和block实现)
通常来说 , 项目中如果使用了国际化 , 那么项目中语言会跟随手机系统的语言变化而变化 ,程序会自动去读取Userdefault中appleLanguage键的值作为app的语言. 而咱们要做的 , 是让应用内的语言不受手机系统的语言影响 , 而是用户在应用内设定的语言为准 , 并且实现应用内随时切换整个应用的语言 (比如微信切换语言)。
整体实现逻辑:
common单例 ,ZQInternationalManager.h文件
#import <Foundation/Foundation.h>
@interface ZQInternationalManager : NSObject
+ (instancetype)sharedInternationalManager;
//当前加载的bundle
@property (strong, nonatomic) NSBundle *languageBunle;
@end
common单例 , ZQInternationalManager.m文件
#import "ZQInternationalManager.h"
static ZQInternationalManager *manager = nil;
@implementation ZQInternationalManager
+ (instancetype)sharedInternationalManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[ZQInternationalManager alloc]init];
});
return manager;
}
@end
2 . 程序入口Appdelegate 判断本地沙盒是否有存储的 userLanguage (app应用中使用的语言) , 如果有 , app使用存储的语言 . 如果没有 , app使用手机系统语言 . 代码如下:
#import "AppDelegate.h"
#import "ZQInternationalManager.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//1.程序启动 , 获取用户设置的app语言
NSString *userLanguage = [[NSUserDefaults standardUserDefaults]objectForKey:@"userLanguage"];
//2.如果没有 , 则设定手机设置语言为app语言(系统会自动完成,读取@"appleLanguage") ,存储手机设置语言为app语言
if (!userLanguage.length) {
//2.1获取手机设置语言
NSString *phoneLanguage = [[[NSUserDefaults standardUserDefaults]objectForKey:@"AppleLanguages"]firstObject];
//2.2 存储app语言到沙盒
[[NSUserDefaults standardUserDefaults]setObject: phoneLanguage forKey:@"userLanguage"];
[[NSUserDefaults standardUserDefaults]synchronize];
//3.有app语言情况
}else{
//3.1设置app语言
//3.11获取app语言对应的bundle
NSString *userLanguagePath = [[NSBundle mainBundle]pathForResource:userLanguage ofType:@"lproj"];
NSBundle *userLanguageBundle = [NSBundle bundleWithPath:userLanguagePath];
//存储当前app内语言
[ZQInternationalManager sharedInternationalManager].languageBunle = userLanguageBundle;
}
return YES;
}
@end
3. 配置PCH文件 , 方便简化从当前加载bundle获取需要的语言
#ifndef ZQInternationalManager_pch
#define ZQInternationalManager_pch
// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
#define ZQ_LocalizedString(string) [[ZQInternationalManager sharedInternationalManager].languageBunle localizedStringForKey:string value:nil table:nil]
#import "ZQInternationalManager.h"
#endif /* ZQInternationalManager_pch */
4. runtime结合block实现国际化主要代码
#import "ViewController.h"
#import "MultiLanguageBlockButton.h"
#import <objc/message.h>
static void *toBeBorneAlert = "toBeBorneAlert";
static void *insuredChangeToMother = "insuredChangeToMother";
typedef void (^actionBlock)(NSInteger integer);
@interface ViewController ()<UITableViewDelegate, UITableViewDataSource,UIAlertViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *languageList;
@property (nonatomic, strong) UIButton *showLanguage;
@property (nonatomic, strong) UIButton *showAlert;
@property (nonatomic, strong) UIAlertController *alert;
@property (nonatomic, strong) UIAlertController *alert1;
@end
@implementation ViewController
- (NSArray *)languageList
{
if (!_languageList)
{
_languageList = [NSArray arrayWithObjects:@"English", @"Zh_CN", @"ZH_HK", nil];
}
return _languageList;
}
- (UITableView *)tableView
{
if (!_tableView)
{
_tableView = [[UITableView alloc] initWithFrame:CGRectZero];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.separatorInset=UIEdgeInsetsMake(0,0, 0, 0);
_tableView.frame = CGRectMake(CGRectGetMinX(self.showLanguage.frame), CGRectGetMaxY(self.showLanguage.frame), CGRectGetWidth(self.showLanguage.frame), 132);
}
return _tableView;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.languageList.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *brand_region_Cell = @"MyCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:brand_region_Cell];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleDefault reuseIdentifier:@"MyCell" ];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor yellowColor];
NSString *langauge = [self.languageList objectAtIndex:indexPath.row];
cell.textLabel.text = langauge;
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0)
{
[self changeLanguageTo:@"en"];
} else if (indexPath.row == 1)
{
[self changeLanguageTo:@"zh-Hans"];
} else if (indexPath.row == 2)
{
[self changeLanguageTo:@"zh-Hant"];
}
[self.tableView removeFromSuperview];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.showLanguage = [[UIButton alloc] initWithFrame:CGRectMake(200, 300, 200, 100)];
self.showLanguage.backgroundColor = [UIColor redColor];
self.showLanguage.center = self.view.center;
[self.showLanguage setTitle:ZQ_LocalizedString(@"switch language") forState:UIControlStateNormal];
[self.showLanguage addTarget:self action:@selector(showLanguageList:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.showLanguage];
self.showAlert = [[UIButton alloc] initWithFrame:CGRectMake(CGRectGetMinX(self.showLanguage.frame), CGRectGetMinY(self.showLanguage.frame)- 200, 200, 100)];
self.showAlert.backgroundColor = [UIColor redColor];
CGPoint point = self.showAlert.center;
point.y = self.view.center.y - 200;
self.showAlert.center= point;
[self.view addSubview:self.showAlert];
[self.showAlert setTitle:ZQ_LocalizedString(@"show alert") forState:UIControlStateNormal];
[self.showAlert addTarget:self action:@selector(showrunTimeBlockAlert:) forControlEvents:UIControlEventTouchUpInside];
//注册通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(UIConfig) name:@"UIConfig" object:nil];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//发出通知
[[NSNotificationCenter defaultCenter]postNotificationName:@"UIConfig" object:nil];
}
- (void) UIConfig
{
[self.showLanguage setTitle:ZQ_LocalizedString(@"switch language") forState:UIControlStateNormal];
[self.showAlert setTitle:ZQ_LocalizedString(@"show alert") forState:UIControlStateNormal];
}
- (void)showrunTimeBlockAlert:(id)sender
{
NSString *title = ZQ_LocalizedString(@"pop.up.message.title.smart.tips");
NSString *message = ZQ_LocalizedString(@"insured.yet.to.be.borne");
UIAlertAction *okAction1 = [self createAction:objc_getAssociatedObject(self.alert, toBeBorneAlert)];
UIAlertAction *okAction2 = [self createAction:objc_getAssociatedObject(self.alert1, insuredChangeToMother)];
UIAlertAction *okAction3 = [self createAction:nil];
UIAlertAction *noAction = [UIAlertAction actionWithTitle:@"NO" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
void (^block)(NSInteger) = objc_getAssociatedObject(self.alert, toBeBorneAlert);
if (block != NULL) {
block(0);
}
}];
self.alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[self.alert addAction:noAction];
[self.alert addAction:okAction1];
[self presentViewController:self.alert animated:YES completion:nil];
void (^block)(NSInteger) = ^(NSInteger index) {
if (index == 0) {
UIAlertController *alert4 = [UIAlertController alertControllerWithTitle:ZQ_LocalizedString(@"pop.up.message.title.smart.tips") message:ZQ_LocalizedString(@"continue.policy") preferredStyle:UIAlertControllerStyleAlert];
[alert4 addAction:okAction3];
[self presentViewController:alert4 animated:YES completion:nil];
} else {
NSString *message1 = ZQ_LocalizedString(@"change.insuredInfo.to.mother");
self.alert1 = [UIAlertController alertControllerWithTitle:ZQ_LocalizedString(@"pop.up.message.title.smart.tips") message:message1 preferredStyle:UIAlertControllerStyleAlert];
[self.alert1 addAction:okAction2];
[self presentViewController:self.alert1 animated:YES completion:nil];
void (^block1)(NSInteger) = ^(NSInteger index) {
UIAlertController *alert3 = [UIAlertController alertControllerWithTitle:ZQ_LocalizedString(@"pop.up.message.title.smart.tips") message:ZQ_LocalizedString(@"renew.select") preferredStyle:UIAlertControllerStyleAlert];
[alert3 addAction:okAction3];
[self presentViewController:alert3 animated:YES completion:nil];
};
objc_setAssociatedObject(self.alert1, insuredChangeToMother, block1, OBJC_ASSOCIATION_COPY);
}
};
objc_setAssociatedObject(self.alert, toBeBorneAlert, block, OBJC_ASSOCIATION_COPY);
}
- (UIAlertAction *) createAction:(actionBlock)block
{
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (block) {
block(1);
}
}];
return okAction;
}
- (void) showLanguageList:(UIView *) view
{
[self.view addSubview:self.tableView];
}
- (void)changeLanguageTo:(NSString *)language {
//对当前语言进行本地存储
[[NSUserDefaults standardUserDefaults]setObject:@"en" forKey:@"userLanguage"];
[[NSUserDefaults standardUserDefaults]synchronize];
//改变bundle //英文,汉语语言资源文件 en.lproj zh_Hans
NSString *path = [[NSBundle mainBundle]pathForResource:language ofType:@"lproj"];
NSBundle *needBundle = [NSBundle bundleWithPath:path];
[ZQInternationalManager sharedInternationalManager].languageBunle = needBundle;
//发出通知
[[NSNotificationCenter defaultCenter]postNotificationName:@"UIConfig" object:nil];
}
@end
5. 实验效果:
(四)写在最后
整体逻辑 :
1. 当我们国际化一个项目后 , app里加载什么语言的string文件是依据手机设置的系统语言而定 , 程序每次运行都会读取手机系统语言 .
2. 当我们想要项目中的语言 , 不跟随手机系统 , 而由我们自己决定时 , 我们就必须手动来实现这个功能 , 苹果并未给我们API . 然而 ,存放国际化语言的文件 又存在于 Iproj文件中 , 所以我们可以拿到这个文件的bundle , 存放于公共全局单例中 , 并设置成宏 , 就像系统宏 NSLocalizedString(<#key#>, <#comment#>)一样 , 然后所有的赋值 , 用这个宏来赋值 (如果是简单的国际化 , 系统已经帮我们实现了从对应bundle中读取值 , 这里我们需要手动实现)
3. 通过点击切换按钮 ,改变当前需要读取语言的bundle(也就是InternationalManager.languageBunlde的值), 发出通知 ,对涉及国际化的地方 , 进行重新赋值.
4.设置全局单例 –> 入口Appdelegate判断是否本地有存储的用户设定的语言 . 如果有 , 则当前需要读取的bundle就是用户设置语言的bundle . 如果没有 , 设置bundle为系统语言资源的bundle , 并将语言存入沙盒 —->设置单例 属性 存储当前bundle值 , 因为全局都需要调用此单例属性 —>设置PCH文件 , 将从单例属性bundle中读取对应的语言value抽成宏 , 方便使用 —>设置基类 , viewDidLoad注册通知 , viewWillApear 实现通知方法 , runtime结合block实现国际化。