Back to blog

8 Real-World Examples of Translations in Django

2026-04-13 17 min read
8 Real-World Examples of Translations in Django

You’ve already done the easy part. You marked strings, ran makemessages, and now your repo is full of half-empty .po files waiting for attention. That’s where most Django i18n workflows go bad.

The messy part isn’t extracting strings. It’s keeping placeholders intact, not breaking HTML, handling copy changes without retranslating everything, and reviewing translation changes in Git like they’re normal code. If your current process involves copy-pasting msgid values into a browser tab, you already know how fragile that gets.

A lot of content about examples of translations is still stuck in school math. It talks about sliding triangles across a grid, not shipping a multilingual Django app without corrupting %(name)s or turning email markup into invalid HTML. That gap matters because developers keep dealing with translation problems that generic localization guides barely touch.

One reason I take format safety seriously is that technical translation work shows how costly small string mistakes become in specialized domains. In a set of engineering translation projects, client data sheets had terminology inconsistency across annual reports before expert translation, then reached much tighter consistency afterward, with zero breakage in technical placeholders and much faster Git-diff review cycles once outputs became reproducible in these technical translation examples. Django apps aren’t nuclear reports, but the lesson is the same. Broken formatting in translated strings causes real review pain and real release risk.

The examples below stay close to the code. No screenshots from a portal you’ll stop using in a month. Just the kind of before-and-after diffs that show what good translation looks like in a local Django workflow.

1. Django Placeholder Preservation

The fastest way to break a translated Django app is to let a tool rewrite placeholders.

If your source string contains %(name)s, %s, or {0}, the translation has to preserve it exactly. Not approximately. Not with “cleaned up” spacing. Exactly.

A speech bubble illustration showing a placeholder variable alongside a padlock icon representing protected translation content.

What correct output looks like

Take a common Django string:

msgid "Hello %(name)s, welcome to our platform"
msgstr ""

A safe French translation looks like this:

 msgid "Hello %(name)s, welcome to our platform"
-msgstr ""
+msgstr "Bonjour %(name)s, bienvenue sur notre plateforme"

The important part isn’t the French. It’s that %(name)s survived untouched.

Same pattern for transactional strings:

 msgid "Your order %(order_id)s has shipped"
-msgstr ""
+msgstr "Votre commande %(order_id)s a été expédiée"

And for trial messaging:

 msgid "Hi %(first_name)s, your trial ends in %(days)s days"
-msgstr ""
+msgstr "Hola %(first_name)s, tu prueba termina en %(days)s días"

That’s one of the most useful examples of translations in app code because it’s where “looks fine in review” often turns into a runtime error.

Practical rule: If a translation tool can’t guarantee placeholder preservation, don’t let it touch your .po files.

What works and what doesn’t

What works:

What doesn’t work:

Good writing helps too. If you want source strings that survive translation better, write them like you expect another language to read them. The advice in writing for translation applies directly to Django templates and gettext() calls.

2. HTML Tag Preservation in Rich Text

HTML inside translatable strings is annoying, but sometimes it’s the least bad option.

You see it in email templates, CMS snippets, banner copy, and admin help text. The problem is simple. The translator needs to change the words, not the markup.

A safe rich-text translation

Start with this:

msgid "<strong>Sale ends soon</strong>"
msgstr ""

A valid French result keeps the tag structure intact:

 msgid "<strong>Sale ends soon</strong>"
-msgstr ""
+msgstr "<strong>L'offre se termine bientôt</strong>"

The same rule applies when placeholders and tags appear together:

 msgid "<em>New message from %(user)s</em>"
-msgstr ""
+msgstr "<em>Nouveau message de %(user)s</em>"

And for links:

 msgid "<a href=\"/help\">Click here</a> for support"
-msgstr ""
+msgstr "<a href=\"/help\">Cliquez ici</a> pour obtenir de l'aide"

What you want is balanced tags, preserved attributes, and translated text only.

Keep the markup boring

Most HTML translation problems are self-inflicted.

If your msgid contains nested spans, inline styles, and a chunk of template logic, the translator has no chance. Keep the string small and keep the markup simple.

A good pattern in templates:

{% blocktrans trimmed %}
<strong>Limited time:</strong> Save on your next order
{% endblocktrans %}

A less good pattern:

{% blocktrans %}
<span class="promo" data-id="{{ promo.id }}"><strong>Limited time:</strong> Save on your next order</span>
{% endblocktrans %}

Move the noisy parts outside the translatable text when you can.

HTML in translations should carry meaning, not layout.

That means strong, em, and links are usually fine. A pile of presentational markup usually isn’t.

Rendered output matters more than the .po diff here. Review the page in the target language. Check broken wrapping. Check whether linked text still makes sense. Check whether email clients mangle the result. A string can be technically valid and still look bad in production.

