How can I change locale programmatically with Swift
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")
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")
Swift 4.2
In my case, if user change the language setting, I have to update 2 things at runtime.
1. Localizable.strings
2. Storyboard Localization
I make @John Pang code more swifty
BundleExtension.swift
import UIKitprivate var bundleKey: UInt8 = 0final 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 Foundationenum 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)
"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