Localization

Internationalization – 3

In our previous two posts we covered how to localize our UI files. In this post we will show how to internationalize our code! Lets start with an example:

Say we are going to set the text for a UILabel in the code (either because the UI was developed programmatically, or because the text in the label is situational). To set the text, we would do something like this:

label.text = "I like cheese!"

As you may have noticed, “I like cheese” itself is not very localized…  With the UI files we were given a .strings file to work with and created more as we added other languages.  When internationalizing our code, we create our own .strings file to work with. We do so by:

  1. Pressing cmd-N (⌘-N) to add a new file.
  2. Scroll down to the Resource section and select the “Strings File”
  3. Add a file name and select a location and we are ready to rock!

If you remember from our previous posts when the UI localized our text the .strings file contained a key-value sort of pairing that looked like this:

"GU8-dk-kKo.placeholder" = "Username";

where the LHS referenced the UI objectID and attributed, and the RHS is the displayed text (the part that is localized). The .strings file we are using will follow a similar format.  Add the following line to your file:

"I like cheese!" = "I like cheese!";

Just like in the other .strings files, the LHS is the key used for localization lookup, the RHS is the text that will be passed into your controllers. Now lets see how to access the information in this file.  We do so by using the NSLocalizedString(key: String>, comment: String>) function. As you clever developers have guessed, the key is what is in the LHS of our .strings file, the comment can be left as an empty string for now. To update the code above to use the localized string change the line like so:

label.text = NSLocalizedString("I like cheese!", comment: "")

Now, you may be thinking, the whole “I like cheese!” = “I like cheese!”; think looks a little ridiculous.  Well, it is, and I don’t recommend you do it that way.  I like to follow the paradigm that interface builder uses when providing its .strings files: use an object related description as the key for the text. In our case we might change the text to something like this:

// In the .swift file:
label.text = NSLocalizedString("cheeseLabel.text", comment: "")

// In the .strings file
"cheeseLabel.text" = "I like cheese!";

This way, your references are not simply a reiteration of text, but rather a string which gives context to how the string is being used.  Another benefit to doing this is that if you decide to change the localization from something like “I like cheese!” to say “Cheese is great!” your .strings file doesn’t contain any strange statements:

"I like cheese!" = "Cheese is great!";
// VS
"cheeseLabel.text" = "Cheese is great!";

One thing you should be aware of is that you shouldn’t overload keys, or to compose strings from multiple keys.  For example. say we have two phrases, “exit room” and “exit building” in our app It could be tempting to make keys that combine these like this:

"exit" = "exit";
"room" = "room";
"building" = "building";

and then to combine them something like this:

label.text = NSLocalizableString("exit",comment:"") + " " + NSLocalizableString("room",comment:"")

The issue with this is that different languages might combine these terms differently (genders, order, etc).  Thus you should always use a separate key-value pair for each unique phrase

 

TL;DR

  • Localize your source code using the NSLocalizableString method: NSLocalizedString(key: String>, comment: String>)
  • Always use a separate key-value pair for each unique phrase.

 

 

Sources

https://developer.apple.com/documentation/foundation/nslocalizedstring

https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/InternationalizingYourCode/InternationalizingYourCode.html#//apple_ref/doc/uid/10000171i-CH4-SW3

http://nshipster.com/nslocalizedstring/

Localization

Internationalization – 2

In Interface Builder, use Auto Layout so views adjust to the localized text.

In our previous post (here) we began the internationalization process by localizing the text in our UI files. This, however, is only have the work that needs to be done. You see, not everybody reads left to right, top to bottom. Some read right to left, some read bottom to top, etc. Additionally, a UI naively configured will be ill suited to handle the different sizes of text that translation necessarily requires. In this post we will learn how to handle these differences gracefully.

