IOS-App (iPhone 和 iPad)中的手动语言选择

我的问题是:

我的 iPhone 应用程序如何告诉 iOS,用户确实在应用程序首选项中选择了一种语言,这种语言不同于一般设置中设置的语言?

同一问题的其他表述:

我如何告诉系统,NSLocalizedString (@"text", @"comment");不应该访问系统范围内选择的语言,而是应用程序内选择的语言?

背景,例子:

请以这种情况为例: 一个德国移民的儿子住在法国东北部,毗邻卢森堡和德国。他的母语是法语,所以他把 iPhone 的用户界面语言设置为法语(设置→通用→国际→语言→法语)。但是由于他的文化背景和他所居住的地区是双语的,他的德语也讲得很好。但他连十个英语单词都不会说。在 iPhone (以及 iPad)上,他没有机会选择第二语言,所以手机只知道他说法语。它不了解其他语言的用户技能。

现在是我的应用程序: 我确实用英语和德语开发了它(德语是我的母语,英语是 IT 的标准语言)。我的确是根据多语言 iOS 应用程序的所有规则和最佳实践来开发它的。我的应用程序的“第一”语言(默认语言)是英语。

这意味着:

如果有人在他的设置中选择了英语或德语,应用程序用户界面将自动使用选定的语言。用户甚至不会注意到还有其他语言可用。

但是,如果他在一般设置中选择了任何其他语言(如汉语、波兰语或法语) ,他将得到应用程序的默认语言,在我的情况下,就是英语。但是对我的法国-德国朋友来说,这不是最好的选择。他想使用现有的德语版本,但似乎没有办法让用户选择这个版本。

添加一个法语翻译将为我们的法德朋友解决问题,但不是为人们讲两种其他语言(如意大利语和德语) ,我不能支持我的应用程序与所有语言在这个星球上。 将默认语言设置为德语也不是最佳选择,因为这会给讲法语(作为母语)和英语(作为第二语言)的人带来同样的问题。

因此,我认为我的应用程序必须有可能手动选择一种语言,这是不同于预先选定的语言。给应用程序设置添加语言选择面板不是问题。但是我如何告诉系统,NSLocalizedString (@"text", @"comment");不应该访问系统范围内选择的语言,而是应用程序内选择的语言?

66542 次浏览

Simply add the following to the screen with language choice:

    NSString *tempValue = //user chosen language. Can be picker view/button/segmented control/whatever. Just get the text out of it
NSString *currentLanguage = @"";
if ([tempValue rangeOfString:NSLocalizedString(@"English", nil)].location != NSNotFound) {
currentLanguage = @"en";
} else if ([tempValue rangeOfString:NSLocalizedString(@"German", nil)].location != NSNotFound) {
currentLanguage = @"de";
} else if ([tempValue rangeOfString:NSLocalizedString(@"Russian", nil)].location != NSNotFound) {
currentLanguage = @"ru";
}
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:currentLanguage, nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults]synchronize];

Then ask them to restart the app and the app will be in other language.

Hope it helps

In the meantime I did find a solution for my problem on myself:

I created a new class "LocalizeHelper":


Header LocalizeHelper.h

//LocalizeHelper.h


#import <Foundation/Foundation.h>


// some macros (optional, but makes life easy)


// Use "LocalizedString(key)" the same way you would use "NSLocalizedString(key,comment)"
#define LocalizedString(key) [[LocalizeHelper sharedLocalSystem] localizedStringForKey:(key)]


// "language" can be (for american english): "en", "en-US", "english". Analogous for other languages.
#define LocalizationSetLanguage(language) [[LocalizeHelper sharedLocalSystem] setLanguage:(language)]


@interface LocalizeHelper : NSObject


// a singleton:
+ (LocalizeHelper*) sharedLocalSystem;


// this gets the string localized:
- (NSString*) localizedStringForKey:(NSString*) key;


//set a new language:
- (void) setLanguage:(NSString*) lang;


@end

iMplementation LocalizeHelper.m

// LocalizeHelper.m
#import "LocalizeHelper.h"


