ios localization - 如何強制NSLocalizedString使用特定語言




13 Answers

NSLocalizedString() (及其變體)訪問NSLocalizedString()的“AppleLanguages”鍵以確定用戶對首選語言的設置。 這將返回一組語言代碼,第一個是用戶為其電話設置的語言代碼,如果資源不適用於首選語言,則會使用後續語言代碼作為後備代碼。 (在桌面上,用戶可以在系統偏好設置中使用自定義順序指定多種語言)

如果您希望通過使用setObject:forKey:方法來設置您自己的語言列表,您可以覆蓋您自己的應用程序的全局設置。 這將優先於全局設置的值,並返回到您的應用程序中執行本地化的任何代碼。 這個代碼看起來像這樣:

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"de", @"en", @"fr", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize]; //to make the change immediate

這將使德語成為您的應用程序的首選語言,英語和法語作為後備。 您可能想在應用程序啟動的早些時候調用它。 您可以在此閱讀有關語言/區域設置的更多信息: 國際化編程主題:獲取當前語言和區域設置

swift storyboard

在iPhone上, NSLocalizedString返回iPhone語言中的字符串。 是否有可能強制NSLocalizedString使用特定語言使應用程序使用與設備不同的語言?




我最近有同樣的問題,我不想開始和修補我的整個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)
    }
}



如前所述,只要做到:

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

但為了避免重新啟動應用程序,請在UIApplicationMain (...)之前將該行放在main.m的main方法中。




我最喜歡毛羅德里奧的方法。 我也在Project_Prefix.pch中添加了以下內容

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

因此,如果您想要使用標準方法(使用NSLocalizedString),則可以在所有文件中進行快速語法替換。




NSLocalizedString()從標準用戶默認值( [NSUserDefaults standardUserDefaults] )中讀取關鍵AppleLanguages的值。 它使用該值在運行時在所有現有本地化中選擇適當的本地化。 當Apple在應用程序啟動時構建用戶默認字典時,他們會在系統偏好設置中查找首選語言鍵,並從中復制該值。 這也解釋了為什麼在OS X中更改語言設置對於運行應用程序沒有影響,只能在之後啟動的應用程序上運行。 一旦被複製,該值不會僅僅因為設置改變而更新。 這就是為什麼iOS會重新啟動所有應用程序,如果你改變語言。

但是,用戶默認字典的所有值都可以被命令行參數覆蓋。 請參閱NSUserDefaults上的NSArgumentDomain文檔。 這甚至包括從應用首選項(.plist)文件加載的值。 如果您想為測試改變一次值,這真的很好。

所以,如果你想改變語言只是為了測試,你可能不想改變你的代碼(如果你以後忘了刪除這個代碼......),而是告訴Xcode使用命令行參數啟動你的應用程序(如使用西班牙語本地化):

根本不需要觸摸你的代碼。 只需為不同的語言創建不同的方案,您就可以通過切換方案快速啟動一種語言的應用程序,一次啟動應用程序。




我想出了一個允許您使用NSLocalizedString的解決方案。 我創建了一個NSBundle類的調用NSBundle+RunTimeLanguage 。 界面是這樣的。

// NSBundle+RunTimeLanguage.h
#import <Foundation/Foundation.h>
@interface NSBundle (RunTimeLanguage)
#define NSLocalizedString(key, comment) [[NSBundle mainBundle] runTimeLocalizedStringForKey:(key) value:@"" table:nil]
- (NSString *)runTimeLocalizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName;
@end

實現是這樣的。

// NSBundle+RunTimeLanguage.m
#import "NSBundle+RunTimeLanguage.h"
#import "AppDelegate.h"

@implementation NSBundle (RunTimeLanguage)

- (NSString *)runTimeLocalizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
{
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    NSString *path= [[NSBundle mainBundle] pathForResource:[appDelegate languageCode] ofType:@"lproj"];
    NSBundle *languageBundle = [NSBundle bundleWithPath:path];
    NSString *localizedString=[languageBundle localizedStringForKey:key value:key table:nil];
    return localizedString;
}
@end

不僅僅是將導入NSBundle+RunTimeLanguage.h添加到使用NSLocalizedString的文件中。

正如你所看到的,我將我的languageCode存儲在AppDelegate的屬性中。 這可以存儲在任何你想要的地方。

我唯一不喜歡的是一個警告,即NSLocalizedString marco重新定義。 也許有人可以幫我解決這個問題。




在文件.pch中定義:

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


#define NSLocalizedString(str,nil) NSLocalizedStringFromTableInBundle(str, nil, currentLanguageBundle, @"")



您對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.



對於我的情況,我有兩個本地化的文件,ja和en

如果系統中的首選語言不是en或ja,我想強制它

我要編輯main.m文件

我會檢查第一首選是en還是ja,如果不是,那麼我會將第二首選語言改為en。