Our best tool for configuring a dynamic, internationalized UI is auto layout. Correctly using auto layout makes internationalization almost trivial. Here are the key points to remember:

  1. Don’t use fixed width constraints! Any label that is going to be localized should be able to resize itself to adjust for the difference in text from each localization.
  2. Utilize intrinsic content sizes. The intrinsic content size of a view is the minimum space needed to display all of the views content without clipping or shrinking its data. For textFields and labels, this means to resize itself automatically to accommodate the text stored therein.
  3. Use the leading and trailing attributes. If you use the leading and trailing attributes when applying constraints, you allow the UI to be able to change itself from left-to-right to right-to-left. This will provide a more natural UI experiences for localizations where reading is done right-to-left (such as Hebrew or Arabic).
  4. Pin views to adjacent views. Rather than pining views inside its parent view, pin views to their neighbors. This then allows a domino effect when a view is resized and prevents view overlap.
  5. Test! Test! Test! Constantly test your UI with different language settings.

Now that we know what we should be doing, lets look at an example of how to do it.

Lets say I had ben tasked with designing a simple user profile view. And lets say that it needs to look like this:

So, how do we handle this situation? It might be tempting to pin everything to the left hand side of the view, wipe our hand and call it good. However, this is certainly not the way to go!

Our first step should probably be to deal with the image view. So, you talk to the designer and they tell you that they want it to be 10 points from the left, and 20 points from top.  You ask about the width and height, they tell you they want it to be 100 x 100. Applying these constraints might look something like this:

Note that the top constraint is actually 0, not 20.  This is because we are cleverly using the safe zone and not the view edge to work from. “But!” you gasp, “You have violated rule 1 already!” “No, no..” I state, shanking my head from side to side while chuckling. “You see, the image docent need to be able to adjust for different sizes during localization! Thus, it is OK to apply fixed with to this view. Rule 1 applies to views which may change their size during the localization process.”

OK, So we’ve got the view pinned, now lets try dealing with the labels. First, lets apply constraints to the name label. Keeping in mind rule 4, we pin the label to the image view, and align their tops. This way, any movement, vertical or horizontal of the image view will adjust my name label (and any views pinned to it). The only other constraint to the view we need to apply is to prevent it from moving off screen, this is done with a trailing space, >= constraint. This also helps us to conform to rules 1, 2, and 3 by not setting a fixed and pinning its trailing attribute to the next closest view (the >= constraint allows for expansion and movement, and thus allows the view to fit its intrinsic content size). Here are the constraints:

Sweet! Now lets apply the constraints to the handle label. Again, we are going to try to pin to the nearest views. So we pin the top space to the name label and we are going to align their leading attributes. We are using align here instead of pinning to the image view since the name is already pinned to the image view and what we really care about is that their text is left aligned. While pinning to the image view would get the job done, if we ever needed to change the gap, we would need to change each gap individually. This way, we would only need to change one constraint! Finally, we need to set the trailing space constraint as we did above.  Here are the constraints:

The views for the occupation label are almost the same as the handle label constraints, only the occupation label is pinned to the handle label, not the name label.

We have now followed the first 4 rules. Lets look at rule 5. How can we test if we haven’t yet localized our text? Xcode has got some nifty tools that allow you to start testing for internationalization conformance of your interface builder files that you can use without even compiling your code! First, open your interface builder and make sure that your assistant editor is open (⌥⌘↩︎) Next select the preview option from the dropdown menu on what file to show:

This preview view is a fantastic too! it allows you to view your view in different devices, orientations, etc. All without compiling and running your code! So neat. Not only that but it also has an option for adjusting the text to view it under different situations. In the bottom right corner of the assistant editor is a button that states the language the text is being displayed in.  For me it is English.  Click this and a drop down menu appears with different choices:

Each pseudolanguage allows you to test your UI against various challenges language can present to text layout, including accents and longer than expected strings.  For instance, choosing double-length, capitalizes all text and doubles the string in the preview:

Excellent! While this doesn’t look like there is a problem, lets try inputing some information and trying again. Below is the preview next to the double length rendering:

The left image is form interface builder, the right is from the assistant editor.  While the first two labels look good on the left, all three look like they need adjusting on the right.

