31

I have seems some apps can change the language internally within the app without the need of restarting the app, I am wondering how they are implemented.

For example, for us using NSLocalizedString, I know it is possible to set the language at runtime at main.m when your AppDelegate is not initialized, but once it is initialized (particularly your view controller is created), change it has not effect until the next restart

[[NSUserDefaults standardUserDefaults] 
    setObject:[NSMutableArray arrayWithObjects:language, nil] 
    forKey:@"AppleLanguages"];

Anyone have idea how those dynamic language change can be done without restarting the app?

Jakuje
  • 24,773
  • 12
  • 69
  • 75
Ryan
  • 10,041
  • 27
  • 91
  • 156
  • how about deleting the window and rebuilt the interface like in `application:didFinishLaunchingWithOptions:`. Haven't tried myself but maybe this works –  Sep 01 '12 at 10:46
  • 1
    review [this Link][1] may be helped you. [1]: http://stackoverflow.com/questions/1576904/how-to-change-iphone-app-language-during-runtime – Vikas S Singh Sep 08 '12 at 09:33
  • Possible duplication of [this one](http://stackoverflow.com/questions/11578298/change-application-language-setting-in-ios-not-the-whole-device/11578590#11578590). Also, I've posted a simple answer there. You can create a document with all needed strings for every language and load them from it. – Pavel Oganesyan Sep 01 '12 at 15:53
  • 1
    Please check my answer here, it will answer your question: http://stackoverflow.com/a/20257557/306764 – Gilad Novik Apr 21 '14 at 16:20

6 Answers6

6

There's some discussion of other approaches here, in particular a notification based approach:

iOS: How to change app language programmatically WITHOUT restarting the app?

In my view there are really three tasks here: (1) re-localization of resources automatically loaded from nibs. (for example if you dynamically instantiate another custom UIView from a nib, the "old" language strings and settings (images, text direction) will still be loaded) (2) re-localization of strings currently displayed on the screen. (3) re-localization of strings inserted by the developer (you) in program code.

Let's start with (3). If you look for the definition you will notice that NSLocalizedString is a macro. So if you don't want to change existing code too much, you can probably solve the problem of (3) by creating a new header file. In that header file, #undef and then re-#define NSLocalizedString to pick the localized string from the appropriate place--not the one that iOS defaults to, but one that you keep track of in some global variable (e.g., in an app delegate ivar). If you don't want to redefine NSLocalizedString but you still make your own alternative , you should probably still #undef NSLocalizedString if you don't want future developers to accidentally call it instead of the macro you replace it with. Not an ideal solution, but maybe the most practical.

As for (1), if you haven't done your localization in Interface Builder, but rather you do it dynamically in viewDidLoad, etc., no problem. You can use the same behavior just discussed (i.e., the modified NSLocalizedString, etc.). Otherwise you can either (a) implement a notification system as described in the link above (complicated), or (b) consider moving localization from IB to viewDidLoad, or (c) try overriding initWithNibName: and swap out the object loaded with the old language resources, with one loaded with the new language resources. This was an approach mentioned by Mohamed at the very bottom of this discussion: http://learning-ios.blogspot.ca/2011/04/advance-localization-in-ios-apps.html. He claims it causes problems (viewDidLoad isn't called). Even if it doesn't work, trying it out might point you towards something that does.

Finally, (2) is presumably the easiest task: just remove and re-add the current view (or in some cases, just redraw it).

Community
  • 1
  • 1
Merk
  • 1,441
  • 1
  • 15
  • 28
2

the idea is to write a new macro like NSLocalizedString which should check if to take the translation from another specific bundle or not.

The method 2 in this article explain exactly how to do it. In this particular case, the author doesn't use a new macro, but directly set a custom class for [NSBundle mainBundle].

I hope that @holex will understand the problem reading this.

Matteo Gobbi
  • 17,697
  • 3
  • 27
  • 41
1

I'm always using this way, it works perfectly, it might help you as well.


you should set all the texts with NSLocalizableString(...) for the UI for the current language in the -viewWillAppear: method of your every UIViewController.

using this way you (I mean, the users) don't need to restart the application after changing the language of iOS in the Settings.


of course, I'm using the Apple's standard localisation architecture.

UPDATE on (24 Oct 2013)

I've experienced the –viewWillAppear: method won't be performed for the actual view when the application enters to foreground; to solve that issue I also commit the procedure (see above) when I receive UIApplicationWillEnterForegroundNotification notification in the view.

Community
  • 1
  • 1
holex
  • 23,961
  • 7
  • 62
  • 76
  • How do you change the language? – Dejell Oct 24 '13 at 05:55
  • in the _Settings_ application: _General_ -> _International_ -> _Language_, if your application supports the selected language, when you switch back to your application it will appear with using the new language; you can continue the session in your app what you've started before. – holex Oct 24 '13 at 07:40
  • 5
    but this is not answering the question how to do it program wise – Dejell Oct 25 '13 at 06:18
  • @MatteoGobbi, unfortunately, I think developers who have never worked with localization they can't understand how powerful this solution is – obviously an essential knowledge of the view lifecycle is still mandatory to understand the concept. I will remove this answer when I lose more reputation as I gain, but until that point I'm keeping it here, because there are some developer how can appreciate the idea – and I personally don't worry / care about the hobby developers who does not understand it. ;) – holex Feb 06 '15 at 11:03
  • 1
    No, the problem is you are not understanding the question. You are talking about change the language IN SETTINGS. Here the point is to change the language in the APP. And to do this, the user need to restart the app then. We everybody know how to change language by settings of the device.. – Matteo Gobbi Feb 06 '15 at 12:36
  • @holex In my application have a settings page with arabic and english. once i change the language it should flip the view controller and the nslocalizedstrings. It works only once i restart the app. but it should change without closing the app, is possible? – Arun Kumar P Jun 01 '16 at 07:07
