5. Localizing Your App

I am not an Athenian or a Greek, I am a citizen of the world.

Socrates

Localizing your app is the process of taking the text in your application, both within the code and in the UI, and enabling it to be represented by the language the customer has chosen. It’s enabling your app to support this setting automatically. This chapter walks you through the steps, the process, and the requirements to make your application not an isolationist but a citizen of the world.

The chapter is broken into three general sections:

1. The first section gives the basic “10,000-foot level” description of the localization process with iOS applications. We’ll also cover some specifics of iOS localization, including base localization.

2. The second section takes our sample app from Chapter 4—I18nExerciser—and walks through with more detail in the process of localizing it for French. We’ll also walk through localizing images as well as the name of the app icon.

3. The last section covers other localization tools available, working with translation services, and iOS support for Unicode Common Locale Data Repository (CLDR).

Localization

This section walks through the localization process starting with what localization is, what gets localized, and how the process works. This will first be generalized, and then I’ll get into the specifics.

What Is Localization?

When we talk about localization, we’re talking about the translation of strings. But even more than that, we’re talking about context. This needs to be not just the “word for word” translation, but the context as well. Locales have grammar and linguistic rules that we need to understand and respect. For now, we want to set up our application to be able to display different strings when we change our system language. And we want this change to be automatic. We do not want to work with hard-coded strings and a series of if statements that look for our locale’s current language. Thankfully, Apple has done the heavy lifting for us and given us a sweet suite of tools. Some of the tools are step-intensive; hopefully I can make those steps clear.

What Gets Localized?

Of course, we’re talking about strings. To put a finer point on it, this includes hard-coded strings within our code and strings within our UI. So that includes any NSStrings we might have in our code (e.g., @"Hello world") and any text we’ve included in our UI, XIBs, or Storyboards, be that text in labels, buttons, table views, and so on. These strings needs to be parsed out of their source files and stored in a .strings file.

How Do the “Dot-Strings” Files Get Created?

For our strings to get parsed out of the code source files, there are two requirements. The first is that our strings be wrapped in a macro, specifically the NSLocalizedString() macro. This identifies the strings so the tools know what to parse out. The tool in question is a command-line utility called genstrings. The genstrings command parses the specified files, typically all the implementation or “.m” files, and creates a text file, Localizable.strings, in the project folder. Because this is a text file, this is something you can create manually and populate with the key-value pairs that are discussed in the section “The genstrings Command.”

For strings in your XIBs and Storyboards, this is much friendlier and can be done through Interface builder, specifically the Localization section in the File Inspector tab.


Base Localization

Base localization allows you to work with XIBs and Storyboards in your native language. Its most powerful feature is that it generates .strings files (Main.strings) for your UI-facing text whenever you add a localization language to your project. Before Xcode 4.5, separate XIB files were required for each language you localized for. If your app supported ten languages, ten separate XIBs were required to support the UI for those languages. This was a lot of overhead and version control headaches dealing with changes to XIB files. If a UI tweak was needed, say a button moved eight pixels, that change would need to be made in all ten XIB files.

Now support comes through the .strings file. Whenever you add a new language to localize for, you simply copy the Main.strings file from the Base.lproj folder to your newly added language resource folder, such as jp.lproj.


Where Do the Strings Get Parsed?

The tools parse through either the code or the UI and generate .strings files. These .strings files can be thought of as dictionaries of sorts because they contain “keys” or “tokens” with values associated with them. A quick example from a .strings file looks like this:

"First name" = "First name"

Here, the token “First name” has a value of "First name", and running under an English language, every instance of “First name” would be replaced with "First name" automatically. Each language you have localized for will have a .strings file associated with it.

Generalized Steps to Localize

The following steps are just the general, shorthand steps to localize your app. As already mentioned, we’ll go into these with detailed steps and screen captures.

1. As you are writing your code, wrap any hard-coded strings presented to the user in the NSLocalizedString() macro. You can go back and apply this macro to existing strings in your code as well.

2. Add the language you want to localize via the Project Info tab under the Localization section.


Note

When you add a language to localize for, a resource folder for that language is created in your project. Xcode’s naming convention for this folder is language_identifier.lproj. So adding support for Spanish localization would result in a new folder being added with the name es.lproj.


3. For strings in your UI elements, select the XIBs and Storyboards in Xcode’s Project Explorer and then enable any languages listed in the Localization section under the File Inspector tab. This will result in the localized Main.strings files listed in the Project Explorer.

4. Launch the Terminal app and switch to the project’s folder.

5. Run the genstrings command as follows: genstrings *.m. This will create the Localizable.strings file.

6. Switch to Finder and bring up a window showing the project’s folder.

7. Copy the Localizable.strings file to the localized project folders (*.lproj).

8. Move the Localizable.strings file to the Base project folder (Base.lproj).

9. Copy each of these Localizable.strings files to Xcode’s Project Explorer. This creates localized entries for the .strings files that are labeled with the language name, such as Localizable.strings (English).

