web-dev-qa-db-fra.com

iOS: Comment changer la langue de l'application par programme SANS redémarrer l'application?

Lorsque je modifie la langue utilisée par l'application indépendamment sur la langue de l'appareil, elle ne prend effet que lorsque je ferme l'application et la redémarre. Comment ne pas exiger le redémarrage de l'application pour charger à nouveau tous les fichiers nib et les fichiers .strings en fonction de la langue sélectionnée?

J'utilise ceci pour changer la langue au moment de l'exécution:

NSArray* languages = [NSArray arrayWithObjects:@"ar", @"en", nil]; 
[[NSUserDefaults standardUserDefaults] setObject:languages forKey:@"AppleLanguages"];
35
Ahmed Said

J'avais une exigence similaire pour une application iPad en mode kiosque avec navigation par onglets. Non seulement l'application devait prendre en charge les changements de langue à la volée, mais elle devait le faire en sachant que la plupart des onglets étaient déjà chargés à partir des nibs car l'application n'était redémarrée (en moyenne) qu'une fois par semaine environ lorsqu'un nouveau la version a été chargée.

J'ai essayé plusieurs suggestions pour tirer parti des mécanismes de localisation Apple Apple existants et ils avaient tous de sérieux inconvénients, y compris un support bancal dans XCode 4.2 pour les nibs localisés - mes variables de connexion IBoutlet sembleraient être définies correctement dans IB , mais à l'exécution, ils sont souvent nuls!?

J'ai fini par implémenter une classe qui imitait la classe Apple NSLocalizedString mais qui pouvait gérer les changements d'exécution, et chaque fois qu'un changement de langue était effectué par un utilisateur, ma classe publiait une notification. Écrans nécessitant des chaînes localisées ( et images) pour changer, a déclaré une méthode handleLocaleChange, qui était appelée lors de viewDidLoad, et chaque fois que LocaleChangedNotification était publié.

Tous mes boutons et graphiques ont été conçus pour être indépendants de la langue, bien que le texte du titre et le texte de l'étiquette aient généralement été mis à jour en réponse aux changements de paramètres régionaux. Si je devais changer les images, j'aurais pu le faire dans les méthodes handleLocaleChange pour chaque écran, je suppose.

Voici le code. Il inclut un support pour les chemins nib/bundle que je n'utilise pas dans le projet final.

MyLanguage.h // // MyLanguage.h // //

#import <Foundation/Foundation.h>

#define DEFAULT_DICTIONARY_FOR_STRINGS                      @""
#define ACCESSING_ALTERNATE_DICTIONARY_SETS_DEFAULT         1

#define LANGUAGE_ENGLISH_INT  0
#define LANGUAGE_SPANISH_INT  1
#define LANGUAGE_ENGLISH_SHORT_ID  @"en"
#define LANGUAGE_SPANISH_SHORT_ID  @"es"

#define LANGUAGE_CHANGED_NOTIFICATION   @"LANGUAGE_CHANGED"


@interface MyLanguage : NSObject
{
    NSString        *currentLanguage;    
    NSDictionary    *currentDictionary;
    NSBundle        *currentLanguageBundle;
}

+(void) setLanguage:(NSString *)languageName;


+(NSString *)stringFor:(NSString *)srcString forLanguage:(NSString *)languageName;
+(NSString *)stringFor:(NSString *)srcString;

+ (MyLanguage *)singleton;

@property (nonatomic, retain) NSBundle        *currentLanguageBundle;
@property (nonatomic, retain) NSString        *currentLanguage;    
@property (nonatomic, retain) NSDictionary    *currentDictionary;

@end

MyLanguage.m: // // MyLanguage.m

#import "MyLanguage.h"
#import "Valet.h"

#define GUI_STRING_FILE_POSTFIX   @"GUIStrings.plist"

@implementation MyLanguage

@synthesize currentLanguage;   
@synthesize currentDictionary;
@synthesize currentLanguageBundle;

+(NSDictionary *)getDictionaryNamed:(NSString *)languageName
{
    NSDictionary *results = nil;

    // for now, we store dictionaries in a PLIST with the same name.
    NSString *dictionaryPlistFile = [languageName stringByAppendingString:GUI_STRING_FILE_POSTFIX];

    NSString *plistBundlePath = [Valet getBundlePathForFileName:dictionaryPlistFile];

    if ( [[NSFileManager defaultManager] fileExistsAtPath:plistBundlePath] )
    {
        // read it into a dictionary
        NSDictionary *newDict = [NSDictionary dictionaryWithContentsOfFile:plistBundlePath]; 
        results = [newDict valueForKey:@"languageDictionary"];

    }// end if

    return results;
}

