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 l
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
-anoop
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 :)
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.
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:
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
}
}
}
Add some rules to make sure you UI components will be always updated:
protocol Localizable {
func localizeUI()
}
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")
}
}
Inherit any controller u want to conform dynamic app switch functionality and implement localizeUI()
func
final class WelcomeTableViewController: LoadableTableViewController
Switch language as needed:
LanguageService.service.switchToLanguage(.en)
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
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/
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)...