Now this is where feedback from the design team is needed. Perhaps they do, in fact want truncation. However, they probably would rather that some word wrapping happened. So we simply go back to our interface builder, and set the number of lines in the label to 0 (zero) Which allows the labels to have as many lines as they need.  Looking back at our assistant may, or may not render the correct scene.  In my case the handle label wrapped in the assistant editor, but The others still truncated.  If you tweet the height of the labels, it causes a UI update and wrapping happens.  Build and running also will show the correct rendering…

Now lets check our left to right. To do this we are going to need to use the simulator (or your device) as the preview assistant is still a bit buggy (see above). Run the app on the simulator and hit ⌘⇧H to exit your app.  Navigate to the settings app and got to General ->Language & Region -> iPhone Language.  Next hit ⌘⇧H to exit settings and navigate back to your app.

At this point the UI should look exactly the same.  This is because we haven’t add a translation for Hebrew and so the app uses the base translation (English).  Lets fix that by adding a translation real quick. Remember from our last tutorial, we go to our Project file -> Info, then click the plus under Localizations:

This time choose Hebrew form the drop down menu and click OK on the following popups.  Now run your app again and you should see something like this:

Notice how the whole UI is backwards, eg right-to-left! Nothing is stuck on the left that should now be on the right (because we used trailing attributes).

Now to change back to English open settings and tap the gear icon, option 4 from the top, and the first option.  Alternatively you can go to Hardware->Erase all content and settings.

So far things are looking pretty good with our app.  All we have to do now is to keep on testing; making sure to test each translation as it becomes available.

TL;DR

Prepare your app for internationalization by:

  • Not using fixed width constraints!
  • Utilizing intrinsic content sizes.
  • Using the leading and trailing attributes.
  • Pinning views to adjacent views.
  • Test! Test! Test!

Sources

https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/InternationalizingYourUserInterface/InternationalizingYourUserInterface.html#//apple_ref/doc/uid/10000171i-CH3-SW3

http://wordgirl.wikia.com/wiki/Huggy_Face

Localization

Internationalization – 1

So, I’ve been thinking of some of the more useful things I’ve inkearned as I’ve developed iOS applications and one of the most useful must be internationalization.  “You mean localization?” you ask.  Well, partially.  I’ve never actually needed to translate an app into any other language (localization), but I have made a habit of internationalizing my applications (preparing an app for localization). The process of internationalization has helped me to better understand the MVC paradigm. So, for the next few posts I’m going to be covering internationalization and discussing how easy Apple has made it for us developers to do! So with out further adieu:

The what, when, why, how

Apple puts it very nicely in their introduction to Internationalization and Localization:

Localization is the process of translating your app into multiple languages. But before you can localize your app, you internationalize it. Internationalization is the process of making your app able to adapt to different languages, regions, and cultures. Because a single language can be used in multiple parts of the world, your app should adapt to the regional and cultural conventions of where a person resides. An internationalized app appears as if it is a native app in all the languages and regions it supports.

So, localization is translating the app, while internationalization is the preparing the app for localization. Internationalization is usually the part that most concerns developers.  We need to prepare the UI and code base to accept localization.

In a broad sense we can prepare an application for internationalization by capturing any instance of user facing text. I say text since that is what is usually the most applicable, though any portion of an app may be subject to localization, such as images, sounds, etc. But in all cases, the process is essentially the same. But I digress. So, we capture user facing text and separate the user facing text (UFT) from the string value we have in code. Apple outlines six steps for internationalizing your app:

  1. Use base internationalization to separate user-facing text from your .storyboard and .xib files.
  2. In Interface Builder, use Auto Layout so views adjust to the localized text.
  3. Separate other user-facing text from your code.
  4. Use standard APIs to handle the complexity of different writing systems and locale formats.
  5. Adhere to the user’s settings when formatting, sorting, searching, and parsing data.
  6. If the app supports right-to-left languages, mirror the user interface and change the text direction as needed.

In order to keep my posts relatively short, I’ll cover each of these steps in a separate post.

 

Use base internationalization to separate user-facing text from your .storyboard and .xib files.

