Set iOS language without restarting
Asked Answered
B

2

13

I am aware of quite a few posts that say this should not be done, or is not possible. I tinkered with a few ideas and now I'm asking this question, because I want to be absolutely sure there are no other options.

Option 1:

The most popular solution is to change AppleLanguages as in this post. I do not mind the idea of requiring a restart, so this would be an acceptable solution for me, except that you cannot restart your app programmatically (can't find the method, or would be rejected by Apple). Asking the user to manually restart the application wouldn't be ideal.

Option 2:

The next solution is to get the appropriate bundle and perform a localizedStringForKey lookup on each and every UILabel, UIButton, etc. This can be a little tedious but is okay for me, since I already added localizationProperties (similar to this) to these views so that I can have a centralized strings file.

AppDelegate.swift:

static var userLanguage: String?
    {
        set
        {
            let defaults = NSUserDefaults.standardUserDefaults();
            defaults.setObject(newValue, forKey: LanguageKey);
            defaults.synchronize();

            let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle());
            instance.window?.rootViewController = storyboard.instantiateInitialViewController();
        }

        get
        {
            let defaults = NSUserDefaults.standardUserDefaults();
            return defaults.stringForKey(LanguageKey);
        }
    }

Localization.swift:

private var bundle: NSBundle
{
    get
    {
        let bundle: NSBundle;
        #if TARGET_INTERFACE_BUILDER
            bundle = NSBundle(forClass: self.dynamicType);
        #else
            bundle = NSBundle.mainBundle();
        #endif

        let lang: String;
        if(AppDelegate.userLanguage == nil || AppDelegate.userLanguage == "en")
        {
            lang = "Base";
        }
        else
        {
            lang = AppDelegate.userLanguage!;
        }

        let path = bundle.pathForResource(lang, ofType: "lproj");
        if(path != nil)
        {
            let toreturn = NSBundle(path: path!);
            if(toreturn != nil)
            {
                return toreturn!;
            }
        }

        return bundle;
    }
}

extension UILabel
{
    @IBInspectable var localizedText: String?
    {
        get { return "" }

        set
        {
            if(newValue != nil)
            {
                text = bundle.localizedStringForKey(newValue!, value:"", table: nil);
            }
        }
    }
}

The problem with option 2 is that this only sets the language, for those fields. Layout direction will be unchanged, and files such as language specific layouts would not be used.

By extending UIApplication I am able to specify a custom userInterfaceLayoutDirection which successfully swaps all layouts between LTR and RTL.

DemoApplication.swift:

class DemoApplication: UIApplication
{
    override internal var userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection
    {
        get
        {
            if(AppDelegate.userLanguage == "ar")
            {
                return UIUserInterfaceLayoutDirection.RightToLeft;
            }

            return UIUserInterfaceLayoutDirection.LeftToRight;
        }
    }
}

Now when I set AppDelegate.userLanguage the application will reset to the initial view controller, displaying the new language, flipping the layout between LTR and RTL. This does not address the issue of language specific files, and I've also noticed that text remains left or right aligned within its own bounds.

Since I can't find the source code for native iOS classes, I can't see what language specific variables are set at startup so I assumed it is linked to the NSBundle.mainBundle. I tried to override it by using method swizzling.

extension NSBundle
{
    override public class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0;
        }

        // make sure this isn't a subclass
        if (self !== NSBundle.self)
        {
            return;
        }

        dispatch_once(&Static.token)
        {
            do
            {
                try jr_swizzleClassMethod("mainBundle", withClassMethod: "mainBundleExt");
            }
            catch
            {
                print("\(error)");
            }
        }

        super.initialize();
    }

    public class func mainBundleExt() -> NSBundle
    {
        let bundle = self.mainBundleExt();  // Due to swizzling, this is the "super" method

        let lang: String;
        if(AppDelegate.userLanguage == nil || AppDelegate.userLanguage == "en")
        {
            lang = "Base";
        }
        else
        {
            lang = AppDelegate.userLanguage!;
        }

        let path = bundle.pathForResource(lang, ofType: "lproj");
        if(path != nil)
        {
            let toreturn = NSBundle(path: path!);
            if(toreturn != nil)
            {
                return toreturn!;
            }
        }
    }
}

This does not work though, it seems as though the default mainBundle is still used.

So my question is this: How is mainBundle assigned a value? Which other language specific variables are set at startup, such as userInterfaceLayoutDirection. I assume there are 2 or 3 of these variables. Finally, is it possible for this to work or am I just wasting my time?

Thanks.

Babiche answered 27/10, 2015 at 13:18 Comment(7)
check this question. This is easiest & best way without restart application...Paramorphism
Thanks for the link. I looked through it and it seems close to what I tried. It doesn't look like switching between LTR and RTL is handled, or am I missing something?Babiche
@Babiche Did you find the solution for the LTR and RTL issue?Corbie
@Corbie Unfortunately not, and I haven't been working in iOS for a while now. Sorry to not be of any help.Babiche
@Babiche . Please can u tell how to change language without restarting the appPhilibeg
Setting semanticContentAttribute on the appearance() proxy is not supported. You're going to run into many other issues and bugs since the app still believes it's running in a language that isn't the one you're overriding. Adding a language switcher to your app is only going to make it more confusing; users expect their apps to follow the language that their device is set to.Agleam
You can try using this github.com/MoathOthman/Localization102 I am using it in one of my projects and it works fine. You won't even need to restart your application.Edd
B
0

I had this problem before and I have used a library. it helped me to change the language on the fly. try to use this: https://github.com/Decybel07/L10n-swift

L10n.shared.language = "en"
L10n.shared.language = "en-GB"

At runtime, you can switch the language at any time by setting the language property

Blazon answered 9/1, 2019 at 13:29 Comment(1)
Correct me if I'm wrong, but this library does not seem to deal with userInterfaceLayoutDirection. It provides several UI elements that populate their content depending on L10nBabiche
A
0

Use this line of code it will change layout without closing application. From right to left

UIView.appearance().semanticContentAttribute = .forceRightToLeft

And for Left to Right Flip

UIView.appearance().semanticContentAttribute = .forceLeftToRight

and if you want to change textfield layout or text change then use this code because i faced this issue . textfield's texts was not changning layout. check this code to change layout of textfield text

extension UITextField {
open override func awakeFromNib() {
        super.awakeFromNib()
        if UserDefaults.languageCode == "ar" {
            if textAlignment == .natural {
                self.textAlignment = .right
            }
        }
    }
}
Animality answered 16/4, 2019 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.