Multiple Localized .strings Files in iOS App Bundle
Solution 1
I now have a hacky solution to this, but would appreciate if someone has a better answer (or explanation for why the above doesn't work).
I expanded my NSBundle category to include a preferred language resource:
Header
@interface NSBundle (MyBundle)
+ (NSBundle*) myResourcesBundle;
+ (NSBundle*) myPreferredLanguageResourcesBundle;
@end
Implementation
@implementation NSBundle (MyBundle)
+ (NSBundle*) myResourcesBundle
{
static dispatch_once_t onceToken;
static NSBundle *myLibraryResourcesBundle = nil;
dispatch_once(&onceToken, ^
{
myLibraryResourcesBundle = [NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:@"MyResources" withExtension:@"bundle"]];
});
return myLibraryResourcesBundle;
}
+ (NSBundle*) myPreferredLanguageResourcesBundle
{
static dispatch_once_t onceToken;
static NSBundle *myLanguageResourcesBundle = nil;
dispatch_once(&onceToken, ^
{
NSString *language = [[[NSBundle myResourcesBundle] preferredLocalizations] firstObject];
myLanguageResourcesBundle = [NSBundle bundleWithPath:[[NSBundle myResourcesBundle] pathForResource:language ofType:@"lproj"]];
if( myLanguageResourcesBundle == nil )
{
myLanguageResourcesBundle = [NSBundle myResourcesBundle];
}
});
return myLanguageResourcesBundle;
}
@end
I then have a simple macro for getting my localized strings:
#define MyLocalizedDocumentation(key, comment, chapter) \
NSLocalizedStringFromTableInBundle((key),(chapter),[NSBundle myPreferredLanguageResourcesBundle],(comment))
This solution simply gets the preferred language code from NSLocale
and then checks to see if a bundle exists for that language. If not, it falls back to the main resource bundle (perhaps it should iterate through the NSLocale preferredLanguage indices to check if a bundle exists? Does anyone know?)
Solution 2
I was able to reproduce and fix the issue, though the solution does imply there's a bug in NSBundle.
I reproduced it with the following bundle structure:
MyApp.app/
|
- MyResources.bundle/
|
- en.lproj/
|
- fr.lproj/
and code:
NSLog(@"A key: %@", NSLocalizedString(@"A key", nil));
NSBundle *bundle = [NSBundle bundleWithPath: [[NSBundle mainBundle] pathForResource: @"MyResources" ofType: @"bundle"]];
NSLog(@"Current locale: %@", [[NSLocale currentLocale] localeIdentifier]);
NSLog(@"Bundle localizations: %@", [bundle localizations]);
NSLog(@"Key from bundle: %@", [bundle localizedStringForKey: @"A key" value: @"Can't find it." table: nil]);
NSLog(@"Key using bundle macro: %@", NSLocalizedStringFromTableInBundle(@"A key",
nil,
bundle,
nil));
With the locale set to fr_FR (i.e. French) the bundle picked the string from the English strings table - even the string "can't find it" doesn't appear.
Without changing the code, I was able to get the French string using the following structure for the bundle instead:
MyApp.app/
|
- MyResources.bundle/
|
- Resources/
|
- en.lproj/
|
- fr.lproj/
It looks like NSBundle still expects the old Mac OS X bundle structure instead of what iOS is supposed to use. So a simple change of bundle structure should solve the problem...
Solution 3
To add to what David Doyle said.
Make sure you set the available languages in the info section of your project in both the bundle and the application itself. So for instance, if you support french and english in your app, make sure both your bundle and your app have the French and English languages defined in the available localizations for your projects.
Solution 4
Not clearly understand your question, but I use this macro to use multiple localized string files:
#define CustomLocalizedString(key, comment) \
[[[NSBundle mainBundle] localizedStringForKey:(key) value:nil table:nil] isEqualToString:(key)] ? \
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:@"MyTable"] : \
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
or you can try
[[NSBundle mainBundle] localizedStringForKey:(key) value:[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:@"MyTable"] table:nil]
This'll check Localizable.strings first, if the key does not exist, it'll return the key itself, then check and use MyTable.strings. Of course, you'd better name your key with a prefix. e.g. "KYName" = "Name";
.
If it is what you want, feel free to check THIS question I've asked before. ;)
Solution 5
I have another solution.
#define localizedString(key) [NSLocalizedString(key, nil) isEqualToString:key] ? \
[[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"]] localizedStringForKey:key value:@"" table:nil] : \
NSLocalizedString(key, nil)
so for instance if we're going to find a key "title" = "Manager"; in Localizable.strings (fr) and if not there then the result for the key "title" will be just same as the key.
In this case we can find the key "title" in localizable.string (en) but if can find in (fr) we can just use it.
simeon
Updated on July 19, 2022Comments
-
simeon almost 2 years
I have a fairly complicated project, consisting of several large localized sub-projects.
Most of my sub-projects are localized through a single Localizable.strings file. This file is copied into a
SubProjectName.bundle
target, which is used in conjunction with aSubProjectName.a
static library in the main project. This works fine.However, one of my sub-projects contains many localized .strings files. This project fails to read strings in any language other than English, regardless of how the device (or simulator) is configured.
For example, this line of code always returns the English string:
[[NSBundle myResourcesBundle] localizedStringForKey:@"MY_TEST_STRING" value:@"" table:@"MyTable"]
Where
MyTable
corresponds to a MyTable.strings file localized into several languages. When I peek into the .app package, all the localizations are there, sitting inside the "MyBundle.bundle" resource within the app.The following code, however, correctly finds the translations for a given string in all localizations:
for (NSString *language in [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"]) { NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle myResourcesBundle] pathForResource:language ofType:@"lproj"]]; NSLog(@"%@: %@", language, NSLocalizedStringFromTableInBundle(@"MY_TEST_STRING", @"MyTable", bundle, nil)); }
So when the bundle is the actual
MyBundle.bundle/<LanguageCode>.lproj
folder, the string lookup works. But obviously this defeats the purpose of the automatic lookup provided by iOS.(Note that
[NSBundle myResourcesBundle]
above is simply a static convenience method to fetch my custom bundle for the sub-project).--
Edit: I've been experimenting with this some more, and if I delete the
en.lproj
folder from my sub-project's bundle, then it correctly uses the locale of the device or simulator.For example, I have:
MyApp.app/ | - MyResources.bundle/ | - en.lproj/ | - zh-Hans.lproj/
When I set the simulator (or device) to Chinese Simplified it looks up strings in
en.lproj
even though the locale iszh-Hans
. If I delete theen.lproj
folder and restart the app it correctly uses the zh-Hans localization. -
simeon over 11 yearsThis answer does not allow for partial localizations (i.e. one translated .strings file out of ten).
-
simeon over 11 yearsThe problem occurs when I use: [MyCustomBundle localizedStringForKey:(key) value:@"" table:@"MyCustomStringsFile"] — I always get the English result (as in, from the en.lproj/ folder). Even when there are other languages and the device is correct configured in that language.
-
simeon over 11 yearsWow — great detective work! I'm trying to implement your answer. How did you maintain your Resources/ folder structure in the bundle while keeping project localisations? When I add my .strings into a Resources/ folder and drop it into Xcode as a folder reference I lose the ability to select the .strings files for localisation.
-
David Doyle over 11 yearsI'll be honest, I cheated :). I added the bundle to the project as a .bundle folder, which works as expected, then re-organised the bundle folder in Finder. Xcode wasn't involved except to add the original bundle.
-
simeon over 11 yearsThanks David. I'll award the bounty to you as you seem to have figured out why it does this, even though there's no way to fix it. I'll accept my hack-workaround as the answer because it's a way to get around the problem.
-
LoriHC about 11 yearsNot sure if this solves the problem posed above, but glad I found it, as it solves the problem I had!
-
Kjuly about 11 years@LoriHC glad to hear that :)
-
Vladimir Grigorov over 10 yearsThis should be the accepted answer. If the app does not support Fr locale, then that locale is not loaded for the custom bundle too, even though it may have fr.lproj directory in it. If the app supports Fr, then Fr is loaded from the custom bundle. There is no need to change bundle internal structure by adding 'Resources' dir.
-
some.birdie over 10 yearsThis helps, but why doesn't is work in normal way? Have you found the reason?
-
simeon over 10 years@Anastasia see the answer by David Doyle, above. It appears to be a bug in NSBundle.
-
Alex Zavatone over 8 yearsAs of 2015, that will no longer work. You will want to change the
NSString *language = [NSLocale preferredLanguages][0];
to this:[[[NSBundle mainBundle] preferredLocalizations] firstObject];
-
simeon over 8 years@AlexZavatone any idea why it stopped working? (It's not listed as deprecated).
preferredLocalizations
lists the localizations contained in the bundle in order of preference based on locale, whilepreferredLanguages
lists all languages in order of preference regardless of what is in the bundle. I have edited my answer anyway aspreferredLocalizations
is better to use even ifpreferredLanguages
still works. -
Alex Zavatone over 8 yearsThere are several reasons, but Apple states in the NSLocale header to use mainBundle. One of the BIG problems is that if you set your language to non English AND your region to not being in the US, NSLocale will return something like this: "ar-IT" if you were using Arabic in the Italian region. Here are the comments from the NSLocale header:
+ (NSArray<NSString *> *)preferredLanguages NS_AVAILABLE(10_5, 2_0); // note that this list does not indicate what language the app is actually running in; the [NSBundle mainBundle] object determines that at launch and knows that information
-
RamaKrishna Chunduri about 8 yearsThe solution is just awesome for the apps that seeks the primary language from developer (or the user dynamically in a screen) i.e., forced localization. But if the app should work based on the users locale settings, then it fails to seek the correct bundle ex:indian english is EN_IN while us is EN_US.. dev might not have added the lproj for indian english but would have the lproj for en_us . Also date formatter might not seek the format from the bundle instead seeks from region settings MM/dd/yyyy in US & dd/MM/yyyy in India. But still solution is best among given.. Thanks for it..
-
Accid Bright about 6 yearsAlso want to add, if you have multiple languages in your project config, but no files are localized really (there is
0 Files Localized
message near the desired language), you won't get localized strings from your custom bundle (just checked by myself).