Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing language on the fly in swift

Now I know that apple does not recommend this.

In general, you should not change the iOS system language (via use of the AppleLanguages pref key) from within your application. This goes against the basic iOS user model for switching languages in the Settings app, and also uses a preference key that is not documented, meaning that at some point in the future, the key name could change, which would break your application.

However, this is an application that changing the language on the fly makes sense, just trust me on that. I also know this question was asked here: Changing language on the fly, in running iOS, programmatically. This however is getting old and I was wondering if there are any newer, better, or easier ways to do this. Currently in my app, I have a language choosing screen. Clicking on of the buttons in this view calls the following function with the language the button is associated with:

 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?())
    }

}

Example:

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

    }

If the user picks a language different than their own, they get a confirmation alert, and then are requested to restart there device. This is what I want to change. It appears that this section of NSUSerDefaults is not synchronized until the app restarts. Evidence:

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

The current internationalization system apple has is great, and I plan on using it. However, how can I change the language on the fly, maybe by forcing an update on the NSUSerDefaults?

Edit: I recommend using this library to do this now. Best of luck!

like image 724
modesitt Avatar asked Aug 05 '15 18:08

modesitt


People also ask

How do I change the language in IOS Swift?

There is a language setting within the "Settings" app (a system app), and there the user can set the language. All installed apps will use this language setting.

How do I change the button click language in Swift?

Click on the + button under the localizations section. You will see the following list of applications. Select the language you want to add into your app. After selecting the language, following popup will be appeared.


2 Answers

Basically you have to teach you bundle how to switch languages by loading different bundle files.

I translated my Objective-C code to Swift — with leaving the NSBundle category untouched.

enter image description here

The result is a view controller class that offers a languageDidChange() method for overriding.


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: "")

    }
}

instead of using subclasses of BaseViewController, your viewcontrollers could also post "LANGUAGE_WILL_CHANGE" and listen for "LANGUAGE_DID_CHANGE"

I pushed the complete project here: ImmediateLanguageSwitchSwift

like image 102
vikingosegundo Avatar answered Oct 20 '22 05:10

vikingosegundo


As answered by "vikingosegundo" above was having category in Objective c So here is Swift extension version

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)
    }
}

remains code will same as mentioned by "vikingosegundo"

Corrections are always welcome :)

like image 32
singh.jitendra Avatar answered Oct 20 '22 05:10

singh.jitendra