10. Update the values in the .strings files—both Localizable.strings and Main.strings—to the appropriate translations.

Simple? Ready to localize on your own? No, probably not. Let’s walk through localizing the “Quotes and Scripts” view controller in our I18nExerciser application from the preceding chapter.

Localizing a View Controller from the Sample App

We’ll take our project from the preceding chapter, the I18nExerciser, and focus on localizing one view controller, Quotes and Scripts. The detailed steps we’ll go through can be applied to any other code, any other view controller, and any other project.

Adding Localization to a Project

We need to set up our project to support different languages, which is done by adding a resource folder for that language in our project. Modify the project by following these steps:

1. In Xcode, select the project and then the Info tab.

2. In the Localization section, click the plus button.

3. From the menu, choose French, as shown in Figure 5.1.

Image

Figure 5.1 Adding French localization to our project.

4. On the next screen (see Figure 5.2), choose the files to localize and click Finish.

Image

Figure 5.2 The Choose Files screen, which appears after you’ve added a localization language to your project.

Localizing Code

We have options to localize strings in our code, and with Xcode the approach is to wrap those strings with the NSLocalizedString() macro. Macros in general wrap up a larger snippet of code into a manageable size.

For its arguments key and comment, the key argument is equivalent to a key entry with a dictionary, and the comment is just that, a comment to add clarity and context to the translator. comment is not a required argument, but including a meaningful comment is an excellent practice to follow. By default, our generated .strings files that contain the key, the value, and the comment we’ve supplied and the name of the key will be the same as the value we have assigned. If, for our key argument, we’ve supplied the string Tap to continue, by default, the value for this key is the same, Tap to continue.

Recall from the preceding chapter that we wrapped our strings in the QuotesAndScriptViewController.m view controller implementation file in the NSLocalizedString()macro. Here are the specific lines we changed:

NSString *quotedString = NSLocalizedString(@"Remember a panda eats shoots and leaves.", @"String used to demo quotation marks");

self.scriptCode.text = NSLocalizedString(@"No Script code for current locale.", @"Error message if there is no script code");

self.variantCode.text = NSLocalizedString(@"No Variant code for current locale.", @"Error message if there is no variant code");

Now we need to create our Localizable.strings files. These files contain the keys, the localized values for those keys, and the comments for translators. At our app runtime, the NSLocalizedString() macro will read in the values from the appropriate language’s .strings file.

The genstrings Command

genstrings is the command-line utility that parses specified files, typically all the *.m files, and creates the Localizable.strings files based on the NSLocalizedString() macro entries.

Follow these steps:

1. Launch the Terminal app.

2. Make your project folder the current working directory.

3. We’re focused on the Quotes and Scripts view controller, so run the following command:

genstrings QuotesAndScriptViewController.m

You can find more information about the genstrings command by looking at its man page in Terminal: man genstrings.


Note

Here are some other samples for genstrings:

Generate strings from *.m files located in the current folder and place the resulting Localizable.strings file in the en.lproj subfolder:

    genstrings -o en.lproj *.m

Generate strings from *.m files located in the current folder plus its subfolders, and place the resulting Localizable.strings file in the en.lproj subfolder:

    find . -name *.m | xargs genstrings -o en.lproj

Generate strings from *.m files in the current folder plus its subfolders but exclude otherFolder and place the resulting Localizable.strings file in the en.lproj subfolder:

    find . -name *.m -not -path "./otherFolder/*" -print0 | xargs -0 genstrings -o
en.lproj -1


Copying Localizable.strings Files

Now we switch to Finder and open a window for the project folder. You should see our generated Localizable.strings file. We need to make this available to your applied localizations by copying it to our *.lproj resource folders. Copy the file to the en.lproj and fr.lpoj folders.

Finally, move the Localizable.strings file to the Base.lproj folder.

Xcode Reference

Lastly, we need to bring these files into our Xcode project. Drag all three Localizable.strings files to the Xcode Project Explorer. At the Choose Options for Adding sheet make sure to enable Copy Items and click Finish. Note that the localized Localizable.strings files are listed in the Explorer, as shown in Figure 5.3.

Image

Figure 5.3 Referenced .strings files in Xcode Project Explorer.

Click the Localizable.strings (English) entry, which is also listed here:

/* Error message if there is no script code */
"No Script code for current locale." = "No Script code for current locale.";

/* Error message if there is no variant code */
"No Variant code for current locale." = "No Variant code for current locale.";

/* String used to demo quotation marks */
"Remember a panda eats shoots and leaves." = "Remember a panda eats shoots and
leaves.";

Note how the argument for our comment is placed within comment characters, and by default the name of the key is the same as its value. At this point we can change the value to whatever is appropriate. If we change the name of the key, that change would need to occur within the code as well. Let’s add our localized text for the French .strings file by clicking on its listing in the Xcode Project Explorer and add the following changes:

/* Error message if there is no script code */
"No Script code for current locale." = "Pas de code disponible.";

/* Error message if there is no variant code */
"No Variant code for current locale." = "Pas de code disponible.";

