10

I am trying to implement a function that can change app language.
I tried to set it like this:

let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("de", forKey: "AppleLanguages")

And after app restart language is still 'system default'.
Now I test different languages by setting Scheme > Application Language > language.
But is it possible to user click a button and after restart to see picked language?

Also what is the proper way to do this to avoid changing language on restart?

1110
  • 7,829
  • 55
  • 176
  • 334

7 Answers7

20

You have to set the AppleLanguages key with an array, not a string:

UserDefaults.standard.set(["de"], forKey: "AppleLanguages")
Junfeng
  • 940
  • 10
  • 19
  • 1
    No, it doesn't. `synchronize()` is not recommended to use anymore, according to Apple's documentation: https://developer.apple.com/documentation/foundation/userdefaults/1414005-synchronize – Junfeng Jan 24 '18 at 12:12
11

Yes you can change the app language immediately like,

var language = "de"
let path = NSBundle.mainBundle().pathForResource(language, ofType: "lproj")
let bundle = NSBundle(path: path!)
let string = bundle?.localizedStringForKey("key", value: nil, table: nil)

use your NSUserDefaults value to language.

Paddy
  • 766
  • 5
  • 16
  • I tried this in app delegate and it doesn't change anything – 1110 Jun 10 '15 at 07:45
  • Have you checked does your NSUserDefaults hold the value you have saved? – Paddy Jun 10 '15 at 07:50
  • Sorry your code works I didn't understand point of this. This means that when user change language I have to save it (language code) somehow in userdefaults and get it on each translation Am I right? – 1110 Jun 10 '15 at 07:59
  • @Paddy. I am struck in changing language since 1 week , How can i change my language programmatically when i add this code i am getting crash – Uma Madhavi Sep 15 '17 at 12:04
  • Will this work while sending Localized Push Notifications? – Abhishek Bedi Nov 20 '17 at 18:49
7

Here is how you change the language prior to launch in swift -

Lets say i want to force Hebrew localization:

1. Create your own Main.swift class

import Foundation
import UIKit

// Your initialization code here
let langCultureCode: String = "he_IL"
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject([langCultureCode], forKey: "AppleLanguages")
defaults.synchronize()

UIApplicationMain(Process.argc, Process.unsafeArgv, nil, NSStringFromClass(AppDelegate))

2. Remove your AppDelegate.swift "main" responsibility

//@UIApplicationMain <-- COMMENT THIS OUT
class AppDelegate: UIResponder, UIApplicationDelegate 

This will make your app forced to a locale WITHOUT needing any launch

royherma
  • 4,095
  • 1
  • 31
  • 42
  • I have created Main.swift file and then under which function or function name I need to write the code which is provided by you in the first point of your answer ?? – iDevAmit Dec 23 '16 at 05:04
  • @iDeveloper no function, thats how the `Main.swift` file looks exactly. – royherma Dec 23 '16 at 08:58
  • 2
    this sadly doesn't work for me anymore...anyone also facing issues in swift 3 xcode 7+? – royherma Sep 01 '17 at 16:49
  • first it's main.swift not Main.swift, second, this is not working with XCode 8. – DeyaEldeen Sep 27 '17 at 08:05
  • @DeyaEldeen Have you found any alternative to this approach? I currently need Hebrew localization only in my app not even English, But I am not able to enforce it from initially. I have to kill the app once and then the localization get executed in case I am using "AppLanguages" key in App Delegate. – Shubham Ojha Mar 29 '19 at 11:47
  • @royherma What's the new approach that will work in current iOS ? – Shubham Ojha Mar 29 '19 at 11:49
  • @ShubhamOjha : I'm using MOLH library (you can find it on github), it uses method swizzling... – DeyaEldeen Mar 29 '19 at 14:49
  • Have added an updated Swift Syntax answer for main.swift – Andreas Jul 01 '19 at 10:57
3

