Changing language on the fly, in running iOS, programmatically
Asked Answered
W

7

35

I've been stackling and googling for hours. And I'm kind of desperate now. I would like to change the language of my application inside the app not only with the default language.

From what I've tried I stuck like everybody with the reboot step. Meaning, apples forces you to restart the app manually. Meaning you have to quit the app and then starting it up again.

Well, after googling, I was trying to setup an alarm and then forcing later the app to exit with

exit(0);

My bad, apple seems not to like this and prevent developer from using it... I guess I'm not pointing in the right direction.

Finally, despite all the problem, I could meet I would like to discuss about that.

Any hints?


EDIT, infos from APPLE

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.

If you want to switch languages in your application, you can do so via manually loading resource files in your bundle. You can use NSBundle:pathForResource:ofType:inDirectory:forLocalization: for this purpose, but keep in mind that your application would be responsible for all loading of localized data.

Regarding the exit(0) question, Apple DTS cannot comment on the app approval process. You should contact [email protected] to get an answer for this question.

Well, I have to choose so far.

Window answered 24/2, 2011 at 19:6 Comment(4)
I have used exit(0) on apps before AFTER a UIAlertView advising the user that the app was about to qui - never had an issue with approvals with Apple.Ironhanded
Check this gist.github.com/1922569Bighorn
Hi @Window would you know the link to that "info from APPLE" ?Microphysics
Hi, this was something in the doc. Quiet old :)Window
R
22

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

Roter answered 2/9, 2011 at 23:7 Comment(3)
This is really helpful. Was struggling with this. Thanks SwissdudeKristykristyn
@Roter Thank you so much, I also struggled with this. I modified your code from Aggressive Mediocrity a little bit for my use case, I hope this is ok. If anyone is interested in my slighly extended version, please get in touch.Ribble
Something I recently found and that may be interesting for all of you when changing the NSLocalizedString call to something customized:blog.spritebandits.com/2012/01/25/…Ribble
E
8

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

Enneahedron answered 22/4, 2014 at 5:9 Comment(0)
I
4

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

Incontrovertible answered 24/2, 2011 at 21:0 Comment(0)
S
1

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.

Stereochromy answered 24/2, 2011 at 21:27 Comment(2)
Yes I've already try to get the currentLocale. I've try this : NSUserDefaults* defs = [NSUserDefaults standardUserDefaults]; NSArray* languages = [defs objectForKey:@"AppleLanguages"]; NSString* preferredLang = [languages objectAtIndex:0]; NSLog(@"preferredLang: %@", preferredLang); But I only get the preferred language of the app. It seems awkward to me that it could not be possible to reload entirely all the ressources depending on an specific event ?? Thanks for your help !Window
for the record. I've been using the UIAlertview method in order to prevent user that the app will quit and he will have to relaunch. It works !Window
E
1

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.

Emphasis answered 7/12, 2018 at 11:19 Comment(0)
P
0

