How can I change locale programmatically with Swift

IosXcodeSwiftLocalization

Ios Problem Overview


I am making ios app on XCODE 6.3 by Swift. And my app will have the choose language function like the image below

enter image description here

I already have storyboard for my local language. But i can't find out how to change the localization programmatically off the app by the button.

Anyone know how to do it

Ios Solutions


Solution 1 - Ios

Here's a way to change it on the fly with Swift, add an extension function to String:

extension String {
func localized(lang:String) ->String {
   
    let path = NSBundle.mainBundle().pathForResource(lang, ofType: "lproj")
    let bundle = NSBundle(path: path!)
   
    return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}}

Swift 4:

extension String {
func localized(_ lang:String) ->String {
    
    let path = Bundle.main.path(forResource: lang, ofType: "lproj")
    let bundle = Bundle(path: path!)
    
    return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}}

then assuming you have the regular Localizable.strings set up with lang_id.lproj ( e.g. en.lproj, de.lproj etc. ) you can use this anywhere you need:

var val = "MY_LOCALIZED_STRING".localized("de")

Solution 2 - Ios

This allows to change the language just by updating a UserDefaults key.

This is based on the great answer from @dijipiji. This is a Swift 3 version.

extension String {
    var localized: String {
        if let _ = UserDefaults.standard.string(forKey: "i18n_language") {} else {
            // we set a default, just in case
            UserDefaults.standard.set("fr", forKey: "i18n_language")
            UserDefaults.standard.synchronize()
        }

        let lang = UserDefaults.standard.string(forKey: "i18n_language")
    
        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        let bundle = Bundle(path: path!)
    
        return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}

Usage

Just add .localized to your string, as such :

"MyString".localized , MyString being a key in the Localizable.strings file.

Changing the language

UserDefaults.standard.set("en", forKey: "i18n_language")

Solution 3 - Ios

Swift 4.2

In my case, if user change the language setting, I have to update 2 things at runtime.

1. Localizable.strings

Localizable strings

2. Storyboard Localization

Storyboard localization

I make @John Pang code more swifty

> BundleExtension.swift

import UIKit

private var bundleKey: UInt8 = 0

final class BundleExtension: Bundle {

     override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        return (objc_getAssociatedObject(self, &bundleKey) as? Bundle)?.localizedString(forKey: key, value: value, table: tableName) ?? super.localizedString(forKey: key, value: value, table: tableName)
    }
}

extension Bundle {

    static let once: Void = { object_setClass(Bundle.main, type(of: BundleExtension())) }()

