Skip to content

Instantly share code, notes, and snippets.

@tempelmann
Last active August 24, 2020 00:51
Show Gist options
  • Save tempelmann/091a9dad628126726260889c94b2936a to your computer and use it in GitHub Desktop.
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
// 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