/* String used to demo quotation marks */
"Remember a panda eats shoots and leaves." = "Rappelez vous, un Panda mange des pousses
et des feuilles.";

Localizing UI

That takes care of localizing the strings within our code for the Quotes and Scripts view controller. Now let’s take care of the strings within the UI. Recall with our I18nExerciser project we have both Storyboards and XIBs. I intentionally did this so we could see how to handle either one for localization.

Recall that because we are using base localization, we only need to work with .strings files; otherwise, we would have to have separate XIBs for each language. Now we have only one XIB and separate .strings files for each language.


Note

Before base localization, a separate XIB file would have to be maintained for each language you wanted to support. Each language’s .lproj folder would have its own associated XIB file. Much like the genstrings command-line tool, .strings files could be created from the XIBs and XIBs populated via the ibtool command. The ibtool command also takes your translated .strings file and creates new XIBs populated with those supplied strings.

Let’s say we have a view controller named SampleViewController that we want to be able to translate into German. First you run ibtool to pull the strings from your development language XIB (say, English for argument’s sake). From Terminal in your project’s folder you run this:

ibtool --export-strings-file ~/Desktop/SampleViewController.strings
en.lproj/SampleViewController.xib

This creates a SampleViewController.strings file on the desktop. You’ll take this file and have it translated into German. Now you want to run ibtool again to use the translated .strings file and create a German localized XIB file. If you don’t already have one, you will need to create a de.lproj folder in your project folder. Run the following command in Terminal:

ibtool --import-strings-file ~/Desktop/SampleViewController.strings –write
de.lproj/SampleViewController.xib en.lproj/SampleViewController.xib

The command reads, “Create SampleViewController.xib in the de.lproj folder based on the SampleViewController.xib in the en.lproj folder, and replace all its English strings with my translated German strings.”

“Hey Siri...” Not today. Maybe someday!


Storyboard Localization

Notice the change to the Main.storyboard listing Xcode’s Project Explorer. Figure 5.4 shows that the file now has a disclosure triangle associated with it, as well as a listing for Base and French localization. This happened automatically when we added French localization to our project. But where is English, our “base” development language?

Image

Figure 5.4 Xcode Explorer listing for Main.storyboard after French localization has been added.

We just need to enable English localization with our Storyboard. Select Main.storyboard in the Explorer and open the Utilities pane. Notice in the Localization section from the File Inspector tab that English is not enabled (see Figure 5.5). Enable it now.

Image

Figure 5.5 English is not enabled for Storyboard localization.

XIB Localization

To localize our QuotesAndScriptViewController.xib, first select it. From the Utilities pane, File Inspector tab, click Localized. You’ll see what’s shown in Figure 5.6. This prompt displays to ask what folder the XIB file should be moved to. We want to go with the default and place it in the Base.lproj folder. Click the Localize button.

Image

Figure 5.6 Prompt when you click the Localize button in the File Inspector.

This creates a QuotesAndScriptViewController.strings file to hold our localization strings specific to this view controller. So you need to do this for every XIB, and every XIB gets a .strings file. You might need to enable the language listings in the Localization section (see Figure 5.7).

Image

Figure 5.7 Localization languages applied to the QuotesAndScriptViewController.xib.

Enable English and French. Then the XIB gets a disclosure triangle (see Figure 5.8).

Image

Figure 5.8 Localized strings for view controller XIB.

Running Under the Localized Language

Now let’s change the language in the Simulator via the Settings app. Choose General, then International. Change the Language to Français. Figure 5.9 shows the French language being applied.

Image

Figure 5.9 Changing the System language to “Français.”

Notice how some of our apps now have localized app names (see Figure 5.10): Réglages, Calendrier, Kiosque. We’ll be doing that ourselves later in this chapter.

Image

Figure 5.10 The Settings app is now referred to as “Réglages” when running under the French language.

Fire up the I18nExerciser app, and let’s look at our Quotes and Scripts view controller by tapping More and then Quotes and Scripts (see Figure 5.11).

Image

Figure 5.11 Quotes and Scripts view controller running under French language.

But all the strings are still in English? We added the French localization and added it correctly. We’ve correctly changed our System’s language setting to French. Why are we not seeing French strings? I’ll tell you the next step. We do have a French localization file for our French strings, namely, Localizable.strings (French), as displayed in the Xcode Project Explorer, which contains all of our necessary keys and values. Remember, we wrapped our strings from the implementation file in the NSLocalizedString() macro.

The NSLocalizedString() macro uses the localizedStringForKey:value:table: method (from the NSBundle class) to look up the string specified by the key, and do this in the current System language. It passes nil for the table name so as to use the default .strings filename, Localizable.strings.


Note

The NSLocalizedString macro definition is as follows:

#define NSLocalizedString(key, comment)      [[NSBundle mainBundle]
localizedStringForKey:(key) value:@"" table:nil]

One important thing to note is that the text supplied to the comment argument is not used in the code, and therefore there is no disadvantage to using long, descriptive comments.


