web-dev-qa-db-ja.com

iOS:アプリを再起動せずにプログラムでアプリの言語を変更する方法

デバイスの言語でアプリの使用言語を個別に変更すると、アプリを閉じて再起動するまで有効になりません。選択した言語に応じて、すべてのnibファイルと.stringsファイルを再度ロードするためにアプリを再起動する必要がないようにするにはどうすればよいですか?

これを使用して、実行時に言語を変更します。

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

タブ付きナビゲーションを備えたキオスクモードのiPadアプリについても、同様の要件がありました。アプリはオンザフライの言語変更をサポートする必要があっただけでなく、アプリが(平均して)週に一度だけ新しいものが再起動されたため、ほとんどのタブが既にペン先からロードされていることを知っていなければなりませんでしたバージョンがロードされました。

既存のAppleローカリゼーションメカニズムを活用するためにいくつかの提案を試みましたが、それらはすべてローカライズされたペン先のXCode 4.2での不安定なサポートなど、重大な欠点がありました-IBoutlet接続変数はIBで正しく設定されているようです、しかし実行時にそれらはしばしばヌルになります!?

Apple NSLocalizedStringクラスを模倣するクラスを実装しましたが、実行時の変更を処理でき、ユーザーが言語の変更を行うたびに、私のクラスが通知を投稿しました。およびimage)変更するには、viewDidLoadで呼び出されたハンドルLocaleChangeメソッドを宣言し、LocaleChangedNotificationがポストされるたびに変更します。

すべてのボタンとグラフィックスは言語に依存しないように設計されていますが、タイトルテキストとラベルテキストは通常​​ロケールの変更に応じて更新されます。画像を変更する必要がある場合は、各画面のhandleLocaleChangeメソッドで変更できたはずです。

コードは次のとおりです。最終プロジェクトでは実際には使用しないnib/bundleパスのサポートが含まれています。

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

Nibファイルに設定した文字列に依存しないでください。ペン先は、ビューのレイアウトと設定にのみ使用してください。ユーザーに表示される文字列(ボタンテキストなど)はLocalizable.stringsファイルにある必要があり、ペン先をロードするときに、それに応じて対応するビュー/コントロールにテキストを設定する必要があります。

現在の言語のバンドルを取得するには:

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

そして、バンドルを使用してローカライズされた文字列を取得するには:

NSLocalizedStringFromTableInBundle(stringThatNeedsToBeLocalized, nil, localeBundle, nil);

また、日付のフォーマットについては、

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

これを使用するには、使用する言語/国に対応するNSLocaleを作成する必要があります。

7
mamills

これは私のために働く:Swift 4:

BundleExtension.Swiftという名前のファイルを作成し、次のコードを追加します-

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)
  }
}

言語を変更する必要があるときはいつでも、このメソッドを呼び出してください:

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

私がやったこと。 NSLocalizedStringの代わりにNSLocalizedStringFromTableInBundleを使用することがトリックだったと思います。

すべての文字列に対して、これを使用します

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

言語を変更するには、このコードを実行します

    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"] ];
    }

この後、更新コードを呼び出して文字列を新しい言語に更新します。たとえば、これをもう一度実行します

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

それで全部です。アプリを再起動する必要はありません。システム設定とも互換性があります(iOSの設定で言語を設定すると、それも機能します)。外部ライブラリは必要ありません。脱獄は必要ありません。また、genstringsでも機能します。

もちろん、アプリの設定が維持されるように、通常どおり行う必要があります。

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

(そして、viewDidLoadまたは何かでチェックを行います)

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

NSLocalizedStringに似た独自のマクロを作成する必要がありますが、設定したNSUserDefaults値に基づいて文字列を選択するバンドルをベースにする必要があります(つまり、リンゴの言語のデフォルト値が何であるかを心配しないでください)

言語を変更するときは、通知を送信する必要があります。ビューコントローラー、ビューなどは、リッスンして更新する必要があります

2
wattson12