如何强制NSLocalizedString使用特定语言

272
在iPhone上,NSLocalizedString返回iPhone所使用的语言对应的字符串。是否有可能强制NSLocalizedString使用指定的语言,使得应用程序与设备语言不同?

请参考此链接:https://dev59.com/22ox5IYBdhLWcg3wIQ1o#48187049,非常好用。 - Ankit Kumar Gupta
28个回答

266
NSLocalizedString()(及其变体)访问NSUserDefaults中的“AppleLanguages”键,以确定用户首选语言设置。这将返回一个语言代码数组,其中第一个是用户为其手机设置的语言,后续的语言代码用作备用选项,如果在首选语言中找不到资源,则使用这些备用选项。(在桌面上,用户可以在系统偏好设置中指定多个语言并进行自定义排序)
如果您希望覆盖全局设置,并为自己的应用程序设置自己的语言列表,可以使用setObject:forKey:方法。这将优先于全局设置值,并返回到执行本地化的应用程序中的任何代码中。此操作的代码类似于:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"de", @"en", @"fr", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize]; //to make the change immediate

这将使德语成为您的应用程序首选语言,英语和法语作为备选项。您需要在应用程序启动早期调用此功能。您可以在此处阅读有关语言/区域设置首选项的更多信息:国际化编程主题:获取当前语言和区域设置


4
对我来说这不起作用,它无论如何都会使用默认语言。 - quano
51
在启动应用程序之前设置语言偏好可能会更有效。我在main()函数中进行设置,在调用UIApplicationMain()之前。一旦应用程序正在运行,它不会更改所使用的语言,但只是为下一次设置语言偏好。 - geon
3
在初始化UIKit之前,您必须设置语言,并且必须指定完整的语言+地区本地化设置——请查看此完整示例http://blog.federicomestrone.com/2010/09/15/iphone-apps-and-device-language-setting/。请注意,翻译保持原意,同时通过简化和澄清使其更易于理解。 - fedmest
18
如果在主函数中设置AppleLanguages,它将在运行时更改语言 - 是的!但是...应用程序首次安装后,nsuserdefaults在调用UIApplicationMain()之后初始化,这将忽略先前设置的AppleLanguages,并需要进行丑陋的强制重新启动应用程序才能看到所需的语言。 - JohnPayne
3
这在 Localizable.stringsdict 中定义的复数项目上不起作用。有没有可能修复的想法? - Mihai Fratu
显示剩余12条评论

157

我最近也遇到了同样的问题,我不想修改整个NSLocalizedString,也不想强制应用程序重新启动以使新语言生效。我希望一切都能像现在这样工作。

我的解决方案是动态更改主束的类并在那里加载适当的束:

头文件

@interface NSBundle (Language)
+(void)setLanguage:(NSString*)language;
@end

实现

#import <objc/runtime.h>

static const char _bundle=0;

@interface BundleEx : NSBundle
@end

@implementation BundleEx
-(NSString*)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
{
    NSBundle* bundle=objc_getAssociatedObject(self, &_bundle);
    return bundle ? [bundle localizedStringForKey:key value:value table:tableName] : [super localizedStringForKey:key value:value table:tableName];
}
@end

@implementation NSBundle (Language)
+(void)setLanguage:(NSString*)language
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        object_setClass([NSBundle mainBundle],[BundleEx class]);
    });
    objc_setAssociatedObject([NSBundle mainBundle], &_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

因此,基本上在您的应用程序启动并加载第一个控制器之前,只需调用:

[NSBundle setLanguage:@"en"];

当用户在您的设置屏幕更改其首选语言时,只需再次调用它即可:
[NSBundle setLanguage:@"fr"];

要重置为系统默认设置,只需传递 nil:

[NSBundle setLanguage:nil];

适用于需要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)
    }
}

7
你好,@Wirsing,到目前为止,这对我非常有效。我甚至已经将一个应用程序上传到商店,Apple也没有任何抱怨。 - Gilad Novik
8
这是一个很棒的解决方案,我也在这里引用了它:https://medium.com/ios-apprentice/905e4052b9de - James Tang
3
@Gilad,你的方法对于动态字符串(即在localizable.strings中定义的字符串)非常有效,但是对于故事板中的按钮和标签,仅适用于主函数。那么,你能否扩展你的答案以包括UI控件本地化?我的意思是,在调用[NSBundle setLanguage:@"??"]后,如何刷新(而无需关闭)故事板?您好,@Gilad,您的方法完美适用于动态字符串(在localizable.strings中定义的字符串),但对于故事板中的按钮和标签,只能在主方法中使用。所以,您能否扩展您的回答以包括UI控件的本地化?我的意思是,在调用[NSBundle setLanguage:@"??"]之后,如何刷新(而不是关闭)故事板呢? - Jawad Al Shaikh
2
关于XIB/storyboard:您需要确保在加载视图之前调用此方法。至于实时更新视图:在我的情况下,我只是重新加载了视图控制器(我将其放在导航控制器中并替换了控制器。重新加载后,它获得了新的翻译字符串)。我正在为此编写一个Cocoapod,我可能也会在那里包含一个示例。敬请关注... - Gilad Novik
1
@mohammadalabid 真的没有必要手动加载语言 - 我已经成功地在 XIB 中使用了这段代码。我的技巧是将根控制器设置为 UINavigationController,然后在更改语言后,我使用它的 'viewControllers' 属性重新加载 XIB。rootNavigationController.viewControllers=@[[storyboard instantiateViewControllerWithIdentifier:@"main"]]; 我很快会在 Github 上发布一个示例。 - Gilad Novik
显示剩余25条评论

136

我通常会以这种方式处理,但是你需要在项目中拥有所有的本地化文件。

@implementation Language

static NSBundle *bundle = nil;

+(void)initialize 
{
    NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
    NSArray* languages = [defs objectForKey:@"AppleLanguages"];
    NSString *current = [[languages objectAtIndex:0] retain];
    [self setLanguage:current];
}

/*
  example calls:
    [Language setLanguage:@"it"];
    [Language setLanguage:@"de"];
*/
+(void)setLanguage:(NSString *)l
{
    NSLog(@"preferredLang: %@", l);
    NSString *path = [[ NSBundle mainBundle ] pathForResource:l ofType:@"lproj" ];
    bundle = [[NSBundle bundleWithPath:path] retain];
}

+(NSString *)get:(NSString *)key alter:(NSString *)alternate 
{
    return [bundle localizedStringForKey:key value:alternate table:nil];
}

@end

4
很好的马乌罗。我注意到它也可以用于项目之外的文件。如果因为某些原因(如我的情况),您需要从网络下载字符串文件,并将它们存储在“文档”目录中(具有文件夹结构Documents/en.lproj/Localizable.strings,Documents/fr.lproj/Localizable.strings等)。即使这样,您也可以创建一个NSBundle。只需使用此路径代码: NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"/Documents/%@.lproj", l, nil]]; - arnaud del.
1
在我看来,这是最好的方法,再加上Alessandro的贡献。它不会那么侵入性,也不需要重新启动。 - PKCLsoft
1
我哪里做错了?我创建了一个继承自NSObject的Language类,将方法名称放在.h文件中,实现代码放在.m文件中,然后在我的更改语言按钮中添加[Language setLanguage:@"es"];代码运行(调用按钮方法并进入Language类),但什么都没有发生。我也设置了我的本地化字符串(西班牙语)。但似乎很多其他人都可以正常工作。请有人帮忙详细解释一下。 - SirRupertIII
1
@KKendall 你可以这样调用:[Language setLanguage:@"es"]; NSString *stringToUse = [Language get:@"Your Text" alter:nil]; - Mikrasya
1
这在 Localizable.stringsdict 中定义的复数项目上不起作用。有没有可能修复的想法? - Mihai Fratu
显示剩余6条评论

41

不要在iOS 9上使用。这会对通过它传递的所有字符串返回nil。

我找到了另一种解决方案,可以更新语言字符串,而无需重新启动应用程序,并且与genstrings兼容:

将此宏放入Prefix.pch中:

#define currentLanguageBundle [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSLocale preferredLanguages] objectAtIndex:0] ofType:@"lproj"]]

无论何时需要本地化字符串,请使用:

NSLocalizedStringFromTableInBundle(@"GalleryTitleKey", nil, currentLanguageBundle, @"")

要设置语言,请使用:

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"de"] forKey:@"AppleLanguages"];

