manual language selection in an iOS-App (iPhone and iPad)
Asked Answered
C

8

76

My question:

How can my iPhone-app tell the iOS, that the user did select a language in the apps preferences, that is different from the language set in the general settings?

Other formulation of the same question:

How can i tell the system, that NSLocalizedString (@"text", @"comment"); should not access the systemwide selected language, but the in-app-selected language?

background, example:

Please take this situation as an example: A son of german immigrants is living in the north-east of France next to Luxemburg and Germany. His native language is French, so he did set the user-interfaces language of his iPhone to French (Settings → General → International → Language → Français). But due to his cultural background and because the region where he is living is bilingual, he also speaks German very well. But he doesn't speak ten words of English. On an iPhone (and iPad as well) he has no chance to select a second language, so the phone only knows that he spreaks french. It has no knowledge of the users skills in other languages.

Now comes my app: I did develop it in English and German (German is my native language and English is standard-language in IT). I did develop it according to all rules and best practices for mulilingual iOS-Apps. "First" Language (default language) of my app is English.

This means:

If somebody has chosen English or German in his Settings, the apps user-interface automatically will use the selected language. The user will not even notice that there are other languages available.

But if he did select any other language (like Chinese, Polish or French) in the general settings, he will get the apps default-language, which, in my case, is English. But for my french-german friend this is not the best choice. He would like to use the existing german version, but there seems to be no way to let the user select this version.

Adding a french translation would solve the problem for our french-german friend, but not for people speaking two other languages (such as italian and german), and I can not support my app with all languages spoken on this planet. Setting the default-language to German is also not optimal, because this would rise the same problem for people speaking french (as native language) and English (as second language).

So I think my app must have the possibility to manually select a language that is different from the pre-selected language. Adding a language-selection to the apps settings-panel ist not the problem. But how can i tell the system, that NSLocalizedString (@"text", @"comment"); should not access the systemwide selected language, but the in-app-selected language?

Consignee answered 30/3, 2012 at 8:58 Comment(3)
This is not a widely known feature but it is actually possible to set multiple preferred language in the Settings app. You have to select them in reverse order, for instance your friend would need to switch his iPhone to German, and then to French. This will make French the preferred language, falling back to German if not available, and then to the app Base localization.Sarcocarp
FIRST: This is not just not widely known, it is completely undocumented. SECOND: I, as a developer of an app, have not the power to tell this feature to all the 800 millions of iOS-Users out there. And even if I could: It is very unlikely that every user would do this. So this is a nice workaround that a dozen power-users might do, but this is not a solution that I, as a developer, can write into my app.Ani
Ahm. Well. I'm not sure how to say it without hurting you, but: You did not help. This is what I wanted to say with phrases like "it is completely undocumented" and "this is not a solution".Ani
C
85

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!

Consignee answered 13/4, 2012 at 14:6 Comment(21)
This seems like a good solution for in-code text strings. But what about Base Internationalization and storyboards? How do you force MainStoryboard.storyboard, for example, to switch to the new language? You've replaced NSLocalizedString(key, comment) with LocalizedString(key), which allows you to use the appropriate bundle. But that doesn't force the change on the storyboard. Is there a way to change which bundle the storyboard is using?Leventhal
@ashokdy: It's all described above in my answer. To change language call LocalizationSetLanguage(someLanguage);. You have to create some kind of form within your app where the user can manually select the language (there is NO automatic assignment; thats the big feature of my solution because automatic assignment often leads to wrong results). This means that you can test it at any time by just selecting a language in your self-coded form.Ani
Is there a way to check what language has been set?Eyed
@AlexanderZ: This is what the entry "languageOfBundle" is good for in each version of Localizable.strings. Just display LocalizedString(@"languageOfBundle") and you will see the selected languages name.Ani
Everything works fine.. One more thing is how to set language for xib files? please explainSiglos
@Manigandasaravanan: Sorry, in my projects I don't use nib/xib files, so I have no experience with itAni
@HubertSchölnast: my project has to be in english and italian. Making keys for all static and dynamic data in Localizable.strings does not seem to be a good idea. Is there any other way out? What about genstrings?Domination
@mars: I don't use genstrings, so I'm not very familiar with it. But as far as I know it's just a tool to extract all NSLocalizedStrings from an existing project to generate a Localizable.strings-file. You still need to translate this file (a copy of it) into any other language you want. And then you can use those files either as recommended by Apple, or as described here (which is just an extension of the recommended method)Ani
@HubertSchölnast I did all steps as you mention in your post. But when i am getting localized text then it always return me a key only not a value. Can you please help me with that ..Prenatal
@MinuMaster: No, sorry, I can't help, because without seeing your code I can't see what you've done wrong. Please carefully read my instructions and check at every single step if you've really done it as described here.Ani
@HubertSchölnast No worry I found an issue. I am not use same name for localizable.string file. That cause an issue. now problem resolved.Prenatal
How do you make sure all VCs are reloaded to update the labels with the new, selected language? I'm using ' [self viewDidLoad]; [self viewWillAppear:(BOOL) NO];' but sometimes it doesn't work and the language is not changed even if all labels are set in the right methods. I have one german language file and one base file which is in english.Wohlen
Novarg's solution is simpler and less "reinventing the wheel" and it works with base-localization.Tortosa
@JustAMartin: Read my comments below his answer, mainly the one from Mar 30 '12 at 13:05, then you will understand, why it did not solve my problem. His solution is simpler, yes, thats true, but it was not the full solution of my problem. My solution offers ab better usability to the customer.Ani
Is there a way to do this using Storyboard or xib files?Prod
Hi , How to localize dynamic text which is coming from server, in my application every text i have to display in simplified chinese language, But how to display the text in chinese which is coming in english. help me.Argolis
@MaheshNarla: What I described is NOT a translation algorithm. What you need, is a translation machine, and this a subject of many decades of intense research that still has not brought a satisfactory solution. But it seems to be clear, that a mobile phone of 2017 will not be able to provide the resources for a translation machine.Ani
@HubertSchölnast if possible then can you please share one demo project link with me. I use above solution that you shared but something issue with me so I can not able to get Solution. thanks in advanceCourbet
@BhumeshPurohit: No, sorry. I posted this answer 6 years ago, and in the meantime I quit developing smartphone apps. I don't have the code for this project anymore. This code was written in Objective-C 2.0 and was valid for IOS 5.1 on iPhone 4s. Now you write Apps in Swift 4 on IOS 11.4 for iPhone X.Ani
@HubertSchölnast okay, but I really thanks for Your valuable responce.Courbet
is it possible to add RTLMigration
H
40

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

