7
Localization

The appeal of iOS is global – iOS users live in many countries and speak many languages. You can ensure that your application is ready for a global audience through the processes of internationalization and localization.

Internationalization is making sure your native cultural information (like language, currency, date format, number format, etc.) is not hardcoded into your application. Localization is the process of providing the appropriate data in your application based on the user’s Language and Region Format settings. You can find these settings in the iOS Settings application (Figure 7.1). Select the General row and then the Language & Region row.

Figure 7.1  Language and region settings

Screenshot of the Language and Region settings of an iPhone.

Here, users can set their region, like United States or United Kingdom. (Why does Apple use region instead of country? Some countries have more than one region with different settings. Scroll through the options in Region to see for yourself.)

Apple makes internationalization and localization relatively simple. An application that takes advantage of the localization APIs does not even need to be recompiled to be distributed in other languages or regions. (By the way, because internationalization and localization are long words, you will sometimes see them abbreviated as i18n and L10n, respectively.)

In this chapter, you will first internationalize the WorldTrotter application and then localize it into Spanish (Figure 7.2).

Figure 7.2  Localized WorldTrotter

Screenshot shows the intended output of the WorldTrotter app after localizing it to Spanish.

Internationalization

In this first section, you will use the NumberFormatter and NSNumber classes to internationalize the ConversionViewController.

Formatters

In Chapter 4, you used an instance of NumberFormatter to set the text of the Celsius label in ConversionViewController. NumberFormatter has a locale property, which is set to the device’s current locale. Whenever you use a NumberFormatter to create a number, it checks its locale property and sets the format accordingly. So the text of the Celsius label has been internationalized from the start.

Locale knows how different regions display symbols, dates, and decimals and whether they use the metric system. An instance of Locale represents one region’s settings for these variables. When you access the current property on Locale, the instance of Locale that represents the user’s region setting is returned. Once you have that instance of Locale, you can ask it questions, like, Does this region use the metric system? or, What is the currency symbol for this region?

let currentLocale = Locale.current
let isMetric = currentLocale.usesMetricSystem
let currencySymbol = currentLocale.currencySymbol

Even though the Celsius label is already internationalized, there is still a problem with it. Change the system region to Spain to see. Select the active scheme pop-up and select Edit Scheme... (Figure 7.3).

Figure 7.3  Edit scheme

Screenshot depicts how to select Edit Scheme.

Make sure that Run is selected on the lefthand side and then select the Options tab at the top. In the Application Region pop-up, select Europe and then Spain (Figure 7.4). Finally, Close the active scheme window.

Figure 7.4  Selecting a different region

Screenshot of the Active Scheme EditorWindow.

Build and run the application. On the ConversionViewController, tap the text field and make sure the software keyboard is visible. You may already notice one difference: In Spain, the decimal separator is a comma instead of a period (and the thousands separator is a period instead of a comma), so the number written 123,456.789 in the United States would be written 123.456,789 in Spain.

Attempt to type in multiple decimal separators (the comma) and notice that the application happily allows it. Whoops! Your code for disallowing multiple decimal separators checks for a period instead of using a locale-specific decimal separator. Let’s fix that.

Open ConversionViewController.swift and update textfield(_:shouldChangeCharactersIn:replacementString:) to use the locale-specific decimal separator.