3. Incremental Translation Detection

Retranslating the whole file every time copy changes is wasteful. It’s also how teams start avoiding translation updates because the process feels expensive and slow.

The better pattern is boring and effective. Translate only what changed.

A realistic diff

Say your product team changes a button label:

-msgid "Sign up"
+msgid "Create account"

You don’t want to touch every existing French, Spanish, and German string because one msgid changed. You want the tool to detect the new or fuzzy entry and update only that unit.

A typical flow looks like this:

django-admin makemessages -a
translatebot --languages fr,es,de
git diff locale/

Then your diff stays small:

 #: templates/signup.html:18
-msgid "Sign up"
-msgstr "S'inscrire"
+msgid "Create account"
+msgstr "Créer un compte"

That’s the difference between a reviewable commit and a noisy translation dump.

Why this matters in practice

In fast-moving apps, copy changes constantly. Button text changes. Error messages get clarified. Onboarding screens get rewritten. If translation only runs on delta strings, you can keep localization close to normal product work instead of batching it into a dreaded “language pass.”

That style of workflow also maps well to translation memory ideas. If you’re comparing approaches, this piece on translation memory programs is a useful reference point for understanding why repeated content shouldn’t be processed like brand new text every time.

A few habits make incremental updates work better:

One reason delta-based workflows are worth caring about is that software localization content is still underserved compared with generic “translation” content. A lot of top-ranking material focuses on geometry, not placeholder-safe .po updates. The gap shows up in developer frustration too. A summary of that gap points out unresolved questions around Django gettext placeholder corruption and the lack of practical CLI-based guidance in common search results in this discussion of translation examples versus localization reality.

4. Glossary-Driven Consistency

Translation quality often fails on the boring words. Not on poetry. On product nouns.

“Workspace.” “Organization.” “Terms of Service.” “Cart.” If those drift between releases, your app feels sloppy fast.

Put the glossary in Git

For Django teams, a plain TRANSLATING.md file in the repo beats a hidden glossary inside some separate platform.

Example:

# TRANSLATING.md

## Product terms

- workspace
  - fr: espace de travail
  - note: never translate as a personal desk or physical office

- organization
  - fr: organisation
  - note: refers to a user group inside the app, not a company name

- Terms of Service
  - fr: Conditions d'utilisation
  - note: keep legal wording consistent across auth, footer, and checkout

Now the rules live next to code changes. If product language changes, the glossary changes in the same PR.

Real examples where consistency matters

E-commerce teams run into this with “cart.” In French, you probably want panier, not a literal word that sounds like a shopping trolley in the wrong context.

SaaS teams hit it with “organization.” If one screen says the equivalent of “company” and another means “user group,” users notice. Legal text is even less forgiving. “Terms of Service” and “Conditions of Use” aren’t interchangeable once your product has settled on one phrasing.

Glossary-driven translation has become more relevant as teams push localization into CI. One future-dated conference trend summary says glossary-driven models improved terminology consistency in CI/CD pipelines according to this 2025 projection referenced in a translation examples PDF context. The exact number matters less than the workflow lesson. Teams get better output when they write down terminology rules instead of hoping the model infers them every time.

Write glossary rules the same way you write API constraints. Short, explicit, versioned.

Start with the terms users see everywhere. Brand names. Plan names. Billing language. Security wording. Don’t try to document every noun in week one. The high-frequency terms do most of the work.

5. Plural Form Handling

Pluralization is where “looks translated” and “is correct” split apart.

English lets people get lazy. Plenty of apps fake it with string concatenation or one singular and one plural form. That falls apart fast once you add languages with more complex rules.

A diagram illustrating plural and singular noun translation rules based on numerical count values in software localization.

Use ngettext() or accept bugs

Bad pattern:

message = str(count) + " items"

Good pattern:

from django.utils.translation import ngettext

message = ngettext(
    "%(count)s item",
    "%(count)s items",
    count,
) % {"count": count}

In a .po file, that gives translators the plural forms Django expects for the target locale.

French is easy enough to illustrate:

 msgid "%(count)s item"
 msgid_plural "%(count)s items"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%(count)s article"
+msgstr[1] "%(count)s articles"

The main point isn’t French. It’s that your code should give the translation layer enough structure to apply language-specific plural rules.

Where teams get this wrong

The most common bug is still concatenation. The second most common is reusing one English-shaped message in every locale and assuming it generalizes.

It doesn’t.

Polish, Russian, and Arabic need more thought. Chinese needs different thought. If your app has carts, notifications, invoices, or usage quotas, plural handling isn’t optional.

Test actual counts. Don’t stop at 1 and 2. Render 0, 1, 2, 5, and 21 in the target language and look at the final UI.

One useful reminder from AI translation evaluation in advertising content is that context and linguistic authenticity matter a lot. In that comparison, Bard did better on native Hindi phrasing in some cases, while Claude handled technical language and contextual nuance especially well, and manual translations previously often needed multiple revision cycles before AI-assisted workflows reduced revision needs and improved first-pass authenticity in the ARF case study on translation of text. For plural strings, that same lesson applies. Don’t judge quality by “did the words get translated.” Judge it by whether the output reads like a real sentence for that count.

6. Context Comments for Ambiguous Strings

Some English strings are traps.

“Account” can mean billing account or user profile. “Present” can be a gift or a verb. “Bank” can be finance or geography. Translating those without context is bad engineering.

Add guidance where the ambiguity starts

Django already gives you tools for this.

In code:

from django.utils.translation import pgettext

label = pgettext("user profile section", "Account")

Or with a translator comment:

# Translators: "Account" means a user's profile, not a bank account.
title = gettext("Account")

That flows into the .po file:

#. Translators: "Account" means a user's profile, not a bank account.
msgid "Account"
msgstr ""

That one sentence often fixes the whole problem.

Context beats cleanup later

Developers usually add comments only after someone reports an awkward translation. That’s backwards.

If a word has multiple meanings in English, document it at extraction time. You already know the context while writing the feature. A translator or model won’t.

Examples that deserve comments:

Keep comments short. One sentence. Domain plus meaning. That’s enough.

A translator can’t recover context you never encoded.

One of the better habits for examples of translations in Django is to show not just the string, but the note above it. The note often makes the difference between a polished locale file and one full of technically correct but wrong-in-context phrasing.

7. CI/CD Integration in Git Workflows

If translation happens outside your normal release flow, it gets skipped.

That’s why portal-first localization feels wrong for many small Django teams. You make code changes in Git, but translations live somewhere else, behind manual upload steps and UI clicks nobody wants to own.

Keep the pipeline boring

A translation job should look like any other build step.

Here’s a simple GitHub Actions shape:

name: translate

on:
  push:
    branches: [main]

jobs:
  translate:
    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: django-admin makemessages -a
      - run: translatebot --languages fr,es,de
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      - run: django-admin compilemessages

That’s enough for many teams. Extract. Translate. Compile. Review the diff.

For teams already investing in modern CI/CD pipelines, localization belongs in the same automation path as tests and asset builds.

Here’s a quick demo format that matches the same idea:

Why Git-based automation is better than portal clicks

The big win is reviewability. A pull request shows exactly which strings changed. You don’t have to trust a sync from some dashboard.

Another win is that the tooling stays local. The workflow described in translation and technology fits how developers already work. Terminal commands, environment variables, CI jobs, and diffs.

A few practical rules help:

The less your team has to leave Git, the more likely translations stay current.

8. Reproducible Translation Diffs

A translation platform can tell you that French was updated. Git can tell you exactly what changed, when it changed, and what else changed with it.

That’s the standard I want for localization work.

The audit trail lives in the repo

