Internationalizing Strava

Strava Engineering
strava-engineering
Published in
4 min readSep 21, 2014

Internationalization is the kind of problem whose solution is comparable to replacing the wheels of a moving train — it’s not acceptable to slow down the product development cycle while it is happening. In nature, Internationalization is not a feature of a product: it’s a process that becomes part of how your company operates. Marketing, product, support, design, business development, engineering: every single person and team was involved in the effort. Even our database admins had to recently update the schema of some tables to support the full range of the UTF-8 charset.

So, as an engineer, where do you start? By breaking things. Or rather: pointing out things that are unknowingly broken. Pseudolocalization helps showing which parts of your UI are not localization-friendly. A few years ago, Google developed and open-sourced a pseudolocalization library which implements a variety of schemes, each identifying different shortcomings of your UI layer. In the past year, we have maintained a fork of this library, Maven-ized it and added support for more file formats: YAML, Android XML and Apple strings.

A localization pipeline can be tedious to maintain, which is why most of it should be automated. In our case, the only human intervention in the pipeline is the translation (which happens outside our walls) and the QA. Strava maintains all of its UI translations in a separate git repository, updated every few hours with the latest work of our translators. Each product (Android / iOS / web) is then responsible for pulling the translations into their respective repositories on their own schedule. This normally happens automatically, before the nightly versions are built. New messages are pushed to our translation vendor every day at the end of the work day. Engineers have access to a dashboard to know when the translations are back and their feature is ready to ship.

Strava is running a Rails frontend and we’ve progressively tuned the configuration of our stack to meet our needs: translation fallbacks, plurals, etc… Before our first international release, we wanted to remain careful and have a chance to enable a localized experience progressively across our website. We wrote a filter which is in charge of detecting the locale and that can be included on a per-controller basis.

module I18n
module LocaleAware
LANGUAGE_PARAMETER = :sikrit

# To be included in most web controllers
module Web
SUPPORTED_WEB_LANGUAGES = [
# This is where we list our supported languages
].freeze

def self.included(base)
if base.respond_to? :before_filter
base.before_filter :set_web_language
end
end

def set_web_language
detected_language = ::I18n::LocaleAware.language_from_param(params[LANGUAGE_PARAMETER]) ||
::I18n::LocaleAware.language_from_cookie(cookies['ui_language']) ||
::I18n::LocaleAware.language_from_header(request.headers['Accept-Language']) ||
::I18n::LanguageCodes.default_language
if SUPPORTED_WEB_LANGUAGES.include?(detected_language)
I18n.locale = detected_language
else
I18n.locale = ::I18n::LanguageCodes.default_language
end
end
end
end
end

class AthletesController
include I18n::LocaleAware::Web

def index
# Make use of I18n.locale
end
end

We first look at the Accept-Language header to pick which language to show. It’s not perfect but it gets us 90% of the way, and it doesn’t require user intervention. In case we get it wrong, athletes may override that using the language picker at the bottom of every page on Strava. To allow developers to quickly switch languages, we also support a URL parameter which overrides any other input. We ended-up writing several filters of the sort, in order to, e.g., release beta languages to a specific set of athletes before opening them up to the rest of the world.

CLDR is the single source of truth for any aspect of your code that is locale-dependent. Date and number formats are common examples, but there’s also data about currencies, territories, languages and their names is every other language, plural forms. We’ve been making very heavy usage of the CLDR bindings in the two main languages we use on the frontend: Ruby and Javascript. We wrapped TwitterCldr into custom formatters to correctly display the various metrics that are core to the Strava experience: distance, elevation, speed, pace, etc…

TwitterCldr JS is included in pretty much every page on Strava. We rolled out our own solution for managing JS translations. For simplicity’s sake, we wanted to keep YAML as a storage format and keep Rails’ message format for placeholders and plurals. We configured our asset pipeline to marshall the YAML file containing messages used in our JS code into JSON, and we wrote a small Coffeescript library that mimicks Rails’ built-in t method.

Many more challenges needed to be tackled and some of them remain to this day: localizing content stored in our database (e.g. Strava Challenges), letting athletes submit support questions in their native language, localizing assets, supporting international payments…

In the past year, Strava has shipped in 13 languages on both mobile platforms and we’re launching our 10th language on the web today:

In numbers, internationalization took us from being natively available to a mere 400 million people to over 1.4 billion. If you are a Strava athlete using our services in English and haven’t taken notice of this, then it means this has been a success. Even though Strava is a relatively young organization, legacy code starts creeping in from day 1. Internationalization made us take a hard look at the parts of our codebase that rarely see the light of day and question past decisions — if no one can reasonably explain why things are done in a given way, it’s probably a good idea to try getting rid of it.

It took 6 months to ship the web app in French, our first non-English language. Each subsequent international launch took less and less time, down to just an hour to add support for our newest language. This is the kind of scalability and velocity that makes the product better for everyone.

Originally published at labs.strava.com by Julien Silland.

--

--