int main(int argc, char *argv[])
{

    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"AppleLanguages"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    NSString *lang = [[NSLocale preferredLanguages] objectAtIndex:0];

    if (![lang isEqualToString:@"en"]  &&  ![lang isEqualToString:@"ja"]){

        NSMutableArray *array = [[NSMutableArray alloc] initWithArray:[NSLocale preferredLanguages]];
        [array replaceObjectAtIndex:1 withObject:@"en"];

        [[NSUserDefaults standardUserDefaults] setObject:array forKey:@"AppleLanguages"];
        [[NSUserDefaults standardUserDefaults] synchronize];


    }

    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));


    }


}



根據Tudorizer的回答改變語言而不離開或重新啟動應用程序。

為了檢查是否存在特定的語言代碼,可以使用類來訪問首選語言,而不是使用宏。

以下是用於獲取適用於iOS 9的當前語言包的類:

@implementation OSLocalization

+ (NSBundle *)currentLanguageBundle
{
    // Default language incase an unsupported language is found
    NSString *language = @"en";

    if ([NSLocale preferredLanguages].count) {
        // Check first object to be of type "en","es" etc
        // Codes seen by my eyes: "en-US","en","es-US","es" etc

        NSString *letterCode = [[NSLocale preferredLanguages] objectAtIndex:0];

        if ([letterCode rangeOfString:@"en"].location != NSNotFound) {
            // English
            language = @"en";
        } else if ([letterCode rangeOfString:@"es"].location != NSNotFound) {
            // Spanish
            language = @"es";
        } else if ([letterCode rangeOfString:@"fr"].location != NSNotFound) {
            // French
            language = @"fr";
        } // Add more if needed
    }

    return [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]];
}

/// Check if preferred language is English
+ (BOOL)isCurrentLanguageEnglish
{
    if (![NSLocale preferredLanguages].count) {
        // Just incase check for no items in array
        return YES;
    }

    if ([[[NSLocale preferredLanguages] objectAtIndex:0] rangeOfString:@"en"].location == NSNotFound) {
        // No letter code for english found
        return NO;
    } else {
        // Tis English
        return YES;
    }
}

/*  Swap language between English & Spanish
 *  Could send a string argument to directly pass the new language
 */
+ (void)changeCurrentLanguage
{
    if ([self isCurrentLanguageEnglish]) {
        [[NSUserDefaults standardUserDefaults] setObject:@[@"es"] forKey:@"AppleLanguages"];
    } else {
        [[NSUserDefaults standardUserDefaults] setObject:@[@"en"] forKey:@"AppleLanguages"];
    }
}
@end

使用上面的類來引用一個字符串文件/ image / video / etc:

// Access a localized image
[[OSLocalization currentLanguageBundle] pathForResource:@"my_image_name.png" ofType:nil]
// Access  a localized string from Localizable.strings file
NSLocalizedStringFromTableInBundle(@"StringKey", nil, [OSLocalization currentLanguageBundle], @"comment")

像下面那樣在線更改語言,或更新上面的類中的“changeCurrentLanguage”方法,以獲取引用新語言的字符串參數。

[[NSUserDefaults standardUserDefaults] setObject:@[@"es"] forKey:@"AppleLanguages"];



這個函數會嘗試為當前語言獲取本地化的字符串,如果沒有找到,它會使用英語。

- (NSString*)L:(NSString*)key
{
    static NSString* valueNotFound = @"VALUE_NOT_FOUND";
    static NSBundle* enBundle = nil;

    NSString* pl = [NSLocale preferredLanguages][0];
    NSString* bp = [[NSBundle mainBundle] pathForResource:pl ofType:@"lproj"];
    NSBundle* b = [NSBundle bundleWithPath:bp];

    NSString* s = [b localizedStringForKey:key value:valueNotFound table:nil];
    if ( [s isEqualToString:valueNotFound] ) {
        if ( !enBundle ) {
            bp = [[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"];
            enBundle = [NSBundle bundleWithPath:bp];
        }
        s = [enBundle localizedStringForKey:key value:key table:nil];
    }

    return s;
}



Here is a decent solution for this problem, and it does not require application restart.

https://github.com/cmaftuleac/BundleLocalization

This implementation works by tweaking inside NSBundle. The idea is that you override the method localizedStringForKey on the instance of NSBundle object, and then call this method on a different bundle with a different language. Simple and elegant fully compatible with all types of resources.




whatever you all do, the best way is to take the short_name for the specified language, ie: fr, en, nl, de, it, etc... and assign the same to a global value.

make a picker view to pop up like a drop down menu (combination of a button on click of which a picker view appears from below with a list of languages) and select the language you desire. let the short name be stored internally. make a .h + .m file named LocalisedString.

Set the global value of short_name to be equal to the obtained value in LocalisedString.m When the required language is selected assign the NSBundlePath to create project sub-directories for the needed language. for eg, nl.proj, en.proj.

When the particular proj folder is selected call the localised string for the respective language and change the language dynamically.

no rules broken.




Related

ios objective-c localization internationalization