Back to blog

Translate a Page in Django: A CLI Workflow

2026-05-03 9 min read
Translate a Page in Django: A CLI Workflow

Meta description: Translate a page in Django without copy-paste or a TMS. Use a CLI workflow to generate, review, and ship .po translations in Git.

You ran python manage.py makemessages -l es and got exactly what Django promised: a fresh locale/es/LC_MESSAGES/django.po full of empty msgstr values.

That isn't the hard part. The hard part is everything after.

You can paste strings into browser translators and hope you don't break %(name)s, %s, {0}, or inline HTML. Or you can move your app into a TMS and accept a separate UI, separate billing, and a workflow that feels detached from your codebase. Neither option fits how most Django teams ship.

The Gap Between Makemessages and a Translated Site

Django's i18n stack is solid. You mark strings with gettext_lazy, use {% translate %} in templates, run makemessages, and commit your locale files. Then you hit the dead zone between extraction and a translated site.

A frustrated developer looking at a broken bridge between code markup and an untranslated website interface.

Most content about translate a page is about browser features for readers, not developers maintaining dynamic apps. Google's Chrome help covers page translation for browsing, but that doesn't solve .po files, placeholder safety, or Git-reviewable diffs for Django projects (Chrome page translation help).

The two paths that usually waste time

One path is manual copy-paste.

The other path is a full TMS.

Practical rule: If your translations don't end up as normal code changes, review quality drops and release friction goes up.

There's a third option that fits engineers better. Keep translation in the terminal, write output directly to locale/<lang>/LC_MESSAGES/django.po, and review it in pull requests like any other generated artifact. If you work across multiple stacks, the same developer-first mindset shows up in guides on optimizing Sitecore's global language support, where the underlying problem isn't toggling language features. It's building a workflow your team can maintain.

Generating Translations with a Single Command

The useful version of automation starts where Django stops. You already have source strings. You need a command that fills msgstr, preserves format strings, and leaves a readable diff behind.

Start with a real .po file

A fresh Spanish file often looks like this:

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

#: templates/billing.html:48
msgid "Upgrade plan"
msgstr ""

#: templates/account.html:33
msgid "<strong>Email</strong> is required."
msgstr ""

#: views.py:91
#, python-format
msgid "You have %s remaining credits."
msgstr ""

That file is already the contract. Your translation step should update msgstr only, and it shouldn't damage placeholders or tags.

Run the translation command

Install the package in your Django project:

pip install translatebot-django

Add minimal configuration in settings.py:

INSTALLED_APPS = [
    # ...
    "translatebot_django",
]

TRANSLATEBOT_PROVIDER = "openai"
TRANSLATEBOT_API_KEY = "your-api-key"
TRANSLATEBOT_MODEL = "gpt-4o-mini"

Then run:

python manage.py translate --target-lang es

If you want the exact command options and supported arguments, check the TranslateBot command reference.

After the command runs, the same file should look like this:

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

#: templates/billing.html:48
msgid "Upgrade plan"
msgstr "Mejorar plan"

#: templates/account.html:33
msgid "<strong>Email</strong> is required."
msgstr "El <strong>correo electrónico</strong> es obligatorio."

#: views.py:91
#, python-format
msgid "You have %s remaining credits."
msgstr "Te quedan %s créditos."

What works and what usually breaks

The command is the easy part. The value is in the constraints.

Concern What you want
Placeholders Preserve %(name)s, %s, {0} exactly
HTML Keep tags intact
Git diff Write back to django.po, not an external export
Incremental runs Translate only new or changed strings
Provider choice Use the model or API your team already trusts

AI is good at generating first-pass translations for UI copy, notifications, and standard product text. It still needs help when strings are too short, too ambiguous, or too tied to your brand voice.

Translate the file in place. Review the diff. Compile messages. That's the loop.

Django still does its normal job afterward:

python manage.py compilemessages

If your app serves translated pages directly, this closes the gap between extraction and an actual localized UI without dragging the team into a browser tab for every release.

Guiding the AI with Context and Glossaries

A raw string like View is a bad translation prompt. Is it a verb, a noun, a button label, or a permission name? If you want better output, feed the model better context.

A conceptual illustration showing a robotic head processing data from a glossary to generate better context.

Research on decomposed translation shows better results when translation is split into phases like research, drafting, and refinement. In practice, a versioned glossary file plays that research role and improves terminology consistency (decomposed translation approaches on arXiv).

Use pgettext when the English string is ambiguous

Django already gives you the tool for this.

from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy

button_label = pgettext_lazy("button action", "View")
table_column = pgettext_lazy("analytics table column", "View")
page_title = _("Account settings")

Those message contexts matter. They reduce bad guesses before translation even starts.

Short labels are where AI drifts most. A single word with no context can be translated correctly in one screen and incorrectly in another.

