iOS: 如何在不重启应用程序的情况下以编程方式更改应用程序语言?

9 浏览
0 Comments

iOS: 如何在不重启应用程序的情况下以编程方式更改应用程序语言?

当我在设备上独立更改应用程序使用的语言时,直到我关闭应用程序并重新启动它,更改才会生效。如何在选择的语言上不要求重新加载所有nib文件和.strings文件时,无需重新启动应用程序?

我在运行时使用以下代码更改语言:

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

0
0 Comments

在iOS中,如果想要在应用程序运行时动态地改变应用程序的语言而不需要重启应用程序,可以遵循以下几个步骤。

首先,不要依赖在nib文件中设置的字符串。nib文件应该仅用于布局和设置视图。所有展示给用户的字符串(按钮文本等)应该放在Localizable.strings文件中,并且在加载nib文件时,需要根据对应的视图/控件设置文本。

其次,要获取当前语言的NSBundle(资源包),可以使用以下代码:

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

然后,可以使用NSBundle来获取本地化的字符串,可以使用以下代码:

NSLocalizedStringFromTableInBundle(stringThatNeedsToBeLocalized, nil, localeBundle, nil);

另外,对于日期格式化,可以使用以下代码:

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

需要注意的是,为了使用这些代码,需要为想要使用的语言/国家创建一个对应的NSLocale。

对于在xib文件中的本地化图片(例如按钮图片),可以参考stackoverflow上的一个例子,使用以下链接查看:[loading a localized uiimage](http://stackoverflow.com/questions/3787751)。

此外,可能需要动态调整标签等控件的大小。例如,可以使用`-[NSString sizeWithFont: constrainedToSize: lineBreakMode:]`方法来确定某些文本所需的高度(或宽度),然后相应地设置控件的frame。

总之,使用不同于设备设置的语言/区域设置并不简单。即使按照以上步骤进行操作,[NSError localizedErrorDescription]仍将返回根据设备设置(或者可能根据NSUserDefaults的"AppleLanguages")的文本。根据其他问题和回答所见,你必须在启动UIApplication之前在main文件中设置语言,因此在应用程序运行时动态更改语言而不重启应用程序是不可能的。

0
0 Comments

iOS: 如何在不重新启动应用程序的情况下通过编程方式更改应用程序语言?

问题的出现原因:

在iOS应用程序中,通常需要通过更改应用程序的语言来实现国际化和本地化。然而,在传统的方法中,更改应用程序语言通常需要重新启动应用程序才能生效。这对于用户体验来说可能不是很好,因为重新启动应用程序会中断用户正在进行的操作。因此,需要一种方法来在不重新启动应用程序的情况下实现动态更改应用程序语言的功能。

解决方法:

为了在不重新启动应用程序的情况下更改应用程序语言,可以创建一个自定义的Bundle类,并通过关联对象机制将其与主Bundle关联起来。然后,可以通过修改关联的Bundle对象来实现动态更改应用程序语言的效果。

以下是解决问题的具体步骤:

1. 创建一个名为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)

}

}

2. 在需要更改应用程序语言的地方调用setLanguage方法。例如,在点击语言切换按钮时调用languageButtonAction方法:

func languageButtonAction() {

// 设置网络请求的Accept-Language为"hi" (使用Alamofire),可以根据具体需求修改

UserDefaults.standard.set(["hi"], forKey: "AppleLanguages")

UserDefaults.standard.synchronize()

// 通过交换Bundle对象来更新应用程序语言

Bundle.setLanguage("hi")

// 重新实例化主Storyboard

let storyboard = UIStoryboard.init(name: "Main", bundle: nil)

UIApplication.shared.keyWindow?.rootViewController = storyboard.instantiateInitialViewController()

}

通过以上步骤,就可以实现在不重新启动应用程序的情况下动态更改应用程序语言。这样用户就可以在应用程序运行过程中切换语言,而无需重新启动应用程序。