When translations write directly to locale/*/django.po, you get normal Git history:

git log --oneline, locale/fr/django.po
git diff HEAD~1, locale/es/django.po
git blame locale/de/django.po

That matters for real maintenance work.

If a release introduces awkward wording, you can inspect the commit. If legal text changed in one locale but not another, the diff shows it. If someone asks why a product term changed, the commit that updated TRANSLATING.md can sit next to the locale update.

A clean commit often looks like this:

git add locale/ TRANSLATING.md
git commit -m "Update onboarding copy translations with glossary changes"

That’s much easier to reason about than “someone changed strings in the portal last week.”

What review looks like

Say a translator or tool changes a phrase in French:

-msgstr "Créer un espace de travail"
+msgstr "Créer un espace de travail d'équipe"

Now you can ask the right question. Did the product meaning change, or did the wording drift? Git makes that visible.

If you want a quick visual comparison outside the terminal, a tool like diff check online can help for one-off reviews, but the long-term value still comes from versioned locale files in the repo.

The strongest examples of translations for developers aren’t glamorous. They’re reproducible. They survive review, can be reverted cleanly, and don’t disappear into a vendor account.

8-Example Translation Comparison

Feature Implementation Complexity 🔄 Resource / Setup Effort ⚡ Expected Outcomes ⭐ Ideal Use Cases 📊 Key Advantages & Tips 💡
Django Placeholder Preservation: %(name)s → %(name)s (French) Low, automatic detection and protection of format placeholders Low, minimal infra; translator guidance recommended Prevents runtime VariableDoesNotExist errors; preserves injection safety UI strings, emails, notifications, e-commerce order messages Document placeholders (TRANSLATING.md), run makemessages, test with real data
HTML Tag Preservation in Rich Text: 'Sale ends soon' → French Medium, HTML parsing, locking and tag validation required Medium, translator training for inline HTML and rendering tests Avoids broken HTML and sanitization errors; preserves styling Marketing copy, email templates, rich-text UI fields Keep tags minimal, move complex markup to templates, test rendered output
Incremental Translation Detection: Changed Strings Only → Re-translation Workflow High, change detection, fuzzy tracking, version-aware logic Low ongoing token costs; initial setup for .po management Large reductions in cost and latency; only changed strings translated Rapid-iteration SaaS, frequent releases, A/B testing Mark fuzzy entries, run in CI, commit .po files for clear diffs
Glossary-Driven Consistency: TRANSLATING.md Context Rules → Unified Terminology Medium, integrate versioned glossary with LLM prompts Medium, initial curation and periodic maintenance effort Consistent terminology and reproducible translations across releases Brand-sensitive products, legal/technical domains, multi-team projects Start with 20–30 key terms, review quarterly, commit glossary changes with code
Plural Form Handling: '1 item' vs. '%(count)s items' → Language-Specific Rules Medium, ngettext integration and CLDR rule application Low–Medium, testing of plural edge cases per language Grammatically correct plural forms for target languages Notification counts, cart badges, quantity displays Use ngettext, test 0/1/2/5/21, document complex plural contexts
Context Comments for Ambiguous Strings: '%(context)s' → Translator Guidance Low–Medium, add pgettext and concise translator comments Low, developer time to add contextual comments Reduces mistranslations of ambiguous single words Short labels, UI buttons, domain-specific one-word strings Keep comments concise (1–2 sentences), include domain context, use pgettext
CI/CD Integration: Automated locale/ File Updates in Git Workflows High, CI pipeline, secrets, timeouts, and Git automation Medium, CI configuration and runner resources; review workflows Automated, reviewable translations committed to repo; reproducible builds Teams using Git-based reviews and automated releases Run after makemessages, store keys in secrets, review translation diffs before merge
Reproducible Translation Diffs: Version-Controlled locale/*/django.po → Auditable History Medium, write .po to repo and manage commit metadata Low–Medium, Git discipline and conflict resolution practices Full audit trail, easy rollback, transparent terminology evolution Compliance-sensitive projects, open-source, teams needing traceability Commit locale changes separately, include TranslateBot version, use git blame

Translation as Code, Not as a Chore

These examples all point to the same shift. Good Django translation work doesn’t happen in a side channel. It happens in the same places you already trust for code changes. Your editor. Your terminal. Your tests. Your Git history.

That matters because the worst translation workflows fail in familiar ways. They hide changes behind a web UI. They mix language review with file export problems. They make simple copy edits feel expensive. They turn .po files into artifacts you only touch right before release. Then everyone avoids touching them until the app drifts out of sync across languages.

A code-level workflow fixes a lot of that.

Placeholder preservation stops being a manual spot-check and becomes a format safety guarantee. HTML preservation stops depending on whether someone notices a broken closing tag in review. Incremental translation means copy updates don’t drag the whole project into another full localization pass. A versioned glossary stops product terminology from drifting because one screen said “workspace” and another said the equivalent of “office.” Context comments make ambiguous English strings translatable on the first pass instead of after a bug report.

CI helps too, but only if you keep it simple. Extract strings. Translate changed entries. Compile messages. Review diffs. Merge. That’s a workflow developers will keep running. The more it looks like normal engineering work, the less likely it gets ignored.

The Git part is easy to underestimate. Reproducible diffs are not just nice for process people. They’re practical. They let you answer very concrete questions. What changed in French between releases? When did legal wording shift in Spanish? Which commit introduced an awkward translation in onboarding? Portal-based systems usually answer those questions badly, or not at all. A repo answers them in seconds.

There’s also a quality lesson hiding underneath all this. Translation quality is rarely about one dramatic mistake. It’s usually death by small cuts. A placeholder gets edited. A count string uses the wrong plural form. A rich text string loses its tag balance. A product noun drifts. A vague English word gets translated because no one added a comment. None of those issues are hard individually. They become hard when the workflow makes them invisible.

That’s why I think “examples of translations” are most useful when they’re shown as code diffs instead of abstract language theory. Developers don’t need another definition of translation. They need to see what a safe, reviewable, boringly reliable translation change looks like inside locale/fr/django.po.

Start with one thing. Add a TRANSLATING.md file to your repo. Write down the handful of terms that users see everywhere. Then run your next translation pass in a way that updates only changed strings and leaves a diff you can review like any other commit. If the output is small, readable, and safe to merge, you’re on the right path.


If you want that workflow without another portal, TranslateBot is built for it. It translates Django .po files from the CLI, preserves placeholders and HTML tags, updates only new or changed strings, and writes the results straight back to locale/*/django.po for clean Git diffs. For a small team or solo project, that’s usually the difference between “we should localize later” and shipping translated releases.

Stop editing .po files manually

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