Keep a TRANSLATING.md file in the repo

A glossary in Markdown works well because your team can edit it in the same place as the code. No portal. No hidden project memory.

# Translation glossary

## Product terms
- AcmeCloud stays as "AcmeCloud" in every language.
- Workspace means the customer account area, not a physical office.
- Seat means a paid user license.

## Tone
- Use informal second-person tone in Spanish.
- Avoid translating "API", "SSO", and "SAML".

## UI conventions
- "Billing" should map to the subscription/payment area.
- "Member" and "User" are different concepts and should not be merged.

TranslateBot supports this kind of context directly. The docs for translation context and glossary guidance are worth reading before you automate anything beyond a toy project.

If your team also experiments with operational automation outside code, the same pattern shows up in articles about streamline workflows with no-code AI. The tool changes, but the principle doesn't. Outputs get better when you define terms and guardrails up front.

Better translations usually come from better inputs, not more retries.

Reviewing Translations in Your Pull Request

Automation should produce a draft, not a final answer.

The reason a CLI-first workflow works so well is that the output lands in Git. Your reviewer doesn't need access to a translation platform. They need a diff.

A computer screen showing translation code strings, terminal commands, and an approved stamp with a hand holding a stylus.

Translation quality work has moved away from single-pass checks and toward review and adjudication. The TRAPD method treats review as part of the process, not an optional cleanup step. For engineering teams, the practical version is clear: put translated strings in a pull request and let humans review them where they already review code (TRAPD and translation quality assessment).

What reviewers should look for

Don't review every string with the same suspicion. Focus where AI tends to miss.

A normal PR review works well here:

git status
git add locale/es/LC_MESSAGES/django.po
git commit -m "Update Spanish translations"

Then the team reviews the diff like any generated file. Native speakers can suggest wording changes inline. Product people can comment on tone. Engineers can catch placeholder damage before deploy.

Keep the quality gate boring

You don't need a committee for every locale. You need a repeatable rule.

Review changed strings in the PR, then run compilemessages before merge.

That keeps translation in the release cycle instead of pushing it into a side process that nobody owns.

Automating Your Translation Pipeline with CI/CD

Once the local workflow is stable, move it into CI. Nightly updates work well for apps with constant copy changes. Pull-request or main-branch triggers work better when you want tighter review.

A GitHub Actions job can run translation, compile messages, and open a commit for review:

name: Update translations

on:
  workflow_dispatch:
  schedule:
    - cron: "0 2 * * *"

jobs:
  translate:
    runs-on: ubuntu-latest

    steps:
      - name: Check out code
        uses: actions/checkout@v4
        with:
          persist-credentials: true

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          pip install -r requirements.txt

      - name: Generate translations
        env:
          TRANSLATEBOT_API_KEY: ${{ secrets.TRANSLATEBOT_API_KEY }}
        run: |
          python manage.py translate --target-lang es
          python manage.py compilemessages

      - name: Commit changes
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add locale/
          git diff --cached --quiet || git commit -m "Update translations"
          git push

A safer variation opens a pull request instead of pushing directly. That's usually the better default for customer-facing copy.

If you're designing wider automation chains around AI tasks, examples from streamlining AI workflows with Select.ax are useful because they show the same engineering trade-off. Keep the automation visible, reviewable, and easy to rerun.

How This Workflow Compares to Other Options

The translation market is projected to reach $4.2 billion by 2030, and that growth explains why there are so many tools competing for the same job. It doesn't change the day-to-day question for a Django team: what fits your codebase and budget. The cost spread is wide, from AI translation at roughly $0.15 per million tokens for GPT-4o-mini, to human translation around $0.12 to $0.25 per word, to TMS plans starting at $50+ per month (translation market and pricing context).

A comparison table showcasing the benefits of CLI translation workflows against manual translation and third-party integration services.

A deeper comparison of tooling options helps if you're still deciding between approaches. This overview of software for translating is useful for that.

Translation Workflow Comparison

Method Cost Developer Workflow Review Process
Manual browser translation No platform fee, but heavy time cost Outside the editor, copy-paste into django.po Ad hoc, usually after mistakes show up
Traditional TMS Subscription, often starting at $50+/month Separate UI, sync/import/export steps Structured, but outside normal code review
CLI-based translation AI cost per run, often pennies Terminal-first, writes directly to locale files Pull request review in Git

If your app changes often, the CLI model fits better than either extreme. Manual work doesn't scale. A TMS can be the right answer for large content operations, but it's often too much process for a product team that just wants updated locale files before the next deploy.


If you want that Git-based workflow in a Django project, TranslateBot is built for it. It runs as a manage.py translate command, preserves placeholders and HTML, and keeps translations in your repo where your team can review them normally.

Stop editing .po files manually

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