Etsy Icon>

Code as Craft

Localizing Logically for a Global Marketplace main image

Localizing Logically for a Global Marketplace

  image

An often-overlooked (or underestimated) aspect of internationalizing a website is determining how to localize for a given visitor.  You might detect that a visitor is “German” because their Geo IP locates them as connecting from Germany, or because their browser accept language is German.  But what if your detection is faulty... they’re using a shared computer, or on vacation, or the Geo IP is just plain wrong?

At Etsy, we wanted to use a tiny bit of magic to help provide the best localized experience for visitors, while still allowing users control over their experience.  Websites often use too much magic (e.g. automatically setting incorrect localization settings with a difficult-to-find escape hatch) or use too little (e.g. forcing visitors to a lame splash page where they must choose their home country/language from dropdowns).

For a global marketplace like Etsy, localization breaks down into three components:

  • Language—what language should we use to display the site (UI chrome, shops, listings, help content, emails, etc.)?
  • Region—what region (country) should we assume the user is from, when showing regional content (e.g. blog posts) or during shopping (e.g. search, checkout, shipping)?
  • Currency—what currency should we display prices in?

For a given visitor, localizing to the correct language, region and currency provides the best experience on Etsy.  We spent time understanding our visitors and members, determining the best cues for our detection logic, and created an EtsyLocale helper class to encapsulate all localization-related functionality on the site.

Understand your visitors

We started by examining primary and secondary use cases.

Primary use cases:- English-speaking American user (en/US/USD)

  • English-speaking Canadian user (en/US/CAD)
  • British English-speaking UK user (en-GB/UK/GBP)
  • British English-speaking Australian user (en-GB/AU/AUD)
  • German-speaking German user (de/DE/EUR)
  • French-speaking French user (fr/FR/EUR)

Secondary use cases:- Curious American user wanting to see German translations (de/DE/EUR with easy route back to en/US/USD)

  • German user on vacation in America (de/DE/EUR)
  • Spanish-speaking American (es/US/USD)
  • French-speaking Canadian (fr/CA/CAD)
  • British ex-pat living in Japan (en-GB/JP/JPY)
  • English-speaking user using friend’s German computer (en/DE/EUR)

Some countries have clear majority preferences for language, but some don’t!  And there are plenty of edge case exceptions to think through for any userbase.  For that reason, we allow language, region & currency to be set independently.

Cues

At Etsy, we use a series of cues to determine which language, region & currency to show a user.  In decreasing order of “signal strength”:

  • User preference (language, currency and/or region preference set manually by signed-in member)
  • Cookie preference (language, currency and/or region preference set manually by signed-out visitor)
  • Primary browser accept language (e.g. en-US or de-DE or de or kr)
  • User profile address country (e.g. DE or US)
  • GeoIP region (e.g. DE or US)

We also have ccTLDs (e.g. etsy.de) and subdomains (e.g. de.etsy.com) that we use for marketing efforts and for search indexing (SEO) purposes, which we’ll discuss in a later blog post.

Magic

When a visitor comes to Etsy without preferences set, we iterate through the above cues in order, and for each cue we see if there’s a clear mapping between the cue and our set of supported languages, regions and currencies.  Once we’ve computed our best guess for language, region & currency, we show a gentle nag at the bottom of the page:

The nag is persistent, and is in both the detected language and English.  We always nag instead of auto-setting to minimize confusion/surprises (magic). We don’t nag our primary market users (English-speaking users in the US).  All visitors/members have the ability to change their language, region & currency preferences by clicking links in the footer of any page:

We store these preferences as cookie preferences for signed-out visitors.  When a user registers, we migrate these cookie preferences to user preferences.  When a user signs out, we don’t write their language/region/currency preferences back out as a cookie, providing a “clean slate” experience for other users of that browser.  When we add support for new languages/regions/currencies, we treat that as a new “version” of the preferences, re-nagging where appropriate.

Encapsulate

We encapsulate all of this logic in an EtsyLocale() object, which is available across our stack, for easy access to the current visitor/member’s language, region and currency, e.g.

if (EtsyLocale::getInstance()->getRegion() == “DE”) {
    include “hello_etsy_berlin_meetup.tpl”;
}

We make use of Smarty modifiers to format and display prices based on EtsyLocale->getCurrency().  Our translation tools (specifically, translateMsg()) make use of EtsyLocale->getLanguage() to determine which translations to use.

We use PHP’s built-in setlocale() methods for date formatting (including month name translations), number formatting, string alphabetization and so on.  PHP’s setlocale() function has varying support for locale formats.  For example, if an Etsy visitor has German language preferences but French region preferences, we might represent that locale string as “de_FR” in PHP.   However, setlocale() doesn’t understand that we should use German month names and number formatting for “de_FR”.  So, to be safe, we pass in a list of locale strings that setlocale() should attempt to use including a more generic language-only locale—in this case (“de_FR”, “de_DE”).

You get a lot for free with PHP locales, but there’s still a lot of holes to plug.  At Etsy, we needed to develop date formats for each region, e.g. short dates (“Dec 1, 2010” for en-US, “01. Dez. 2010” for de) and long dates (“December 1, 2010” for en-US, “01 décembre 2010” for fr).  Using setlocale() too aggressively for number formatting can cause SQL-incompatible float writing (e.g. “1.234,56” instead of “1,234.56“).  And keep in mind you often need to use multibyte-aware functions in PHP to take advantage of locale settings.

Na, was sagt ihr?

We’d love to hear from you... any examples of well-localized sites, problems you’ve come across, PHP tricks/solutions?  Share with us below. Stay tuned for more about Etsy’s internationalization.