在Swift中实现动态更改语言

18

现在我知道苹果不建议这样做。

一般来说,你不应该通过使用AppleLanguages偏好键从你的应用程序中更改iOS系统语言。这违反了基本的iOS用户模型,在设置应用程序中切换语言,同时还使用了一个未记录的偏好键。这意味着在将来的某个时候,偏好键的名称可能会更改,导致你的应用程序无法正常运行。

然而,对于这个应用程序来说,在运行时更改语言是有意义的,相信我吧。我也知道这个问题在这里被问到了:Changing language on the fly, in running iOS, programmatically。然而,这已经过时了,我想知道是否有任何更新、更好或更容易的方法来实现这一点。目前,在我的应用程序中,我有一个语言选择界面。点击这个视图中的一个按钮会调用以下函数,并传入与该按钮相关联的语言:

 func changeLang(language: String) {

    if language != (currentLang as! String?)! {
        func handleCancel(alertView: UIAlertAction!)
        {

        }
        var alert = UIAlertController(title: NSLocalizedString("language", comment: "Language"), message: NSLocalizedString("languageWarning", comment: "Warn User of Language Change Different Than Defaults"), preferredStyle: UIAlertControllerStyle.Alert)

        alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler:handleCancel))
        alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.Default, handler:{ (UIAlertAction) in

            NSUserDefaults.standardUserDefaults().setObject([language], forKey: "AppleLanguages")
            NSUserDefaults.standardUserDefaults().synchronize()
            println(self.currentLang)

            let alert = UIAlertView()
            alert.title = NSLocalizedString("language", comment: "Sign In Failed")
            alert.message = NSLocalizedString("languageChangeNotification", comment: "Notify of language change")
            alert.addButtonWithTitle(NSLocalizedString("ok", comment: "Okay"))
            alert.show()

            self.performSegueWithIdentifier("welcome", sender: AnyObject?())


        }))
        self.presentViewController(alert, animated: true, completion: {
        })
    } else {
        self.performSegueWithIdentifier("welcome", sender: AnyObject?())
    }

}

例子:

@IBAction func english(sender: UIButton) {
        changeLang("en")

    }

如果用户选择与其本身语言不同的语言,则会收到确认警报,然后被要求重新启动设备。这是我想要改变的。看起来,在应用程序重新启动之前,NSUSerDefaults的此部分未同步。证据:

let currentLang: AnyObject? = NSLocale.preferredLanguages()[0]
println(currentLang)
// Prints english
changeLang("zh-Hans")
println(currentLang)
// Prints english still until restart 

目前苹果的国际化系统非常出色,我打算使用它。不过,我该如何实现动态更改语言呢?也许可以通过强制更新NSUserDefaults来实现吗?

编辑:我建议现在使用这个库来完成这项工作。祝你好运!


NSLocalizedString - 为了添加新语言,我们必须发布新的应用程序,这是唯一一个 NSLocalizedString 不适用的地方。 - Balagurubaran
3个回答

30

基本上,你需要教你的包如何通过加载不同的包文件来切换语言。

我将我的Objective-C代码翻译成了Swift,并且保留了NSBundle类别的原始代码。

enter image description here

结果是一个视图控制器类,提供了一个可以重写的languageDidChange()方法。


NSBundle+Language.h

#import <Foundation/Foundation.h>

@interface NSBundle (Language)
+(void)setLanguage:(NSString*)language;

@end

NSBundle+Language.m

#import "NSBundle+Language.h"
#import <objc/runtime.h>

static const char associatedLanguageBundle=0;

@interface PrivateBundle : NSBundle
@end

@implementation PrivateBundle
-(NSString*)localizedStringForKey:(NSString *)key
                            value:(NSString *)value
                            table:(NSString *)tableName
{
    NSBundle* bundle=objc_getAssociatedObject(self, &associatedLanguageBundle);
    return bundle ? [bundle localizedStringForKey:key
                                            value:value
                                            table:tableName] : [super localizedStringForKey:key
                                                                                      value:value
                                                                                      table:tableName];
}
@end

