开发者

Localizing strings in iOS: default (fallback) language?

开发者 https://www.devze.com 2023-01-07 05:31 出处:网络
Is there a way to set a default language to be used when the device UI language is not supported by an app?

Is there a way to set a default language to be used when the device UI language is not supported by an app?

Example: My app is localized into English and German:

// en.lproj:
"POWER_TO_THE_PEOPLE_BTN" = "Power";
"POWER_PLUG_BTN" = "Power";

// de.lproj:
"POWER_TO_THE_PEOPLE_BTN"  = "Macht";
"POWER_PLUG_BTN" = "Spannung";

Now, if I run the app on a device with UI language set to Italian the app will use the key strings POWER_TO_THE_PEOPLE开发者_C百科_BTN and POWER_PLUG_BTN.

There must be a way to specify a default (fallback) language to be used by the application in such a case.

From the above example it should be clear that using the English string as a key will not work.

The only option I see right now is to use NSLocalizedStringWithDefaultValue instead of NSLocalizedString.


To avoid all those lengthy syntax and more having more descriptive var name for translators, I derived my own helper method L() for translation and falling back to English

NSString * L(NSString * translation_key) {
    NSString * s = NSLocalizedString(translation_key, nil);
    if (![[[NSLocale preferredLanguages] objectAtIndex:0] isEqualToString:@"en"] && [s isEqualToString:translation_key]) {
    NSString * path = [[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"];
    NSBundle * languageBundle = [NSBundle bundleWithPath:path];
    s = [languageBundle localizedStringForKey:translation_key value:@"" table:nil];
    }
    return s;
}

My Localizable.strings would look like this

"SOME_ACTION_BUTTON" = "Do action";

So in my code, i would use L(@"SOME_ACTION_BUTTON") to get the correct string

Though sometime the key is longer than the translation itself HELP_BUTTON_IN_NAV_BAR = 'Help' but it saves me a lot of time explaining what it is to whoever is helping me doing the translation


You need to make sure that the value of CFBundleDevelopmentRegion in your Info.plist is the language region that you would like to fallback to. (e.g. "en")


Perhaps this should help? -- iPhone: localization / internationalization default strings file

It should fallback to English by default. I've just switched my phone to a language into which my app is not localized, and the text was all in English, as expected.

Important: as @hyperspasm commented : To expand on/rephrase this, the fallback language is the language which was most recently chosen by the user in the device Settings, that is also represented in the app's bundle.


@Bogus answer in Swift 4, works like a charm on iOS 11.1:

public func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String {
    let fallbackLanguage = "en"
    guard let fallbackBundlePath = Bundle.main.path(forResource: fallbackLanguage, ofType: "lproj") else { return key }
    guard let fallbackBundle = Bundle(path: fallbackBundlePath) else { return key }
    let fallbackString = fallbackBundle.localizedString(forKey: key, value: comment, table: nil)
    return Bundle.main.localizedString(forKey: key, value: fallbackString, table: nil)
}


A fast way to do this without replacing any methods is "overriding" the NSLocalizedString define and using the methods that Apple uses for this define to replace it and add the additional fallback logic in the "overridden" method.

#undef NSLocalizedString
#define NSLocalizedString(key, comment) [self localizedStringForKey:(key) replaceValue:(comment)]

+ (NSString *)localizedStringForKey:(NSString *)key replaceValue:(NSString *)comment {
    NSString *fallbackLanguage = @"en";
    NSString *fallbackBundlePath = [[NSBundle mainBundle] pathForResource:fallbackLanguage ofType:@"lproj"];    
    NSBundle *fallbackBundle = [NSBundle bundleWithPath:fallbackBundlePath];
    NSString *fallbackString = [fallbackBundle localizedStringForKey:key value:comment table:nil];    
    NSString *localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:fallbackString table:nil];

    return localizedString;
}


My solution thanks to https://stackoverflow.com/a/25928309/3664461

Global.h

NSString * LString(NSString * translation_key);

Global.m

NSString *LString(NSString *translation_key) {
  NSString *lString = nil;
  NSString *languageCode = nil;

  if ([UIDevice currentDevice].systemVersion.floatValue >= 9) {
    NSString *localeIdentifier = [[NSLocale preferredLanguages] objectAtIndex:0];
    NSDictionary *localeDic = [NSLocale componentsFromLocaleIdentifier:localeIdentifier];
    languageCode = [localeDic objectForKey:@"kCFLocaleLanguageCodeKey"];
  } else {
    languageCode = [[NSLocale preferredLanguages] objectAtIndex:0];
  }

  NSString *path = [[NSBundle mainBundle] pathForResource:languageCode ofType:@"lproj"];
  if (path != nil) {
    lString = NSLocalizedStringFromTableInBundle(translation_key, @"Localizable",
                                             [NSBundle bundleWithPath:path], @"");
  }

   path = [[NSBundle mainBundle] pathForResource:@"Base" ofType:@"lproj"];
   lString = NSLocalizedStringFromTableInBundle(translation_key, @"Localizable",
                                             [NSBundle bundleWithPath:path], @"");
  }
  return lString;
}