At runtime, the NSLocalizedString macro runs the following NSBundle method:

method[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]

This method determines the System preferred language and uses the corresponding Localizable.strings file in the app bundle. If the user’s preferred language is set to Polish, then NSLocalizedString will look to the file pl.lproj/Localizable.strings.

By default, it is going to use the language that we were developing in, our Base language. Go ahead and click on the .strings file in the Xcode Project Explorer, and you’ll see just that.

/* Class = "IBUILabel"; text = "Script and Variant"; ObjectID = "4Lu-iM-OR0"; */
"4Lu-iM-OR0.text" = "Script and Variant";

/* Class = "IBUILabel"; text = "variantCode"; ObjectID = "8NW-X6-HOo"; */
"8NW-X6-HOo.text" = "variantCode";

/* Class = "IBUILabel"; text = "Quoted string to demo quotation marks."; ObjectID =
"F9g-YI-MRH"; */
"F9g-YI-MRH.text" = "Quoted string to demo quotation marks.";

/* Class = "IBUILabel"; text = "Variant:  "; ObjectID = "SsI-gF-TRr"; */
"SsI-gF-TRr.text" = "Variant:  ";

/* Class = "IBUILabel"; text = "scriptCode"; ObjectID = "oII-iT-30m"; */
"oII-iT-30m.text" = "scriptCode";

/* Class = "IBUILabel"; text = "multiLineLabel"; ObjectID = "pPk-UG-tme"; */
"pPk-UG-tme.text" = "multiLineLabel";

/* Class = "IBUILabel"; text = "Quotes & Scripts"; ObjectID = "pfR-sY-Bk2"; */
"pfR-sY-Bk2.text" = "Quotes & Scripts";

/* Class = "IBUILabel"; text = "Script:"; ObjectID = "stt-Fy-X4e"; */
"stt-Fy-X4e.text" = "Script:";

Before we update our key values to French translations, we should note some things that are different between this XIB .strings file (QuotesAndScriptViewController.strings (French)) and the Localizable.strings (French) file. Both .strings files have comments for the localizer, keys, and values for the keys. The “key” difference is that you cannot customize the names of the keys for XIB .strings files. They are referencing the UI elements contained by the XIB by the object ID for that element. You can verify the IDs by Interface Builder’s Document section on the Identity Inspector tab. Figure 5.12 shows this for the “Script:” label. This is also the case for our Storyboard Main.strings files.

Image

Figure 5.12 The Document section of Identity Inspector.

Update the key values to the following for the QuotesAndScriptViewController.strings (French) file. Note that I’ve also updated the default comments to more meaningful information.

/* Static text for section title */
"4Lu-iM-OR0.text" = "Script et Variante";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"8NW-X6-HOo.text" = "variantCode";

/* Sample string of text to demo quotation marks */
"F9g-YI-MRH.text" = "Une phrase avec guillemets.";

/* Static label for locale variants */
"SsI-gF-TRr.text" = "Variante:  ";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"oII-iT-30m.text" = "scriptCode";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"pPk-UG-tme.text" = "multiLineLabel";

/* Static text for the title of the view controller */
"pfR-sY-Bk2.text" = "Quotations et Scripts";

/* Static label for locale script */
"stt-Fy-X4e.text" = "d'ordinateur:";

Now let’s do the same for the Storyboard. Select the Main.strings (French) listing and make the following changes in Xcode:

/* Static label for "Calendar:" */
"3gc-Lt-U2Y.text" = "Calendrier:";

/* Static label for "Language:" */
"6Ud-Gs-Obj.text" = "Langue:";

/* Static label for "Current system settings" */
"BN8-uM-1rU.text" = "Parametres courants du systeme";

/* Static label for the name of the app */
"Fvv-yF-k6c.text" = "Votre I18 outil";

/* Label for Nav Bar Button for returning to the Home Screen */
"Lzm-bV-lyZ.title" = "Site";

/* Static label for "Date:"; */
"MBe-Fd-lbK.text" = "Date:";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE  */
"Srg-3T-q1Y.text" = "currCal";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE  */
"fiJ-c1-ZID.text" = "currLoc";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"gqN-co-QBb.text" = "currLang";

/* Class = "IBUINavigationItem"; title = "Navigation"; ObjectID = "pda-KE-rL6"; */
"pda-KE-rL6.title" = "Navigation";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE  */
"r9W-cm-1d7.text" = "currDate";

/* Static label for "Locale:" */
"sxj-dL-W14.text" = "Locale:";

/* Label for the "More" button which displays list of View Controllers */
"vij-EV-xfg.normalTitle" = "plus >";


Note

To ensure that your localization changes are reflected in the locale’s XIB, first delete your app from the Simulator or device. This way, Xcode will respect your resource file changes. If you find that your localization changes are still not showing up in your app, do the following:

1. From your device or the Simulator, delete your app.

2. Open your project.

3. Choose Clean from the Project menu.

4. While holding the Option key, click Project and then Clean Build Folder.

