How we localize Buttondown

Our homegrown localization is one open-sourced JSON file, a template tag, and a wrapper component — nothing fancy, and kept honest by a bunch of weird tests.

Justin Duke
Justin Duke
July 29, 2026

In working on an upcoming opus how we use and don't use Django, I realized that despite the massive success we've had with our homegrown approach to localization, we haven't actually written about it anywhere.

Why roll our own at all?

First, why do we need a custom localization approach at all? To answer this, you need to know a little bit more about Buttondown's structure, which is that we use Django as the core application server for the author- and subscriber-facing app, but use a combination of a Vue SPA to power more interactive bits like the subscribe form itself rather than just vanilla Django. This means we have a multi-locale app which needs to gracefully and easily handle localization across a number of different surfaces: the Vue front end, the conventional Django backend, and arbitrary templates not rendered in the request/response lifecycle, such as transactional emails.

flowchart LR
    JSON["translations.json
(one open-sourced file)"] JSON -->|"{% localized %} tag"| Django["Django backend
(templates)"] JSON -->|"get_translation()"| Emails["Transactional emails
(outside the request cycle)"] JSON -->|"createTranslator()"| Vue["Vue SPA
(subscribe form, etc.)"]
Buttondown is the last email platform you’ll switch to.