Usage:

#import "Global.h"
printf(LString(@"MyKey").UTF8String);

This solution doesn't take the users preference order into consideration. Instead, it will always fallback to what you have under Base if the users first language is not localized. Also if a specific key is not localized for the current language, but it exists in the base localication, you will get the base localization.

Update:

Since iOS 9, region is included in the language locales. I updated the code to handle that.


I've created category NSBundle+FallbackLanguage to support fallback language, you can check it out on the github folder. You only need to specify the array of supported languages in the implementation.

NSBundle+FallbackLanguage.h

#import <Foundation/Foundation.h>

#undef NSLocalizedString
#define NSLocalizedString(key, comment) [[NSBundle mainBundle] localizedStringForKey:(key) replaceValue:(comment)]

@interface NSBundle (FallbackLanguage)

- (NSString *)localizedStringForKey:(NSString *)key replaceValue:(NSString *)comment;

@end

NSBundle+FallbackLanguage.m

#import "NSBundle+FallbackLanguage.h"

@implementation NSBundle (FallbackLanguage)

- (NSString *)localizedStringForKey:(NSString *)key replaceValue:(NSString *)comment {        
    NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
    NSString *localizedString;

    if ([@[@"en", @"de", @"fr"] containsObject:language]){
        localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:nil];
    }
    else{
        NSString *fallbackLanguage = @"en";
        NSString *falbackBundlePath = [[NSBundle mainBundle] pathForResource:fallbackLanguage ofType:@"lproj"];
        NSBundle *fallbackBundle = [NSBundle bundleWithPath:falbackBundlePath];
        NSString *fallbackString = [fallbackBundle localizedStringForKey:key value:comment table:nil];
        localizedString = fallbackString;
    }

    return localizedString;
}

@end


Based on Bodus solution (thx btw.) I created this category because you need the "fallbackString" too. So I have to check the current selected language of the device and compare it with my languages I want to support. Just import the header and you can use apples default macro

NSString *myString = NSLocalizedString(@"My Ub0rstring", nil);

Works fine on iOS 9.x and 11.1.

NSString+Helper.h

#import <Foundation/Foundation.h>

#undef NSLocalizedString
#define NSLocalizedString(key, comment) [NSString localizedStringForKey:(key) replaceValue:(comment)]

@interface NSString (Helper)

+ (NSString *)localizedStringForKey:(NSString *)key replaceValue:(NSString *)comment;

@end


NSString+Helper.m

#import "NSString+Helper.h"

@implementation NSString (Helper)

+ (NSString *)localizedStringForKey:(NSString *)key replaceValue:(NSString *)comment
{
    NSString *fallbackLanguage      = @"en";
    NSString *fallbackBundlePath    = [[NSBundle mainBundle] pathForResource:fallbackLanguage ofType:@"lproj"];
    NSBundle *fallbackBundle        = [NSBundle bundleWithPath:fallbackBundlePath];
    NSString *fallbackString        = [fallbackBundle localizedStringForKey:key value:comment table:nil];
    NSString *localizedString       = [[NSBundle mainBundle] localizedStringForKey:key value:fallbackString table:nil];

    NSString *language              = [[NSLocale preferredLanguages] firstObject];
    NSDictionary *languageDic       = [NSLocale componentsFromLocaleIdentifier:language];
    NSString *languageCode          = [languageDic objectForKey:@"kCFLocaleLanguageCodeKey"];

    if ([languageCode isEqualToString:@"de"] || [languageCode isEqualToString:@"en"]) {
        return localizedString;
    }
    else {
        return fallbackString;
    }
}

@end


Old issue, but still an evergreen.

Here we are with a swift 4.2 quick solution to force the app on an WHATEVER_THE_FALLBACK_LANGUAGE_WE_WANT_IT_TO_BE fallback.

The example forces to "en"

extension String {

  var localized: String {

    var preferred = "-"
    if let pl = NSLocale.preferredLanguages.first, let pref = pl.split(separator: "-").first { preferred = String(pref) } //<- selected device language or "-"

    guard let _ = Bundle.main.path(forResource: preferred, ofType: "lproj") else {
        //PREFERRED ISN'T LISTED. FALLING BACK TO EN
        guard let en_path = Bundle.main.path(forResource: "en", ofType: "lproj"), let languageBundle = Bundle(path: en_path) else {
            //EN ISN'T LISTED. RETURNING UNINTERNATIONALIZED STRING
            return self
        }
        //EN EXISTS
        return languageBundle.localizedString(forKey: self, value: self, table: nil)
    }
    //PREFERRED IS LISTED. STRAIGHT I18N IS OKAY
    return NSLocalizedString(self, comment: "")
  }

}
0

精彩评论

暂无评论...
验证码 换一张
取 消