+(NSString *)stringFor:(NSString *)srcString forDictionary:(NSString *)languageName;
{
    MyLanguage *gsObject = [MyLanguage singleton];

    // if default dictionary matches the requested one, use it.
    if ([gsObject.currentLanguage isEqualToString:languageName])
    {
        // use default
        return [MyLanguage stringFor:srcString];
    }// end if
    else
    {
        // get the desired dictionary
        NSDictionary *newDict = [MyLanguage getDictionaryNamed:languageName];

        // default is not desired!
        if (ACCESSING_ALTERNATE_DICTIONARY_SETS_DEFAULT)
        {
            gsObject.currentDictionary = newDict;
            gsObject.currentLanguage = languageName;
            return [MyLanguage stringFor:srcString];
        }// end if
        else
        {
            // use current dictionary for translation.
            NSString *results = [gsObject.currentDictionary valueForKey:srcString];

            if (results == nil)
            {
                return srcString;
            }// end if

            return results;
        }
    }

}

+(void) setLanguage:(NSString *)languageName;
{
    MyLanguage *gsObject = [MyLanguage singleton];

    // for now, we store dictionaries in a PLIST with the same name.
    // get the desired dictionary
    NSDictionary *newDict = [MyLanguage getDictionaryNamed:languageName];

    gsObject.currentDictionary = newDict;
    gsObject.currentLanguage = languageName;   


    // now set up the bundle for nibs
    NSString *shortLanguageIdentifier = @"en";
    if ([languageName contains:@"spanish"] || [languageName contains:@"espanol"] || [languageName isEqualToString:LANGUAGE_SPANISH_SHORT_ID])
    {
        shortLanguageIdentifier = LANGUAGE_SPANISH_SHORT_ID;
    }// end if
    else
        shortLanguageIdentifier = LANGUAGE_ENGLISH_SHORT_ID;

//    NSArray *languages = [NSArray arrayWithObject:shortLanguageIdentifier];
//    [[NSUserDefaults standardUserDefaults] setObject:languages forKey:@"AppleLanguages"]; 
//    
    NSString *path= [[NSBundle mainBundle] pathForResource:shortLanguageIdentifier ofType:@"lproj"];
    NSBundle *languageBundle = [NSBundle bundleWithPath:path];
    gsObject.currentLanguageBundle = languageBundle;


    [[NSNotificationCenter defaultCenter] postNotificationName:LANGUAGE_CHANGED_NOTIFICATION object:nil];

}


+(NSString *)stringFor:(NSString *)srcString;
{
    MyLanguage *gsObject = [MyLanguage singleton];
    // default is to do nothing.
    if (gsObject.currentDictionary == nil || gsObject.currentLanguage == nil || [gsObject.currentLanguage isEqualToString:DEFAULT_DICTIONARY_FOR_STRINGS] )
    {
        return srcString;
    }// end if

    // use current dictionary for translation.
    NSString *results = [gsObject.currentDictionary valueForKey:srcString];

    if (results == nil)
    {
        return srcString;
    }// end if


    return results;
}



#pragma mark -
#pragma mark Singleton methods

static MyLanguage *mySharedSingleton = nil;

-(void) lateInit;
{

}

// PUT THIS METHOD DECLARATION INTO THE HEADER
+ (MyLanguage *)singleton;
{
    if (mySharedSingleton == nil) {
        mySharedSingleton = [[super allocWithZone:NULL] init];
        [mySharedSingleton lateInit];
    }
    return mySharedSingleton;
}

+ (id)allocWithZone:(NSZone *)zone
{    return [[self singleton] retain]; }

- (id)copyWithZone:(NSZone *)zone
{    return self; }

- (id)retain
{    return self; }

- (NSUInteger)retainCount //denotes an object that cannot be released
{    return NSUIntegerMax;  }

- (oneway void)release    //do nothing
{   }

- (id)autorelease
{     return self; }


@end
11
software evolved

Ne vous fiez pas aux chaînes que vous avez définies dans votre fichier nib. Utilisez votre plume uniquement pour la mise en page et la configuration des vues. Toute chaîne qui est montrée à l'utilisateur (texte du bouton, etc.) doit être dans vos fichiers Localizable.strings, et lorsque vous chargez votre plume, vous devez définir le texte sur la vue/le contrôle correspondant en conséquence.

