iOS:如何在不重新启动应用程序的情况下以编程方式更改应用程序语言?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9416923/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iOS: How to change app language programmatically WITHOUT restarting the app?
提问by Ahmed Said
When I change the app used language independently on the device language it doesn't take effect until I close the app and restart it. How to not require app to be restarted for loading all nib files and .strings files again depending on the selected language?
当我在设备语言上独立更改应用程序使用的语言时,它不会生效,直到我关闭应用程序并重新启动它。如何不要求重新启动应用程序以根据所选语言再次加载所有 nib 文件和 .strings 文件?
I use this to change language at runtime:
我使用它在运行时更改语言:
NSArray* languages = [NSArray arrayWithObjects:@"ar", @"en", nil];
[[NSUserDefaults standardUserDefaults] setObject:languages forKey:@"AppleLanguages"];
回答by Ankit Kumar Gupta
This works for me : Swift 4 :
这对我有用:Swift 4:
Create a file named BundleExtension.swift and add the following code to it -
创建一个名为 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)
}
}
Now whenever you need to change the language call this method :
现在,每当您需要更改语言时,请调用此方法:
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()
}
回答by software evolved
I had a similar requirement for a Kiosk mode iPad app with tabbed navigation. Not only did the app need to support on-the-fly language changes, but had to do so knowing that most of the tabs were already loaded from the nibs since the app was only restarted (on average) about once a week when a new version was loaded.
我对带有选项卡式导航的 Kiosk 模式 iPad 应用程序有类似的要求。该应用程序不仅需要支持即时语言更改,而且还必须知道大多数选项卡已经从笔尖加载,因为该应用程序仅大约每周重新启动一次(平均)一次新的版本已加载。
I tried several suggestions to leverage the existing Apple localization mechanisms and they all had serious drawbacks, including wonky support in XCode 4.2 for localized nibs -- my IBoutlet connection variables would appear to be set correctly in IB, but at runtime they would often be null!?
我尝试了一些建议来利用现有的 Apple 本地化机制,但它们都有严重的缺点,包括 XCode 4.2 对本地化笔尖的支持不稳定——我的 IBoutlet 连接变量似乎在 IB 中设置正确,但在运行时它们通常为空!?
I wound up implementing a class that mimicked the Apple NSLocalizedString class but which could handle runtime changes, and whenever a language change was made by a user my class posted a notification. Screens that needed localized strings (and images) to change declared a handleLocaleChange method, which was called at viewDidLoad, and whenever the LocaleChangedNotification was posted.
我最终实现了一个模仿 Apple NSLocalizedString 类但可以处理运行时更改的类,并且每当用户更改语言时,我的类都会发布通知。需要本地化字符串(和图像)来更改的屏幕声明了一个 handleLocaleChange 方法,该方法在 viewDidLoad 和发布 LocaleChangedNotification 时调用。
All of my buttons and graphics were designed to be language independent, although the title text and label text was typically updated in response to locale changes. If I had to change images, I could have done so in the handleLocaleChange methods for each screen, I suppose.
我的所有按钮和图形都设计为与语言无关,尽管标题文本和标签文本通常会根据区域设置的变化而更新。如果我必须更改图像,我想我可以在每个屏幕的 handleLocaleChange 方法中这样做。
Here is the code. It includes some support for nib/bundle paths which I actually don't use in the final project.
这是代码。它包括对我在最终项目中实际上没有使用的笔尖/捆绑路径的一些支持。
MyLanguage.h // // MyLanguage.h // //
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
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
回答by mamills
Don't rely on strings that you have set in your nib file. Use your nib only for layout & setup of views. Any string that is shown to the user (button text, etc) needs to be in your Localizable.strings files, and when you load your nib you need to set the text on the corresponding view/control accordingly.
不要依赖您在 nib 文件中设置的字符串。仅将您的笔尖用于视图的布局和设置。向用户显示的任何字符串(按钮文本等)都需要在您的 Localizable.strings 文件中,并且当您加载笔尖时,您需要相应地在相应的视图/控件上设置文本。
To get the bundle for the current language:
要获取当前语言的包:
NSString *path = [[NSBundle mainBundle] pathForResource:currentLanguage ofType:@"lproj"];
if (path) {
NSBundle *localeBundle = [NSBundle bundleWithPath:path];
}
And to use the bundle to obtain your localized strings:
并使用捆绑包获取本地化字符串:
NSLocalizedStringFromTableInBundle(stringThatNeedsToBeLocalized, nil, localeBundle, nil);
Also for date formatting, you might want to look into
同样对于日期格式,您可能需要查看
[NSDateFormatter dateFormatFromTemplate:@"HH:mm:ss"" options:0 locale:locale];
To use that you will need to create a NSLocale for the corresponding language/country which you wish to use.
要使用它,您需要为您希望使用的相应语言/国家/地区创建一个 NSLocale。
回答by Pnar Sbi Wer
Heres what I did. I guess the trick was to use NSLocalizedStringFromTableInBundle instead of NSLocalizedString.
这就是我所做的。我想诀窍是使用 NSLocalizedStringFromTableInBundle 而不是 NSLocalizedString。
For all strings, use this
对于所有字符串,使用这个
someLabel.text = NSLocalizedStringFromTableInBundle(@"Your String to be localized, %@",nil,self.localeBundle,@"some context for translators");
To change language, run this 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"] ];
}
After this, you will likely want to call whatever update code to update the strings to the new languages, for e.g. run this again
在此之后,您可能想要调用任何更新代码来将字符串更新为新语言,例如再次运行
someLabel.text = NSLocalizedStringFromTableInBundle(@"Your String to be localized, %@",nil,self.localeBundle,@"some context for translators");
Thats all. No need restart app. Compatible with system settings as well (if you set a language through iOS settings, it will work too). No need external library. No need jailbreak. And it works with genstrings too.
就这样。无需重新启动应用程序。也与系统设置兼容(如果您通过 iOS 设置设置语言,它也可以工作)。不需要外部库。无需越狱。它也适用于 genstrings。
Of course, you should still do the usual for your app settings to persist:
当然,您仍然应该像往常一样让您的应用程序设置保持不变:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"zh-Hans", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];
(and do a check in your viewDidLoad or something)
(并检查您的 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"] ];
}
回答by wattson12
You should create your own macro similar to NSLocalizedString but bases the bundle it chooses a string from on a NSUserDefaults value you set (i.e. don't worry about what the value of apples language defaults value is)
您应该创建自己的类似于 NSLocalizedString 的宏,但是基于您设置的 NSUserDefaults 值从中选择字符串的包(即不要担心苹果语言默认值的值是什么)
When you change the language you should send out a notification, which view controllers, views etc should listen for and refresh themselves
当您更改语言时,您应该发出通知,哪些视图控制器、视图等应该自己监听和刷新