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

目录

  1. 准备工作
  2. 字符串本地化
  3. 图片本地化
  4. App名称/系统权限提示框本地化
  5. storyboard/xib本地化
  6. 多人开发中本地化
  7. 应用内切换语言

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里面添加以下键值对:

    CFBundleDisplayName
    $(PRODUCT_NAME)

    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
    NSLocationAlwaysAndWhenInUseUsageDescription
    We will use your location to search for nearby WiFi hotspots.
    NSLocationAlwaysUsageDescription
    We will use your location to search for nearby WiFi hotspots.
    NSLocationWhenInUseUsageDescription
    We will use your location to search for nearby WiFi hotspots.
    NSMicrophoneUsageDescription
    App needs to get microphone permission when using the intercom function
    NSPhotoLibraryAddUsageDescription
    App needs to get album permission when saving or browsing photos/videos
    NSPhotoLibraryUsageDescription
    App needs your consent to access the album when saving or browsing photos/videos

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

你可能感兴趣的:(iOS开发之进阶篇(4)—— 语言本地化(国际化))