Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

Latest commit

 

History

History
285 lines (223 loc) · 8.9 KB

2012-09-03-locale.md

File metadata and controls

285 lines (223 loc) · 8.9 KB
title author category tags excerpt revisions status
Locale
Mattt
Cocoa
nshipster
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 privelage to travel or to meet people from other places, you may not even be aware that things could _be_ any other way.
2012-09-03 2018-11-28
First Publication
Updated for Swift 4.2
swift reviewed
4.2
November 28, 2018

“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"]

{% info %}

Most methods and properties that take a Locale do so optionally or with a default value such that --- if left unspecified --- the current locale is used.

{% endinfo %}

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.

{% warning %}

Support for identifiers specifying other than language or region is inconsistent across system APIs and different platforms, so you shouldn't rely on specific behavior.

{% endwarning %}

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

{% asset locale-preferences.png alt="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:

{::nomarkdown}

Language and Script
languageCode, scriptCode, exemplarCharacterSet
Region
regionCode
Collation
collationIdentifier, collatorIdentifier
Calendar
calendar
Currency
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:

{% asset locale-languages.png alt="Locale Languages Menu on iOS" %}

{% info %}

Behind the scenes, all of this locale information is provided by the Common Locale Data Repository CLDR, a companion project to the Unicode standard.

{% endinfo %}

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!

{% info %} HTTP content negotiation is a complex topic, and apps interacting with localized, remote resources should use Locale.preferredLanguages as a starting point for generating a more precise Accept-Language field value. For example, you might specify an associated quality value like in en-SG, en;q=0.9 to tell the server "I prefer Singaporean English, but will accept other types of English".

For more information, see RFC 7231. {% endinfo %}


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.