According to Apple guidelines this is not a good idea to change language in the app programmatically, but in case u have no power to change requested behaviour, you can do something like next:

  1. Prepare some service to manage your language even after app restart

    enum LanguageName: String {
        case undefined
        case en
        case es
    }
    
    let DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey = "DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey"
    
    func dynamicLocalizableString(_ key: String) -> String {
        return LanguageService.service.dynamicLocalizedString(key)
    }
    
    class LanguageService {
    
        private struct Defaults {
            static let keyAppleLanguage = "AppleLanguages"
            static let keyCurrentLanguage = "KeyCurrentLanguage"
        }
    
        static let service:LanguageService = LanguageService()
    
        var languageCode: String {
            get {
                return language.rawValue
            }
        }
    
        var currentLanguage:LanguageName {
            get {
                var currentLanguage = UserDefaults.standard.object(forKey: Defaults.keyCurrentLanguage)
                if let currentLanguage = currentLanguage as? String {
                    UserDefaults.standard.set([currentLanguage], forKey: Defaults.keyAppleLanguage)
                    UserDefaults.standard.synchronize()
                } else {
                    if let languages = UserDefaults.standard.object(forKey: Defaults.keyAppleLanguage) as? [String] {
                        currentLanguage = languages.first
                    }
                }
                if let currentLanguage = currentLanguage as? String, 
                    let lang = LanguageName(rawValue: currentLanguage) {
                    return lang
                }
                return LanguageName.undefined
            }
        }
    
        func switchToLanguage(_ lang:LanguageName) {
            language = lang
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
        }
    
        private var localeBundle:Bundle?
    
        fileprivate var language: LanguageName = LanguageName.en {
            didSet {
                let currentLanguage = language.rawValue
    
                UserDefaults.standard.set([currentLanguage], forKey:Defaults.keyAppleLanguage)
                UserDefaults.standard.setValue(currentLanguage, forKey:Defaults.keyCurrentLanguage)
                UserDefaults.standard.synchronize()
    
                setLocaleWithLanguage(currentLanguage)            
            }
        }
    
        // MARK: - LifeCycle
    
        private init() {
            prepareDefaultLocaleBundle()
        }
    
        //MARK: - Private
    
        fileprivate func dynamicLocalizedString(_ key: String) -> String {
            var localizedString = key
            if let bundle = localeBundle {
                localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
            } else {
                localizedString = NSLocalizedString(key, comment: "")
            }
            return localizedString
        }
    
        private func prepareDefaultLocaleBundle() {
            var currentLanguage = UserDefaults.standard.object(forKey: Defaults.keyCurrentLanguage)
            if let currentLanguage = currentLanguage as? String {
                UserDefaults.standard.set([currentLanguage], forKey: Defaults.keyAppleLanguage)
                UserDefaults.standard.synchronize()
            } else {
                if let languages = UserDefaults.standard.object(forKey: Defaults.keyAppleLanguage) as? [String] {
                    currentLanguage = languages.first
                }
            }
    
            if let currentLanguage = currentLanguage as? String {
                updateCurrentLanguageWithName(currentLanguage)
            }
        }
    
        private func updateCurrentLanguageWithName(_ languageName: String) {
            if let lang = LanguageName(rawValue: languageName) {
                language = lang
            }
        }
    
        private func setLocaleWithLanguage(_ selectedLanguage: String) {
            if let pathSelected = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
                let bundleSelected = Bundle(path: pathSelected)  {
                localeBundle = bundleSelected
            } else if let pathDefault = Bundle.main.path(forResource: LanguageName.en.rawValue, ofType: "lproj"),
                let bundleDefault = Bundle(path: pathDefault) {
                localeBundle = bundleDefault
            }
        }
    }
    
  2. Add some rules to make sure you UI components will be always updated:

    protocol Localizable {
        func localizeUI()
    }
    
  3. Implement them

    class LocalizableViewController: UIViewController {
    
        // MARK: - LifeCycle
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            NotificationCenter.default.addObserver(self, selector: #selector(self.localizeUI), name: NSNotification.Name(rawValue:DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            localizeUI()
        }
    
        deinit {
            NotificationCenter.default.removeObserver(self)
        }
    }
    
    extension LocalizableViewController: Localizable {
        // MARK: - Localizable
    
        func localizeUI() {
            fatalError("Must Override to provide inApp localization functionality")
        }
    }
    
  4. Inherit any controller u want to conform dynamic app switch functionality and implement localizeUI() func

    final class WelcomeTableViewController: LoadableTableViewController
    
  5. Switch language as needed:

    LanguageService.service.switchToLanguage(.en)
    
  6. All localizabled string should be set as :

    label.text = dynamicLocalizableString(<KEY_IN_STRINGS>)
    

Note: dont forget to add Localizable.strings with same codes as in LanguageName

enter image description here

Pinery answered 21/3, 2017 at 12:53 Comment(2)
Hi , How to localize dynamic text which is coming from server, in my application every text i have to display in simplified chinese language, But how to display the text in chinese which is coming in english. help me.Alejandro
@MaheshNarla for the dynamic text you can use Google Translate APIs. it is paid. cloud.google.com/translate/docs/simple-translate-callModestamodeste
M
0

With iOS 13 users can select App-specific language. if you really want to provide the facility to select language via app only then you can provide the facility to open settings within the app to select language. https://developer.apple.com/videos/play/wwdc2019/403/

Modestamodeste answered 26/12, 2019 at 5:1 Comment(6)
Did you try it? How do you enable it? I have a project with all localizations .. I ran it in iOS 13 ...no settigs came up...do I need to create a settings specifically?... Apple says that everything comes automaticallyEnneahedron
@Enneahedron Yes, I did. you just need to call UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!). it will open settings within App. you can see an additional setting for "PREFERRED LANGUAGE"->Language. but make sure that you have already added some languages. that you can do from General->Language & Region -> iPhone Language.Modestamodeste
One strange thing I noticed, say if your app doesn’t have any settings at all... for eg: no location, no photo, no need of internet etc... settings will never appear... just create a fresh project and try opening the settings via url, u will end up in the general settingsEnneahedron
@Enneahedron yes correct and this is the default behaviour. for our App, we are checking if the user has not configured any language then we are prompting and providing instructions that how to add the language. So that user should not confuse.Modestamodeste
What instructions? To set preferred language in the iPhone settings? I already had lot of language in preferred list.. but the app by itself doesn’t have a settings... just try this,.. create a plain app ,install it on the phone... see if it has settings by going to General-settings, now add localization files to that project... try the app again... still you won’t even see the settings in General- settings..Enneahedron
@Enneahedron ok got it. maybe in my app, we have a lot of other settings like contact permission, location, notification etc.. so we did not experience what you are saying. I will try with a blank App plus localization.Modestamodeste

© 2022 - 2024 — McMap. All rights reserved.