Changing language on the fly, in running iOS, programmatically

This is a fairly old question, but I was just struggling with the same problem and found this solution:

http://aggressive-mediocrity.blogspot.com/2010/03/custom-localization-system-for-your.html

Which does exactly what you need (and might be helpful for others who with the same problem :)


Below link has a nice implementation of having custom language from with in the application.

manual language selection in an iOS-App (iPhone and iPad)

Attempted a SWIFT version find it here LanguageSettings_Swift

LanguageChange

-anoop


yes, i had the same problem, then i managed it with my own language setting in my prefFile, where i set a variable for the language setting:

// write a new value in file and set the var
- (void)changeLangInPrefFile:(NSString *)newLanguage {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"myPreference.plist"];
    NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
    //here add elements to data file and write data to file
    [data setObject:newLanguage forKey:@"language"];
    [data writeToFile:path atomically:YES];
    [data release];

// NSString *chosenLang; <- declared in .h file
    if (chosenLang != nil){
        [chosenLang release];
        chosenLang = nil;
    }
    chosenLang = [[NSString alloc] initWithString:(@"%@",newLanguage)];

}

// read the language from file and set the var:
- (void)readFromFileInBundleDocuments {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"myPreference.plist"];
    NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:path];

    NSString *chosenLangTemp = [savedStock objectForKey:@"language"];
    NSLog (@"read in file: %@", chosenLangTemp);
    if (chosenLang != nil){
        [chosenLang release];
        chosenLang = nil;
    }
    chosenLang = [[NSString alloc] initWithString:(@"%@",chosenLangTemp)];
    [savedStock release];
}

then i load all the contents from different files depending on the language for example i can load "an_image_eng.png" or "an_image_ita.png", or have 2 different .xib file and for the text to load i use different dictionary-files, one for each language, with all words/expressions translated, i just load the chosen one and read in it the right expression for every text to be load (the code to load it is similar to the method i wrote in this example, you can just arrange it to read the right word for every expression: just look at the value for objectForKey in the right dictionary file, where objectForKey is the word to translate and its value is the word translated)...


Generally, the language the user sees is determined by the locale setting, which is a system-wide setting. Only the user can change it, and when he does, SpringBoard and every running application on the device must restart. There is no way around this because all system apps and frameworks assume that the locale doesn't change once they start running. Changing the apps and frameworks to not require a relaunch would be very difficult for Apple to do.

I'm guessing that you either want to vary the language of your app's interface completely independently of the system locale setting, or you want to use the system locale setting by default but allow the user to override it for just your app.

You can get the current locale and examine its various values using +[NSLocale currentLocale]. To display your app's user interface in a language that is independent of the system locale, you'll need to avoid usage of NSLocalizedString() entirely, and use some sort of custom state of your own to determine which strings to display to the user and how to modify the interface to fit your app's language. It'll be up to you to keep your app's language state and modify its user interface appropriately.


This is an old question, but i was developing an helper that notifies me when the language change on the fly.

Take a look at the code of helper:

import Foundation

class LocalizableLanguage {

    // MARK: Constants

    fileprivate static let APPLE_LANGUAGE_KEY = "AppleLanguages"

    /// Notification Name to observe when language change
    static let ApplicationDidChangeLanguage = Notification.Name("ApplicationDidChangeLanguage")

    // MARK: Properties

    /// An array with all available languages as String
    static var availableLanguages: [String]? = {
        return UserDefaults.standard.object(forKey: APPLE_LANGUAGE_KEY) as? [String]
    }()

    /// The first element of available languages that is the current language
    static var currentLanguageCode: String? = {
        return availableLanguages?.first
    }()

    /// The current language code with just 2 characters
    static var currentShortLanguageCode: String? = {
        guard let currentLanguageCode = currentLanguageCode else {
            return nil
        }

        let strIndex = currentLanguageCode.index(currentLanguageCode.startIndex, offsetBy: 2)
        return currentLanguageCode.substring(to: strIndex)
    }()

    // MARK: Handle functions

    /// This accepts the short language code or full language code
    /// Setting this will send a notification with name "ApplicationDidChangeLanguage", that can be observed in order to refresh your localizable strings
    class func setLanguage(withCode langCode: String) {

        let matchedLangCode = availableLanguages?.filter {
            $0.contains(langCode)
        }.first

        guard let fullLangCode = matchedLangCode else {
            return
        }

        var reOrderedArray = availableLanguages?.filter {
            $0.contains(langCode) == false
        }

        reOrderedArray?.insert(fullLangCode, at: 0)

        guard let langArray = reOrderedArray else {
            return
        }

        UserDefaults.standard.set(langArray, forKey: APPLE_LANGUAGE_KEY)
        UserDefaults.standard.synchronize()

        LocalizableLanguage.refreshAppBundle()

        NotificationCenter.default.post(name: ApplicationDidChangeLanguage, object: fullLangCode)
    }
}

// MARK: Refresh Bundle Helper

private extension LocalizableLanguage {

    class func refreshAppBundle() {
        MethodSwizzleGivenClassName(cls: Bundle.self, originalSelector: #selector(Bundle.localizedString(forKey:value:table:)), overrideSelector: #selector(Bundle.specialLocalizedStringForKey(_:value:table:)))
    }

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

extension Bundle {

    func specialLocalizedStringForKey(_ key: String, value: String?, table tableName: String?) -> String {

        let availableLanguages = UserDefaults.standard.object(forKey: LocalizableLanguage.APPLE_LANGUAGE_KEY) as? [String]
        let currentLanguageCode = availableLanguages?.first ?? "en-US"
        let currentShortLanguageCode = currentLanguageCode.substring(to: currentLanguageCode.index(currentLanguageCode.startIndex, offsetBy: 2))

        let path =
                Bundle.main.path(forResource: currentLanguageCode, ofType: "lproj") ??
                Bundle.main.path(forResource: currentShortLanguageCode, ofType: "lproj") ??
                Bundle.main.path(forResource: "Base", ofType: "lproj")

        guard
            self == Bundle.main,
            let bundlePath = path,
            let bundle = Bundle(path: bundlePath)
        else {
            return self.specialLocalizedStringForKey(key, value: value, table: tableName)
        }

        return bundle.specialLocalizedStringForKey(key, value: value, table: tableName)
    }
}

You just need to copy that code and put in your project.

Then, you simple implement the listener like this:

NotificationCenter.default.addObserver(forName: LocalizableLanguage.ApplicationDidChangeLanguage, object: nil, queue: nil) { notification in
            guard let langCode = notification.object as? String else {
                return
            }
            self.accountStore.languageCode.value = langCode
        } 

Note that this line self.accountStore.languageCode.value = langCode is what i need to refresh when the app language as changed, then i can easily change all strings of my ViewModels in order to change the language to the user immediately.

In order to change the language, you can just call:

LocalizableLanguage.setLanguage(withCode: "en")

Other helper that could be nice to you is:

import Foundation

extension String {

    var localized: String {
        return NSLocalizedString(self, comment: "")
    }

}

So if you have in your localizable files something like that:

main.view.title = "Title test";

You can simple call:

"main.view.title".localized

And you have your string translated.