func textField(_ textField: UITextField,
               shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {

    let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
    let replacementTextHasDecimalSeparator = string.range(of: ".")

    let currentLocale = Locale.current
    let decimalSeparator = currentLocale.decimalSeparator ?? "."

    let existingTextHasDecimalSeparator
            = textField.text?.range(of: decimalSeparator)
    let replacementTextHasDecimalSeparator = string.range(of: decimalSeparator)

    if existingTextHasDecimalSeparator != nil,
        replacementTextHasDecimalSeparator != nil {
        return false
    } else {
        return true
    }
}

Build and run the application. The application no longer allows you to type in multiple decimal separators, and it does this in a way that is independent of the user’s region choice.

But there is still a problem. If you type in a number with a decimal separator that is not a period, the conversion to Celsius is not happening – the Celsius label displays ???. What is going on here? In fahrenheitFieldEditingChanged(_:), you are using an initializer for the Double type that takes in a string as its argument. This initializer does not know how to handle a string that uses something other than a period for its decimal separator.

Let’s fix this code using the NumberFormatter class. In ConversionViewController.swift, update fahrenheitFieldEditingChanged(_:) to convert the text field’s string into a number in a locale-independent way.

@IBAction func fahrenheitFieldEditingChanged(_ textField: UITextField) {

    if let text = textField.text, let value = Double(text) {
        fahrenheitValue = Measurement(value: value, unit: .fahrenheit)
    if let text = textField.text, let number = numberFormatter.number(from: text) {
        fahrenheitValue = Measurement(value: number.doubleValue, unit: .fahrenheit)
    } else {
        fahrenheitValue = nil
    }
}

Here you are using the number formatter’s instance method number(from:) to convert the string into a number. Because the number formatter is aware of the locale, it is able to convert the string into a number. If the string contains a valid number, the method returns an instance of NSNumber. NSNumber is a class that can represent a variety of number types, including Int, Float, Double, and more. You can ask an instance of NSNumber for its value represented as one of those values. You are doing that here to get the doubleValue of the number.

Build and run the application. Now that you are converting the string in a locale-independent way, the text field’s value is properly converted to its Celsius value (Figure 7.5).

Figure 7.5  Conversion with a comma separator

Screenshot of the Conversion view with an updated separator - comma. The Fahrenheit value and Celsius value have a comma in the place of the decimal point.

Base internationalization

When internationalizing, you ask the instance of Locale questions. But the Locale only has a few region-specific variables. This is where localization – creating application-specific substitutions for different region and language settings – comes into play. Localization usually involves either generating multiple copies of resources (like images, sounds, and interface files) for different regions and languages or creating and accessing strings tables (which you will see later in the chapter) to translate text into different languages.

Before you go through the process of localizing resources, you must understand how an iOS application handles localized resources.

When you build a target in Xcode, an application bundle is created. All of the resources that you added to the target in Xcode are copied into this bundle along with the executable itself. This bundle is represented at runtime by an instance of Bundle known as the main bundle. Many classes work with the Bundle to load resources.

Localizing a resource puts another copy of the resource in the application bundle. These resources are organized into language-specific directories, known as lproj directories. Each one of these directories is the name of the localization suffixed with lproj. For example, the American English localization is en_US, where en is the English language code and US is the United States of America region code, so the directory for American English resources is en_US.lproj. (The region can be omitted if you do not need to make regional distinctions in your resource files.) These language and region codes are standard on all platforms, not just iOS.

When a bundle is asked for the path of a resource file, it first looks at the root level of the bundle for a file of that name. If it does not find one, it looks at the locale and language settings of the device, finds the appropriate lproj directory, and looks for the file there. Thus, just by localizing resource files, your application will automatically load the correct file.

One option for localizing resource files is to create separate storyboard files and manually edit each string in each file. However, this approach does not scale well if you are planning multiple localizations. What happens when you add a new label or button to your localized storyboard? You have to add this view to the storyboard for every language. Not fun.

To simplify the process of localizing interface files, Xcode has a feature called base internationalization. Base internationalization creates the Base.lproj directory, which contains the main interface files. Localizing individual interface files can then be done by creating just the Localizable.strings files. It is still possible to create the full interface files, in case localization cannot be done by changing strings alone. However, with the help of Auto Layout, string replacement is sufficient for most localization needs. In the next section, you will use Auto Layout to prepare your layout for localization.

Preparing for localization

Open Main.storyboard and show the assistant editor either by clicking ViewAssistant EditorShow Assistant Editor or with the keyboard shortcut Option-Command-Return. From the jump bar dropdown, select Preview (Figure 7.6). The preview assistant allows you to easily see how your interface will look across screen sizes and orientations as well as between different localized languages.

Figure 7.6  Opening the preview assistant

Screenshot of the Preview Assistant with new constraints, the text inside the labels is still cut off at both the edges.

In the storyboard, select the Conversion View Controller to see its preview (Figure 7.7).

Figure 7.7  Preview assistant

Screenshot of the Preview Assistant with new constraints, the text inside the labels is still cut off at both the edges.

Notice the controls in the lower corners of the preview assistant. The + button on the left side allows you to add additional screen sizes to the preview canvas. This allows you to easily see how changes to your interface propagate across screen sizes and orientations simultaneously. The button on the right side allows you to select a language to preview this interface in.

(If your preview is for a configuration other than iPhone 7, use the + button to add this configuration. Then click on whatever preview opened by default and press the Delete key to remove it.)

You have not localized the application into another language yet, but Xcode supplies a pseudolanguage for you to use. Pseudolanguages help you internationalize your applications before receiving translations for all of your strings and assets. The built-in pseudolanguage, Double-Length Pseudolanguage, mimics languages that are more verbose by repeating whatever text string is in the text element. So, for example, is really becomes is really is really.

Select the Language pop-up that says English and choose Double-Length Pseudolanguage. The labels all have their text doubled (Figure 7.8).

Figure 7.8  Doubled text strings

Screenshot shows the result of double-length pseudolanguage. The resultant text in the app are redundant and are improperly aligned, causing them to fall out of the screen on both edges.

The double-length pseudolanguage reveals a problem immediately: The labels go off both the left and right edges of the screen, and you are unable to read the entire strings. The fix is to constrain all of the labels so that their leading and trailing edges stay within the margins of their superview. Then you will need to change the line count for the labels to 0, which tells the labels that their text should wrap to multiple lines if needed. You are going to start by fixing one label, then repeat the steps for the rest of the labels.

In the canvas, select the degrees Fahrenheit label. You are going to add constraints to this label in a new way. Control-drag from the label to the left side of the superview. When you do, a context-sensitive pop-up will appear giving you the constraints that make sense for this direction (Figure 7.9). Select Leading Space to Container Margin from the list.

Figure 7.9  Creating constraints by Control-dragging

Screenshot shows the resulting horizontal constraints menu when a horizontal control drag is performed.

The direction that you drag influences which possible constraints are displayed. A horizontal drag will show horizontal constraints, and a vertical drag will show vertical constraints. A diagonal drag will show both horizontal and vertical constraints, which is useful for setting up many constraints simultaneously.

Now Control-drag from the degrees Fahrenheit label to the right side of the superview and select Trailing Space to Container Margin.

On their own, these constraints are not very good. They maintain the existing fixed distance between the leading and trailing edges of the label, as you can see in the preview assistant (Figure 7.10).

Figure 7.10  Preview assistant with new constraints

Screenshot of the Preview Assistant with new constraints, the text inside the labels is still cut off at both the edges.

What you really want is for the distance between the label and the margins to be greater than or equal to 0. You can do this with inequality constraints.

Select the leading constraint by clicking on the I-bar to the left of the label. Open its attributes inspector and change the Relation to Greater Than or Equal and the Constant to 0 (Figure 7.11).

Figure 7.11  Inequality constraint

Screenshot of the Inequality constraint specific to horizontal space.

Do the same for the trailing constraint. Take a look at the preview assistant; the interface is looking better, but the label is still being truncated.

Select the label and open its attributes inspector. Change the Lines count to 0. Now take a look at the preview assistant; the label is no longer being truncated and instead the text flows to a second line. Because the other labels are each related to the label above them, they have automatically been moved down.

Repeat the steps above for the other labels. You will need to:

  • Add a leading and trailing constraint to each label.

  • Set the constraints’ relation to Greater Than or Equal and the constant to 0. (A shortcut for editing a constraint is to double-click on it.)

  • Change the label’s line count to 0.

When you are done, the preview assistant with the double-length pseudolanguage will look like Figure 7.12.

Figure 7.12  Preview assistant with final constraints

Screenshot of the preview assistant with the double-length pseudo language, but with the updated alignment. The text of the labels are completely visible and not cut off on the edges.

At this point, you are done with the preview assistant. You can close the assistant editor with the x in the top-right corner.

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

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