Django Internationalization: Avoiding Ambiguous Translations with Contextual Markers

When you localize a non-English product into English (or any other language), you’ll often hit a frustrating issue with ambiguous source strings:

  • A button labeled “banco” is translated as “bank” even when the UI clearly means “bench.”
  • In a restaurant UI, “carta” is translated as “letter” even though it should be “menu.”
  • The label “cura” is translated as “cure” in a context where it actually means “priest.”

The root cause is simple:

Computers don’t understand context. If two strings are identical, they’re treated as the same.

This article shows how to keep your source code unchanged while attaching different translations to the same string using Django’s Contextual Markers.


Why Does This Happen? The Basics of gettext

Django’s translation system is built on GNU gettext. It follows a very simple rule set:

  • Original string → msgid
  • Translated string → msgstr
  • If msgid is the same, the same msgstr is used
# django.po
msgid "banco"
msgstr "bank"

Once a mapping exists in the .po file, every occurrence of “Polish” will use the same translation, regardless of its intended meaning.

This is why the two UI elements above cannot be distinguished.

To avoid this, developers sometimes change the source code:

<!-- Avoid this approach -->
{% trans "Polish (verb)" %}
{% trans "Polish (language)" %}

While the translations may work, the visible strings become awkward or hard to reuse. What we really want is:

  • Keep the source string as "Polish"
  • Provide a separate explanation of which meaning is intended

That’s where Contextual Markers come in.


Solving It in Templates: {% translate %} + context

In Django templates, you can attach a context option to the {% translate %} (or legacy {% trans %}) tag to differentiate the same string by context.

1) Original Code (conflict)

{% load i18n %}

<button>{% translate "Polish" %}</button>
<span>{% translate "Polish" %}</span>

In the .po file, both entries have msgid "Polish", so only one translation is possible.

2) Improved Code (using context)

{% load i18n %}

<button>
  {% translate "Polish" context "verb: to refine UI" %}
</button>

<span>
  {% translate "Polish" context "language name" %}
</span>

Key points:

  • The string after context is not shown to users.
  • It’s purely metadata for the translation system and translators.
  • Keep it short and descriptive: e.g., "verb: to refine UI", "language name", "menu label", "button text".

3) Using {% blocktranslate %}

For longer sentences, you can still use context with {% blocktranslate %} (or {% blocktrans %}):

{% load i18n %}

{% blocktranslate context "greeting message" with username=user.username %}
  Hello {{ username }}
{% endblocktranslate %}

This allows the same sentence style to be reused in different contexts.


Solving It in Python Code: pgettext

In Python code (views, models, forms), use the pgettext family instead of plain gettext.

  • pgettext(context, message)
  • pgettext_lazy(context, message) – lazy evaluation (e.g., model fields)
  • npgettext(context, singular, plural, number) – handles plural forms with context

1) Basic Example

from django.utils.translation import pgettext

def my_view(request):
    # 1. Month name "May"
    month = pgettext("month name", "May")

    # 2. Person name "May"
    person = pgettext("person name", "May")

    # 3. Auxiliary verb "may" (~might)
    verb = pgettext("auxiliary verb", "may")

All three use the same msgid but different contexts, yielding distinct translations.

2) Using pgettext_lazy in Models

from django.db import models
from django.utils.translation import pgettext_lazy

class Order(models.Model):
    type = models.CharField(
        verbose_name=pgettext_lazy("order model field", "Order type"),
        max_length=20,
    )

    STATUS_CHOICES = [
        ("open",  pgettext_lazy("order status", "Open")),
        ("opened", pgettext_lazy("log action", "Open")),
    ]

    status = models.CharField(max_length=20, choices=STATUS_CHOICES)

Even with the same word "Open", you can distinguish status vs. action.

3) Handling Plurals: npgettext

from django.utils.translation import npgettext

def get_notification(count):
    return npgettext(
        "user notification",        # context
        "You have %(count)d message",   # singular
        "You have %(count)d messages",  # plural
        count
    ) % {"count": count}

What the .po File Looks Like

After extracting messages with python manage.py makemessages -l ko, the .po file includes a msgctxt field:

# django.po

# 1) "Polish" = UI refinement (verb)
msgctxt "verb: to refine UI"
msgid "Polish"
msgstr "다듬기"

# 2) "Polish" = language name
msgctxt "language name"
msgid "Polish"
msgstr "폴란드어"

Because msgctxt differs, translation tools treat them as separate entries, making it easier for translators to understand the intended meaning.

Key takeaways:

  • The source string remains unchanged.
  • The .po file becomes smarter with msgctxt.

Tips for Writing Good Context

  1. Describe the role (e.g., "button label", "menu item", "tooltip", "error message", "form field label").
  2. Add a concept if the same word appears in multiple domains (e.g., "File" as a menu item vs. an uploaded file object).
  3. Never put the translated string in the context; keep it in English.

When to Use Contextual Markers

Consider using them when:

  1. Short strings (1–2 words) appear in multiple places.
  2. The same word is reused with different meanings in the UI.
  3. Translators work without seeing the live UI (e.g., via a translation platform).

Summary

  • Django maps a single msgid to one translation by default.
  • Homonyms like "Polish", "May", or "Book" often cause confusion.
  • Don’t alter the source string; instead, add context:
  • Templates: {% translate "…" context "…" %}
  • Python: pgettext, pgettext_lazy, npgettext
  • The .po file will contain msgctxt, allowing distinct translations for the same word.
  • This keeps code readable while improving translation quality and maintainability.

In an era where multilingual support is increasingly essential, mastering Contextual Markers is a must‑know i18n tool for Django developers.


Related Articles