Our first step in internationalization is to prepare the text in our .storyboard and .xib files. This is actually fairly straightforward.  Lets say we have a simple login UI with a username and password UITextFields and a LogIn button:

So it looks nice, but how do we prepare it for translation into, say Ukrainian? I can’t just change the placeholder text and compile again. But here is an idea.  What if we could take the text in the UI and translate it on the fly? “Pfffft!” you scoff. “Thats impossible!” Nothing is impossible for the intrepid developer!

First, lets think about a dictionaries. A dictionary in Swift is a storage device that utilizes key-value pairs.  You input a key, and the dictionary gives you the value for that key. Localization in iOS happens in the same way. You give the string you want localized and it spits out the value that you previously encoded for that ‘key’.  These key-value translations are all stored in special .strings files. Lets see how to generate some .strings files that are tied to our UI.

First, open a new project in Xcode and select “Single View App” as your template. Next open the Main.storyboard file and add the UITextFields and UIButton to your view controller.  Edit the properties of these items to make them look like our view above.  Next open up the File Inspector (its the file icon on the right hand side, or just ⌥⌘1). About half way down is a section called…(Wait for it….) Localization:

Now, note the two listed localizations, “Base” and then an unchecked “English”.  “Base” is referring to whatever language you are coding in, be it English, Spanish, or simply programmer lingo. It is what is currently in the UI. Now lets see what happens after we check the “English” box. (Did you do it?).  OK, I’m assuming you did. The first think you might have noticed (or not) is that your Main.storyboard file now has a disclosure icon to its left:

Lets expand it and see what wonderful things have happened:

So our Main.storyboard now has a couple of files connected to it:

  1. Main.storyboard (Base)
  2. Main.strings (English)

The image above shows us what is in the .strings file.  Examining the comments and the words helps us to understand what is going on. The comment on line 2 indicates a UITextField, that has an objectID of “GU8-dk-kKo” whose placeholder text is “Username”. (PS to see any object’s objectID: select it, and open the Identity Inspector, ⌥⌘3, its shown in the Document section). But what is the next line? Being clever programmers we realize that this is a code of sorts. What we have (in very general terms) is this:

"GU8-dk-kKo.placeholder" = "Username";

//....Or, more generally...

"objectID.objectPropertyName" = "DesiredValue";

Neat! But what does this really mean, or do? Quiet simply, this is our “dictionary”. The LHS is the Key and the RHS is the Value.  Thus if we change the RHS, we change the value that is returned when a lookup is preformed. And that means that the text shown to our users will be different. Go on! Give it a try! Change the word “Username” to something else (I chose “Call Sign”) and hit run, if your local is set to English, then whatever you changed you text to will show up instead!

What is going on, is pretty much as described earlier.  When the UI goes to render, it checks to see if there are any .strings files for the local the phone is set to.  If not, it just uses the base values. But if it does have one, then it looks up the “Key”.  If the “Key” exists, then it returns its “value” (translation).  If it doesn’t, it defaults to the Base value once more.

Adding Translations

Now that we’ve started down this internationalization road, how do we add a translation for, say Ukrainian? Well, that’s easy! To add a translation navigate to your project file in the project navigator. Next select the project info file. In the info section, you will see the localizations connected to this project:

As you may have guessed (since you are clever), simply click the ‘+’ button to add a localization.  This will give you a list of languages to choose from:

Selecting Ukrainian (or your language of choice) and hitting next on the dialog, adds to the list in the Project file, as well as creates and additional .strings file inside our storyboard files (and our LaunchScreen and Main).  Note, localizing .xib files works the same way.

 

TL;DR

  1. To internationalize .storyboard and .xib files, select ‘Localize…’ inside the File Inspector for each file.
  2. To add a language for localization, select the Project file, and go to the info for the project (not a target) and add to the localizations list.

Sources

https://www.objc.io/issues/9-strings/string-localization/

https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPInternational/Introduction/Introduction.html#//apple_ref/doc/uid/10000171i-CH1-SW1

https://medium.com/lean-localization/ios-localization-tutorial-938231f9f881