Back to blog

Translation of Software for Django Developers

2026-05-19 10 min read
Translation of Software for Django Developers

Meta description: Django software translation breaks on placeholders, plurals, and CI drift. Build a safer .po workflow that fits your release process.

You ran makemessages, opened locale/de/LC_MESSAGES/django.po, and got hit with a wall of empty msgstr "" entries. Then the usual bad options show up. Copy-paste into Google Translate. Send a spreadsheet to a contractor. Push strings into a portal your team hates using. Wait. Pull files back. Hope nobody broke %(name)s or HTML.

That's the point where translation of software stops being a language problem and turns into an engineering problem. Your app already has a release process, code review, CI, and rollback paths. Translations need to fit that same system or they become a source of regressions.

I've seen the same failure pattern over and over. Teams treat .po files like content blobs. They aren't. They're build inputs. If they change outside Git, outside review, or outside repeatable tooling, your multilingual app gets fragile fast.

You Ran makemessages. Now What?

The first run feels fine. A handful of strings. One locale. You edit them by hand and move on.

The second run is where it starts to rot. New strings arrive every sprint. Old strings change slightly. Someone marks a translation fuzzy. Another person edits the same file on a release branch. Your German file is current, your Spanish file is half-updated, and nobody knows whether the compiled .mo files match what's in the repo.

Practical rule: Treat .po files like code artifacts, not editorial attachments.

What usually breaks isn't translation quality first. It's workflow.

That's why a battle-tested workflow starts inside Django's existing toolchain. Keep extraction, translation, review, compilation, and deploy in one path. The result should be boring: reproducible diffs, deterministic output, and no surprises in CI.

Where Translation Fits in the i18n Pipeline

Django already gives you the internationalization half of the job. You mark strings with gettext or gettext_lazy, add LocaleMiddleware, configure languages, and structure locale files correctly. That's the wiring.

Localization is the adaptation work that happens after the codebase is ready. Translation is part of that, but it isn't the whole thing.

A diagram illustrating the i18n pipeline process, showing internationalization and localization steps for software development.

Django handles readiness, not completion

A useful mental model is this:

If you work across web and mobile, the same split shows up outside Django too. A good example is Zephony's guide to implementing React Native internationalization, where the codebase gets prepared first and locale adaptation comes after.

For a Django refresher on the terminology, TranslateBot has a short explainer on what i18n means in practice.

Why this matters operationally

Software translation has moved out of the “nice to have” bucket. One 2026 industry summary on the AI language translation market says it grew from $1.88 billion in 2023 to $2.34 billion in 2024, a 24.9% CAGR. That lines up with what engineering teams already feel. Translation is now part of product infrastructure, not a side task for launch week.

Once you see it that way, the job changes. You're not just filling msgstr. You're maintaining a versioned multilingual build.

The Manual Workflow for Django .po Files'

Django's default path is still the right starting point. Mark strings, extract them, translate them, compile them. The problem isn't the basics. The problem is what happens when humans do all of it by hand.

The canonical flow

In Python:

from django.utils.translation import gettext_lazy as _

class BillingView:
    page_title = _("Billing settings")

In templates:

<h1>{% load i18n %}{% translate "Billing settings" %}</h1>
<p>{% blocktranslate with name=user.first_name %}Welcome back, {{ name }}.{% endblocktranslate %}</p>

Then extract messages:

python manage.py makemessages -l de

That gives you a file like:

#: billing/views.py:4
msgid "Billing settings"
msgstr ""

#: templates/dashboard.html:2
#, python-format
msgid "Welcome back, %(name)s."
msgstr ""

Now you fill in msgstr manually:

#: billing/views.py:4
msgid "Billing settings"
msgstr "Abrechnungseinstellungen"

#: templates/dashboard.html:2
#, python-format
msgid "Welcome back, %(name)s."
msgstr "Willkommen zurück, %(name)s."

Then compile:

python manage.py compilemessages

The file lives where you expect:

locale/de/LC_MESSAGES/django.po

A developer working on Django internationalization code with a translation workflow visible on monitors and notebooks.

Where the manual path fails

The process above works for tiny apps. It starts falling apart once your product changes often.

A Redokun summary of translator tooling usage cites a survey where 88% of full-time professional translators use at least one CAT tool, and translators estimate software can raise productivity by at least 30%. That gap matters because most Django teams are still doing translator work with developer tooling alone.

The recurring failure modes are predictable:

If you need a quick refresher on the file structure itself, this overview of the gettext .po file format is useful.

A translation workflow that lives outside Git will eventually fight your release process.

Why Translating Software Is Not Just Translating Text

A novel can tolerate interpretation. Your UI can't. Software strings carry structure, grammar rules, and runtime data. Break any of those, and you ship bugs.

An infographic titled Beyond Words detailing five unique challenges of software translation, including formatting and cultural nuances.

A practitioner guide on software translation notes that strings must fit UI constraints, support variables like {username}, and preserve pluralization rules. It also points out the engineering gap most articles skip: the hard part is proving translated output is deterministic, reviewable in Git, and safe for CI/CD, not just producing translated text (SimpleLocalize on software translation edge cases).

Placeholders break before wording does

Django uses multiple placeholder styles depending on the source string and interpolation path.

#, python-format
msgid "Hello, %(name)s"
msgstr "Hallo, %(name)s"

If %(name)s becomes %( Name )s, %name, or plain text, you don't get a slightly worse translation. You get a runtime failure or corrupted output.

The same problem shows up with brace formats and template variables:

msgid "You have {count} unread messages"
msgstr "Sie haben {count} ungelesene Nachrichten"

Generic translators often treat placeholders as words. They aren't words. They're part of the contract.

Plurals are grammar plus code

English makes people lazy here because singular and plural look manageable. Then you hit a locale with more plural forms and weak tooling starts guessing.

In Django:

from django.utils.translation import ngettext

message = ngettext(
    "%(count)s file deleted",
    "%(count)s files deleted",
    count
) % {"count": count}

Generated .po entries need the plural forms preserved correctly:

msgid "%(count)s file deleted"
msgid_plural "%(count)s files deleted"
msgstr[0] ""
msgstr[1] ""

That's not optional formatting. It's executable behavior tied to locale rules.

Context is usually missing

Short UI strings are the worst candidates for blind automation.

msgid "Save"
msgstr ""

Is that a verb? A noun? A button label? A menu item? The same source token can require different translations depending on context. Django gives you pgettext for a reason.

from django.utils.translation import pgettext_lazy

label = pgettext_lazy("button label", "Save")
noun = pgettext_lazy("saved items section", "Save")

Without context, even a fluent translation can still be wrong.

HTML and markup need protection

A translated string can preserve meaning and still destroy rendering.

msgid "<strong>Warning:</strong> Your session expires in %(minutes)s minutes."
msgstr "<strong>Warnung:</strong> Ihre Sitzung läuft in %(minutes)s Minuten ab."

HTML tags, entities, and placeholders all need to survive unchanged. That's why code-integrated checks matter more than stylistic promises.

If your translation tool can't preserve placeholders and markup deterministically, don't put it in CI.

Choosing a Workflow Manual vs TMS vs AI

You've got three realistic options. None is magic. Each one solves a different problem and creates a different kind of friction.

Translation Workflow Comparison

Criteria Manual (e.g. Google Translate) TMS Platform (e.g. Lokalise, Phrase) CLI Tool (e.g. TranslateBot)
Primary workflow Copy-paste in browser Web portal with sync/import steps Local command inside repo
Source of truth Usually ad hoc files Mixed between repo and vendor UI Git-tracked .po files
Best use case Very small apps, one-off tasks Larger teams with dedicated localization ops Engineering-led teams shipping often
Placeholder safety Easy to break manually Usually guarded by platform rules Depends on tool, should be testable in code
Review model Informal In-platform review Pull request review
Cost shape Time-heavy, hidden labor Recurring subscription API usage plus dev dependency
Branching fit Poor Better, but often detached from Git flow Strong if files stay in repo
Lock-in risk Low Higher Lower if output stays standard .po

A machine-first workflow is fine for technical content when you treat it as a draft layer. XTM's guidance on machine translation for technical workflows makes the trade-off clear: MT is useful for fast first drafts, but quality improves with human post-editing and controls like glossaries and translation memory.

What works in practice

Manual translation still has a place. It's acceptable when your app barely changes and one person owns every locale file. However, this approach is quickly outgrown.

TMS platforms help when multiple translators, reviewers, and PMs need shared workflow. The trade-off is process overhead. Developers leave the editor, state lives in another system, and the portal often becomes the de facto source of truth.

CLI-based AI translation fits a different team shape. It works best when engineers already own i18n and want translations to behave like any other generated artifact. If you're thinking about the broader automation pattern, PushOps has a good piece on building AI content generation workflows that maps well to translation pipelines too.

The trade-off that matters

Don't compare these options on “AI versus human” alone. Compare them on operational safety.

The best setup for most Django apps is selective automation. Low-risk strings can move through an automated path. High-risk strings still need review.

How to Automate .po File Translation with a Single Command

The safest automation keeps Django's normal flow and inserts translation in the middle. Extract, translate, review, compile.

A flow chart illustrating the six-step automated process for translating Django web application software.

A practical command path looks like this:

python manage.py makemessages -l de
python manage.py translate --locale de
python manage.py compilemessages

That middle step only becomes safe when the tool writes back to standard .po files, preserves placeholders and HTML, and leaves a reviewable diff in Git. If it hides output in a vendor database, you lose most of the benefit.

What your CI should care about

A 2026 localization guide focused on hybrid workflows describes the direction clearly: AI or MT first, human review for high-risk text, with the central question being which parts can be safely automated in products that ship continuously. That's the right frame for Django too.

Here's a minimal GitHub Actions example:

name: i18n

on:
  pull_request:

jobs:
  translations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - run: pip install -r requirements.txt

      - run: python manage.py makemessages -l de
      - run: python manage.py translate --locale de
      - run: python manage.py compilemessages

Place the human review where it belongs, in the pull request. Review changed msgstr entries the same way you review migrations or generated client code.

What to check before merge

Don't review every translated string with the same intensity. Review the risky ones.

If you want a concrete walkthrough of the command-driven approach, TranslateBot documents how to automate translation in a Django workflow. The useful part isn't the package name. It's the shape of the workflow: one command, standard .po output, Git diffs, and CI-safe automation.

A short demo helps if you want to see the pattern in motion:

Run the pipeline locally before you wire it into deploys. Start with one locale. Check placeholder integrity, review the diff, compile messages, and only then add it to CI.


If you want that workflow without a portal, TranslateBot is built for it. It plugs into makemessages and compilemessages, translates .po files from a Django management command, preserves placeholders and HTML, and keeps everything reviewable in Git.

Stop editing .po files manually

TranslateBot automates Django translations with AI. One command, all your languages, pennies per translation.