@implementation NSBundle (Language)
+(void)setLanguage:(NSString*)language
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object_setClass([NSBundle mainBundle],[PrivateBundle class]);
    });

    objc_setAssociatedObject([NSBundle mainBundle], &associatedLanguageBundle, language ?
                             [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "languageWillChange:", name: "LANGUAGE_WILL_CHANGE", object: nil)

        let targetLang = NSUserDefaults.standardUserDefaults().objectForKey("selectedLanguage") as? String

        NSBundle.setLanguage((targetLang != nil) ? targetLang : "en")
        return true
    }

    func languageWillChange(notification:NSNotification){
        let targetLang = notification.object as! String
        NSUserDefaults.standardUserDefaults().setObject(targetLang, forKey: "selectedLanguage")
        NSBundle.setLanguage(targetLang)
        NSNotificationCenter.defaultCenter().postNotificationName("LANGUAGE_DID_CHANGE", object: targetLang)
    }    
}

BaseViewController.swift

import UIKit



class BaseViewController: UIViewController {

    @IBOutlet weak var englishButton: UIButton!
    @IBOutlet weak var spanishButton: UIButton!

    deinit{
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "languageDidChangeNotification:", name: "LANGUAGE_DID_CHANGE", object: nil)
        languageDidChange()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func switchLanguage(sender: UIButton) {

        var localeString:String?
        switch sender {
        case englishButton: localeString = "en"
        case spanishButton: localeString = "es"
        default: localeString = nil
        }


        if localeString != nil {
            NSNotificationCenter.defaultCenter().postNotificationName("LANGUAGE_WILL_CHANGE", object: localeString)
        }
    }


    func languageDidChangeNotification(notification:NSNotification){
        languageDidChange()
    }

    func languageDidChange(){

    }


}

ViewController.swift

import UIKit

class ViewController: BaseViewController {

    @IBOutlet weak var helloLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func languageDidChange() {
        super.languageDidChange()
        self.helloLabel.text = NSLocalizedString("Hello", comment: "")

    }
}

除了使用BaseViewController的子类,你的视图控制器还可以发布"LANGUAGE_WILL_CHANGE"并监听"LANGUAGE_DID_CHANGE"

我已将完整项目推送到这里:ImmediateLanguageSwitchSwift


嘿,super.languageDidChange()应该是一个空函数吗?我尝试实现你的代码,但它对我不起作用 :/ - LinusGeffarth
是的,由于您必须实现语言更改对源版本控制的影响,因此请仅返回翻译后的文本。 - vikingosegundo
太棒了,终于搞定了。谢谢!顺便问一下,这个是使用 MIT 许可证吗?我很愿意使用它 :) - LinusGeffarth
1
WTFPL,尽管使用。顺便说一句:希望能收到一个赞。 - vikingosegundo
谢谢,当然,你应得的 :) - LinusGeffarth
显示剩余5条评论

7

如上面"vikingosegundo"所回答的,问题涉及到Objective C的类别, 这里是Swift扩展版本。

import ObjectiveC

private var associatedLanguageBundle:Character = "0"

class PrivateBundle: Bundle {
    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        let bundle: Bundle? = objc_getAssociatedObject(self, &associatedLanguageBundle) as? Bundle
        return (bundle != nil) ? (bundle!.localizedString(forKey: key, value: value, table: tableName)) : (super.localizedString(forKey: key, value: value, table: tableName))

    }
}

extension Bundle {
    class func setLanguage(_ language: String) {
        var onceToken: Int = 0

        if (onceToken == 0) {
            /* TODO: move below code to a static variable initializer (dispatch_once is deprecated) */
            object_setClass(Bundle.main, PrivateBundle.self)
        }
        onceToken = 1
        objc_setAssociatedObject(Bundle.main, &associatedLanguageBundle, (language != nil) ? Bundle(path: Bundle.main.path(forResource: language, ofType: "lproj") ?? "") : nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

剩余的代码与“ vikingosegundo”所述的相同。

欢迎进行更正 :)


1
谢谢,这真的很有帮助。 - Syed Arsalan Shah
@ArsalanShah 欢迎随时光临 :) - singh.jitendra
class func setLanguage(_ language: String?) 因为你想要检查 nil 值。 - J A S K I E R

0
使用此行代码,它将更改布局而无需关闭应用程序。 从右到左。
UIView.appearance().semanticContentAttribute = .forceRightToLeft

对于从左到右翻转

UIView.appearance().semanticContentAttribute = .forceLeftToRight

如果你想要更改文本字段的布局或文本内容,则可以使用以下代码,因为我也遇到过类似的问题。文本字段的文本未更改布局,所以可以参考这段代码来更改文本字段文本的布局。

extension UITextField {
  open override func awakeFromNib() {
super.awakeFromNib()
  if UserDefaults.languageCode == "ar" {
    if textAlignment == .natural {
        self.textAlignment = .right
    }
  }
 }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接