// Singleton
static LocalizeHelper* SingleLocalSystem = nil;


// my Bundle (not the main bundle!)
static NSBundle* myBundle = nil;




@implementation LocalizeHelper




//-------------------------------------------------------------
// allways return the same singleton
//-------------------------------------------------------------
+ (LocalizeHelper*) sharedLocalSystem {
// lazy instantiation
if (SingleLocalSystem == nil) {
SingleLocalSystem = [[LocalizeHelper alloc] init];
}
return SingleLocalSystem;
}




//-------------------------------------------------------------
// initiating
//-------------------------------------------------------------
- (id) init {
self = [super init];
if (self) {
// use systems main bundle as default bundle
myBundle = [NSBundle mainBundle];
}
return self;
}




//-------------------------------------------------------------
// translate a string
//-------------------------------------------------------------
// you can use this macro:
// LocalizedString(@"Text");
- (NSString*) localizedStringForKey:(NSString*) key {
// this is almost exactly what is done when calling the macro NSLocalizedString(@"Text",@"comment")
// the difference is: here we do not use the systems main bundle, but a bundle
// we selected manually before (see "setLanguage")
return [myBundle localizedStringForKey:key value:@"" table:nil];
}




//-------------------------------------------------------------
// set a new language
//-------------------------------------------------------------
// you can use this macro:
// LocalizationSetLanguage(@"German") or LocalizationSetLanguage(@"de");
- (void) setLanguage:(NSString*) lang {


// path to this languages bundle
NSString *path = [[NSBundle mainBundle] pathForResource:lang ofType:@"lproj" ];
if (path == nil) {
// there is no bundle for that language
// use main bundle instead
myBundle = [NSBundle mainBundle];
} else {


// use this bundle as my bundle from now on:
myBundle = [NSBundle bundleWithPath:path];


// to be absolutely shure (this is probably unnecessary):
if (myBundle == nil) {
myBundle = [NSBundle mainBundle];
}
}
}




@end

For each language you want to support you need a file named Localizable.strings. This works exactly as described in Apples documentation for localization. The only difference: Now you even can use languages like hindi or esperanto, that are not supported by Apple.

To give you an example, here are the first lines of my english and german versions of Localizable.strings:

English

/* English - English */


/* for debugging */
"languageOfBundle" = "English - English";


/* Header-Title of the Table displaying all lists and projects */
"summary" = "Summary";


/* Section-Titles in table "summary" */
"help" = "Help";
"lists" = "Lists";
"projects" = "Projects";
"listTemplates" = "List Templates";
"projectTemplates" = "Project Templates";

German

/* German - Deutsch */


/* for debugging */
"languageOfBundle" = "German - Deutsch";


/* Header-Title of the Table displaying all lists and projects */
"summary" = "Überblick";


/* Section-Titles in table "summary" */
"help" = "Hilfe";
"lists" = "Listen";
"projects" = "Projekte";
"listTemplates" = "Vorlagen für Listen";
"projectTemplates" = "Vorlagen für Projekte";

To use localizing, you must have some settings-routines in your app, and in the language-selection you call the macro:

LocalizationSetLanguage(selectedLanguage);

After that you must enshure, that everything that was displayed in the old language, gets redrawn in the new language right now (hidden texts must be redrawn as soon as they get visible again).

To have localized texts available for every situation, you NEVER must write fix texts to the objects titles. ALWAYS use the macro LocalizedString(keyword).

don't:

cell.textLabel.text = @"nice title";

do:

cell.textLabel.text = LocalizedString(@"nice title");

and have a "nice title" entry in every version of Localizable.strings!

Here's a ready-to-use and step-by-step guide on how to use Novarg's approach in Swift 3:


Step #1: Implement a language chooser

How to best do this is up to you and depends on the project. But use

Bundle.main.localizations.filter({ $0 != "Base" }) // => ["en", "de", "tr"]

to get a list of all your supported locales language codes programmatically. Also you can use

Locale.current.localizedString(forLanguageCode: "en") // replace "en" with your variable

to present the language name in the apps current language.

As a complete example you could present a popover action sheet after clicking a button like this:

@IBOutlet var changeLanguageButton: UIButton!


@IBAction func didPressChangeLanguageButton() {
let message = "Change language of this app including its content."
let sheetCtrl = UIAlertController(title: "Choose language", message: message, preferredStyle: .actionSheet)


for languageCode in Bundle.main.localizations.filter({ $0 != "Base" }) {
let langName = Locale.current.localizedString(forLanguageCode: languageCode)
let action = UIAlertAction(title: langName, style: .default) { _ in
self.changeToLanguage(languageCode) // see step #2
}
sheetCtrl.addAction(action)
}


let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
sheetCtrl.addAction(cancelAction)


sheetCtrl.popoverPresentationController?.sourceView = self.view
sheetCtrl.popoverPresentationController?.sourceRect = self.changeLanguageButton.frame
present(sheetCtrl, animated: true, completion: nil)
}

Step #2: Explain user what to do + Change language with restart

You might have noticed that the code in step #1 calls a method named changeToLanguage(langCode:). That's what you should do, too when the user chooses a new language to change to, no matter how you designed your chooser. Here's its implementation, just copy it to your project:

private func changeToLanguage(_ langCode: String) {
if Bundle.main.preferredLocalizations.first != langCode {
let message = "In order to change the language, the App must be closed and reopened by you."
let confirmAlertCtrl = UIAlertController(title: "App restart required", message: message, preferredStyle: .alert)


let confirmAction = UIAlertAction(title: "Close now", style: .destructive) { _ in
UserDefaults.standard.set([langCode], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
exit(EXIT_SUCCESS)
}
confirmAlertCtrl.addAction(confirmAction)


let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
confirmAlertCtrl.addAction(cancelAction)


present(confirmAlertCtrl, animated: true, completion: nil)
}
}

This will both ask and inform the user about if he wants to do the change and how to do it. Also it sets the apps language on the next start using:

UserDefaults.standard.set([langCode], forKey: "AppleLanguages")
UserDefaults.standard.synchronize() // required on real device

Step #3 (optional): Localize the Strings

You might want to localize the strings like "Close now" by using the NSLocalizedString macro (or any other enhanced method).


Real World Example

I'm using this exact implementation in an app targeted for iOS 10, I can confirm it works for me both on simulator and device. The app is actually open source, so you can find the above code distributed into different classes here.

Its very simple and easy to change language manually. First of all you need to localize your app, then you can use below code to change language manually in your app.

UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"App restart required", @"App restart required") message:NSLocalizedString(@"In order to change the language, the App must be closed and reopened by you.", @"In order to change the language, the App must be closed and reopened by you.") preferredStyle:UIAlertControllerStyleActionSheet];


[actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {




[self dismissViewControllerAnimated:YES completion:^{




}];
}]];


[actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Restart", @"Restart") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {


[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"ar", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults]synchronize];


exit(EXIT_SUCCESS);




}]];




[self presentViewController:actionSheet animated:YES completion:nil];
}

Localize-Swift - Swift friendly localization and i18n with in-app language switching

My answer may be a matter of preference since I would disdain manual language selection in the app: you will have to override all system-provided buttons and make sure not to use them. It also adds another layer of complexity and may lead the user to confusion.

However, since this has to be an answer, I think your use case is solved without hacking language selection.

In iOS preferences, you may set additional languages:

iOS language selection preferences

Your example son of immigrants could have set French as main language and German as an additional language.

Then, when your app is localized to English and German, that young man's iPhone would pick German resources.

Would that solve the problem?

I had a similar issue in my current work project. I figured out a simple way to do this and I had to explain it to my partners in order to discuss the solution. I made a simple app with 2 buttons (English||Spanish) to select the language inside the app, the code is in Objective-C (Because our current app is in Objective-C) here is the code: https://bitbucket.org/gastonmontes/gmlanguageselectiondemo

Starting with iOS 13, there is now a way to set an individual language for each app, supported by iOS itself. In the Apple Settings app, open the settings of the specific app, and select the language under "Preferred Language". You can select from the languages that the app supports.