即使连续切换语言也能工作,例如:

NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"fr"] forKey:@"AppleLanguages"];
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"it"] forKey:@"AppleLanguages"];
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"de"] forKey:@"AppleLanguages"];
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));

2
@powerj1984:不会,它只会更改您源文件中的字符串。如果您想更改xib语言,则必须手动从所选语言的捆绑包中重新加载xib。 - gklka
@DkKumar:你需要做类似于这个的事情:https://dev59.com/jnvaa4cB1Zd3GeqPF6Vy#21322176 - gklka
@gklka,您的答案似乎是正确的,我已经按照您在答案中描述的步骤进行了操作,但问题在于如果我们更改捆绑包路径,那么它将在同一捆绑包中查找所有资源(即XIB中使用的.png文件),因此我们必须在所有捆绑包(如en.lproj、es.lproj等)中复制图像文件夹,这将影响IPA大小的增加,所以有没有办法解决这个问题?就像您在帖子中所说的那样,让我试试这个方法,并让您更多地了解这个问题。感谢您对我的帮助。 - Dk Kumar
@gklka 我已经检查过了,它可以工作,但是有两个主要问题:#1 每次都必须初始化XIB #2 我的IPA大小从16.5-->195 MB,这是一个很大的变化,我认为这是因为我的应用程序中有很多图片。 - Dk Kumar
1
在iOS 9中,这个功能出现了严重的问题,所有字符串都返回null。 - Alex Zavatone
显示剩余3条评论

32

如前所述,只需执行:

[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:@"el", nil] forKey:@"AppleLanguages"];

但是为了避免重新启动应用程序,将该行代码放在main.m的主方法中,在UIApplicationMain(...)之前。


3
非常有帮助的回答!顺便说一句,对于初学者来说可能很明显,但是你应该在“NSAutoreleasePool * pool..”之后插入这行代码,否则会有一些自动释放的对象泄露。 - ySgPjx
1
重要提示:如果您在调用[[NSBundle mainBundle] URLForResource:withExtension:]之前,此操作将无法执行。 - Kof
3
如果使用Swift,那么就没有main.m文件了? - turingtested
@turingtested 你尝试过在Swift和Storyboard上吗?它能正常工作吗? - iDevAmit
你好,如何本地化从服务器获取的动态文本,在我的应用程序中,我必须以简体中文显示每个文本,但是如何显示以英语呈现的中文文本。请帮助我。 - Mahesh Narla
我在 UIApplicationMain(argc 之前添加了这个代码,但是语言更改只有在重新启动后才会生效。有什么方法可以避免这种情况吗? - user3752049

32

它也适用于 iOS7,我不得不从Brian的解决方案转换到这个,因为似乎iOS再次覆盖了语言选项,因此我被困在了第一次设置语言的状态。你的解决方案非常好用! - haxpor

15

您认为这个适用于Swift 3的解决方案怎么样?

extension String {

    func localized(forLanguage language: String = Locale.preferredLanguages.first!.components(separatedBy: "-").first!) -> String {

        guard let path = Bundle.main.path(forResource: language == "en" ? "Base" : language, ofType: "lproj") else {

            let basePath = Bundle.main.path(forResource: "Base", ofType: "lproj")!

            return Bundle(path: basePath)!.localizedString(forKey: self, value: "", table: nil)
        }

        return Bundle(path: path)!.localizedString(forKey: self, value: "", table: nil)
    }
}

简单使用:

"report".localized(forLanguage: "pl") //forced language
"report".localized() //default language selected by user in settings, in case when your app doesnt support selected lanaguage, the default one is selected, here is an english.

你好,如何本地化从服务器获取的动态文本,在我的应用程序中,我必须以简体中文显示每个文本,但是如何显示以英语呈现的中文文本。请帮助我。 - Mahesh Narla
这是一个很棒的答案;) - Bartłomiej Semańczyk
很棒的答案。帮助了我。 - Dilip Tiwari

14

正如Brian Webster所提到的那样,语言需要“在应用程序启动的早期某个时候”设置。我认为AppDelegateapplicationDidFinishLaunching:方法应该是一个适合的地方,因为在这里我进行了所有其他的初始化。