This forces a series of “re’s” on your app—recompiled, rebundled, reinstalled.


Let’s build and run the app and bring up the Quotes and Scripts view controller (see Figure 5.13).

Image

Figure 5.13 The Quotes and Scripts view controller localized for French.

Voilà. Très bien!

I’m sure we’re both noticing now we have labels with “too much” text and the result is being truncated. We’ll want to adjust our layout to handle this automatically through Auto Layout and constraints. Please be patient! We’ll cover that in Chapter 6, “Adjusting the UI.”

Translation Results with Japanese

To keep things interesting, here are the translations for the Localizable.strings (Japanese) file:

/* Static text for section title */
"4Lu-iM-OR0.text" = Image

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"8NW-X6-HOo.text" = "variantCode";

/* Sample string of text to demo quotation marks */
"F9g-YI-MRH.text" = Image

/* Static label for locale variants */
"SsI-gF-TRr.text" = Image

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"oII-iT-30m.text" = "scriptCode";

/* Placeholder label.  Value is set via code DO NOT LOCALIZE */
"pPk-UG-tme.text" = "multiLineLabel";

/* Static text for the title of the view controller */
"pfR-sY-Bk2.text" = Image

/* Static label for locale script */
"stt-Fy-X4e.text" = Image

Figure 5.14 shows our app now running with the System language setting set to Japanese.

Image

Figure 5.14 The Quotes and Scripts view controller localized for Japanese.

Working with Localized Images

Our app can be set up such that it handles localized images as well. This is internationalization at its best, giving the customers the feeling that this is their app, fully embracing their locale. So they have an app that not only supports their language and the formats of their locale, but any specific pictures or graphics as well. This could include their country’s flag or a region-specific image. We’ll modify our I18nExerciser project to replace the “globe” image on the home screen to a country map for the language we choose. The process is straightforward because the image is just a resource that’s read in. We need to ensure that the correct resource is in the correct .lproj folder. These are the rules to follow:

Image Images for localization all have the same filename. This includes the file extension, so therefore you want to work with the same file format.

Image The appropriate localized files copied to their corresponding .lproj folder.


Note

Right now, I have a perfect refactoring opportunity. The original graphic file I worked with is named globe.jpg. Although this was appropriate at the time, now I want to localize this image. Localizing images requires that all images have the same filename. If I want to work with maps of countries, for example, having a map of Denmark named globe.jpg makes no sense. At this point, I’m going to go to the Finder and rename my graphic to the more generic name HomeScreen.jpg. I’ll make the same change in the code as well. Be sure to remove any references to globe.jpg from within Xcode.


Follow these steps:

1. Copy or save a .jpg graphics file to the I18nExerciser/I18nExerciser/fr.lproj folder. Name it HomeScreen.jpg. I’m choosing a Creative Commons license graphic off of Wikipedia for a map of France.

2. Do the same for the English.lproj folder.

3. Move the original, now renamed HomeScreen.jpg to the Base.lproj folder.

4. Open the I18nExerciser project.

5. Switch back to Finder and drag all three images into the Xcode Project Explorer.

6. Set the Simulator language setting to French and launch the app.

Figure 5.15 shows the sample app, correctly displaying the graphic I chose for a map of France.

Image

Figure 5.15 The home screen with a localized image.


Display a Country Based on Language?

I’m making a callout here to avoid any confusion with displaying a country as a localized image. When we localize an image, it needs to be stored as a resource in the language-specific lproj folder. When the System is set to a language we’ve localized our app to support, it will apply all the resources associated with that language. Even though with our sample project we are displaying a country, which is in actuality a “region” or “locale,” this image will change only when the System’s language is changed, not when the System’s region setting is changed. Chapter 6’s “UI Localization” section goes into detail about what you must consider when localizing images. For our sample app, I decided to display the image of a country where French is the official language.


Localizing App Icon Name

A finishing touch you can give your application is to localize the app name that displays below its icon on the home screen. If your application’s name is unique, for example Apple’s “Safari,” you won’t want to translate it. However, this detail shows design savvy and will get your app noticed in the app store quicker than a nontranslated screen. Also, for any trademarked app name, you will want to leave in that trademarked language.

App names displayed on the home screen are limited by device and iOS version. The exact number of characters available varies because of this, and therefore you should set your expectations to no more than 12 characters. To localize your name requires changing the app’s Info.plist file and InfoPlist.strings files. The following steps assume you have an existing application that has more than one localization language setting:

1. Modify the application’s Info.plist file. This is typically in the Supporting Files group in Xcode’s Project Explorer. (Its actual name is YOUR_PROJECT’S_NAME-Info.plist.) Click the file listing to modify it.

2. Click the plus symbol next to Information Property List. From the drop-down shown in Figure 5.16, select Application Has Localized Display Name. Set the value to YES.

Image

Figure 5.16 The application Info.plist file.

3. If the key named “Bundle display name” is present, delete it by clicking the minus sign.

