iOS内部切换语言

项目中之前已经做了国际化,但是之前只有中文及英文,直接是根据系统的语言来切换的,现在为项目中加入内部切换语言的功能。

  • 国际化简述
  • 内部切换语言

国际化概述

为了在项目中加入国际化支持,我们首先在项目配置中加入我们需要的语言:

然后我们根据提示继续往下操作就能在项目中加入对应语言的支持,后续我们只需要加入语言翻译即可。

内部切换语言

核心方法

国际化语言时会走到 Bundle 的一个方法,在这个方法中我们我们去 Bundle 中找到对应的语言资源包进行语言的加载。

1
2
func localizedString(forKey key: String, value: String?, table tableName: String?) -> String

拦截方法

既然我们需要自己切换语言。那么只需要拦截到这个方法,并在方法内部切换需要显示的 Bundle 即可。

我们可以通过 Runtime 的方式来拦截,并替换为自定义的 CustomBundle。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class CustomBundle: Bundle {
override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
// 这个方法在后面定义:为了获取包含当前语言资源的 Bundle。
if let bundle = Bundle.getLanguageBundel() {
return bundle.localizedString(forKey: key, value: value, table: tableName)
} else {
return super.localizedString(forKey: key, value: value, table: tableName)
}
}
}

extension Bundle {
public class func exchangeMethod() {
// 这里的 DispatchQueue 是自定义的,使代码只执行一次。
DispatchQueue.once(token: "Change") {
//替换 Bundle.main 为自定义的 Bundle
object_setClass(Bundle.main, CustomBundle.self)
}
}
}

extension DispatchQueue {
private static var _onceTracker = [String]()
public class func once(token: String, block: () -> ()) {
objc_sync_enter(self)
defer {
objc_sync_exit(self)
}
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}

这里通过修改了 Bundle.main 对象的 isa 指针,使其指向它的子类 CustomBundle,这样可以调用子类的方法。这里其实也可以使用 method_swizzling 来交换 Bundle.main 的实现。

获取语言的 Bundle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension Bundle {
class func getLanguageBundel() -> Bundle? {
// languageResourceName:这里的名字可以在项目目录中找到。
let languageBundlePath = Bundle.main.path(forResource: languageResourceName, ofType: "lproj")
guard let path = languageBundlePath else {
return nil
}
let languageBundle = Bundle(path: path)
guard let bundle = languageBundle else {
return nil
}
return bundle
}
}

语言设置

  • UserLanguageKey: 保存用户设置的语言
  • AppleLanguages: 系统提供的一个键,设置系统的语言。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 用户自定义语言
static func setUserLanguage(languageName: String) {
// save user language setting
UserDefaults.standard.set(languageName, forKey: "UserLanguageKey")

// set language
UserDefaults.standard.set([languageName], forKey: "AppleLanguages")

UserDefaults.standard.synchronize()
}

// 重置系统语言
static func setUserLanguage() {
UserDefaults.standard.removeObject(forKey: "UserLanguageKey")

// set language
UserDefaults.standard.set(nil, forKey: "AppleLanguages")

UserDefaults.standard.synchronize()
}

切换控制器

这里初始化 ViewController 的操作是以从 StoryBoard 中初始化为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 初始化导航控制器
let nav = UINavigationController()

// 初始化几个ViewController,使界面回到切换语言界面
var vcs: [UIViewController] = []
let vc1 = UIStoryboard(name: "DeviceCenter", bundle: Bundle.main).instantiateViewController(withIdentifier: "DeviceCenterController")
let vc2 = UIStoryboard(name: "Account", bundle: Bundle.main).instantiateViewController(withIdentifier: "AccountController")
let vc3 = UIStoryboard(name: "Account", bundle: Bundle.main).instantiateViewController(withIdentifier: "LanguageController")
vcs.append(vc1)
vcs.append(vc2)
vcs.append(vc3)

// 将初始化的 VC 用导航控制器管理
nav.viewControllers = vcs
//切换 rootViewController
UIApplication.shared.keyWindow?.rootViewController = nav