Multiple Localized .strings Files in iOS App Bundle - ios

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 a SubProjectName.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 is zh-Hans. If I delete the en.lproj folder and restart the app it correctly uses the zh-Hans localization.

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

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.

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?)

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. ;)

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.

Related

ios9 : NSLocalizedString inside framework

I had create a framework that send data to server. Whenever I get response from the server I translate it using NSLocalizedString but it not working.
I tried to change Main Bundle to my Framework Bundle like this :
NSString* mainBundlePath = [[NSBundle mainBundle] resourcePath];
NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:#"Frameworks/MYFRAMEWORK.framework"];
NSBundle *bundle = [NSBundle bundleWithPath:frameworkBundlePath];
[bundle localizedStringForKey:#"Message" value:#"" table:nil];
But still not working. is there any way to localized message when the Localizable.string is inside the framework ?
Thanks
Finally, I tried to recreate Localizable.string and whenever I want to localize I use this :
NSString *message = [[NSBundle bundleForClass:self.class] localizedStringForKey:#"MESSAGE" value:nil table:nil];
I put this code inside class of framework, so bundle of self.class is direct to framework bundle. Thank's for everybody help.
Try to use [NSBundle bundleForClass:self.class].

NSBundle while unit testing with OCUnit returns (null)

Problem : Retrieving from NSBundle returns (null) while running Unit Test, returns valid object on run time.
What have I done ?
I have searched SO with similar problems, apple's developer and other online resources for this problem but with no luck.
I have tried solution from SO questions; OCUnit & NSBundle, NSBundle pathForResource returns nil.
All solutions I went through online point out to one thing Bundle for run time and tests are different.
All of them recommend having
[NSBundle bundleForClass: [self class]];
instead of
[NSBundle mainBundle];
Question : I don't understand if the above fix is with setter injection or in the source file itself. ? Is there any other way I can test the method getIpAdress ?
Code
Class that returns (null),
// iRNetworkingObject.m
#implementation iRNetworkingObject
-(NSString*) getIpAdress {
NSDictionary *infoDict = [[NSBundle bundleForClass:[self class]] infoDictionary];
NSString *lookUpAdress = [infoDict objectForKey:#"LookUpIpAdress"];
return lookUpAdress;
}
#end
Test class
- (void) testGetIpAdress {
iRNetworkingObject* networkingObject = [[iRNetworkingObject alloc] init];
NSString* testCase = #"192.168.2.1";
NSString* encondedString = [networkingObject getIpAdress]];
STAssertTrue([testCase isEqualToString:encondedString], #"Not equal");
}
bundleForClass: is important in test code, to make sure you get the application bundle instead of the test bundle. You shouldn't have to do that in production code.
Your key #"LookUpIpAdress" is misspelled. Is that the problem?

ios - printing HTML from a .html file not working in a UIWebView

I got "hello world" text to print after I hardcoded some html right into my UIWebView functions, but now I am trying to move that HTML to a file elsewhere on the file system, and it isnt rendering.
Here is what I have:
- (void)viewDidAppear:(BOOL)animated
{
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:#"learn" ofType:#"html" inDirectory:#"src/html_files"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
[theWebView loadHTMLString:htmlString baseURL:nil];
}
and my HTML file is in a directory that I made called src/html_files and the file is named learn.html
What am I doing incorrectly that the HTML is not rendering on the screen?
Thank you!
Ok, so Groups are just a construct in Xcode for keeping your app's resources organized. Although Xcode uses the little folder icon, it doesn't necessarily mean those are actually separate folders on the (Mac or iOS) filesystem.
But, it sounds like you have added that file as a bundle resource. That's what the code you posted looks like, too, but I had to ask, to be sure.
Most likely, the only thing wrong is that this:
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:#"learn"
ofType:#"html"
inDirectory:#"src/html_files"];
should be this instead:
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:#"learn"
ofType:#"html"];
From the Apple documentation for NSBundle,
+ (NSString *)pathForResource:(NSString *)name
ofType:(NSString *)extension
inDirectory:(NSString *)bundlePath
bundlePath
The path of a top-level bundle directory. This must be a valid path. For example, to
specify the bundle directory for a Mac app, you might specify the path /Applications/MyApp.app.
The bundlePath parameter is not meant to specify relative paths to your bundle resources. The version of pathForResource:ofType: that does not have a bundlePath parameter is almost always what you'll use. It will find the learn.html file wherever it lives, once your app is installed, and return the full path to that. You don't really have to worry about how it's nested. It's just a bundle resource.
Give that a try. As I suggested in my comment, though, I always recommend taking advantage of the error parameter for debugging:
NSError* error;
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error: &error];
if (error != nil) {
NSLog(#"Error with stringWithContentsOfFile: %#", [error localizedDescription]);
}

Localization doesn't affect some .strings files

I use SORelativeDateTransformer in my project.
I localized my project (translated storyboard and created Localizable.strings(I used NSLocalizedString)) and that part translates perfect.
The problem is that SORelativeDateTransformer uses it's own SORelativeDateTransformer.strings(uses NSLocalizedStringFromTable),
which works in demo, but not in my project.
Upd.: Found also that some labels, that I translated to Norwegian localization keep English words. Oughh!
Any ideas?
I know this question is quite old but here is a solution I used. There is a way to force SORelativeDateTransformer to load a particular Localisation string base on your preference. If you did manual installation instead of using pod then you can edit the .m base on your preference. For instance this is how I am implementing it in the + (NSBundle*) bundle method
+ (NSBundle *)bundle {
static NSBundle *bundle = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *systemLanguage = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];//you can use conditional statement after here to load a specific bundle since you now have you language codes here (e.g. 'en' for English, 'zh-Hans' for Chinese (Simplified) etc)
NSURL *url = [[NSBundle mainBundle] URLForResource:#"SORelativeDateTransformer" withExtension:#"bundle"];
bundle = [[NSBundle alloc] initWithURL:url];
NSString *path = [bundle pathForResource:systemLanguage ofType:#"lproj"];
bundle = [NSBundle bundleWithPath:path];
});
return bundle;
}
This should do the trick.

iPhone : Get the file path which is within subfolder of Resource folder

I am new to iPhone programming. I want to read the content of a text file located in a subfolder of the Resource folder.
The Resource folder structure is the following:
Resource
Folder1---->Data.txt
Folder2---->Data.txt
Folder3---->Folder1---->Data.txt
There are multiple files named "Data.txt", so how can I access the files in each folder? I know how to read the text file, but if the Resource structure is similar to the above structure then how can I get the path?
For example, if I want to access the "Data.txt" file from Folder3, how can I get the file path?
Please suggest.
Your "resource folder" is actually the contents of your main bundle, also know as the application bundle. You use pathForResource:ofType: or pathForResource:ofType:inDirectory: to get the full path for a resource.
Loading the contents of a file as a string is done with the stringWithContentsOfFile:encoding:error: method for an autoreleased string of with initWithContentsOfFile:encoding:error: if you want a retained string.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Data"
ofType:#"txt"
inDirectory:#"Folder1"];
if (filePath != nil) {
theContents = [NSString stringWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:NULL];
// Do stuff to theContents
}
This is almost the same answer as given by Shirkrin previously, but with the slight difference that it works on target. This is because initWithContentsOfFile: is deprecated on Mac OS X, and not available at all iPhone OS.
To continue psychotiks answer a full example would look like this:
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];
NSString *filePath = nil;
if (filePath = [thisBundle pathForResource:#"Data" ofType:#"txt" inDirectory:#"Folder1"]) {
theContents = [[NSString alloc] initWithContentsOfFile:filePath];
// when completed, it is the developer's responsibility to release theContents
}
Notice that you can use -pathForResource:ofType:inDirectory to access ressources in sub directories.
Shirkrin's answer and PeyloW's answer above were both useful, and I managed to use pathForResource:ofType:inDirectory: to access files with the same name in different folders in my app bundle.
I also found an alternative solution here that suited my requirements slightly better, so I thought I'd share it. In particular, see this link.
For example, say I have the following Folder References (blue icons, Groups are yellow):
Then I can access the image files like this:
NSString * filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"pin_images/1/2.jpg"];
UIImage * image = [UIImage imageWithContentsOfFile:filePath];
As a side note, the pathForResource:ofType:inDirectory: equivalent looks like this:
NSString * filePath = [[NSBundle mainBundle] pathForResource:#"2" ofType:#"jpg" inDirectory:#"pin_images/1/"];
NSBundle* bundle = [NSBundle mainBundle];
NSString* path = [bundle bundlePath];
This gives you the path to your bundle. From there on, you can navigate your folder structure.

Resources