前言

对于APP的国际化,大家应该都不陌生,常用的微信、微博、支付宝等应用均支持多种语言(国家数不一定一致)。默认情况下显示语言是跟随系统变化的,而APP内手动修改后不会随系统改变(本地存储偏好)。多语言对于受众不止于中国的APP还是很有必要的,至少同时支持英文,当然不排除只支持英文的国际APP如Instagram。

本文主要对国际化中遇到的问题进行记录与解答,可能在通用熟知的部分会简单描述带过。

新增语言支持设置

注:项目默认开发语言为English英文

  • 添加新语言,项目 -> Project -> Info -> Localizations -> 点击加号添加语言如简体中文

  • 创建用于项目内自定义需要国际化的字符串集Localizable.strings,这里面放除info.plist以外的所有国际化

    在iOS下新建Strings File类型并命名为Localizable

  • 创建配置info.plist国际化的字符串集InfoPlist.strings,此文件放App名字、info.plist里面参数国际化

    参照上一步在iOS下新建String File类型并命名为InfoPlist

  • 分别对Localizable.string和InfoPlist.string添加支持多语言,任意选择一个语言选择Localize,然后把所有支持的语言勾上

  • 纯代码定义不同语言内容时,需要在引号后面加分号,不然编译会通不过

通用国际化(项目内自定义显示文字)

在每个语言的strings文件中添加需要支持国际话的说明,每个语言里面的key需要一样。我的处理方法是定义了一个专门放key的一个常量定义文件,并且定义和key一致,方便在纯代码国际化的时候调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MARK: Common

/// 下拉刷新 PullToRefresh
let PullToRefresh = "PullToRefresh"
/// 松开刷新 ReleaseToRefresh
let ReleaseToRefresh = "ReleaseToRefresh"
/// 刷新中 Refreshing
let Refreshing = "Refreshing"


// MARK: Home

/// 总资产
let TitleTotalAssets = "TitleTotalAssets"
/// 我的卡片 My cards [title]
let TitleMyCards = "TitleMyCards"

然后在该文件里面同时定义了一个方便国际化的全局使用函数Localize,即国际化使用纯代码需要用的函数,我这里没有传comment,有需求的可以自己更改

1
2
3
func localize(key: String) -> String {
return NSLocalizedString(key, comment: "default")
}

使用:在代码中需要国际话的地方,调用localize函数即可,传入自己定义的key,如

1
assetsLbl.text = localize(key: TitleTotalAssets)

配置相关国际化(如隐私权限,App名称等info.plist)

  • APP名字国际化

    在InfoPlist.strings各个文件下添加CFBundleDisplayName=”对应语言的APP名字”;

    1
    CFBundleDisplayName = "对应语言的APP名字";
  • 相机权限

    1
    NSCameraUsageDescription = "对应语言的相机权限说明";
  • 相册访问权限

    1
    NSPhotoLibraryUsageDescription = "对应语言使用相册权限说明";
  • 相册存储权限

    1
    NSPhotoLibraryAddUsageDescription = "对应语言相册存储权限说明";
  • 蓝牙访问权限

    1
    NSBluetoothPeripheralUsageDescription = "对应语言蓝牙访问权限说明";

推送国际化

根据苹果官方文档介绍,提供了两种方案,一种是服务器做国际化,一种是APP本地做国际化,我这里选择的是APP做国际化,服务器配合。APP做推送国际化,用到loc-keyloc-args,将他们通过payload的方式推送到手机上,以个推为例,服务器需要通过setAPNInfo(Payload payload)将这些信息推到客户端。

我们的推送只包含了Title和一条基本信息,也就是最基本的推送信息展示,所以和服务器商量好分别定义好Title、body的key和body的args,定义如下:

1
2
3
"title-loc-key" : "NOTIFICATION_TITLE",	// 这个key将会在Localizable.strings需要国际化的通知标题。内容自己定义,只要和服务器商量好
"body-loc-key" : "NOTIFICATION_MESSAGE",// Localizable.strings需要国际化的内容
"body-loc-args" : ["0xasdfadsfasdfasdfasdfasdfasdffasdf"] // 内容的参数,此处只有一个也可以多个,在国际化的时候通过%@来读取参数,有多少个参数依次用多少个%@

国际化示例:

英文

1
2
"NOTIFICATION_TITLE" = "Transaction confirmed";
"NOTIFICATION_MESSAGE" = "Transaction ID: %@";//%@会显示消息推送过来的body-loc-args内容

中文

1
2
"NOTIFICATION_TITLE" = "交易已确认";
"NOTIFICATION_MESSAGE" = "交易ID:%@";

应用内切换

原理,重新加载本地语言文件,即在应用内切换语言后,改变该APP的语言,然后重新加载对应语言包。

当前APP的语言偏好保存在本地,key为AppleLanguages,应用内切换时可以更改该值,启动时可以通过该key获取。

切换语言包,可以写一个集成Bundle的类重写localizedString方法,以便重新加载,代码如下

1
2
3
4
5
6
7
8
9
class ExBundle: Bundle {
override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
if let bundle = Bundle.getLanguageBundle() {
return bundle.localizedString(forKey: key, value: value, table: tableName)
}else {
return super.localizedString(forKey: key, value: value, table: tableName)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extension Bundle {

private static var onLanguageDispatchOnce: ()->Void = {
//替换Bundle.main为自定义的ExBundle
object_setClass(Bundle.main, ExBundle.self)
}

func onLanguage(){
Bundle.onLanguageDispatchOnce()
if LocalizeManager.isArabicLanguage() {
UIView.appearance().semanticContentAttribute = .forceLeftToRight
}
}

static func getLanguageBundle() -> Bundle? {
guard LocalizeManager.currentLanguage() != .auto else { return nil }
let tpath = Bundle.main.path(forResource: LocalizeManager.currentLanguage().value, ofType: "lproj")
guard let path = tpath, !path.isEmpty else { return nil }

return Bundle(path: path)
}
}

在APP启动时,调用一次onLanguage swizze使用ExBundle

闪烁问题解决:将切换语言VC添加到根navigation中,代码如下:

1
2
3
4
5
6
7
8
9
guard let nav = StoryboardSegue.sceneNamed("RootNav@Main") as? UINavigationController else { return }
guard let settingVC = StoryboardSegue.sceneNamed(SettingViewController.reuseIdentifier+segueSuffix) as? SettingViewController else { return }

var vcs = nav.viewControllers
vcs.append(settingVC)
DispatchQueue.main.async {
UIApplication.shared.keyWindow?.rootViewController = nav
nav.viewControllers = vcs
}

其他(如阿拉伯语布局)

当语言为阿拉伯语时,系统布局会自动为从右到左排列和显示,包括返回。前提是你的布局严格按照autolayout来做,如果发现任然是从左到右的,那可能是你的布局约束存在问题。如果有些控件显示,如光标显示没有颠倒,这个时候就需要代码手动设置,任何你想要设置的控件都可以使用以下代码实现:

1
view.semanticContentAttribute = .forceRightToLeft