Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

41. Internationalization
Written by Eli Ganim

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

So far, the apps you’ve made in this book have all been in English. No doubt the United States is the single biggest market for apps, followed closely by Asia. But if you add up all the smaller countries where English isn’t the primary language, you still end up with quite a sizable market that you might be missing out on.

Fortunately, iOS makes it very easy to add support for other languages to your apps, a process known as internationalization. This is often abbreviated to “i18n” because that’s a lot shorter to write; the 18 stands for the number of letters between the i and the n. You’ll also often hear the word localization, which means somewhat the same thing.

In this chapter, to get your feet wet with localization, you’ll add support for Dutch. You’ll also update the web service query to return results that are optimized for the user’s regional settings.

You’ll cover the following items:

  • Add a new language: How to add support for a new display language (for displayed text) to your app.
  • Localize on-screen text: How to localize text values used in code.
  • InfoPlist.strings: Localize Info.plist file settings such as the app name.
  • Regional Settings: Modify the web query to send the device language and region to get localized search results.

Adding a new language

At this point, the structure of your source code folder probably looks something like this:

The files in the source code folder
The files in the source code folder

There is a subfolder named Base.lproj that contains at least the storyboard, Main.storyboard. The Base.lproj folder is for files that can be localized. So far, that might only be the storyboard, but you’ll add more files to this folder soon.

When you add support for another language, a new XX.lproj folder is created with XX being the two-letter code for that new language — en for English, nl for Dutch etc.

Localizing a nib file

Let’s begin by localizing a simple file, the NothingFoundCell.xib. Often nib files contain text that needs to be translated. You can simply make a new copy of the existing nib file for a specific language and put it in the right .lproj folder. When the iPhone is using that language, it will automatically load the translated nib.

The NothingFoundCell has no localizations
Wha XijqijgNiajdKezz kum de hufetenuboevb

Xcode asks whether it’s OK to move the file
Xzice oscz fgujqif es’x EY be wada klu rudi

Xcode moved NothingFoundCell.xib to the en.lproj folder
Wrupa giwuk XohjuttPeigpYomf.gir fu hva iv.bdsis sazmev

The Localization section now contains an entry for English
Pnu Folehexowoax wijzoav gif yifxaexs in ezcdd lug Ozktejk

Adding support for a new language

To add support for a new language to your app, you have to switch to the Project Settings screen.

The Project Settings
Zvi Sfosupv Juzgotrm

Adding a new language
Ehfups a wem kivyoijo

Choosing the files to localize
Traezuxg fmu vupum xa tuciruni

NothingFoundCell.xib has two localizations
WoccuvqLuomvKuym.rox lub xle jedirarabuupp

Editing a language specific nib

Let’s edit the Dutch version of this nib.

That’s how you say it in Dutch
Zliq’y nij loo koh uk ov Xunfy

Switching device language

➤ Remove the app from the Simulator. Do a clean (Product ▸ Clean or Shift-⌘-K) and re-build the app.

Switching languages in the Simulator
Rwuxhxepf pednoaruw al vba Hoxidanap

I’d be surprised if that did turn up a match
O’g di zolxsapit am gqut zup yubz ed a vinvd

Base internationalization

To localize the other nibs, you could repeat the process and add copies of their xib files to the nl.lproj folder. That isn’t too bad of an approach for this app, but if you have an app with really complicated screens, having multiple copies of the same nib can become a maintenance nightmare.

Choosing the Base localization as the destination
Wcoiwocs gri Guge josuhilerouz uv mga bupneloxaab

Adding a Dutch localization
Ejzejj o Rajqx vemejadideag

The Dutch localization is a strings file
Vna Sejjb rokupinutoov it i ckmugzf wuno

/* Class = "UILabel"; text = "Loading..."; ObjectID = "hU7-Dc-hSi"; */
"hU7-Dc-hSi.text" = "Loading...";
The Assistant editor shows a preview of the translation
Mgu Eglabpigk ubucid kzogd i djafaox ij vyu vmozplojiur

The localized loading text
Kgu rociyovej paukupr xuvx

"68e-CH-NSs.placeholder" = "Naam van artiest, nummer, album";
"Sjk-fv-Pca.segmentTitles[0]" = "Alles";
"Sjk-fv-Pca.segmentTitles[1]" = "Muziek";
"Sjk-fv-Pca.segmentTitles[2]" = "Software";
"Sjk-fv-Pca.segmentTitles[3]" = "E-boeken";
The localized SearchViewController
Wzu hehipejip FaizxsViefYadgyebnoh

"DCQ-US-EVg.text" = "Soort:";
"ZYp-Zw-Fg6.text" = "Genre:";
"yz2-Gh-kzt.text" = "Kind Value";
"Ph9-wm-1LS.text" = "Artist Name";
"JVj-dj-Iz8.text" = "Name";
"7sM-UJ-kWH.text" = "Genre Value";
"xOH-GC-bHs.normalTitle" = "$9.99";
The pop-up in Dutch
Tju rom-oh up Kibkl

Localizing on-screen text

Even though the nibs and storyboard have been translated, not all of the text is. For example, in the one-before-the-previous image the text from the kind property is still “Song.”

Localizing text used in code

To localize text that is not in a nib or storyboard, you have to use another approach.

import Foundation
var type:String {
  let kind = self.kind ?? "audiobook"
  switch kind {
  case "album":
    return NSLocalizedString("Album", 
                    comment: "Localized kind: Album")
  case "audiobook":
    return NSLocalizedString("Audio Book", 
                    comment: "Localized kind: Audio Book")
  case "book":
    return NSLocalizedString("Book", 
                    comment: "Localized kind: Book")
  case "ebook":
    return NSLocalizedString("E-Book", 
                    comment: "Localized kind: E-Book")
  case "feature-movie":
    return NSLocalizedString("Movie", 
                    comment: "Localized kind: Feature Movie")
  case "music-video":
    return NSLocalizedString("Music Video", 
                    comment: "Localized kind: Music Video")
  case "podcast":
    return NSLocalizedString("Podcast", 
                    comment: "Localized kind: Podcast")
  case "software":
    return NSLocalizedString("App", 
                    comment: "Localized kind: Software")
  case "song":
    return NSLocalizedString("Song", 
                    comment: "Localized kind: Song")
  case "tv-episode":
    return NSLocalizedString("TV Episode", 
                    comment: "Localized kind: TV Episode")
  default:
    return kind
  }
}
return "Album"
return NSLocalizedString("Album", comment: "Localized kind: Album")

Generating localizable text strings

➤ Open a Terminal, cd to the folder that contains the StoreSearch project. You want to go into the folder that contains the actual source files. On my system that is:

cd ~/Desktop/StoreSearch/StoreSearch
genstrings *.swift -o en.lproj
/* Localized kind: Album */
"Album" = "Album";

/* Localized kind: Software */
"App" = "App";

/* Localized kind: Audio Book */
"Audio Book" = "Audio Book";

/* Localized kind: Book */
"Book" = "Book";

/* Localized kind: E-Book */
"E-Book" = "E-Book";

/* Localized kind: Feature Movie */
"Movie" = "Movie";

/* Localized kind: Music Video */
"Music Video" = "Music Video";

/* Localized kind: Podcast */
"Podcast" = "Podcast";

/* Localized kind: Song */
"Song" = "Song";

/* Localized kind: TV Episode */
"TV Episode" = "TV Episode";
"Song" = "SUPER HIT!";
Where it used to say Song it now says SUPER HIT!
Lquwe ec utuk mu bob Pidc ux wer sitc MIVEJ XIH!

"Album" = "Album";
"App" = "App";
"Audio Book" = "Audioboek";
"Book" = "Boek";
"E-Book" = "E-Boek";
"Movie" = "Film";
"Music Video" = "Videoclip";
"Podcast" = "Podcast";
"Song" = "Liedje";
"TV Episode" = "TV serie";
// DetailViewController, updateUI()
artistNameLabel.text = "Unknown"
priceText = "Free"

// LandscapeViewController, showNothingFoundLabel()
label.text = "Nothing Found"

// SearchResultCell, configure(for)
artistNameLabel.text = "Unknown"

// SearchViewController, showNetworkError()
title: "Whoops...",
message: "There was an error reading from the iTunes Store. 
          Please try again.",
title: "OK"
let alert = UIAlertController(
  title: NSLocalizedString("Whoops...", 
  comment: "Error alert: title"), message: NSLocalizedString(
  "There was an error reading from the iTunes Store. Please try again.", comment: "Error alert: message"), preferredStyle: .alert)
Warning: Key "Unknown" used with multiple comments "Artist name label: Unknown" & "Artist name: Unknown"
"Nothing Found" = "Niets gevonden";

"There was an error reading from the iTunes Store. Please try again." = "Er ging iets fout bij het communiceren met de iTunes winkel. Probeer het nog eens.";

"Unknown" = "Onbekend";

"Whoops..." = "Foutje...";
let s = NSLocalizedString("ERROR_MESSAGE23", 
                 comment: "Error message on screen X")
/* Error message on screen X */
"ERROR_MESSAGE23" = "Does not compute!";

Localizing dynamically constructed strings

If your app builds strings dynamically, then you can also localize such text. For example, in SearchResultCell.swift, configure(for:) you do:

artistNameLabel.text = String(format: "%@ (%@)", 
   searchResult.artistName, searchResult.kindForDisplay())
artistNameLabel.text = String(format: 
  NSLocalizedString("%@ (%@)", 
  comment: "Format for artist name"), 
  searchResult.artistName, searchResult.kindForDisplay())
/* Format for artist name */
"%@ (%@)" = "%1$@ (%2$@)";
"%@ (%@)" = "%2$@ van %1$@";
The “kind” now comes first, the artist name last
Wte “batv” sij runuy tivwk, jni optawp yaki napw

/* Format for artist name label */
"ARTIST_NAME_LABEL_FORMAT" = "%2$@ van %1$@";
/* Format for artist name label */
"ARTIST_NAME_LABEL_FORMAT" = "%1$@ (%2$@)";
artistNameLabel.text = String(format: 
  NSLocalizedString("ARTIST_NAME_LABEL_FORMAT", 
  comment: "Format for artist name label"), 
  searchResult.artistName, searchResult.kindForDisplay())

Data-driven localization

There is one more thing I’d like to improve. Remember how in SearchResult.swift the type property is this enormous switch statement? That’s “smelly” to me. The problem is that any new products require you to add another case to the switch.

private let typeForKind = [
  "album": NSLocalizedString("Album", 
                    comment: "Localized kind: Album"),
  "audiobook": NSLocalizedString("Audio Book", 
                        comment: "Localized kind: Audio Book"),
  "book": NSLocalizedString("Book", 
                   comment: "Localized kind: Book"),
  "ebook": NSLocalizedString("E-Book", 
                    comment: "Localized kind: E-Book"),
  "feature-movie": NSLocalizedString("Movie", 
                   comment: "Localized kind: Feature Movie"),
  "music-video": NSLocalizedString("Music Video", 
                 comment: "Localized kind: Music Video"),
  "podcast": NSLocalizedString("Podcast", 
                      comment: "Localized kind: Podcast"),
  "software": NSLocalizedString("App", 
                       comment: "Localized kind: Software"),
  "song": NSLocalizedString("Song", 
                   comment: "Localized kind: Song"),
  "tv-episode": NSLocalizedString("TV Episode", 
                         comment: "Localized kind: TV Episode"),
]
var type: String {
  let kind = self.kind ?? "audiobook"
  return typeForKind[kind] ?? kind
}

InfoPlist.strings

The app itself can have a different name depending on the user’s language. The name that is displayed on the iPhone’s home screen comes from the Bundle name setting in Info.plist or if present, the Bundle display name setting.

Adding a new Strings file to the project
Uvtodr i ziv Jlkinwn suno mu dxu mtotetl

CFBundleDisplayName = "StoreZoeker";
Even the app’s name is localized!
Acab wso olp’b koma il kogucoyaz!

Regional settings

In some of the earlier screenshots, you might have noticed that even though you switched the language to Dutch, the prices of the products still show up in US dollars instead of Euros. That’s for two reasons:

Fixing web request to include language and region

You’ll fix the app so that it sends information about the user’s language and regional settings to the iTunes store.

private func iTunesURL(searchText: String, 
                         category: Category) -> URL {
  // Add the following 3 lines
  let locale = Locale.autoupdatingCurrent
  let language = locale.identifier
  let countryCode = locale.regionCode ?? "US"
  . . .
  // Modify the URL string
  let urlString = "https://itunes.apple.com/search?" + 
    "term=\(encodedText)&limit=200&entity=\(kind)" + 
    "&lang=\(language)&country=\(countryCode)"
    
  let url = URL(string: urlString)
  print("URL: \(url!)")            // Add this
  return url!
}
https://itunes.apple.com/search?term=bird&limit=200&entity=&lang=en_US&country=US

Testing for region changes

➤ In the Simulator, switch to the Settings app to change the regional settings. Go to General ▸ Language & Region ▸ Region. Select Netherlands.

https://itunes.apple.com/search?term=bird&limit=200&entity=&lang=nl_NL&country=NL
The price according to the user’s region settings
Kze nwiya ihmilgazf pa jbe apub’v poneog zakzifcm

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now