1

My implementation uses a class to change the language and access the current language bundle. It's an example so if you were to use different languages than I am then change the methods to use your exact language codes.

This class will access the preferred languages from NSLocale and take the first object which is the language being used.

@implementation OSLocalization

+ (NSBundle *)currentLanguageBundle
{
    // Default language incase an unsupported language is found
    NSString *language = @"en";

    if ([NSLocale preferredLanguages].count) {
        // Check first object to be of type "en","es" etc
        // Codes seen by my eyes: "en-US","en","es-US","es" etc

        NSString *letterCode = [[NSLocale preferredLanguages] objectAtIndex:0];

        if ([letterCode rangeOfString:@"en"].location != NSNotFound) {
            // English
            language = @"en";
        } else if ([letterCode rangeOfString:@"es"].location != NSNotFound) {
            // Spanish
            language = @"es";
        } else if ([letterCode rangeOfString:@"fr"].location != NSNotFound) {
            // French
            language = @"fr";
        } // Add more if needed
    }

    return [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]];
}

/// Check if preferred language is English
+ (BOOL)isCurrentLanguageEnglish
{
    if (![NSLocale preferredLanguages].count) {
        // Just incase check for no items in array
        return YES;
    }

    if ([[[NSLocale preferredLanguages] objectAtIndex:0] rangeOfString:@"en"].location == NSNotFound) {
        // No letter code for english found
        return NO;
    } else {
        // Tis English
        return YES;
    }
}

/*  Swap language between English & Spanish
 *  Could send a string argument to directly pass the new language
 */
+ (void)changeCurrentLanguage
{
    if ([self isCurrentLanguageEnglish]) {
        [[NSUserDefaults standardUserDefaults] setObject:@[@"es"] forKey:@"AppleLanguages"];
    } else {
        [[NSUserDefaults standardUserDefaults] setObject:@[@"en"] forKey:@"AppleLanguages"];
    }
}
@end

Use the class above to reference a string file / image / video / etc:

// Access a localized image
[[OSLocalization currentLanguageBundle] pathForResource:@"my_image_name.png" ofType:nil]
// Access  a localized string from Localizable.strings file
NSLocalizedStringFromTableInBundle(@"StringKey", nil, [OSLocalization currentLanguageBundle], @"comment")

Change language in-line like below or update the "changeCurrentLanguage" method in the class above to take a string parameter referencing the new language code.

// Change the preferred language to Spanish
[[NSUserDefaults standardUserDefaults] setObject:@[@"es"] forKey:@"AppleLanguages"];
Antonioni
  • 578
  • 1
  • 5
  • 15
0

I was stuck in same issue, my requirement was "User can select language from drop down & application have to work according selected language (English or arabic)" What i have done i create two XIB and fetch XIB and Text according selected language. In this way user can select language. I used NSBundle for the same. like

For XIB

self.homeScreen =  [[HomeScreen alloc] initWithNibName:@"HomeScreen" bundle:[CommonData sharedCommonData].languageBundle];

For Text

_lblHeading.text = [self languageSelectedStringForKey:@"ViewHeadingInfo"];

/**
 This method is responsible for selecting language bundle according to user's selection. 
 @param: the string which is to be converted in selected language. 
 @return: the converted string.
 @throws:
 */

-(NSString*) languageSelectedStringForKey:(NSString*) key

{

    NSString* str=[[CommonData sharedCommonData].languageBundle localizedStringForKey:key value:@"" table:nil];

    return str;

}
nsgulliver
  • 12,655
  • 23
  • 43
  • 64
Mangesh
  • 2,257
  • 4
  • 24
  • 51
-1

You need to load another bundle like this(where @"en" could be locale you need):

NSString *path = [[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"];
NSBundle *languageBundle = [NSBundle bundleWithPath:path];

and make macros/function like NSLocalizedString which use your loaded bundle or use methods on that bundle directly like this

[languageBundle localizedStringForKey:key value:value table:tableName];

[[NSBundle mainBundle] localizations] lists all app localizations(including "Base").

Also I wrote helper class which does this(note that it has ReactiveCocoa as a dependency). It allows language change without app restart and sends current locale each time it's changed.

Timur Kuchkarov
  • 1,155
  • 7
  • 21
  • Not sure how this improves on the other answers given. – Popeye Mar 02 '15 at 15:52
  • Well, it doesn't require to undef NSLocalizedString as most upvoted answer, it outlines the way to do this which doesn't require app restart and doesn't cause any problems with review and has sample code(with disclaimer) for the interested. If you have suggestions how to improve it - I'd like to see them and improve answer. – Timur Kuchkarov Mar 02 '15 at 15:56
  • I still don't see how your answer improves on what has already been said. When answering old questions which already have an answer that has been provided you need to say why yours might be a better way of doing it and share code not just a link to code because links expire. – Popeye Mar 02 '15 at 15:59
  • @Popeye I've added some code here which is absolute minimum for this way to work. Actually loading another bundle isn't mentioned in any other answer here. Also it doesn't require NSBundle subclassing or category to it. And I don't think pasting the whole code here will help because it's more than couple of strings and will result in a wall of code. – Timur Kuchkarov Mar 02 '15 at 16:25
  • Also this way you use standard mechanisms of localizations(and can migrate any app that uses standard ways to use user-defined app language) and don't have to use custom json etc. – Timur Kuchkarov Mar 02 '15 at 16:31