以上是关于如何在iOS应用程序中通过编程方式更改应用程序语言的解决方法。通过创建自定义的Bundle类,并通过关联对象机制将其与主Bundle关联起来,可以实现在不重新启动应用程序的情况下动态更改应用程序语言。这将提升用户体验,并提供更加灵活的语言切换功能。

0
0 Comments

iOS中如何在不重启应用的情况下编程地更改应用语言?

在这个问题中,用户有一个要求,即在Kiosk模式iPad应用中支持动态切换语言。该应用不仅需要支持实时语言更改,还需要在大多数标签页已从nibs加载的情况下进行更改,因为该应用程序只在每周加载新版本时(平均)重启一次。

经过尝试利用现有的Apple本地化机制的几个建议,发现它们都存在严重缺陷,包括XCode 4.2在本地化nibs方面的不稳定支持 - 我在IB中的IBOutlet连接变量看起来在IB中设置正确,但在运行时它们经常为null!?

最终,我实现了一个类,模仿了Apple的NSLocalizedString类,但能够处理运行时更改。每当用户进行语言更改时,我的类会发布一个通知。需要更改本地化字符串(和图像)的屏幕会声明一个handleLocaleChange方法,在viewDidLoad时调用该方法,并在发布LocaleChangedNotification时调用。

我的所有按钮和图形都被设计为与语言无关,尽管标题文本和标签文本通常会根据区域设置更改。如果我需要更改图像,我可以在每个屏幕的handleLocaleChange方法中这样做。

下面是代码。其中包括一些我在最终项目中没有使用的nib/包路径支持。

MyLanguage.h:

#import 
#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
@property (nonatomic, retain) NSBundle *currentLanguageBundle;
@property (nonatomic, retain) NSString *currentLanguage;
@property (nonatomic, retain) NSDictionary *currentDictionary;
+(void)setLanguage:(NSString *)languageName;
+(NSString *)stringFor:(NSString *)srcString forLanguage:(NSString *)languageName;
+(NSString *)stringFor:(NSString *)srcString;
+ (MyLanguage *)singleton;
@end

MyLanguage.m:

#import "MyLanguage.h"
#import "Valet.h"
#define GUI_STRING_FILE_POSTFIX @"GUIStrings.plist"
@implementation MyLanguage
@synthesize currentLanguageBundle;
@synthesize currentLanguage;
@synthesize currentDictionary;
+ (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"];
    }
    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];
    } 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];
        } else {
            // use current dictionary for translation.
            NSString *results = [gsObject.currentDictionary valueForKey:srcString];
            if (results == nil) {
                return srcString;
            }
            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;
    } else {
        shortLanguageIdentifier = LANGUAGE_ENGLISH_SHORT_ID;
    }
    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;
    }
    // use current dictionary for translation.
    NSString *results = [gsObject.currentDictionary valueForKey:srcString];
    if (results == nil) {
        return srcString;
    }
    return results;
}
#pragma mark - Singleton methods
static MyLanguage *mySharedSingleton = nil;
- (void)lateInit {
}
+ (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 {
    return NSUIntegerMax;
}
- (oneway void)release {
}
- (id)autorelease {
    return self;
}
@end

这只是一些注释(可能不明显),以防有人想重用我的代码:locale PLIST文件是语言简短ID后跟GUIStings.plist,例如esGUIStrings.plist,并且Plist中的根对象是一个名为“languageDictionary”的字典。字典中的条目由要翻译的字符串作为键(例如“解锁”和“登录”),值是翻译后的字符串(例如“Desbloquear”和“Iniciar la sesion”)。

Valet是我创建的一个助手类,它作为比NSFileManager更高级的文件系统接口。在发布代码之前,我试图删除对它的所有引用,但看起来我错过了一个。在代码的后面,您可以看到一个不使用Valet的类似行:NSString *path = [[NSBundle mainBundle] pathForResource:shortLanguageIdentifier ofType:@"lproj"];

0