In part two of this series, we looked at how to implement localization of an iOS app using storyboards. Today we’ll continue with that app, and examine how to localize text that is generated programmatically. We’ll also clean up the project a bit, to bring some organization to the localized data files.
This project is available at the AIS GitHub.
By default, the code will be laid out in a way that has key localization files scattered throughout different groups in your project. You may want to change this, especially if you plan on supporting many different locales. So let’s do a little clean up.
First, let’s organize the data in a sensible way. Right now, we have the files located in a couple of different groups in the Xcode project:
- Create a group under the root project folder, and call it “Base Internationalization”
- Next, move MainStoryboard_iPad.strings, MainStoryboard_iPhone.strings, Localizable.strings, and InfoPList.strings (currently located in the “Supporting Files” group) to the new group. It should now look something like this:
Note that the underlying file/directory structures have not changed. We have simply cleaned things up a bit in Xcode.
A final point: We waited until now to reorganize the data because doing it prior to adding the second language (as we did in Part 2) causes a problem when adding a language. The localization project directory (in this case /es.lproj) is duplicated elsewhere and the file organization gets messed up. A bug report (#13644260; see this thread for discussion of the issue) has been filed with Apple. All this to say: After you have added your first additional language, this reorganization is safe, and a good idea. And you can add as many additional languages as you want as well.
Localization of Hard-Coded Text
To this point, we have handled all text being displayed by the storyboard. However, there are circumstances where we may have an object in the storyboard, but the text is being supplied programmatically, based on a choice made by the user at run-time — like an error or context-sensitive message being displayed to the user. Creating a non-localized string in an iOS app will look something like:
NSString *myString = @"Hello! This is fun!";
self.localizedTextField.text = myString;
The problem, obviously, is that there is nothing here to indicate what the text should be if the app is running in a non-English environment. Cocoa-touch provides a function that handles this: NSLocalizedString(key,comment). So, all instances where a hard-coded string is being used need to be replaced with calls to NSLocalizedString.
NSString *myString = NSLocalizedString(@"helloString", @"Hello! This is fun!");
self.localizedTextField.text = myString;
The NSLocalizedString function uses the key to look up the string that should be displayed at run-time. It references Localizable.strings. Every time you add a new localizable string to the code, you could manually edit each Localizable.strings file, and add the key/value pair, but this is where UpdateCodeStrings.sh simplifies the process. When you run this script from the command-line (again, with the path to the root directory passed in as an argument), the script does the following:
- Create a list of each *.m file that makes a call to NSLocalizedString
- Iterate through that list and parse each call
- Pull out the key and the comment
- Iterate through each existing Localizable.strings file and look for the key
- If the key exists, do nothing; otherwise, append the “key” = “value” line to the end of the file.
Again, it will be your job to actually provide the translations, but the task of updating the .strings files for each language is automated.
1. Add the above code to the IBAction method associated with the button-press and save the file.
2. To find new NSLocalizedString occurrences, go to the command-line and run UpdateCodeStrings.sh.
If you go back to Xcode and look at the Localizable.strings files, you should see:
3. Edit the Spanish version of Localizable.strings, and replace “Hello! This is fun!” with “Hola! Esto es divertido!” The format of the data in Localizable.strings is:
“key” = “value”;
where “value” is what is in the “comment” portion of
4. Run the Simulator, and press the button:
As with UpdateStoryboardStrings.sh, every time you run UpdateLocalizedStrings.sh, only new additions of NSLocalizedStrings() in your code will be captured. Already existing keys will be left untouched.
There a few more things to note:
- The way we are using NSLocalizedString(key,comment) is slightly non-standard. As noted in Apple’s documentation, NSLocalizedString(@”key”, @”comment”) will display the key string by default when the app runs. For example, we are using “helloString” as the key in the above example. The comment “Hello! This is fun!” is simply that: a comment. If you were to run the above code, without having any Localizable.strings lookup files as reference, then “helloString” is what would be displayed. The Localizable.strings files are a necessary part of this process.
- There is a command-line tool, called genstrings, which is the model for UpdateLocalizedStrings.sh. However, genstrings uses the key in both the key and the value portion of the lookup. It completely ignores the comment. We created UpdateLocalizedStrings.sh to take advantage of the comment portion of NSLocalizedString(,).
- Finally, if you make a change to the comment portion of the function in-code, with the intention of this modification being displayed in the app, but the key/value pair already exists in the .strings files, then you will also need to change the value in each .strings file.
Localizing text that is being modified in-code is pretty straightforward, as long as you keep a few things in mind:
- The key/value pairs in the Localizable.strings files are used by NSLocalizedString(,) to display the text in the app.
- UpdateCodeStrings.sh will add new key/value pairs to the .strings files, but any changes to existing functions in-code will not be reflected in the .strings files. You will need to manage that yourself.
- NSLocalizedString(key,comment) will display the key portion of the function call if either the Localizable.strings file for the current language is not available, or the file exists but the key/value lookup is not there.
In the final of this series, we will wrap up the discussion with a look at communicating with a web service and identifying the locale of the device on which the app is running.