    static func set(language: Language) {
        Bundle.once
    
        let isLanguageRTL = Locale.characterDirection(forLanguage: language.code) == .rightToLeft
        UIView.appearance().semanticContentAttribute = isLanguageRTL == true ? .forceRightToLeft : .forceLeftToRight
    
        UserDefaults.standard.set(isLanguageRTL,   forKey: "AppleTe  zxtDirection")
        UserDefaults.standard.set(isLanguageRTL,   forKey: "NSForceRightToLeftWritingDirection")
        UserDefaults.standard.set([language.code], forKey: "AppleLanguages")
        UserDefaults.standard.synchronize()
    
        guard let path = Bundle.main.path(forResource: language.code, ofType: "lproj") else {
            log(.error, "Failed to get a bundle path.")
            return
        }
    
        objc_setAssociatedObject(Bundle.main, &bundleKey, Bundle(path: path), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

> Language.swift

import Foundation

enum Language: Equatable {
    case english(English)
    case chinese(Chinese)
    case korean
    case japanese

    enum English {
        case us
        case uk
        case australian
        case canadian
        case indian
    }

    enum Chinese {
        case simplified
        case traditional
        case hongKong
    }
}

extension Language {

    var code: String {
        switch self {
        case .english(let english):
            switch english {
            case .us:                return "en"
            case .uk:                return "en-GB"
            case .australian:        return "en-AU"
            case .canadian:          return "en-CA"
            case .indian:            return "en-IN"
            }
   
        case .chinese(let chinese):
            switch chinese {
            case .simplified:       return "zh-Hans"
            case .traditional:      return "zh-Hant"
            case .hongKong:         return "zh-HK"
            }
        
        case .korean:               return "ko"
        case .japanese:             return "ja"
        }
    }

    var name: String {
        switch self {
        case .english(let english):
            switch english {
            case .us:                return "English"
            case .uk:                return "English (UK)"
            case .australian:        return "English (Australia)"
            case .canadian:          return "English (Canada)"
            case .indian:            return "English (India)"
            }
        
        case .chinese(let chinese):
            switch chinese {
            case .simplified:       return "简体中文"
            case .traditional:      return "繁體中文"
            case .hongKong:         return "繁體中文 (香港)"
            }
        
        case .korean:               return "한국어"
        case .japanese:             return "日本語"
        }
    }
}

extension Language {

    init?(languageCode: String?) {
        guard let languageCode = languageCode else { return nil }
        switch languageCode {
        case "en", "en-US":     self = .english(.us)
        case "en-GB":           self = .english(.uk)
        case "en-AU":           self = .english(.australian)
        case "en-CA":           self = .english(.canadian)
        case "en-IN":           self = .english(.indian)
        
        case "zh-Hans":         self = .chinese(.simplified)
        case "zh-Hant":         self = .chinese(.traditional)
        case "zh-HK":           self = .chinese(.hongKong)
        
        case "ko":              self = .korean
        case "ja":              self = .japanese
        default:                return nil
        }
    }
}

Use like this

var language: [Language] = [.korean, .english(.us), .english(.uk), .english(.australian), .english(.canadian), .english(.indian),
                            .chinese(.simplified), .chinese(.traditional), .chinese(.hongKong),
                            .japanese]

Bundle.set(language: languages[indexPath.row].language)

Select language screen


"Locale.current.languageCode" will always return system setting language. So we have to use "Locale.preferredLanguages.first". However the return value looks like "ko-US". This is problem ! So I made the LocaleManager to get only the language code.

> LocaleManager.swift

import Foundation

    struct LocaleManager {

    /// "ko-US" → "ko"
    static var languageCode: String? {
        guard var splits = Locale.preferredLanguages.first?.split(separator: "-"), let first = splits.first else { return nil }
        guard 1 < splits.count else { return String(first) }
        splits.removeLast()
        return String(splits.joined(separator: "-"))
}

    static var language: Language? {
        return Language(languageCode: languageCode)
    }
}

Use like this

guard let languageCode = LocaleManager.languageCode, let title = RemoteConfiguration.shared.logIn?.main?.title?[languageCode] else {
      return NSLocalizedString("Welcome!", comment: "")
}
return title

Solution 4 - Ios

Usable code in Swift 4:

extension Bundle {
    private static var bundle: Bundle!
    
    public static func localizedBundle() -> Bundle! {
        if bundle == nil {
            let appLang = UserDefaults.standard.string(forKey: "app_lang") ?? "ru"
            let path = Bundle.main.path(forResource: appLang, ofType: "lproj")
            bundle = Bundle(path: path!)
        }
        
        return bundle;
    }
    
    public static func setLanguage(lang: String) {
        UserDefaults.standard.set(lang, forKey: "app_lang")
        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        bundle = Bundle(path: path!)
    }
}

and

extension String {
    func localized() -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.localizedBundle(), value: "", comment: "")
    }
    
    func localizeWithFormat(arguments: CVarArg...) -> String{
        return String(format: self.localized(), arguments: arguments)
    }
}

call:

let localizedString = "enter".localized()

set new a locale(for example "ru"):

Bundle.setLanguage(lang: "ru")

Solution 5 - Ios

Jeremy's answer (here) works well on Swift 4 too (I just tested with a simple app and I changed language used in initial view controller).

Here is the Swift version of the same piece of code (for some reasons, my teammates prefer Swift-only than mixed with Objective-C, so I translated it):

import UIKit

private var kBundleKey: UInt8 = 0

class BundleEx: Bundle {

	override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
		if let bundle = objc_getAssociatedObject(self, &kBundleKey) {
			return (bundle as! Bundle).localizedString(forKey: key, value: value, table: tableName)
		}
		return super.localizedString(forKey: key, value: value, table: tableName)
	}

}

extension Bundle {

	static let once: Void = {
		object_setClass(Bundle.main, type(of: BundleEx()))
	}()