4. Navigate to your localized InfoPlist.strings files. Add the following two lines to the file, in which "LOCALIZED_NAME" would be replaced with your localized names:

CFBundleDisplayName = "LOCALIZED_NAME";
CFBundleName = "LOCALIZED_NAME";

5. Do this for each InfoPlist.strings file.

Other NSLocalizedString Macros

Other macros are available to wrap code strings in. The list of these macros is shown here, followed by descriptions of their functionality:

Image NSLocalizedStringFromTable

Image NSLocalizedStringFromTableInBundle

Image NSLocalizedStringWithDefaultValue

NSLocalizedStringFromTable()

This macro helps organize your project:

NSString *NSLocalizedStringFromTable(NSString *key, NSString *tableName, NSString
*comment)

This is nothing different from the NSLocalizedString macro other than it includes a third argument to specify a custom table. By default, the NSLocalizedString macro writes out to the Localizable.strings file. NSLocalizedStringFromTable allows you to specify a table to write out to, such as AlanParsonsProject.strings. This allows you to break down the localization files based on individual projects within your app, for example, a table for any “contacts” in your project, another for “product details,” and so on. The syntax to write out to the suggested table would look like the following:

NSLocalizedStringFromTable(@"MainProject",@"AlanParsonsProject", @"Heading label
signifying the main project file")

NSLocalizedStringFromTableInBundle()

The function signature for this macro is as follows:

NSString *NSLocalizedStringFromTableInBundle(NSString *key, NSString *tableName,
NSBundle *bundle, NSString *comment)

This macro is used to generate .strings files named "tableName".strings, which will be located in a custom bundle, like a framework bundle. Because your framework is separate from your main project, it will require its own localization resources. Also, as the .strings files are in the framework’s bundle, that bundle will need to specifically be referenced as opposed to the main bundle.

NSLocalizedStringWithDefaultValue()

The function signature for this macro is as follows:

NSString * NSLocalizedStringWithDefaultValue(NSString *key, NSString *tableName,
  NSBundle *bundle, NSString *value, NSString *comment)

As with the other macros, genstrings works well with this one. It’s a pretty self-explanatory function because whatever string is specified for value will be used as the default value for the keys for the .strings table. This can be useful if you set the default value to nonreadable text—something that isn’t an actual word. So instead of having a value of Tap Here, which is completely readable and easy to overlook, if you have a default value of NOTLOC you can easily identify strings that are missing translation.

Creating a Generalized Key Name

When we’ve talked about wrapping our strings in the NSLocalizedString() macro, we’ve been using the literal string as the first argument. This results in a key having a name that is the same as its value in the Base language. For example, if I have a literal string of No files found in my code, the Localizable.strings file would have this entry:

/* Message to display when no files are found */
"No files found." = "No files found."

An alternative approach is when you are implementing your NSLocalizedString() macro, create a generic key for the first argument rather than the literal string. This makes for more readable Localizable.strings files and quicker debugging to verifying that strings were localized. Looking at our preceding example, a better implementation would be to have the key named msgNoFile:

NSLocalizedString(@"msgNoFile", @"Message to display when no files are found")

After we have run genstrings, our .strings file would have the following entry:

/* Message to display when no files are found */
"msgNoFile" = "msgNoFile"

This provides us a powerful debugging tool. Now as we run our app under the languages we’ve localized for, whenever we have a string that has not been localized, it will display, for our example, as msgNoFile instead of the translated version of No files found. The unfortunate tendency when verifying localized strings is recognizing strings that are in your Base language. It is easy to acknowledge that string, see it as correct, and not realize it’s not the correct string for the given language. That’s where using NSLocalizedString() with a generic token term is most useful. In your UI, the generic token is displayed, which quickly gets recognized as something amiss. Where this approach becomes labor-intensive is with the Base language—you need to manually add the values for the Base language strings because you are not wrapping them in the NSLocalizedString() macro. Look at resources, consider your costs, and then determine whether you can add this extra step to your base localization files.

genstrings Tools

Here are some solid GitHub projects related to genstrings that enhance the genstrings process. The most convenient option these projects have is searching the current folder and subfolders for implementation files to parse in creating the Localizable.strings files.

Update_Localization

Unlike the genstrings utility, this project (https://github.com/iv-mexx/update_localization) will not overwrite any existing files but will append new items. This is valuable when generating your .strings file that have been translated. When a new text element is added to your project, running this tool will append the new entries to the existing .string files as opposed to replacing the entire file.

pygenstrings

This project (https://github.com/dulaccc/pygenstrings) runs genstrings on all *.m files in your project, merges new results into the existing Localizable.strings files, and reports whether everything ran correctly. The advantage this tool has is that it will search for *.m files starting from your project’s root folder in all subfolders.

Translating to Localized Languages

Doing your own translation of strings is possible if you plan ahead and try to limit the amount of text in your app. If you’re finding that your app needs more than a single word, maybe even a paragraph to initially explain functionality, you would be wise to invest in a translation service to ensure your description uses the correct terms and grammar for the given locale. The following sections discuss translation pitfalls and give details about translation services.

No “Mad Libs”

Mad Libs is a game in which a phrase is given with words missing that one needs to fill in. These missing words are represented by blank lines with a clue of what part of speech to fill in: noun, adverb, verb, and such. The fun of the game comes from the nonsensical replacement: “The pig drove the polka dot tree while sleeping.” A noun, an adjective, another noun, and a verb were added correctly, but contextually, the sentence makes no sense. Imagine how a reader will respond if you word-for-word translate a phrase without context. Do not simply translate words. The sentence is king.

Avoid Translation Templates

When you’re working with iOS applications with such limited screen real estate, the more you can avoid long strings of text the better. Try to limit your UI to single words as much as possible: “Next,” “Sort,” and the like. That way context becomes easier to grasp. You do want to make sure not to be clever with what you plan on localizing. Avoid thinking you can translate a sentence by breaking it up—“noun” followed by a “verb” followed by another “noun”—and then having those individually translated. This will only lose context, verbs will not be conjugated properly, and grammatical constructions won’t exist or at the very least will be awkward.

Translation Services

It is tempting to use a service like Google Translate to localize the strings in your app. It’s free and only a reliable Internet connection away. What these services lack is the context that even two words can give.

When your project is large enough that you’re working with a number of languages and a large amount of text to translate, you will want a reputable firm to translate for you. Cost will vary, of course, but figure a cost of $0.20 (twenty cents U.S.) per word, per language. Some services have a minimum charge of $100.

You will submit your localized .strings files, or, depending on the firm, XML or text files. Some firms will parse your file and determine the number of words that will be translated and give you an upfront cost. Some firms have on-demand sites, meaning you can submit your .strings files at any time of the day. Do note what their site specifies for turnaround time.

Look for firms that have a long track record as opposed to a flashy Web site. Also be sure that the company works with native speakers from the regions and locales you want to translate to. These people are the most familiar with and will know the correct and current colloquial phrases.

The basic steps when working with a translation service are shown here:

1. Localize your app.

2. Review your .strings files, especially to ensure that your comments make sense and will be understood by the localizer.

3. Sign in to your account with the translation service.

4. Upload your .strings files.

5. Verify the costs.

6. Submit your translation job to the service.

Apple has a list of more than 20 available third-party vendors at the bottom of this site: https://developer.apple.com/internationalization/. Take advantage of any vendors that will also allow you to upload screenshots so as to have a complete context of how the feature works and how the customer should respond.

Importing Files from Translators

After the translation services have done their job and returned the .strings files to you, take each translated .strings file and copy it to the appropriate .lproj folder. Delete any existing builds of your app, rebuild it, run it under the same language, and verify the strings.

Word Order

When you’re working with nouns and adjectives, the adjective will be ordered first with English, but that’s not the general case for all languages. In French and Russian, for example, the adjective comes after the noun it is modifying or describing. We can still take this into consideration with our localization. How so, you ask? I can imagine your concern—would we need multiple configurations of .strings files and would we need the tokens for combinations of nouns and adjectives? Something along the lines of "blueRectangle" = "blue rectangle" for English strings, "blueRectangle" = "rectangle bleu" for French strings? The answer to both of these questions is no, and our solution can be found in referencing the printf command, of all places. When you’re formatting a string, it is possible to specify explicitly which argument is taken where by writing not just our usual %@ token but %n$@, in which n represents the arguments order.

Let’s look at an example. Part of your app’s functionality is order fulfillment for sporting goods. One of your customers orders a ball, and it’s red. We’ll want to localize the item, its color, and the order.

We can create our order string like this:

NSString *color = NSLocalizedString(@"colorKey", @"The color of the item");
NSString *item = NSLocalizedString(@"itemKey", @"The item the customer is
purchasing");
NSString *order = [NSString
localizedStringWithFormat:NSLocalizedString(@"orderKey", @"Formats the order of
the order"), color, item];

Within our English .strings files we have this:

/* The color of the item */
"colorKey" = "red";
/* The item the customer is purchasing */
"itemKey" = "ball";
/* Formats the order of the order*/
"orderKey" = "%@ %@";

And then within our French .strings files, notice we need to modify the "orderKey" token to reflect the correct order for our noun and our adjective:

/* The color of the item */
"colorKey" = "rouge";
/* The item the customer is purchasing */
"itemKey" = "boule";
/* Formats the order of the order*/
"orderKey" = "%2$@ %1$@";

This has been a specialized example, but I hope it demonstrates the flexibility that’s available to us when localizing. And it’s certainly not limited to “adjectives” and “nouns” but whatever word order you need.

Plural Forms and the CLDR

Languages have different rules for dealing with plurals. Some of these rules include how to represent text when you have different numbers of an item: zero, one, two, or other. The rules for plurals are included in the Unicode Common Locale Data Repository project. This project includes locale information for use in computer applications including but not limited to the following:

Image Translations for language names

Image Translations for territory and country names

Image Translations for currency names, including singular/plural modifications

Image Translations for time zones and sample cities (or similar) for time zones

The CLDR assists us in working with grammar rules and verb conjugation when dealing with the different number of objects. An example of verb conjugation with English would be a single example, “The horse runs across the field,” as opposed to a multiple or plural example, “The horses run across the field.” The iOS operating system has CLDR support built in to correctly handle these.

What Do We Mean by Quantities?

Zero, one, two, other—why are these called out? The quantity the text is referring to will affect the grammar and how the text is represented. For English, when we’re dealing with a single item, the text is represented as “horse,” for example. When that quantity changes, the string changes as well, to “horses.” The verb conjugation changes as well, from “The horse gallops across the field” to “The horses gallop across the field.” Apps that support CLDR categories can respond to differences in quantities and automatically reflect those changes. I’ll walk you through an example of setting up that support after explaining, in Table 5.1, how quantities within the CLDR are categorized.

Image

Table 5.1 Quantity Categories for Common Locale Data Repository

Not all languages support every category. For example, English supports only “zero,” “one,” and “other,” meaning any quantity other than “zero” or “one” will display the text assigned to the “other” category. Other languages support many more categories, with Welsh supporting all five and Slovenian supporting three.

You can find more info at www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html.

Plurals Example

I’ve got an example that will look at an input value and then, depending on that value, return the correct plural form of the word “cactus.” In this example, an artist is painting a number of cacti on a mural. We need the following implemented:

Image Code using the NSString class method localizedStringWithFormat

Image A Localizable.strings file for the localized language

Image A Localizable.stringsdict file for the localized language

The Localizable.stringsdict dictionary we need is displayed in Listing 5.1. The same rules apply for the .stringsdict files as with .strings files. Each localized resource folder (*.lproj) needs a copy of the file and that file referenced in the Xcode project. Unfortunately, there’s not an automatic way to generate the file. Here are some specifics to look at with this file:

Image The return value is from the NSStringLocalizedFormatKey.

Image The spec type is NSStringPluralRuleType.

Image The value to test against is our d variable.

Listing 5.1 Localizable.stringsdict File


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>cactusKey</key>
        <dict>
                 <key>NSStringLocalizedFormatKey</key>
                 <string>The artist painted %#@num_of_cacti@ on the mural.</string>
                 <key>num_of_cacti</key>
                 <dict>
                         <key>NSStringFormatSpecTypeKey</key>
                         <string>NSStringPluralRuleType</string>
                         <key>NSStringFormatValueTypeKey</key>
                         <string>d</string>
                         <key>zero</key>
                         <string>no cacti</string>
                         <key>one</key>
                         <string>a cactus</string>
                         <key>other</key>
                         <string>%d cacti</string>
                 </dict>
        </dict>
</dict>
</plist>


Figure 5.17 displays the XML code shown in Listing 5.1 opened as an ASCII Property List from within Xcode.

Image

Figure 5.17 The Localizable.stringsdict file displayed as an ASCII property list.

Listing 5.2 shows our sample code.

Listing 5.2 Sample Code to Work with CLDR Plurals


-(void)pluralTest{
    NSInteger testInteger = [@"233" integerValue];
    NSString *resultString = [NSString localizedStringWithFormat:NSLocalizedString(@"cactusKey", @"Number of
cacti an artist has painted"), (long)testInteger];

    NSLog(@"%@", resultString);
}


The following is our Localizable.strings file generated from running genstrings from the Terminal app. Note that the value for the cactusKey is what will be returned if the number/quantity of painted cacti is not “zero” or “one.”

/* Number of cacti an artist has painted */
"cactusKey" = "The artist painted %ld cactus on the mural.";

Finally, Table 5.2 displays the results from the different quantities.

Image

Table 5.2 Results from Sample Code

It’s all correctly handled via CLDR support.

Genders

How a locale handles gender support—as in pronoun support—is defined with the CLDR as well. It follows a similar pattern to rules for plurals. Some examples of genders and the supported number for the locale are English with three (male, female, and neutral) and Polish with five (masculine personal [męski osobowy], masculine animate [męski nieosobowy żywotny], masculine inanimate [męski nieosobowy nieżywotny], feminine [żeński], and neuter [nijaki]). The CLDR assigns numbers for each gender. In the case of English, this would be male = 0, female = 1, neutral = 2. Supporting these gender pronouns in their languages follows a pattern similar to the plural rules.

The strings dictionary supports gender rules via NSStringGenderRuleType. Unfortunately, the gender rule only returns null from the strings dictionary as of iOS 7.1.2. For more detail about this, there is an existing Apple radar bug (rdar://16670931).

Summary

In this chapter we covered the basic and detailed steps for localizing your application. We walked through localizing a view controller from our sample app from the preceding chapter and localized both strings within the code and strings within the UI.

Hopefully, you saw that with a little planning, localizing your app doesn’t take that much more effort. Your app automatically responds to the international settings change.

In the next chapter, we’ll look at problems that can occur with layout and UI and the best ways to guard against them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset