Locale
“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) == .ordered Ascending
}
// => ["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: 🇸🇪) == .ordered Ascending
}
// => ["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.
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
-
language
,Code script
,Code exemplar
Character Set - Region
region
Code - Collation
-
collation
,Identifier collator
Identifier - Calendar
calendar
- Currency
-
currency
,Code currency
Symbol - Numbers and Units
-
decimal
,Separator uses
Metric System - Quotation Delimiters
-
quotation
/Begin Delimiter quotation
,End Delimiter alternate
/Quotation Begin Delimiter alternate
Quotation End Delimiter
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")
🇺🇸.localized String(for Identifier: 🇺🇸.identifier)
// => "English (United States)"
🇫🇷.localized String(for Identifier: 🇺🇸.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:
Vox Populi
The last stop in our tour of Locale
is the preferred
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 accept Language = Locale.preferred Languages.joined(separator: ", ")
request.set Value(accept Language, for HTTPHeader Field: "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.