“You take delight not in a city’s seven or seventy wonders,
but in the answer it gives to a question of yours.” Italo Calvino, Invisible Cities

Localization (l10n) is the process of adapting your application for a specific market, or locale. Internationalization (i18n) is the process of preparing your app to be localized. Internationalization is a necessary, but not sufficient condition for localization. And whether or not you decide to localize your app for other markets, it’s always a good idea to do everything with internationalization in mind.

The hardest thing about internationalization (aside from saying the word itself) is learning how to think outside of your cultural context. Unless you’ve had the privilege to travel or to meet people from other places, you may not even be aware that things could be any other way. But when you venture out into the world, everything from the price of bread to the letters in the alphabet to the numbers of hours in a day — all of that is up in the air.

It can be absolutely overwhelming without a guide. Fortunately for us, we have Locale to show us the ways of the world.

Foundation’s Locale type encapsulates the linguistic and cultural conventions of a user, which include:

  • Language and Orthography
  • Text Direction and Layout
  • Keyboards and Input Methods
  • Collation Ordering
  • Personal Name Formatting
  • Calendar and Date Formatting
  • Currency and Number Formatting
  • Units and Measurement Formatting
  • Use of Symbols, Colors, and Iconography

Locale objects are often used as parameters for methods that perform localized operations like sorting a list of strings or formatting a date. Most of the time, you’ll access the Locale.current type property to use the user’s current locale.

import Foundation

let units = ["meter", "smoot", "agate", "ångström"]

units.sorted { (lhs, rhs) in
    lhs.compare(rhs, locale: .current) == .orderedAscending
// => ["agate", "ångström", "meter", "smoot"]

You can also construct a Locale that corresponds to a particular locale identifier. A locale identifier typically comprises an ISO 639-1 language code (such as en for English) and an ISO 3166-2 region code (such as US for the United States).

let 🇸🇪 = Locale(identifier: "sv_SE")
units.sorted { (lhs, rhs) in
    lhs.compare(rhs, locale: 🇸🇪) == .orderedAscending
// => ["agate", "meter", "smoot", "ångström"]

Locale identifiers may also specify an explicit character encoding or other preferences like currency, calendar system, or number format with the following syntax:

language[_region][.character-encoding][@modifier=value[, ...]]

For example, the locale identifier de_DE.UTF8@collation=phonebook,currency=DEM specifies a German language locale in Germany that uses UTF-8 text encoding, phonebook collation, and the pre-Euro Deutsche Mark currency.

Users can change their locale settings in the “Language & Text” system preference on macOS and in “General > International” in the iOS Settings app.

Locale Preferences

Terra Cognita

Locale often takes a passive role, being passed into a property here and a method there. But if you give it a chance, it can tell you more about a place than you ever knew there was to know:

Language and Script
languageCode, scriptCode, exemplarCharacterSet
collationIdentifier, collatorIdentifier
currencyCode, currencySymbol
Numbers and Units
decimalSeparator, usesMetricSystem
Quotation Delimiters
quotationBeginDelimiter / quotationEndDelimiter, alternateQuotationBeginDelimiter / alternateQuotationEndDelimiter

While this all may seem like fairly esoteric stuff, you’d be surprised by how many opportunities this kind of information unlocks for making a world-class app.

It’s the small things, like knowing that quotation marks vary between locales:

English “I can eat glass, it doesn’t harm me.”
German „Ich kann Glas essen, das tut mir nicht weh.“
Japanese 「私はガラスを食べられます。それは私を傷つけません。」

So if you were building a component that added quotations around arbitrary text, you should use these properties rather than hard-coding English quotation marks.

Si Fueris Romae…

As if it weren’t enough to know, among other things, the preferred currency for every region on the planet, Locale can also tell you how you’d refer to it in a particular locale. For example, here in the USA, we call our country the United States. But that’s just an endonym; elsewhere, American has other names — exonyms. Like in France, it’s…

import Foundation

let 🇺🇸 = Locale(identifier: "en_US")
let 🇫🇷 = Locale(identifier: "fr_FR")

🇺🇸.localizedString(forIdentifier: 🇺🇸.identifier)
// => "English (United States)"

🇫🇷.localizedString(forIdentifier: 🇺🇸.identifier)
// => "anglais (États-Unis)"

Use this technique whenever you’re displaying locale, like in this screen from the Settings app, which shows language endonyms alongside exonyms:

Locale Languages Menu on iOS

Vox Populi

The last stop in our tour of Locale is the preferredLanguages property. Contrary to what you might expect for a type (rather than an instance) property, the returned value depends on the current language preferences of the user. Each of the array elements is IETF BCP 47 language identifier and is returned in order of user preference.

If your app communicates with a web app over HTTP, you might use this property to set the Accept-Language header field to give the server an opportunity to return localized resources:

import Foundation

let url = URL(string: "https://nshipster.com")!
var request = URLRequest(url: url)

let acceptLanguage = Locale.preferredLanguages.joined(separator: ", ")
request.setValue(acceptLanguage, forHTTPHeaderField: "Accept-Language")

Even if your server doesn’t yet localize its resources, putting this in place now allows you to flip the switch when the time comes — without having to push an update to the client. Neat!

Travel is like internationalization: it’s something that isn’t exactly fun at the time, but we do it because it makes us better people. Both activities expand our cultural horizons and allow us to better know people we’ll never actually meet.

With Locale you can travel the world without going AFK.


Questions? Corrections? Issues and pull requests are always welcome.

This article uses Swift version 4.2 and was last reviewed on November 28, 2018. Find status information for all articles on the status page.

Written by Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon. He is the founder of NSHipster and Flight School, and the creator of several open source libraries, including AFNetworking and Alamofire.

Next Article

Accessibility, like internationalization, is one of those topics that’s difficult to get developers excited about. But as you know, NSHipster is all about getting developers excited about this kind of stuff.