Hospital answered 30/3, 2012 at 9:3 Comment(7)
I did not try out your proposal, but I'm shure that it will not solve my problem. It seems to do one of two things (I'm not shure with one): Either it sets the global, systemwide language selection to an new language, or it just stores the selectes language in the apps UserDefaults. Changing the global language setting is bad, because a selection in my app must not have impacts to other apps! Storing the selection in the apps UserDefaults does nothing but storing it there. IOS will not know that is should USE the selected language for this app.Ani
@HubertSchölnast how can you be sure that it will not solve your problem it you didn't try it? Also, you can't change "systemwide language selection" from your app. And are you sure that it will do nothing? I'm sure that it will change the language of the app.Hospital
I tried it out, and it really works! Thank you! But there is still a problem: The app must be completely shut down and then restartet. just to press the home-button to move it into background is not enough. I whish there was a more user-friendly solution.Ani
@HubertSchölnast if you want the app to close once you click the home button you can add Application does not run in background to your info.plist file. Then every time you click home button from your app, your app will close. And mark the answer as correct to encourage other users to answer your questions in future.Hospital
Thank you, I did know that. I wish a solution that works like this: *) User did download my app from appstore *) User starts my app and sees, that it doesn't use a language he speaks *) User finds a wrench-and-skrewdriver-icon and presses it *) User selects his preferred language *) User presses "done" and then, without any extra activities, the app refreshes itself without quitting and from that moment on it displays all texts in the new selected language.Ani
@HubertSchölnast I don't know if it will work but try showing a View on the first load with the list of languages. And do it before loading any other views. After selecting the language just proceed to the app. Hopefully it will work as expectedHospital
+1 for "if you want the app to close once you click the home button you can add Application does not run in background to your info.plist file. " - saves you from force-closing the app to reload all view controllersAntho
L
28

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.

Lelahleland answered 20/1, 2017 at 22:38 Comment(9)
Hi , How to localize dynamic text which is coming from server, in my application every text i have to display in simplified chinese language, But how to display the text in chinese which is coming in english. help me.Argolis
As you're saying: The text is dynamic and comes from the server in English. The normal approach would be to tell the server which language you expect in your request and the responsibility of the server to respond with a simplified chinese text if you specify so. This is usually done with a locale set somewhere in the request. If you don't have access to the server then you might want to use a machine translation service like Google Translate – but the results may not be the best...Lelahleland
It has been working for me for quite a while. What version of iOS are you using? Maybe you're not doing it right, I could have a look if you uploaded the portion of code where you are implementing this ...Lelahleland
If i will use exit(EXIT_SUCCESS) code and try to upload application in iTunes Connect. Is Apple will reject application ?Funky
I've used the above code in this app and as you can see it's gotten through review and remains on the App Store til date.Lelahleland
So basically what you suggest behind those 2 alert controllers is to re-start an app. Not too helpfulVizard
Thank you very much for this, and the detailed instructions. I did not want to use any external library in my app so this was the way to go.Cajun
Amazing solution ✅✅✅Ong
@Vizard Yes, that's Novargs approach, as stated in the very first sentence. I just provide some more detailed information, but the approach includes an app restart. There seems to be no other way. If you find the info "It's not possible" more "helpful", then there you have it, it's not. ;)Lelahleland
A
4

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.

Allmon answered 5/10, 2019 at 15:55 Comment(1)
Yes, this is exciting and if someone gives a seamless experience to the user then settings can be open within the App to select the desired language. developer.apple.com/videos/play/wwdc2019/403Poff
S
2

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

Starfish answered 24/7, 2017 at 17:3 Comment(0)
G
2

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?

Glissando answered 29/8, 2017 at 9:48 Comment(0)
A
0

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];
}
Authority answered 24/3, 2017 at 5:52 Comment(2)
If i can do it like your answer apple will accept my application ?Funky
Yes, Apple have no issue with this approach as it's scope is your app only. I have distributed apps with itAuthority
M
0

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

Marrakech answered 14/8, 2019 at 22:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.