iOS开发之进阶篇(4)—— 语言本地化(国际化)

1. 准备工作

本文以中英文切换为例. 因为系统默认语言是英文, 所以我们需要添加中文到项目中.

打开PROJECT:

localization1.png

添加简体中文:

localization2.png

需要注意的是, 这一步必须要选一个文件进行本地化, 不然语言添加不成功:

localization3.png

添加完成:

localization4.png

新建Strings文件:

Localizable1.png

Localizable2.png

取名Localizable.strings (系统默认名):

Localizable3.png

然后点击新建的Localizable.strings, 右边栏实现本地化:

Localizable4.png

Localizable5.png

点击需要本地化的语言(这些语言只有添加到项目中才可见) :

Localizable6.png

完成后就看到文件有了下拉按钮:

Localizable7.png

这个Localizable.strings文件的目的是针对不同语言提供不同的字符串, 后文中用到.

至此, 准备工作完成.

2. 字符串本地化

添加一个字符串:

string.png

分别在中英文的Localizable.strings中添加需要本地化的字符串:

strings_en.png

strings_cn.png

其中, "KK_String"是字符串的key, 系统根据这个key去找对应的本地化目标字符串.

实现字符串本地化:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.stringLabel.text = NSLocalizedString(@"KK_String", nil);
}

效果:

run.png

扩展1
在Xcode中也可切换语言:

change_language1.png

change_language2.png

注意
但是这样可能会和通过手机(模拟器)中的设置去更改语言有冲突(优先级不同), 为避免花时间讨论这个非必要讨论问题, 本文建议直接在设置里切换语言.

扩展2
当我们使用key去找目标字符串的时候, 如果没有找到, 那么系统就会把key字符串当做value字符串返回. 因此, 我们也可以直接使用英文字符串当做key来使用, 这样就可以省略Localizable.strings (English)里内容了.

// 中文Localizable
"String" = "字符串";

// 直接用String当做key
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.stringLabel.text = NSLocalizedString(@"String", nil);
}

3. 图片本地化

3.1 图片名称本地化

其实也就是字符串的本地化. 准备两张图片image_en.png和image_cn.png, 然后在中文Localizable里本地化字符串, 然后使用图片的时候先本地化图片名.

"image_en" = "image_cn";

self.imageView.image = [UIImage imageNamed:NSLocalizedString(@"image_en", nil)];

3.2 图片本地化

点击需要本地化的图片, 在右边栏里选择需要本地化的语言:

imageView.png

image_localization2.png

self.imageView.image = [UIImage imageNamed:@"image"];

run_image.png

这里顺便提一下, App图标也是图片, 但是这个图标没有Localization的选项, 因此不能对图标进行本地化 (至少我没找到方法).

4. App名称/系统权限提示框本地化

在我们的工程中, 有一个info.plist文件, 里面包含了App名称, 请求系统权限的提示文等. 这个info文件在程序运行前就已经被系统加载好了, 我们没办法使用代码去修改这些内容, 也就无法使用Localizable.strings去本地化了.
我们可以新建一个infoPlist.strings文件, 作用是将info.plist里的字符串进行本地化.
和创建Localizable类似, 过程就不重复了.
分别在中英文的InfoPlist.strings里面本地化App名称/权限提示文:

en
// App名称
"CFBundleDisplayName" = "KKLocalizeDemo";

// 权限提示文
"NSAppleMusicUsageDescription" = "App needs to get your media library permissions when using the play sound feature";
"NSCameraUsageDescription" = "App needs to get camera permissions when using the Scan QR code feature";
"NSMicrophoneUsageDescription" = "App needs to get microphone permission when using the intercom function";
"NSPhotoLibraryAddUsageDescription" = "App needs to get album permission when saving or browsing photos";
"NSPhotoLibraryUsageDescription" = "App needs your consent to access the album when saving or browsing photos";
"NSLocationAlwaysUsageDescription" = "We will use your location to search for nearby WiFi hotspots.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "We will use your location to search for nearby WiFi hotspots.";
"NSLocationWhenInUseUsageDescription" = "We will use your location to search for nearby WiFi hotspots.";
cn
// App名称
"CFBundleDisplayName" = "KK本地化";

// 权限提示文
"NSAppleMusicUsageDescription" = "App在使用播放声音功能时,需要获取媒体库权限";
"NSCameraUsageDescription" = "App在使用扫描二维码功能时,需要获取相机权限";
"NSMicrophoneUsageDescription" = "App在使用语音对讲功能时,需要获取麦克风权限";
"NSPhotoLibraryAddUsageDescription" = "App在保存或者浏览照片/视频时,需要获取相册权限";
"NSPhotoLibraryUsageDescription" = "App在保存或者浏览照片/视频时,需要您的同意才能访问相册";
"NSLocationAlwaysUsageDescription" = "我们将使用您的位置搜索附近的WiFi热点。";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "我们将使用您的位置搜索附近的WiFi热点。";
"NSLocationWhenInUseUsageDescription" = "我们将使用您的位置搜索附近的WiFi热点。";

对了, 还需要在info.plist里面添加以下键值对:

    <key>CFBundleDisplayName</key>
	<string>$(PRODUCT_NAME)</string>

    <key>NSAppleMusicUsageDescription</key>
    <string>App needs to get your media library permissions when using the play sound feature</string>
    <key>NSCameraUsageDescription</key>
    <string>App needs to get camera permissions when using the Scan QR code feature</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>We will use your location to search for nearby WiFi hotspots.</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>We will use your location to search for nearby WiFi hotspots.</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>We will use your location to search for nearby WiFi hotspots.</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>App needs to get microphone permission when using the intercom function</string>
    <key>NSPhotoLibraryAddUsageDescription</key>
    <string>App needs to get album permission when saving or browsing photos/videos</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>App needs your consent to access the album when saving or browsing photos/videos</string>

App名称:

run_name.png

请求相册权限:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.stringLabel.text = NSLocalizedString(@"String", nil);
    self.imageView.image = [UIImage imageNamed:@"image"];
    
    // 检查相册权限
    [self checkAlbumAuthorizationWithBlock:nil];
}


// 检查相册权限
- (void)checkAlbumAuthorizationWithBlock:(void (^ _Nullable ) (BOOL authorized))block {
    
    // 判断授权状态
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    
    if (status == PHAuthorizationStatusNotDetermined) {         // 用户还没有做出选择
        
        // 弹框请求用户授权
        [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
            if (status == PHAuthorizationStatusAuthorized) {    // 用户第一次同意了访问相册权限
                if (block) block(YES);
            } else {                                            // 用户第一次拒绝了访问相机权限
                if (block) block(NO);
            }
        }];
        
    } else if (status == PHAuthorizationStatusAuthorized) {     // 用户允许当前应用访问相册
        
        if (block) block(YES);
        
    } else  {                                                   // 用户拒绝当前应用访问相册
        
        if (block) block(NO);
        
    }
}

run_photo.png

  1. storyboard/xib本地化

点击Main.storyboard, 进行Localize…

storyboard1.png

然后点击那个Label, 右边栏找到Object ID:

storyboard2.png

然后在Main.string里进行本地化:

storyboard3.png

这里Object ID对应的是Label, 所以需要添加.text

效果和使用NSLocalizedString一致:

run_image.png

6. 多人开发中本地化

目前为止, 我们所有例子的工程都是共用系统默认的Localizable.strings来进行本地化, 这在多人开发中很可能造成冲突. 因此, 我们需要设计属于自己的本地化, 也就是将Localizable.strings改成自定义的. 方法是使用NSLocalizedStringFromTable取代NSLocalizedString.

NSLocalizedString和NSLocalizedStringFromTable是这样define的:

#define NSLocalizedString(key, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]

从这里我们推测出两个信息:

  1. 本地化字符串的过程实际上是从资源文件(NSBundle)中加载不同的字符串. Localizable.strings是放在mainBundle目录里面的, 系统通过判断App语言加载对应的strings文件, 然后通过key查找对应的字符串;
  2. table参数传nil, 系统则默认加载名为Localizable.strings的文件, 而如果改成我们自定义的名字, 则加载名字对应的文件.

new一个MyLocalizable.strings文件:

MyLocalizable.png

使用NSLocalizedStringFromTable:

self.stringLabel.text = NSLocalizedStringFromTable(@"String", @"MyLocalizable", nil);
// 也可做成宏
#define KKLocalizedTring(key)   NSLocalizedStringFromTable((key), @"MyLocalizable", nil)
// 使用自定义宏
self.stringLabel.text = KKLocalizedTring(@"String");

结果:

run_MyLocalizable.png

7. 应用内切换语言

到目前为止, 我们所做的语言本地化都是跟随系统语言的. 接下来我们讨论如何在App中切换语言.

点击查看Localizable.strings文件在Folder中展示:

showInFolder.png

我们看到不同语言的文件夹:

folder.png

每个文件夹对应一种语言(有多少种语言就有多少个文件夹), 里面装有该语言的所有本地化strings文件:

lproj.png

这些文件夹其实就是资源文件, 后缀名是lproj, 用于装载对应语言的本地化strings文件. 这些资源文件是通过相应Bundle去加载的, Bundle有mainBundle和其他Bundle.

Bundle是一个捆绑包, 对应一个资源目录, 其中包含了程序会使用到的资源. 这些资源可以是图像, 声音, 编译好的代码, nib文件等等(包括我们刚刚提到的.lproj文件).

回想我们之前使用的NSLocalizedString和NSLocalizedStringFromTable, Bundle使用的是mainBundle.

#define NSLocalizedString(key, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]

当我们的App刚启动加载时, 系统会将系统语言所对应的.Iproj文件打包到mainBundle里. 比如说, 当前系统语言是英文, 则系统会将en.Iproj文件打包进mainBundle; 而如果是简体中文, 那么就将zh-Hans.Iproj文件打包进mainBundle.
所以, 当我们使用mainBundle调用localizedStringForKey:value:table:方法, 得到的是系统指定好的本地化结果.

那么, 如何在App内切换语言? 这个问题可以进一步分析成: 如何自主选用指定.Iproj文件(里面包含的本地化strings文件), 来进行本地化呢?

我们反其道而行之, 将指定.Iproj资源文件生成一个path目录, 然后使用这个path去生成一个Bundle, 然后使用这个Bundle去调用localizedStringForKey:value:table:方法, 得到的就是我们自主指定好的本地化结果啦! 🍺🍺🍺
当然还有一点, 如何找到指定.Iproj资源呢? 我们可以记录用户选择的语言, 将所选语言与.Iproj资源文件名相对应, 然后将.Iproj资源文件名保存到本地. 最后在需要本地化的地方取出.Iproj资源文件名–>生成path–>生成Bundle–>调用localizedStringForKey:value:table:.

整个过程行云流水有没有?! 😝😝😝
当然, 如果看起来觉得头晕, 那就让代码治疗一切吧.

// 按钮事件
- (IBAction)btnAction:(UIButton *)sender {
    
    __weak typeof(self) weakSelf = self;
    
    UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"设置语言" message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *systemAction = [UIAlertAction actionWithTitle:@"跟随系统" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [weakSelf setLanguage:KKLanguageType_System];
    }];
    UIAlertAction *enAction = [UIAlertAction actionWithTitle:@"英文" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [weakSelf setLanguage:KKLanguageType_en];
    }];
    UIAlertAction *cnAction = [UIAlertAction actionWithTitle:@"中文" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [weakSelf setLanguage:KKLanguageType_cn];
    }];
    [alertC addAction:systemAction];
    [alertC addAction:enAction];
    [alertC addAction:cnAction];
    [self presentViewController:alertC animated:YES completion:nil];
}

// 切换语言
- (void)setLanguage:(KKLanguageType)type {
    
    // 记录当前语言到本地
    if (type == KKLanguageType_en) {
        [[NSUserDefaults standardUserDefaults] setObject:@"en" forKey:@"appLanguage"];
    }else if (type == KKLanguageType_cn) {
        [[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:@"appLanguage"];
    }else {
        [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"appLanguage"];
    }
    [[NSUserDefaults standardUserDefaults] synchronize];

    // 刷新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        
        // 刷新按钮
        if (type == KKLanguageType_en) {
            [self.btn setTitle:@"英文" forState:UIControlStateNormal];
        }else if (type == KKLanguageType_cn) {
            [self.btn setTitle:@"中文" forState:UIControlStateNormal];
        }else {
            [self.btn setTitle:@"跟随系统" forState:UIControlStateNormal];
        }
        
        // 本地化Label
        NSString *language = [[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"];
        NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:@"lproj"];
        NSBundle *bundle = language ? [NSBundle bundleWithPath:path] : NSBundle.mainBundle;
        self.stringLabel.text = [bundle localizedStringForKey:@"String" value:nil table:@"Localizable"];
        
        // 本地化图片
        self.imageView.image = [UIImage imageNamed:[bundle localizedStringForKey:@"image_en" value:nil table:@"Localizable"]];
    });
}

宏定义那一堆本地化代码, 然后看起来就清爽了:

#define KKLocalized(key) \
    [([[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"] ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"] ofType:@"lproj"]] : NSBundle.mainBundle) localizedStringForKey:(key) value:nil table:@"Localizable"]

// 本地化Label
self.stringLabel.text = KKLocalized(@"String");
        
// 本地化图片
self.imageView.image = [UIImage imageNamed:KKLocalized(@"image_en")];

本例中

  1. 跟随系统语言是向本地的key"appLanguage"对应的值置nil, 然后取出的时候发现是nil就用mainBundle去调用方法.
  2. key"appLanguage"是自由定义的, 主要用于保存对应名称到本地.
  3. table使用Localizable是整个项目共用的, 如果多人开发需自己定义(如本例的MyLocalizable.strings).

效果图:

run_language.gif

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值