Why Dropbox Chose a Hybrid Translation Model
When Facebook's community famously translated the site into French in 24 hours back in March 2008, it looked like the obvious path for any startup wanting to go global. Community translation can yield excellent results — users know the product intimately and naturally adopt the right tone and formality level. It also scales impressively, taking a product from a handful of languages to over 100.
But the engineering cost is substantial. To support community-led translation at scale, we'd need to build an in-context translation mode covering not just the website, but also Windows, Mac, and Linux desktop apps, mobile apps, and emails. We'd need to track which strings require translation or re-review at any moment — tricky when a translated string gets reused in a different context. A voting mechanism would be required to select the best translations and flag offensive ones, plus safeguards against coordinated voting abuse. We'd also have to handle import/export across a messy landscape of formats: gettext PO, iOS and Android XML, property lists, static HTML, and more.
With a team of 50 (Facebook had roughly 500 staffers in 2008), we opted for a hybrid approach instead: professional translators handle the initial translation, then our user base reviews and sends corrections. We hire a firm to translate everything beforehand; users then submit feedback on the translations. The firm reviews every suggestion per English string, one string at a time, and applies the most popular correction. Before each feedback round goes to the translators, we skim it ourselves through a dedicated admin page to fix bugs (untranslated text, incorrectly formatted numbers), evaluate whether reporters are finding real typos and mistranslations versus subjective preferences, and confirm that previously reported corrections get incorporated — if a recurring issue isn't fixed, the admin page flags it loudly.
Users can report dubious translations by clicking a green tab on the left of the screen, then typing a few letters of the translation in question. A list of matches pops up; after selecting one, the original English text displays underneath, and the user types an improved translation with a reason for the change. The same feature exists on experimental builds of the desktop app, released on the forums.
How We Made Translation Feedback Work Technically
Grouping suggestions by the English source string is central to this feature's usability. People can autocomplete the translation in question with just a few characters and view it alongside the original English. It also organizes everything neatly for translator review.
Strings with placeholder variables complicate matters. Users should be able to autocomplete text as it appears on the page — "¡Hola, Dan!", not "¡Hola, %(first_name)s" — while internally we want to group all instances of that placeholder string together. Wrapping the gettext _() function doesn't solve this directly, because placeholder substitution happens outside the translation call. Typical gettext code looks like this:
greeting = _('Hello, %(first_name)s') % {'first_name' : user.first_name}
Our solution: on the server, we subclass Python's unicode builtin type, overriding __mod__ to remember which placeholders were filled in. We then wrap the gettext _() function so that it returns this subclassed string type and keeps a response-wide list of all returned strings. At the end of the response, that list gets serialized into JSON for browser-side JavaScript autocomplete code. Each entry includes both the placeholder form ("¡Hola, %(fname)s") for bookkeeping and the filled-in form ("¡Hola, Dan") for autocompletion. Keeping autocompletion entirely browser-side makes the experience feel instant, and the menu is always narrowed down to text that actually appears on the page.
AJAX requests posed one more wrinkle: some requests, such as those in the events feed, contain translated display text. To let users select and report that text too, each AJAX request packs a list of new translations similar to the list from the initial page request.
Other i18n Challenges
Beyond the translation feedback loop, launching in multiple languages surfaced a range of problems, some standard and some less so.
Typical Issues
- Translating text across Python, JavaScript, Objective-C, and Java code, plus templates, database tables (where emails live), and static documents like the terms of service — all while keeping translations in sync with evolving English and handling string substitutions and plurals.
- Pre-translating Dropbox-specific terminology like "selective sync," "Dropbox guru," and "Packrat," since these phrases are the hardest to get right. The history of marketing translation blunders — like "Jolly Green Giant" becoming "Intimidating Green Ogre" in Arabic — makes a strong case for tackling product vocabulary first.
- Supporting divergent date formats (4/22/1970 versus 22.04.1970) and correctly formatting times, numbers, percents, and currency, which we handled with the Babel library.
- Formatting names properly. Japanese names, for instance, go family-name-first, with a space when the name is ASCII (Takahashi Yukihiro) and no space for East Asian characters (高橋幸宏). Our signup form puts family name first for Japanese users.
- Displaying translated country lists in the address form, correctly sorted, with IP geolocation guessing the likely country and putting it at the top of the list.
- Translating images with embedded text and overdubbing videos, and fixing the many layout breakages that result from stretching fixed-width English designs to fit longer translations.
- Coordinating locale settings across user preferences, cookies, web requests,
Accept-Languageheaders, and desktop app settings. If a user changes locale in the desktop app and clicks a web link within the app, the website should match the new locale — but changing the website's locale shouldn't ripple back to the desktop app.
Less Typical Problems
- Overriding a class in Python's gettext module to pack translations inside Python bytecode rather than in external locale files, keeping the desktop app self-contained across platforms. Adding languages later might mean having the server beam text down to clients.
- Building a lightweight gettext-like library in JavaScript for browser-side translations, and extending the Cheetah templating parser to extract English strings.
- Implementing word wrapping for Japanese in the desktop app, which has slightly different rules and a few hard-to-detect special cases.
- Handling collation. For most of the world, alphabetical order is not ASCIIbetical: in German,
äsorts as if it wereae, while in Swedish it comes afterz. We used ICU and its PyICU bindings for server-side sorting, but browser-side JavaScript lacks ICU, so locale-sensitive sorting in the website's file browser — with dynamic insertions for new files and directories and fast response times — required custom work. Similarly, we had to manually implement Japanese collation (with its multiple scripts) for iPhone and iPad, building our own Japanese browser widget to match Apple's, since the iOS SDK doesn't include one.



