Last active
August 24, 2020 00:51
-
-
Save tempelmann/091a9dad628126726260889c94b2936a to your computer and use it in GitHub Desktop.
Provides a smart LocalizedString() function (short: "LS") that gives you more control over where you pick the translations from
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Written 2019 by Thomas Tempelmann, [email protected] | |
// Tested on macOS, but may work on iOS as well. | |
NSMutableSet *reportedMissingLocs = nil; | |
#define MyAppSupportFolderName "your.bundle.id" // or your app's name | |
static NSBundle *mainBundle = nil; | |
static NSArray<NSString*> *availableLangs; // list of langs the app knows (possibly sorted alphabetically) | |
static NSDictionary<NSString*,NSBundle*> *bundlePerLang = nil; | |
static NSString *KeyNotFound = @"-Key-Not-Found-"; // has to be unique | |
static NSString *languageCode (NSLocale *loc) { | |
// lang:de-DE becomes lang2:de | |
if ([loc respondsToSelector:@selector(languageCode)]) { | |
return [loc languageCode]; | |
} else { | |
return [loc objectForKey:NSLocaleLanguageCode]; | |
} | |
} | |
static NSString * appDataPath2(BOOL createIfMissing) | |
{ | |
NSString *result = nil; | |
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); | |
if (paths.count > 0) { | |
result = [paths.firstObject stringByAppendingPathComponent:@MyAppSupportFolderName]; | |
NSFileManager *mgr = NSFileManager.defaultManager; | |
if (NOT [mgr fileExistsAtPath:result] && createIfMissing) { | |
[mgr createDirectoryAtPath:result withIntermediateDirectories:YES attributes:nil error:nil]; | |
} | |
} | |
return result; | |
} | |
// Localization helper, see https://samwize.com/2018/01/23/localization-fall-back-to-base-language/ | |
NSString* LS(NSString *key /*, NSString *comment */) | |
{ | |
if (key == nil) { | |
return nil; | |
} | |
if (mainBundle == nil) { | |
// setup some data once | |
reportedMissingLocs = [NSMutableSet set]; | |
mainBundle = NSBundle.mainBundle; | |
availableLangs = mainBundle.localizations; | |
// Look for extra localizations in App Support folder | |
NSString *appDataPath = appDataPath2(false); | |
[[NSFileManager.defaultManager contentsOfDirectoryAtPath:appDataPath error:nil] enumerateObjectsUsingBlock:^(NSString * _Nonnull name, NSUInteger idx, BOOL * _Nonnull stop) | |
{ | |
if ([name.pathExtension isEqualToString:@"lproj"]) { | |
NSString *path = [appDataPath stringByAppendingPathComponent:name]; | |
if ([NSFileManager.defaultManager fileExistsAtPath:[path stringByAppendingPathComponent:@"Localizable.strings"]]) { | |
NSString *code = [name stringByDeletingPathExtension]; | |
if ([availableLangs indexOfObject:code] == NSNotFound) { | |
availableLangs = [availableLangs arrayByAddingObject:code]; | |
} | |
} | |
} | |
}]; | |
// cache each lproj now | |
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:availableLangs.count]; | |
bundlePerLang = d; | |
for (NSString *lang in availableLangs) { | |
NSBundle *b = nil; | |
NSString *fname = [lang stringByAppendingPathExtension:@"lproj"]; | |
NSString *path = [appDataPath stringByAppendingPathComponent:fname]; | |
if ([NSFileManager.defaultManager fileExistsAtPath:[path stringByAppendingPathComponent:@"Localizable.strings"]]) { | |
b = [NSBundle bundleWithPath:path]; | |
} else { | |
path = [mainBundle pathForResource:lang ofType:@"lproj"]; | |
if (path) { | |
b = [NSBundle bundleWithPath:path]; | |
} | |
} | |
if (b) { | |
d[lang] = b; | |
} | |
} | |
} | |
NSString *result = nil; // [mainBundle localizedStringForKey:key value:KeyNotFound table:nil]; | |
// Go over all preferred languages until we find the key | |
BOOL hadEN = NO, first = YES; | |
NSArray *langs = NSLocale.preferredLanguages; // All languages the user has chosen in System Preferences | |
for (NSString *lang in langs) { | |
NSLocale *loc = [NSLocale localeWithLocaleIdentifier:lang]; | |
NSString *lang2 = languageCode(loc); // lang:de-DE becomes lang2:de | |
NSBundle *b = bundlePerLang[lang2]; | |
if (b) { | |
result = [b localizedStringForKey:key value:KeyNotFound table:nil]; | |
if (result.length > 0 && result != KeyNotFound) { | |
return result; | |
} | |
if (first) { | |
// Log missing language | |
if (NOT [reportedMissingLocs containsObject:key]) { | |
[reportedMissingLocs addObject:key]; | |
NSLog(@"Missing translation for lang %@: %@", lang2, key); | |
} | |
} | |
first = NO; | |
if ([lang2 isEqualToString:@"en"]) hadEN = YES; | |
} | |
} | |
// Fallback to "en" | |
if (NOT hadEN) { | |
NSBundle *b = bundlePerLang[@"en"]; | |
if (b) { | |
result = [b localizedStringForKey:key value:KeyNotFound table:nil]; | |
if (result.length > 0 && result != KeyNotFound) { | |
return result; | |
} | |
} | |
} | |
if (result == KeyNotFound) { | |
result = key; | |
} | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment