41.
Internationalization
Written by Eli Ganim
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:
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.
➤ Select NothingFoundCell.xib in the Project navigator. Switch to the File inspector pane on the right.
Because the NothingFoundCell.xib file isn’t in any XX.lproj folders, it does not have any localizations yet.
➤ Click the Localize… button in the Localization section.
Xcode asks for confirmation because this involves moving the file to a new folder:
➤ Choose English (not Base) and click Localize to continue.
Look in Finder and you will see there is a new en.lproj — for English — folder and NothingFoundCell.xib has been moved to that folder:
The File inspector for NothingFoundCell.xib now lists English as one of the localizations.
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.
➤ Click on StoreSearch at the top of the Project navigator to open the settings page. From the central sidebar, choose StoreSearch under PROJECT (not under TARGETS).
If the central sidebar isn’t visible, click the small blue icon at the top of the sidebar area to open it.
➤ In the Info tab, under the Localizations section press the + button:
➤ From the pop-up menu choose Dutch (nl).
Xcode now asks which resources you want to localize. Uncheck everything except for NothingFoundCell.xib and click Finish.
If you look in Finder again you’ll notice that a new subfolder has been added, nl.lproj, and that it contains another copy of NothingFoundCell.xib.
That means there are now two nib files for NothingFoundCell. You can also see this in the Project navigator:
Editing a language specific nib
Let’s edit the Dutch version of this nib.
➤ Click on NothingFoundCell.xib (Dutch) to open it in Interface Builder.
➤ Change the label text to Niets gevonden.
It is perfectly all right to resize or move around items in a translated nib. You could make the whole nib look completely different if you wanted to — but that’s probably a bad idea. Some languages, such as German, have very long words and in those cases you may have to tweak label sizes and fonts to get everything to fit.
If you run the app now, nothing will have changed. You have to switch the Simulator to use the Dutch language first. However, before you do that, you really should remove the app from the simulator, clean the project, and do a fresh build.
The reason for this is that the nibs were previously not localized. If you were to switch the simulator’s language now, the app might still use the old, non-localized versions of the nibs, or it might not. It’s better to be safe than tear your hair out wondering what went wrong, right?
Note: For this reason, it’s a good idea to already put all your nib files and storyboards in the en.lproj folder — or in Base.lproj, which we’ll discuss shortly — when you create them. Even if you don’t intend to internationalize your app any time soon, you don’t want your users to run into the same problem later on. It’s not nice to ask your users to uninstall the app — and lose their data — in order to be able to switch languages.
Switching device language
➤ Remove the app from the Simulator. Do a clean (Product ▸ Clean or Shift-⌘-K) and re-build the app.
➤ Open the Settings app in the Simulator and go to General ▸ Language & Region ▸ iPhone Language. From the list pick Nederlands (Dutch).
The Simulator will take a moment to switch between languages. This terminates the app if it was still running.
➤ Search for some nonsense text and the app will now respond in Dutch:
Pretty cool, just by placing some files in the en.lproj and nl.lproj folders, you have internationalized the app! You’re going to keep the Simulator in Dutch for a while because the other nibs need translating too.
Note: If the app crashes for you at this point, then the following might help. Quit Xcode. Reset the Simulator and then quit it. In Finder, go to your Library folder, Developer/Xcode and throw away the entire DerivedData folder. Empty your trashcan. Then open the StoreSearch project again and give it another try. Also, don’t forget to switch the Simulator back to Nederlands.
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.
Whenever you need to change something on that screen, you need to update all of those nibs. There’s a risk that you might overlook one or more nib files and they’ll be out-of-sync. That’s just asking for bugs — in languages that you probably don’t speak!
To prevent this from happening, you can use base internationalization. With this feature enabled, you don’t copy the entire nib, but only the text strings. This is what the Base.lproj folder is for.
Let’s translate the other nibs.
➤ Select LoadingCell.xib in the Project navigator. In the File inspector press the Localize… button. This time use Base as the language:
Verify with Finder that LoadingCell.xib got moved into the Base.lproj folder.
➤ The Localization section in the File inspector for LoadingCell.xib now contains three options: Base (with a checkmark), English, and Dutch. Put a checkmark in front of Dutch:
In Finder you can see that nl.proj doesn’t get a copy of the nib, but a new file does get added: LoadingCell.strings.
➤ Click the disclosure triangle in front of LoadingCell.xib to expand it in the Project navigator and select the LoadingCell.strings (Dutch) file.
You should see something like the following:
There is still only one nib, the one from the Base localization. The Dutch translation consists of a “strings” file with just the text from the labels, buttons, and other controls.
This particular strings file contains:
/* Class = "UILabel"; text = "Loading..."; ObjectID = "hU7-Dc-hSi"; */
"hU7-Dc-hSi.text" = "Loading...";
The green bit is a comment, just like in Swift. The second line says that the text property of the object with ID “hU7-Dc-hSi” contains the text Loading…
The ID is an internal identifier that Xcode uses to keep track of the objects in your nibs; your own nib probably has a different ID than mine. You can see this ID in the Identity inspector for the label.
➤ Change the text from Loading… to Zoeken…
Tip: You can use the Assistant editor in Interface Builder to get a preview of your localized nib. Switch to LoadingCell.xib (Base) and open the Assistant editor. From the Jump bar at the top, choose Preview. In the bottom-right corner it says English. Click this to switch to a Dutch preview.
If you open a second assistant pane (with the +) and set that to Localizations, you can edit the translations and see what they look like at the same time. Very handy!
➤ Do a Product ▸ Clean (to be safe) and run the app again.
Note: If you don’t see the “Zoeken…” text then do the same dance again: quit Xcode, throw away the DerivedData folder, reset the Simulator.
➤ Repeat the steps to add a Dutch localization for Main.storyboard. It already has a Base localization so you simply have to put a check in front of Dutch in the File inspector.
For the Search View Controller screen, two things need to change: the placeholder text in the Search Bar and the labels on the Segmented Control.
➤ In Main.strings (Dutch) change the placeholder text to Naam van artiest, nummer, album.
"68e-CH-NSs.placeholder" = "Naam van artiest, nummer, album";
The segment labels will become: Alles, Muziek, Software, and E-boeken.
"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";
Of course, your object IDs are going to be different - so don’t rely on the IDs to find the right values in the file.
➤ For the Detail pop-up, you only need to change the Type: label to say Soort:
"DCQ-US-EVg.text" = "Soort:";
You don’t need to change these:
"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";
These labels can remain the same because you will replace them with values from the SearchResult object anyway. Also, “Genre” is the same in both languages.
Note: If you wanted to, you could even remove the text that doen’t need localization from the strings file. If a localized version for a specific resource is missing for the user’s language, iOS will fall back to the one from the Base localization.
Thanks to Auto Layout, the labels automatically resize to fit the translated text. A common issue with localization is that English words tend to be shorter than words in other languages, so you have to make sure your labels are big enough to accommodate any language. With Auto Layout that is a piece of cake.
The Landscape View Controller doesn’t have any text to translate.
➤ There is no need to give SearchResultCell.xib a Dutch localization — there is no on-screen text in the nib itself — but do give it a Base localization. This prepares the app for the future, should you need to localize this nib at some point.
When you’re done, there shouldn’t be any xib files outside the .lproj folders.
That’s it for the nibs and the storyboard. Not so bad, was it? I’d say all these changes are commit-worthy.
Tip: You can also test localizations by changing the settings for the active scheme. Click on StoreSearch in the active scheme selector in the Xcode toolbar — next to the Simulator name — and choose Edit Scheme.
In the Options tab you can change the Application Language and Region settings. That’s a bit quicker than restarting the Simulator.
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.”
While in this case you could probably get away with it — everyone in the world probably knows what the word “Song” means — not all of the text from the type property will be understood by non-English speaking users.
Localizing text used in code
To localize text that is not in a nib or storyboard, you have to use another approach.
➤ In SearchResult.swift, make sure the Foundation framework is imported:
import Foundation
➤ Then replace the type property with:
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
}
}
Tip: Rather than typing in the above, you can use Xcode’s powerful Regular Expression Replace feature to make those changes in just a few seconds.
Go to the Find navigator — fourth tab, the one with the magnifying glass icon, on the left sidebar — and change its mode from Find to Replace > Regular Expression.
In the search box type: return “(.+)” and press return to search.
In the replacement box type:
return NSLocalizedString(”$1”, comment: “Localized kind: $1”)
This looks for any lines that match the pattern return “something”. Whatever that something is will be put in the $1 placeholder of the replacement text.
Make sure only the relevant search results from SearchResult.swift are selected — you don’t want to make this change to all of the search results! Click Replace to finish.
Thanks to Scott Gardner for the tip!
The structure of type is still the same as before, but instead of doing:
return "Album"
It now does:
return NSLocalizedString("Album", comment: "Localized kind: Album")
Slightly more complicated, but also a lot more flexible.
NSLocalizedString() takes two parameters: the text to return, "Album", and a comment, "Localized kind: Album".
Here is the cool thing: if your app includes a file named Localizable.strings for the user’s language, then NSLocalizedString() will look up the text (“Album”) and return the translation as specified in Localizable.strings.
If no translation for that text is present, or there is no Localizable.strings file, then NSLocalizedString() simply returns the text as-is.
➤ Run the app again. The “Type:” field in the pop-up — or “Soort:” in Dutch — should still show the same text values as before because you haven’t translated anything yet.
First, you need to create an empty Localizable.strings file.
➤ Right-click on the yellow StoreSearch folder in the Project navigator, select New File…, select the Strings File template under iOS - Resources and tap Next. Save the file as Localizable.strings.
➤ Select Localizable.strings, in the File inspector (on the right) click Localize…, select English from the dropdown, and click Localize.
This creates an empty English Localizable.strings file. You need to use a command line tool named genstrings to populate the file with text strings from your source files. This requires a trip to the Terminal.
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
Then, type the following command:
genstrings *.swift -o en.lproj
This looks at all your source files (*.swift) and writes the text strings from those source files to the Localizable.strings file in the en.lproj folder.
If you open the Localizable.strings file now, this is what it should contain:
/* 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";
The things between the /* and */ symbols are the comments you specified as the second parameter of NSLocalizedString(). They give the translator some context about where the string is supposed to be used in the app.
Tip: It’s a good idea to make these comments as detailed as you can. In the words of fellow raywenderlich.com author Scott Gardner:
“The comment to the translator should be as detailed as necessary to not only state the words to be transcribed, but also the perspective, intention, gender frame of reference, etc. Many languages have different words based on these considerations. I translated an app into Chinese Simplified once and it took multiple passes to get it right because my original comments were not detailed enough.”
➤ Change the “Song” line to:
"Song" = "SUPER HIT!";
➤ Now run the app again and search for music. For any search result that is a song, it will now say “SUPER HIT!” instead of “Song”.
Of course, changing the text in the English localization doesn’t make much sense. Reverse the change to Song and then we’ll do it properly.
➤ In the File inspector, add a Dutch localization for this file. This creates a copy of Localizable.strings in the nl.lproj folder.
➤ Change the translations in the Dutch version of Localizable.strings to:
"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";
If you run the app again, the product types will all be in Dutch. Nice!
Always use NSLocalizedString() from the beginning
There are a bunch of other strings in the app that need translation as well. You can search for anything that begins with
"but it would have been a lot easier if you had usedNSLocalizedString()from the start. Then all you would’ve had to do was run the genstrings tool and you’d get all the strings.Now you have to comb through the source code and add
NSLocalizedString()to all the text strings that will be shown to the user — mea culpa!You should really get into the habit of always using
NSLocalizedString()for strings that you want to display to the user, even if you don’t care about internationalization right away.Adding support for other languages is a great way for your apps to become more popular, and going back through your code to add
NSLocalizedString()is not much fun. It’s better to do it right from the start!
Here are the other strings that need to be NSLocalizedString-ified:
// 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"
➤ Add NSLocalizedString() around these strings. Don’t forget to use descriptive comments!
For example, when instantiating the UIAlertController in showNetworkError(), you could write:
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)
Note: You don’t need to use
NSLocalizedString()with yourprint()’s. Debug output is really intended only for you, the developer, so it’s best if it is in English, or whatever happens to be your native language.
➤ Run the genstrings tool again. Give it the same arguments as before. It will put a clean file with all the new strings in the en.lproj folder.
Unfortunately, there really isn’t a good way to make genstrings merge new strings into existing translations. It will overwrite your entire file and throw away any changes that you made. There is a way to make the tool append its output to an existing file, but then you end up with a lot of duplicate strings.
Tip: Always regenerate only the file in en.lproj and then copy over the missing strings to your other Localizable.strings files. You can use a tool such as FileMerge or Kaleidoscope to compare the two files to find the new strings. There are also several third-party tools on the Mac App Store that are a bit friendlier to use than genstrings.
You might also get a warning similar to the following if you weren’t consistent in using the same comment for the same word when it appears in multiple places:
Warning: Key "Unknown" used with multiple comments "Artist name label: Unknown" & "Artist name: Unknown"
If you check the Localizable.strings file, you’ll notice that you don’t have two instances of the word “Unknown” (or whatever the word was that generated the error) in the file. But there are two comments for the same word.
You can easily fix this — if you wanted to — by going back and using the same comment for the same word and then running the genstrings tool again.
➤ Add these new translations to the Dutch Localizable.strings:
"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...";
It may seem a little odd that such a long string as “There was an error reading from the iTunes Store. Please try again.” would be used as the lookup key for a translated string, but there really isn’t anything wrong with it.
By the way, the semicolons at the end of each line are not optional. If you forget a semicolon, the Localizable.strings file cannot be compiled and the build will fail.
Some people write code for NSLocalizedString like this:
let s = NSLocalizedString("ERROR_MESSAGE23",
comment: "Error message on screen X")
The Localizable.strings file would then look like:
/* Error message on screen X */
"ERROR_MESSAGE23" = "Does not compute!";
This works, but is harder to read. It requires that you always have an English Localizable.strings as well.
Note also that the text "Unknown" occurred only once in Localizable.strings even though it shows up in two different places in the source code. Each piece of text only needs to be translated once.
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())
➤ Internationalize this as follows:
artistNameLabel.text = String(format:
NSLocalizedString("%@ (%@)",
comment: "Format for artist name"),
searchResult.artistName, searchResult.kindForDisplay())
After running genstrings again, this shows up in Localizable.strings as:
/* Format for artist name */
"%@ (%@)" = "%1$@ (%2$@)";
If you wanted to, you could change the order of these parameters in the translated file. For example:
"%@ (%@)" = "%2$@ van %1$@";
It will turn the artist name label into something like this:
In this instance it’s better to use a special key rather than the literal string to find the translation. It’s thinkable that your app will employ the format string "%@ (%@)" in some other place and you may want to translate that completely differently there.
I’d call it something like ARTIST_NAME_LABEL_FORMAT instead (this goes in the Dutch Localizable.strings):
/* Format for artist name label */
"ARTIST_NAME_LABEL_FORMAT" = "%2$@ van %1$@";
You also need to add this key to the English version of Localizable.strings:
/* Format for artist name label */
"ARTIST_NAME_LABEL_FORMAT" = "%1$@ (%2$@)";
Don’t forget to change the code as well:
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.
For situations like these, it’s better to use a data-driven approach. Here, that means you place the product types and their human-readable names in a data structure, a dictionary, rather than a code structure.
➤ Add the following dictionary to SearchResult.swift, above the class (you may want to copy-paste this from type as it’s almost identical):
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"),
]
Now the code for type becomes really short:
var type: String {
let kind = self.kind ?? "audiobook"
return typeForKind[kind] ?? kind
}
It’s nothing more than a simple dictionary lookup.
The ?? is the nil coalescing operator. Remember that dictionary lookups always return an optional, just in case the key you’re looking for — kind in this case — does not exist in the dictionary. That could happen if the iTunes web service added new product types. If the dictionary gives you nil, the ?? operator simply returns the original value of 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.
To localize the strings from Info.plist, you need a file named InfoPlist.strings.
➤ Add a new file to the project. In the template chooser scroll down to the Resource group and choose Strings File. Name it InfoPlist.strings (the capitalization matters!).
➤ Open InfoPlist.strings and press the Localize… button from the File inspector. Choose the English localization.
➤ Also add a Dutch localization for this file.
➤ Open the Dutch version and add the following line:
CFBundleDisplayName = "StoreZoeker";
The key for the “Bundle display name” setting is CFBundleDisplayName.
➤ Run the app and close it so you can see its icon. The Simulator’s springboard should now show the translated app name:
If you switch the Simulator back to English, the app name is StoreSearch again (and of course, all the other text is back to English as well).
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:
-
The language settings are independent of the regional settings. How currencies and numbers are displayed depends on the region settings, not the language.
-
The app does not specify anything about country or language when it sends the requests to the iTunes store, so the web service always returns prices in US dollars.
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.
➤ In Search.swift, change the iTunesURL(searchText:category:) method as follows:
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!
}
The regional settings are also referred to as the user’s locale and of course there is an object to represent it — Locale. You get a reference to the autoupdatingCurrent locale.
This locale object is called “autoupdating” because it always reflects the current state of the user’s locale settings. In other words, if the user changes their regional information while the app is running, the app will automatically use these new settings the next time it does something with the Locale object.
From the locale object you get the language and the country code. You then put these two values into the URL using the &lang= and &country= parameters. Because locale.regionCode may be nil, we use ?? "US" as a failsafe.
The print() lets you see what exactly the URL will be.
➤ Run the app and do a search. Xcode should output something like the following if you have English set as the language:
https://itunes.apple.com/search?term=bird&limit=200&entity=&lang=en_US&country=US
It added “en_US” as the language identifier and just “US” as the country. For products that have descriptions (such as apps) the iTunes web service will return the English version of the description. The prices of all items will have USD as the currency.
Note: It’s also possible you got an error message, which happens when the locale identifier returns something nonsensical such as
nl_US. This is due to the combination of language and region settings on your Mac or the Simulator. If you also change the region (see below), the error should disappear. The iTunes web service does not support all combinations of languages and regions — so an improvement to the app would be to check the value oflanguageagainst a list of allowed languages. I’ll leave that as an exercise for you.
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.
If the Simulator is still in Dutch, then it is under Algemeen ▸ Taal en Regio ▸ Regio. Change it to Nederland. If the language is not set to Dutch, then set the language to Dutch now.
➤ Run StoreSearch again and repeat the search.
Xcode now says:
https://itunes.apple.com/search?term=bird&limit=200&entity=&lang=nl_NL&country=NL
The language and country are both now set to NL — for the Netherlands. If you tap on a search result you’ll see that the price is now in Euros:
Of course, you have to thank NumberFormatter for this. It now knows the region settings are from the Netherlands, so it uses a comma for the decimal point.
And because the web service now returns "EUR" as the currency code, the number formatter puts the Euro symbol in front of the amount. You can get a lot of functionality for free if you know which classes to use!
That’s it as far as internationalization goes. It takes only a small bit of effort, but it definitely pays back.
You can put the Simulator back to English now.
➤ It’s time to commit because you’re going to make some big changes in the next section.
If you’ve also been tagging the code, you can call this v0.9, as you’re rapidly approaching the 1.0 version that is ready for release.
You can find the project files for this chaper under 41 – Internationalization in the Source Code folder.