Localizing Slack

tornikeo1 pts0 comments

Localizing Slack | Engineering at Slack

Localizing Slack – Engineering at Slack

Skip to main content

September 12, 2017

Updated: June 25, 2020

8 min read

Localizing Slack

Scott SandlerPrincipal Engineer

Slack in German, Spanish, and French

Search

Latest Posts

July 14, 2026<br>15 min read

Shipyard: How We Built Slack’s Next-Generation EC2 Platform

June 11, 2026<br>12 min read

Agentic Testing: Where Agents Fit in the E2E Testing Stack

May 28, 2026<br>17 min read

Slack AI: The Path to Multi-Cloud

May 5, 2026<br>15 min read

From SSH to REST: A Security-Driven Modernization of Slack’s EMR Data Pipelines

April 13, 2026<br>14 min read

Managing context in long-run agentic applications

Archives

2026 (7)

2025 (9)

2024 (19)

2023 (16)

2022 (21)

2021 (24)

2020 (26)

2019 (21)

2018 (11)

2017 (21)

2016 (19)

Localization is so easy!

…said no one ever.

This week, we launched French, German, and Spanish localization in Slack. I’d like to share some of the lessons learned, tooling, and processes we put in place to complete this project and build localization into our ongoing workflow.

Handling Strings

The first step in localizing Slack was to prepare the strings in our codebase for localization. While mobile platforms provide clear frameworks for localization that keep strings separate from code, our web and desktop codebase’s strings were embedded in HTML templates and business logic. We needed to create frameworks for representing strings in each programming language we used, and then touch every string in the code to implement the new helpers. We referred to this as “wrapping strings”, since each string needed to be wrapped in a block or function call.

We chose the ICU MessageFormat syntax to represent strings. After comparing it to gettext, we appreciated the power of its select and plural blocks to deal with some of our more complex strings, as well as the flexibility and robust implementations for JavaScript and PHP. We created ICU helpers for our templating languages, Handlebars and Smarty. While ICU was more powerful than gettext, it was also not directly supported by any Translation Management System (TMS) we could find, which meant we needed to turn off most of the TMS’s built in parsing and validation of translations and do that ourselves.

We debated whether to use named keys for each string in the code (like Android’s strings.xml), or use the English string as the key (like iOS’s NSLocalizedString). We chose the latter to keep the code more readable, using a hash of the English as a key. This made string wrapping much simpler as well, but did mean that changing the English string even slightly would require a re-translation.

Here’s an example of a string before localization from our notifications e-mail template:

You have {if $activity.num_dms == 1}a new direct message{else}{$activity.num_dms|number_word} new direct messages{/if}.

And after:

{t<br>num_dms=$activity.num_dms<br>dms_number_word=$activity.num_dms|number_word_localized<br>You have {num_dms, plural, =1 {a new direct message} other {{dms_number_word} new direct messages}}.<br>{/t}

The {t} block serves two purposes

We look for it in static analysis to extract the strings for upload to our Translation Management System (TMS)

At runtime (or build time) it hashes the string, looks up its translation, and renders it using an ICU MessageFormat library

We needed to do this for about 20,000 strings across 2,000 files, which was a massive undertaking. We decided to ask the entire Web Engineering team to help implement these changes. The i18n team held “string jams” where we kicked off with a presentation on how to wrap strings (Keep complete sentences together! Provide context comments with examples for translators!) and sat with each Engineering team for a couple of days to review their code and answer questions. This served as an excellent training exercise to get everyone familiar with how to write localization-friendly code going forward. We invited attendees to a channel in Slack which proved invaluable for discussions, pull requests, and questions in the following months.

One of the most helpful things at this stage was pseudolocalization, which accents each character yielding a visibly different but still legible string. Since we didn’t have translations yet, enabling this mode allowed engineers and QA to make sure that every string in a given view was ready for localization and that the locale setting was being properly applied. We implemented this in each language, adding some smarts to keep things like html tags, placeholders, and emoji unmangled. We also added extra tilde (~) characters to each word to make them 35% longer, simulating longer words from other languages to identify inflexible UI elements.

Testing Slack with pseudolocalization<br>Release Process and Tooling

We set ambitious requirements for our launch: Slack should have a consistent voice in each language with high quality translations, localization should be built...

slack strings string localization read code

Related Articles