项目中之前已经做了国际化,但是之前只有中文及英文,直接是根据系统的语言来切换的,现在为项目中加入内部切换语言的功能。
国际化概述 为了在项目中加入国际化支持,我们首先在项目配置中加入我们需要的语言:
然后我们根据提示继续往下操作就能在项目中加入对应语言的支持,后续我们只需要加入语言翻译即可。
内部切换语言 核心方法 国际化语言时会走到 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