Internationalization is like flossing: everyone knows they should do it, but probably don't.
And like any habit, it becomes second-nature with practice, to the point that you couldn't imagine not doing it. All it takes is for someone to show you the way.
Let NSHipster be your dental hygienist Virgil through these foreign lands.. without all of the lecturing about tooth decay (promsies!)
i18n versus l10n
As is necessary in any discussion about Internationalization (i18n) or Localization (l10n), we must take some time to differentiate the two:
- Localization is the process of adapting your application for a specific market, or locale.
- Internationalization is the process of preparing your app to be localized.
Internationalization is a necessary, but not sufficient condition for localization, and will be the focus of this article. Localization, which involves the translation of text and assets into a particular language, will be covered in a future edition of NSHipster.
What makes internationalization difficult is having to think outside of your cultural context. All of the assumptions you have about the way things are supposed to work must be acknowledged and reconsidered. You have to fight the urge to write off things that may seem trivial, like sorting and collation, and empathize with the pain and confusion even minor differences may cause.
Fortunately for us, we don't have to do this alone. Meet
NSLocale is a Foundation class that encapsulates all of the conventions about language and culture for a particular locale. A locale encompasses all of the linguistic and cultural norms of a particular group of people, including:
- Number, Date, and Time Formats
- Collation and Sorting
- Use of Symbols, Colors, and Iconography
Each locale corresponds to a locale identifier, such as
en_GB, which include a language code (e.g.
en for English) and a region code (e.g.
US for United States).
Locale identifiers can encode more explicit preferences about currency, calendar system, or number formats, such as in the case of
de_DE@collation=phonebook,currency=DDM, which specifies German spoken in Germany, using phonebook collation, and using the pre-Euro Deutsche Mark.
Users can change their locale settings in the "Langauge & Text" (or "International" on older versions of OS X) System Preferences on the Mac, or "General > International" in iOS Settings.
Formatting Dates & Numbers
NSLocale encapsulates a rich set of domain-specific information, its typical usage is rather understated.
If there's just one thing you should learn about
NSLocale, it's that you should always pass
[NSLocale currentLocale] into your
NSNumberFormatter instances. Doing this will ensure that dates, numbers, and currencies will be formatted according to the localization preferences of the user.
Actually, make that a meta lesson about locales: always use
NSNumberFormatter when displaying anything to do with dates or numbers, respectively.
But let's get back to some of the cool features of
NSLocale itself, shall we?
NSLocale typifies Foundation's obsession with domain-specific pedantry, and nowhere is this more visible than in
-objectForKey:. Cue the list of available constants:
While this all may seem like fairly esoteric stuff, you may be surprised by the number of opportunities your application has to use this information to make for a better user experience.
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.“
So if you were building a component that added quotations around arbitrary text, you should use
NSLocaleAlternateQuotationEndDelimiterKey rather than assuming
@"\"" for English quotation marks.
Another impressive, albeit mostly-useless method is
-displayNameForKey:value:, which can return the display name of a locale identifier (
NSLocale *frLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"fr_FR"]; NSLog(@"fr_FR: %@", [frLocale displayNameForKey:NSLocaleIdentifier value:@"fr_FR"]); NSLog(@"en_US: %@", [frLocale displayNameForKey:NSLocaleIdentifier value:@"en_US"]);
frFR: français (France)
enUS: anglais (États-Unis)
You should use this method any time you need to display information about the user's current locale, or any alternative locales available to them, like in this screen from the Settings app:
One final method worth mentioning is
NSLocale +preferredLanguages, which returns an array of IETF BCP 47 language identifier strings, in order of user preference.
An app that communicates with a web server can use these values to define the
Accept-Language HTTP header, such that the server has the option to return localized resources:
NSMutableURLRequest *request = ...; [request setValue:[NSString stringWithFormat:@"%@", [[NSLocale preferredLanguages] componentsJoinedByString:@", "]], forHTTPHeaderField:@"Accept-Language"];
Even if your server doesn't yet localize its resources, putting this in place now will allow you to flip the switch when the time comes, without having to push an update to the client. Neat!
Internationalization is often considered to be an un-sexy topic in programming--just another chore that most projects don't have to worry about. In actuality, designing software for other locales is a valuable exercise (and not just for the economic benefits of expanding your software into other markets).
One of the greatest joys and challenges in programming is in designing systems that can withstand change. The only way designs can survive this level of change is to identify and refactor assumptions about the system that may not always hold. In this way, internationalization represents the greatest challenge, making us question everything about our cultural identity. And in doing so, we become not just better programmers, but better people, too.
So go and be a better person: make
NSLocale part of your daily ritual.