This solution work for me:
1. Create an extension of Bundle



    import Foundation

    class L012Localizer: NSObject {
        class func DoTheSwizzling() {
            MethodSwizzleGivenClassName(cls: Bundle.self, originalSelector: #selector(Bundle.localizedString(forKey:value:table:)), overrideSelector:
                #selector(Bundle.specialLocalizedString(key:value:table:)))
        }
    }

    extension Bundle {
        @objc func specialLocalizedString(key: String, value: String?, table tableName: String?) -> String {
            let currentLanguage = Utils.currentLanguage().rawValue
            var bundle = Bundle();
            if currentLanguage != "" , let _path = Bundle.main.path(forResource: currentLanguage, ofType: "lproj") {
                bundle = Bundle(path: _path)!
            } else {
                let _path = Bundle.main.path(forResource: "Base", ofType: "lproj")!
                bundle = Bundle(path: _path)!
            }
            return (bundle.specialLocalizedString(key: key, value: value, table: tableName))
        }
    }

    func MethodSwizzleGivenClassName(cls: AnyClass, originalSelector: Selector, overrideSelector: Selector){

        let origMethod: Method = class_getInstanceMethod(cls, originalSelector)!;
        let overrideMethod: Method = class_getInstanceMethod(cls, overrideSelector)!;
        if (class_addMethod(cls, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
            class_replaceMethod(cls, overrideSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        } else {
            method_exchangeImplementations(origMethod, overrideMethod);
        }
    }


2. Call DoTheSwizzling in AppDelegate




     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            L012Localizer.DoTheSwizzling()
            return true
        }


3. Create Language Utils



    class Utils: NSObject {
        class func setLanguage(_ lang: LanguageType) {
            UserDefaults.standard.set([lang.rawValue], forKey: "AppleLanguages")
        }

        class func currentLanguage() -> LanguageType {
            if let langs = UserDefaults.standard.object(forKey: "AppleLanguages") as? [String], let currentLang = langs.first {
                return LanguageType(rawValue: currentLang) ?? .english
            }

            return .english
        }
    }


4. Create Language Type



    enum LanguageType: String {
        case english = "en"
        case korea = "ko"
        case vietnamese = "vi-VN"

        func toString() -> String {
            switch self {
            case .korea:
                return "Korea".localized
            case .vietnamese:
                return "Vietnamese".localized
            default:
                return "English".localized
            }
        }
    }


5. Remember that you have to config Application Language in Scheme to SystemLanguage
enter image description here
6. Then Every time you need to localize your app, you only need to call. Utils.setLanguage({LanguageType})

Neo Nguyen
  • 119
  • 1
  • 3
1

You may try below in AppDelegate.swift though changes will not appear instantly but after you kill and relaunch your app. -

NSUserDefaults.standardUserDefaults().removeObjectForKey("AppleLanguages")
NSUserDefaults.standardUserDefaults().setObject(["de"], forKey: "AppleLanguages"   
NSUserDefaults.standardUserDefaults().synchronize()
Shubham Ojha
  • 461
  • 5
  • 17
Santu C
  • 2,644
  • 2
  • 12
  • 20
1

If mean for test purpose, just change the language in simulator settings app.
If you are trying to make a sort of language selector in your app, it' pretty painful and in my opinion you should not.
Your app reads language and locale settings from the device and change the UI accordingly.
Override this behavior is tough and you will never accomplish a full change in language, for instance if you try to display a map in you app and your device language is Spanish, but the app language is english, you will see the map indications written in Spanish.
Here a project that could help.

Community
  • 1
  • 1
Andrea
  • 26,120
  • 10
  • 85
  • 131
  • But if I decide to go the hard way then that means that I need to get localized values in every view in the way @Paddy wrote in his answer. Am I right? – 1110 Jun 10 '15 at 08:09
  • The way Paddy explained force the system to look for localized string in a specified project language directory. It's a way not the only one.If you search on google you'll find a lot of experiments even if they are in obj-c concepts behind are still valid. – Andrea Jun 10 '15 at 08:30
0

Updated Swift syntax of the main.swift file mentioned in @royherma's answer. This will avoid having to restart the app after overriding the UserDefaults settings:

import Foundation
import UIKit

// Your initialization code here
let langCultureCode: String = "LANGUAGE_CODE"

UserDefaults.standard.set([langCultureCode], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()

UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))

paired together with the removal of @UIApplicationMain in your AppDelegate.swift file.

Andreas
  • 656
  • 9
  • 7
  • 1
    How can I change this language, more time? There is not any function, so how can I execute "main.swift" file code next time? – AMIT Feb 02 '20 at 05:26