Force NSLocalizedString to use a specific language using Swift
Asked Answered
C

5

22

With swift, how can I force my app to read data from a specific Localizable.strings.

I put this in didFinishLaunchingWithOptions before instantiate the ViewController but it still show me the App in English.

NSUserDefaults.standardUserDefaults().removeObjectForKey("AppleLanguages")
NSUserDefaults.standardUserDefaults().setObject("fr", forKey: "AppleLanguages"   
NSUserDefaults.standardUserDefaults().synchronize()

And I tried to pass an Array for the "AppleLanguages" key like this but it still doesn't work:

NSUserDefaults.standardUserDefaults().setObject(["fr"], forKey: "AppleLanguages"

And once this is done, can I call this inside the App and take the changes in consideration without restarting the App?

Cheyennecheyne answered 10/1, 2015 at 17:46 Comment(0)
H
45

It's not possible to change app's language immediately by changing the value of AppleLanguages. It requires restarting the app before the change takes effect.

It seems that your problem is accessing the localization strings of different languages rather than changing the app's language, right? If you want your app to support multiple languages, you can just provide the translations and rely on settings.app for the actual change.

If you want to access the localization strings from other than currently used localization, you need to get access to the proper translations bundle. And then just query that bundle for the translations. The following piece of code should do the trick.

let language = "en"
let path = Bundle.main.path(forResource: language, ofType: "lproj")
let bundle = Bundle(path: path!)
let string = bundle?.localizedStringForKey("key", value: nil, table: nil)
Habsburg answered 10/1, 2015 at 17:56 Comment(6)
The language changes was taken in consideration but I missed to restart the app after executing my code.Cheyennecheyne
Good to know, if you want to change the language without the app restart, you do that with the aforementioned code example too. Just wrap it in a function/class and use it instead of NSLocalizedString. Though I'd recommend using the standard Apple way for changing the language.Habsburg
Doesn't work for me. path is nil. It may be that I cannot access these from XCUITests ? That would suck...Linnette
@Linnette I've also encountered the same problem with XCUITest, but made it work thank to Markus, see my answer below.Piteous
Friendly reminder for anyone who may have missed out, I found that it looks into Localizable.strings not Main.strings! Hope this saves someone time! Edit: I found the answer to pull from Main.strings -> let string = bundle.localizedString(forKey: "Language", value: nil, table: "Main")Wastage
It is not working for me I have created a global function to use everywhere in app. let value: String = NSLocalizedString(key, bundle: Bundle.main, comment: comment) debugPrint("default String value: (value)") guard let path: String = Bundle.main.path(forResource: "fr", ofType: "lproj"), let bundle: Bundle = Bundle(path: path) else { return value } debugPrint("lang specific value: (bundle.localizedString(forKey: key, value: nil, table: nil))") return bundle.localizedString(forKey: key, value: nil, table: "Main") }Agonic
S
8

With NSLocalizedString you can specify the bundle.

let language = "fr"
let path = Bundle.main.path(forResource: language, ofType: "lproj")!
let bundle = Bundle(path: path)!
let localizedString = NSLocalizedString(key, bundle: bundle, comment: "")

Or with a bundle, you may also call localizedStringForKey:value:table: directly too.

Shurlocke answered 24/1, 2018 at 6:5 Comment(0)
P
4

I find all the other answers very helpful. However, I'd like to add a little safeguard here. For once, for the case that the requested language might not be available. And twice, for the case that we have a more complex locale identifier.

Especially in the latter case it's not as straight forward as to simply map the locale to the right lproj folder because they might not be named exactly the same way. Thus, I found it easier to leave this part up to the system. Bundle.preferredLocalizations does exactly that.

Imagine you request en-GB but your app only supports en-US or just en in this matter. Since there is no en-GB.lproj folder in the project, no bundle will be returned. However, a call to preferredLocalizations should resolve it to en.lproj. As a side note, the same is mostly true for Chinese as well (zh, zh-Hans, zh-Hant, etc.).

func localizedBundle(locale: String?) -> Bundle {
    if let locale {
        if let preferredLocale = Bundle.preferredLocalizations(from: Bundle.main.localizations, forPreferences: [locale]).first {
            if let path = Bundle.main.path(forResource: preferredLocale, ofType: "lproj") {
                if let bundle = Bundle(path: path) {
                    return bundle
                }
            }
        }
    }
    return Bundle.main
}

Please be aware that preferredLocalizations should at least return one value, which might not necessarily match the requested locale. If you want to know if a locale cannot be resolved, you have to create two Locale objects for each, the input locale string and the returned locale string, and then compare the language tags. But this is not part of this answer here.

Call NSLocalizedString with the bundle as follows.

NSLocalizedString("key", bundle: bundle, comment: "")

By the way, if the bundle is the main bundle, then it's equivalent to calling NSLocalizedString with just the key and comment argument, so no worries.

It also makes sense to cache the bundle and not retrieving it each time before calling NSLocalizedString.

Pazpaza answered 14/4, 2023 at 2:28 Comment(0)
P
1

@Radu I also made this working for XCUITests thanks to @Markus' original answer :

You can specify explicitly the path to your MainBundle, it will only work on your Mac with the Simulator, but it is often used in continuous integration platforms so this might be acceptable :

let language: String = "en"
let path = "/Users/{username}/{path_to_your_project}/\(language).lproj"
let bundle = Bundle(path: path)
let string = bundle?.localizedString(forKey: "key", value: nil, table: nil)
Piteous answered 19/11, 2015 at 19:50 Comment(0)
B
0

In swift 4, I have solved it without needing to restart or use libraries. After trying many options, I found this function, where you pass the stringToLocalize (of Localizable.String, the strings file) that you want to translate, and the language in which you want to translate it, and what it returns is the value for that String that you have in Strings file:

    func localizeString (stringToLocalize: String, language: String) -> String
    {
        let path = Bundle.main.path (forResource: language, ofType: "lproj")
        let languageBundle = Bundle (path: path!)
        return languageBundle! .localizedString (forKey: stringToLocalize, value: "", table: nil)
    }

Taking into account this function, I created it as global in a Swift file:

struct CustomLanguage {

    func createBundlePath () -> Bundle {
        let selectedLanguage = //recover the language chosen by the user (in my case, from UserDefaults)
        let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj")
        return Bundle(path: path!)!
    }
}

To access from the whole app, and in each string of the rest of ViewControllers, instead of putting:

NSLocalizedString ("StringToLocalize", comment: “")

I have replaced it with

let customLang = CustomLanguage() //declare at top

NSLocalizedString("StringToLocalize", tableName: nil, bundle: customLang.createBundlePath(), value: "", comment: “”) //use in each String

I do not know if it's the best way, but I found it very simple, and it works for me, I hope it helps you!

Brill answered 18/3, 2019 at 12:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.