Pour obtenir le bundle pour la langue actuelle:

NSString *path = [[NSBundle mainBundle] pathForResource:currentLanguage ofType:@"lproj"];
if (path) {
    NSBundle *localeBundle = [NSBundle bundleWithPath:path];
}

Et pour utiliser le bundle pour obtenir vos chaînes localisées:

NSLocalizedStringFromTableInBundle(stringThatNeedsToBeLocalized, nil, localeBundle, nil);

Aussi pour le formatage de la date, vous voudrez peut-être examiner

[NSDateFormatter dateFormatFromTemplate:@"HH:mm:ss"" options:0 locale:locale];

Pour l'utiliser, vous devrez créer un NSLocale pour la langue/le pays correspondant que vous souhaitez utiliser.

7
mamills

Cela fonctionne pour moi: Swift 4:

Créez un fichier nommé BundleExtension.Swift et ajoutez-y le code suivant -

var bundleKey: UInt8 = 0

class AnyLanguageBundle: Bundle {

override func localizedString(forKey key: String,
                              value: String?,
                              table tableName: String?) -> String {

    guard let path = objc_getAssociatedObject(self, &bundleKey) as? String,
        let bundle = Bundle(path: path) else {

            return super.localizedString(forKey: key, value: value, table: tableName)
    }

    return bundle.localizedString(forKey: key, value: value, table: tableName)
  }
}

extension Bundle {

class func setLanguage(_ language: String) {

    defer {

        object_setClass(Bundle.main, AnyLanguageBundle.self)
    }

    objc_setAssociatedObject(Bundle.main, &bundleKey,    Bundle.main.path(forResource: language, ofType: "lproj"), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  }
}

Maintenant, chaque fois que vous devez changer la langue, appelez cette méthode:

func languageButtonAction() {
    // This is done so that network calls now have the Accept-Language as "hi" (Using Alamofire) Check if you can remove these
    UserDefaults.standard.set(["hi"], forKey: "AppleLanguages")
    UserDefaults.standard.synchronize()

    // Update the language by swaping bundle
    Bundle.setLanguage("hi")

    // Done to reintantiate the storyboards instantly
    let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
    UIApplication.shared.keyWindow?.rootViewController = storyboard.instantiateInitialViewController()
}
6
Ankit Kumar Gupta

Voici ce que j'ai fait. Je suppose que l'astuce était d'utiliser NSLocalizedStringFromTableInBundle au lieu de NSLocalizedString.

Pour toutes les chaînes, utilisez ceci

someLabel.text = NSLocalizedStringFromTableInBundle(@"Your String to be localized, %@",nil,self.localeBundle,@"some context for translators");

Pour changer de langue, exécutez ce code

    NSString * language = @"zh-Hans"; //or whatever language you want
    NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:@"lproj"];
    if (path) {
        self.localeBundle = [NSBundle bundleWithPath:path];
    }
    else {
        self.localeBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"] ];
    }

Après cela, vous voudrez probablement appeler le code de mise à jour pour mettre à jour les chaînes dans les nouvelles langues, par exemple exécuter à nouveau

someLabel.text = NSLocalizedStringFromTableInBundle(@"Your String to be localized, %@",nil,self.localeBundle,@"some context for translators");

C'est tout. Pas besoin de redémarrer l'application. Compatible également avec les paramètres système (si vous définissez une langue via les paramètres iOS, cela fonctionnera également). Pas besoin de bibliothèque externe. Pas besoin de jailbreak. Et cela fonctionne aussi avec les genstrings.

Bien sûr, vous devez toujours faire comme d'habitude pour que les paramètres de votre application persistent:

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

(et faites une vérification dans votre viewDidLoad ou quelque chose)

NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
    NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:@"lproj"];
    if (path) {
        self.localeBundle = [NSBundle bundleWithPath:path];
    }
    else {
        self.localeBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"] ];
    }
5
Pnar Sbi Wer

Vous devez créer votre propre macro similaire à NSLocalizedString mais base le bundle à partir duquel il choisit une chaîne sur une valeur NSUserDefaults que vous définissez (c'est-à-dire ne vous inquiétez pas de la valeur de la valeur par défaut du langage des pommes).

Lorsque vous changez la langue, vous devez envoyer une notification indiquant quels contrôleurs, vues, etc. doivent écouter et se rafraîchir

2
wattson12