Changing language on the fly, in running iOS, programmatically
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 ke开发者_Go百科y) 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 appreview@apple.com to get an answer for this question.
Well, I have to choose so far.
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
-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.
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()
funcfinal 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/
精彩评论