但正如William Denniss所提到的那样,那似乎只有在应用程序重新启动之后才会生效,这有点无用。

然而,如果我将代码放在main函数中,它似乎可以正常工作:

int main(int argc, char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Force language to Swedish.
    [[NSUserDefaults standardUserDefaults]
     setObject:[NSArray arrayWithObject:@"sv"]
     forKey:@"AppleLanguages"];

    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

我很感激对此的任何评论。


在我的实现中,这是一个用户可设置的事情 - 所以我只需弹出一个对话框告诉他们他们需要重新启动 :) - William Denniss
它几乎适用于所有情况 - 除了本地化的Default.png图像。 - Lukasz
2
如果您允许用户设置语言并告诉他们重新启动应用程序,只需记住如果他们重新启动太快,NSUserDefaults可能不会保存。大多数用户不会这么快,但是当您测试应用程序时,您可能会太快并因此看到不一致的行为。我花了几个小时才意识到为什么我的语言切换有时有效,有时无效! - arlomedia
3
没问题,只需在调用 setObject:forKey: 后加上 [[NSUserDefaults standardUserDefaults] synchronize]; 即可。该操作会使数据立即写入磁盘。 - Ben Baron

13

NSLocalizedString() 从标准用户默认项([NSUserDefaults standardUserDefaults])中读取键 AppleLanguages 的值。它使用该值在运行时从所有现有本地化中选择适当的本地化。当 Apple 在应用程序启动时构建用户默认项字典时,它们在系统偏好设置中查找首选语言键,并将其复制到那里。这也解释了为什么在 OS X 中更改语言设置对正在运行的应用程序没有影响,只对随后启动的应用程序生效。一旦复制,该值就不会因设置更改而更新。这就是为什么如果您更改了语言,则 iOS 会重新启动所有应用程序。

但是,命令行参数可以覆盖用户默认项字典的所有值。请参阅NSUserDefaults文档上关于NSArgumentDomain的说明。这甚至包括从应用程序首选项 (.plist) 文件加载的那些值。如果您想要进行一次性测试并更改某个值,这真的非常重要。

因此,如果您只想测试更改语言,则可能不希望更改代码(如果您忘记稍后删除此代码...),而是告诉Xcode使用命令行参数启动应用程序(例如,使用西班牙语本地化):

enter image description here

完全不需要修改代码。只需为不同的语言创建不同的方案,并通过切换方案一次在一个语言中快速启动应用程序。


它只在第一次工作,之后它又会加载到设备首选语言中。 - Ash
@Aks对我来说每次都有效。确保您启动了正确的方案,确保您从Xcode内部启动(重新启动、分支等不会获取参数),并确保您不要覆盖Options中的语言,因为在较新版本的Xcode中,Apple也提供此选项。 - Mecki
感谢您的回复@Mecki。对我来说,在Xcode中它不起作用,但当我在代码中提到时,它可以工作。但是,如果我将我的设备设置为其他首选语言,则需要重新启动应用程序才能加载到我传递的首选语言。 - Ash
@Aks 当在设备上使用时,它只能在直接从Xcode启动时工作,当您通过在某些设备上点击其图标再次启动应用程序时,它将无法工作,并且当应用程序被杀死(例如因为它进入后台)并由系统重新启动时也不会工作(因为此重启不是由Xcode执行的)-因此,如果iOS在中间需要杀死您的应用程序(例如因为它需要内存),则切换到其他应用程序然后返回可能无法正常工作。 - Mecki
你可以将这个参数传递与StandardUserDefaults的更改结合起来,每次都能完美运行。 - L33MUR

12

我最喜欢Mauro Delrio的方法。 我还在我的Project_Prefix.pch中添加了以下内容。

#import "Language.h"    
#define MyLocalizedString(key, alt) [Language get:key alter:alt]

所以,如果你想使用标准方法(使用 NSLocalizedString),你可以在所有文件中快速进行语法替换。


1
我也喜欢他的方法,我添加了一个加载png图片的方法: +(UIImage )imageNamed:(NSString )imageFileName{ NSString fullPath = [bundle pathForResource:imageFileName ofType:@"png"]; UIImage imageObj = [[UIImage alloc] initWithContentsOfFile:fullPath]; return [imageObj autorelease]; } - Jagie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接