	class func setLanguage(_ language: String?) {
		Bundle.once
		let isLanguageRTL = Bundle.isLanguageRTL(language)
		if (isLanguageRTL) {
			UIView.appearance().semanticContentAttribute = .forceRightToLeft
		} else {
			UIView.appearance().semanticContentAttribute = .forceLeftToRight
		}
		UserDefaults.standard.set(isLanguageRTL, forKey: "AppleTextDirection")
		UserDefaults.standard.set(isLanguageRTL, forKey: "NSForceRightToLeftWritingDirection")
		UserDefaults.standard.synchronize()

		let value = (language != nil ? Bundle.init(path: (Bundle.main.path(forResource: language, ofType: "lproj"))!) : nil)
		objc_setAssociatedObject(Bundle.main, &kBundleKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
	}

	class func isLanguageRTL(_ languageCode: String?) -> Bool {
		return (languageCode != nil && Locale.characterDirection(forLanguage: languageCode!) == .rightToLeft)
	}

}

Solution 6 - Ios

after spending several days I've actually found the solution. doesn't need re-launch, quite elegant: http://www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-your-ios-application.html , check the method #2. It doesn't require to manually re-establish all the titles and texts, just overrides the localization for the custom NSBundle category. Works on both Obj-C and Swift projects (after some tuning) like a charm. I had some doubts if it will be approved by apple, but it actually did.

Solution 7 - Ios

Swift 4

UserDefaults.standard.set(["es", "de", "it"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()

Swift 3

NSUserDefaults.standardUserDefaults().setObject(["es", "de", "it"], forKey: "AppleLanguages")
NSUserDefaults.standardUserDefaults().synchronize()

Source: here

Solution 8 - Ios

Here is what Apple says about changing the language; > 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.

So as a recommendation, you should navigate your users to general settings page of your app which can be found under > Settings -> [your_app_name] -> Preferred Language

In order to open application settings directly from your app, You may use these pieces of code;

For Swift;

let settingsURL = URL(string: UIApplication.openSettingsURLString)!
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)

For Obj-C;

NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:settingsURL options:@{} completionHandler:nil];

Hint: Before navigating to settings page, it is better to popup and say what the users should do after they switched to settings page is a better user experience idea for your application.

Solution 9 - Ios

The solution linked by whiteagle actually works to switch the language on the fly. Here's the post.

I simplified the sample code on there to a single .h/.m that will change the language on-the-fly, in memory. I've shown how to call it from Swift 3.

Header:

//
//  NSBundle+Language.h
//  ios_language_manager
//
//  Created by Maxim Bilan on 1/10/15.
//  Copyright (c) 2015 Maxim Bilan. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSBundle (Language)

+ (void)setLanguage:(NSString *)language;

@end

Implementation:

//
//  NSBundle+Language.m
//  ios_language_manager
//
//  Created by Maxim Bilan on 1/10/15.
//  Copyright (c) 2015 Maxim Bilan. All rights reserved.
//

#import "NSBundle+Language.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

static const char kBundleKey = 0;

@interface BundleEx : NSBundle

@end

@implementation BundleEx

- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
{
	NSBundle *bundle = objc_getAssociatedObject(self, &kBundleKey);
	if (bundle) {
		return [bundle localizedStringForKey:key value:value table:tableName];
	}
	else {
		return [super localizedStringForKey:key value:value table:tableName];
	}
}

@end

@implementation NSBundle (Language)

+ (void)setLanguage:(NSString *)language
{
	static dispatch_once_t onceToken;
	dispatch_once(&onceToken, ^{
		object_setClass([NSBundle mainBundle], [BundleEx class]);
	});

	BOOL isLanguageRTL = [self isLanguageRTL:language];
	if (isLanguageRTL) {
		if ([[[UIView alloc] init] respondsToSelector:@selector(setSemanticContentAttribute:)]) {
			[[UIView appearance] setSemanticContentAttribute:
			 UISemanticContentAttributeForceRightToLeft];
		}
	}else {
		if ([[[UIView alloc] init] respondsToSelector:@selector(setSemanticContentAttribute:)]) {
			[[UIView appearance] setSemanticContentAttribute:UISemanticContentAttributeForceLeftToRight];
		}
	}
	[[NSUserDefaults standardUserDefaults] setBool:isLanguageRTL forKey:@"AppleTextDirection"];
	[[NSUserDefaults standardUserDefaults] setBool:isLanguageRTL forKey:@"NSForceRightToLeftWritingDirection"];
	[[NSUserDefaults standardUserDefaults] synchronize];

	id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil;
	objc_setAssociatedObject([NSBundle mainBundle], &kBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (BOOL)isLanguageRTL:(NSString *)languageCode
{
	return ([NSLocale characterDirectionForLanguage:languageCode] == NSLocaleLanguageDirectionRightToLeft);
}


@end

To call this from Swift, ensure your Bridging Header has:

#import "NSBundle+Language.h"

Then from your code, call:

Bundle.setLanguage("es")

Things to note:

  • I did not include any sample code to show a language picker or anything. The original linked post does include some.

  • I changed this code to not change anything persistently. The next time the app runs, it will still try to use the user's preferred language. (The one exception is right-to-left languages, see below)

  • You can do this anytime before a view is loaded, and the new strings will take effect. However, if you need to change a view that's already loaded, you may want to re-initialize the rootViewController as the original post says.

  • This should work for right-to-left languages, but it sets two internal persistent preferences in NSUserDefaults for those languages. You may want to undo that by setting the language back to the user's default upon app exit: Bundle.setLanguage(Locale.preferredLanguages.first!)

Solution 10 - Ios

First of all - this is bad idea and Apple recommend to use iOS selected language for localization.

But if you really need it you can make some small service for such purpose

enum LanguageName: String {
    case undefined
    case en
    case es
    case fr
    case uk
    case ru
    case de
    case pt
}
    
let DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey = "DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey"


func dynamicLocalizableString(_ key: String) -> String {
    return LanguageService.service.dynamicLocalizedString(key)
}

class LanguageService {

    private struct Defaults {
        static let keyCurrentLanguage = "KeyCurrentLanguage"
    }
    
    static let service:LanguageService = LanguageService()
    
    var languageCode: String {
        get {
            return language.rawValue
        }
    }
    
    var currentLanguage:LanguageName {
        get {
            var currentLanguage = UserDefaults.roxy.object(forKey: Defaults.keyCurrentLanguage)
            if currentLanguage == nil {
                currentLanguage = Locale.preferredLanguages[0]

            }
            if var currentLanguage = currentLanguage as? String, 
                let lang = LanguageName(rawValue: currentLanguage.truncatedBy(by:2)) {
                return lang
            }
            return LanguageName.en
        }
    }
    
    var defaultLanguageForLearning:LanguageName {
        get {
            var language: LanguageName = .es
            if currentLanguage == language {
                language = .en
            }
            return language
        }
    }
    
    func switchToLanguage(_ lang:LanguageName) {
        language = lang
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
    }
    
    func clearLanguages() {
        UserDefaults.roxy.setValue(nil, forKey:Defaults.keyCurrentLanguage)
        print(UserDefaults.roxy.synchronize())
    }
    
    private var localeBundle:Bundle?
    
    fileprivate var language: LanguageName = LanguageName.en {
        didSet {
            let currentLanguage = language.rawValue
            UserDefaults.roxy.setValue(currentLanguage, forKey:Defaults.keyCurrentLanguage)
            UserDefaults.roxy.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.roxy.object(forKey: Defaults.keyCurrentLanguage)
        if currentLanguage == nil {
            currentLanguage = Locale.preferredLanguages[0]
        }
        
        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
        }
    }
}

And than make rootViewControllerClass like:

import Foundation

protocol Localizable {
    func localizeUI()
}

and

class LocalizableViewController: UIViewController, Localizable {
    
    // 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")
    }
}

Than inherit every controller from LocalizableViewController and implement localizeUI()

And instead of NSLocalizedString use dynamicLocalizableString like :

func localizeOnceUI() {
    label.text = dynamicLocalizableString("keyFrom<"Localizable.strings">")
}

To switch language:

LanguageService.service.switchToLanguage(.en)

Also note - additional steps and modification of logic required if you want to dynamically localize your widgets or other app parts.

Solution 11 - Ios

Here is my solution with String extension. Improved safety from @Das answer.

extension String {
  var localized: String {
    guard let path = Bundle.main.path(forResource:    Locale.current.regionCode?.lowercased(), ofType: "lproj"), let bundle = Bundle(path: path) else {
      return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }

    return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
  }
}

Solution 12 - Ios

Here is an updated answer for Swift 4

let language = "es" // replace with Locale code
guard let path = Bundle.main.path(forResource: language, ofType: "lproj") else {
  return self
}
guard let bundle = Bundle(path: path) else {
  return self
}
return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")

Solution 13 - Ios

class ViewController: UIViewController {

@IBOutlet weak var resetOutlet: MyButton! {
    didSet {
        resetOutlet.setTitle("RESET".localized().uppercased(), for: .normal)
    }
}`
}

extension String {

func localized(tableName: String = "Localizable") -> String {
    if let languageCode = Locale.current.languageCode, let preferredLanguagesFirst = Locale.preferredLanguages.first?.prefix(2)  {
        if languageCode != preferredLanguagesFirst {
            if let path = Bundle.main.path(forResource: "en", ofType: "lproj") {
                let bundle = Bundle.init(path: path)
                return NSLocalizedString(self, tableName: tableName, bundle: bundle!, value: self, comment: "")
            }
        }
        }
    return NSLocalizedString(self, tableName: tableName, value: self, comment: "")
}
}

Solution 14 - Ios

How to support per-app language settings in your app:
https://developer.apple.com/news/?id=u2cfuj88

How to transition away from a custom language selector in your app

With systemwide support for in-app language selectors, you no longer need to provide a way to select languages within your app if you support iOS 13 or macOS Catalina or later. If you currently offer such a UI, you should remove it to avoid customer confusion and potential conflict with the system.

If you’d like to guide people to the system settings for language selection, you can replace your app’s custom UI with a flow that launches directly into the Settings app on iOS.

On iOS, add the following:

UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)

On macOS, direct people to System Preferences > Language & Region to add a per-language setting for your app.

Solution 15 - Ios

Optimized code for mr.boyfox.

Set system language code to i18n_language key in StandardUserDefaults.

extension String {

var localized: String {
    if let _ = UserDefaults.standard.string(forKey: "i18n_language") {} else {
        // we set a default, just in case
        
        let lang = Bundle.main.preferredLocalizations.first ?? "en"
        UserDefaults.standard.set(lang, forKey: "i18n_language")
        UserDefaults.standard.synchronize()
    }

    let lang = UserDefaults.standard.string(forKey: "i18n_language")

    let path = Bundle.main.path(forResource: lang, ofType: "lproj")
    let bundle = Bundle(path: path!)

    return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
 } 
}

Solution 16 - Ios

Latest Swift syntax :

import Foundation

extension String {
    func localized(lang:String) ->String {
        
        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        let bundle = Bundle(path: path!)
        
        return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}

Solution 17 - Ios

This is extended John Pang's solution, if you need to translate system Strings immediately (Back, Cancel, Done...):

private var kBundleKey: UInt8 = 0

class BundleEx: Bundle {
    
    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        if let bundle = objc_getAssociatedObject(self, &kBundleKey) {
            return (bundle as! Bundle).localizedString(forKey: key, value: value, table: tableName)
        }
        return super.localizedString(forKey: key, value: value, table: tableName)
    }
    
}

private var kBundleUIKitKey: UInt8 = 0

class BundleUIKitEx: Bundle {

    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        if let bundle = objc_getAssociatedObject(self, &kBundleUIKitKey) {
            return (bundle as! Bundle).localizedString(forKey: key, value: value, table: tableName)
        }
        return super.localizedString(forKey: key, value: value, table: tableName)
    }

}

extension Bundle {
    
    static let once: Void = {
        object_setClass(Bundle.main, type(of: BundleEx()))
        object_setClass(Bundle(identifier:"com.apple.UIKit"), type(of: BundleUIKitEx()))
    }()
    
    class func setLanguage(_ language: String?) {
        Bundle.once
        let isLanguageRTL = Bundle.isLanguageRTL(language)
        if (isLanguageRTL) {
            UIView.appearance().semanticContentAttribute = .forceRightToLeft
        } else {
            UIView.appearance().semanticContentAttribute = .forceLeftToRight
        }
        UserDefaults.standard.set(isLanguageRTL, forKey: "AppleTextDirection")
        UserDefaults.standard.set(isLanguageRTL, forKey: "NSForceRightToLeftWritingDirection")
        UserDefaults.standard.synchronize()
        
        let value = (language != nil ? Bundle.init(path: (Bundle.main.path(forResource: language, ofType: "lproj"))!) : nil)
        objc_setAssociatedObject(Bundle.main, &kBundleKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        if let uiKitBundle = Bundle(identifier: "com.apple.UIKit") {
            var valueUIKit: Bundle? = nil
            if let lang = language,
                let path = uiKitBundle.path(forResource: lang, ofType: "lproj") {
                valueUIKit = Bundle(path: path)
            }
            objc_setAssociatedObject(uiKitBundle, &kBundleUIKitKey, valueUIKit, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    
    class func isLanguageRTL(_ languageCode: String?) -> Bool {
        return (languageCode != nil && Locale.characterDirection(forLanguage: languageCode!) == .rightToLeft)
    }
    
}

If you want to translate system strings, you have to do the same for UIKit Bundle.

Solution 18 - Ios

For localization during runtime can be used one of the next libraries: Localize_Swift or LanguageManager-iOS

If you want to change localization according to Apple recommendations you can find the description in @Muhammad Asyraf's answer.

Solution 19 - Ios

If you're using SwiftUI. In practice, overriding Bundle has proven unreliable.

This will allow you to override the used language on the fly reliably. You just need to make set your app-supported languages to SupportedLanguageCode.

(you might need to reload if you want to localize the current view instantly)

import SwiftUI

class Language {
    static let shared = Language()
    static let overrideKey = "override.language.code"
    var currentBundle: Bundle!

    init() {
        loadCurrentBundle()
    }

    func loadCurrentBundle() {
        let path = Bundle.main.path(forResource: current.rawValue, ofType: "lproj")!
        currentBundle = Bundle(path: path)!
    }

    enum SupportedLanguageCode: String, Equatable, CaseIterable {
        case en
        case ar
        case de
        case es
        case fr
        case hi
        case it
        case ja
        case ko
        case nl
        case ru
        case th
        case tr
        case vi
        case pt_BR = "pt-BR"
        case zh_Hans = "zh-Hans"
        case zh_Hant = "zh-Hant"
    }
    
    func set(language: Language.SupportedLanguageCode) {
        UserDefaults.standard.set(language.rawValue, forKey: type(of: self).overrideKey)
        loadCurrentBundle()
    }

    var current: Language.SupportedLanguageCode {
        let code = UserDefaults.standard.string(forKey: type(of: self).overrideKey) ?? Locale.current.languageCode!
        guard let language = Language.SupportedLanguageCode(rawValue: code) else {
            fatalError("failed to load language")
        }
        return language
    }
}

extension String {
    var localized: String {
        Language.shared.currentBundle.localizedString(forKey: self, value: nil, table: nil)
    }
}

You're just loading up needed Bundle and specifying that bundle when you localize in the overridden localized.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVaris DarasirikulView Question on Stackoverflow
Solution 1 - IosdijipijiView Answer on Stackoverflow
Solution 2 - IosAlexandre G.View Answer on Stackoverflow
Solution 3 - IosDenView Answer on Stackoverflow
Solution 4 - IosSBotirovView Answer on Stackoverflow
Solution 5 - IosJohn PangView Answer on Stackoverflow
Solution 6 - IoswhiteagleView Answer on Stackoverflow
Solution 7 - IosDan RosenstarkView Answer on Stackoverflow
Solution 8 - IosOnur ŞahindurView Answer on Stackoverflow
Solution 9 - IosJeremyView Answer on Stackoverflow
Solution 10 - IoshbkView Answer on Stackoverflow
Solution 11 - IosKristaps GrinbergsView Answer on Stackoverflow
Solution 12 - Iosmoger777View Answer on Stackoverflow
Solution 13 - IosSerhii ZhuravlovView Answer on Stackoverflow
Solution 14 - IosMuhammad AsyrafView Answer on Stackoverflow
Solution 15 - IostounaobunView Answer on Stackoverflow
Solution 16 - IosArunabh DasView Answer on Stackoverflow
Solution 17 - Iosbojanb89View Answer on Stackoverflow
Solution 18 - IosOleh HView Answer on Stackoverflow
Solution 19 - IosAndres CanellaView Answer on Stackoverflow