# Django Internationalization: Avoiding “banco / carta / cura” Translation Collisions with Contextual Markers When you localize a product into English (or any other language), ambiguous source strings can bite you: * A button labeled **“banco”** gets translated as **“bank”**, even though the UI clearly means **“bench.”** * In a restaurant screen, **“carta”** is translated as **“letter”**, when the correct meaning is **“menu.”** * A label **“cura”** is translated as **“cure”**, even where it’s meant to be **“priest.”** The root cause is simple: > Computers don’t understand *context*. > If two strings are identical, they’re treated as the same. This article explains how to keep your source strings unchanged while allowing different translations for the same text using Django’s **Contextual Markers**. --- ## Why does this happen? How gettext matches strings {#sec-1d14ac3fb84b} Django’s translation system is built on **GNU gettext**, which follows a straightforward mapping: * Source string → `msgid` * Translated string → `msgstr` * **If `msgid` is the same, gettext uses the same `msgstr`** ```po # django.po msgid "banco" msgstr "bank" ``` Once that mapping exists in the `.po` file, every occurrence of `"banco"` will be translated as `"bank"`—even in places where it should be `"bench"`. That’s why two UI uses like these can’t be distinguished by default: * `"banco"` = bench (a seat) * `"banco"` = bank (a financial institution) A common workaround is to change the visible source strings: ```html {% trans "banco (bench)" %} {% trans "banco (bank)" %} ``` It works, but it makes the source strings awkward and harder to reuse. What we actually want is: * Keep the source string as **"banco"** * Provide a separate hint describing *which meaning* is intended That’s exactly what **Contextual Markers** are for. --- ## Solving it in templates: `{% translate %}` + `context` {#sec-12e20076e249} In Django templates, you can attach a `context` option to `{% translate %}` (or legacy `{% trans %}`) to differentiate identical strings by meaning. ### 1) Original code (collision) {#sec-9036242f3fc3} ```html {% load i18n %} {% translate "banco" %} ``` Both entries become `msgid "banco"` in the `.po` file, so only one translation can be used. ### 2) Improved code (with context) {#sec-c7dd0107f11c} ```html {% load i18n %} {% translate "banco" context "financial institution" %} ``` Key points: * The `context` string is **not shown to users**. * It’s metadata for the translation system and translators. * Keep it short and specific, such as: * `"seat: bench"` * `"financial institution"` * `"restaurant menu"` * `"letter (mail)"` ### 3) Using `{% blocktranslate %}` with `context` {#sec-ccaee265cd55} For longer text, `{% blocktranslate %}` (or `{% blocktrans %}`) also supports `context`: ```html {% load i18n %} {% blocktranslate context "restaurant UI: menu label" with name=restaurant.name %} View the carta for {{ name }} {% endblocktranslate %} ``` This lets you reuse similar sentences while still disambiguating meaning. --- ## Solving it in Python code: `pgettext` {#sec-768c57da2676} In Python code (views, models, forms), use the `pgettext` family instead of plain `gettext`: * `pgettext(context, message)` * `pgettext_lazy(context, message)` – lazy evaluation (model fields, module-level constants) * `npgettext(context, singular, plural, number)` – plural handling with context ### 1) Basic example {#sec-0c6a80978da7} ```python from django.utils.translation import pgettext def my_view(request): bench = pgettext("seat: bench", "banco") bank = pgettext("financial institution", "banco") menu = pgettext("restaurant menu", "carta") letter = pgettext("letter (mail)", "carta") priest = pgettext("religion: priest", "cura") cure = pgettext("medical: cure", "cura") ``` The `msgid` stays the same (`"banco"`, `"carta"`, `"cura"`), but translations can differ thanks to the context. ### 2) Using `pgettext_lazy` in models {#sec-68489f921b16} Ambiguity shows up in model field labels, help text, and choices too: ```python from django.db import models from django.utils.translation import pgettext_lazy class Example(models.Model): location = models.CharField( max_length=100, verbose_name=pgettext_lazy("UI label: bench/seat", "banco"), ) TYPE_CHOICES = [ ("bank", pgettext_lazy("financial institution", "banco")), ("bench", pgettext_lazy("seat: bench", "banco")), ] kind = models.CharField(max_length=20, choices=TYPE_CHOICES) ``` ### 3) Plurals with context: `npgettext` {#sec-deb245a71dcb} If you need plural forms *and* context: ```python from django.utils.translation import npgettext def notification(count): return npgettext( "user notification", "You have %(count)d item", "You have %(count)d items", count ) % {"count": count} ``` --- ## What the `.po` file looks like with context {#sec-8817d6847a5e} After extracting messages: ```bash python manage.py makemessages -l ``` Django will include a `msgctxt` field for strings that specify context: ```po # django.po msgctxt "seat: bench" msgid "banco" msgstr "bench" msgctxt "financial institution" msgid "banco" msgstr "bank" msgctxt "restaurant menu" msgid "carta" msgstr "menu" msgctxt "letter (mail)" msgid "carta" msgstr "letter" msgctxt "religion: priest" msgid "cura" msgstr "priest" msgctxt "medical: cure" msgid "cura" msgstr "cure" ``` Even though `msgid` is identical, gettext treats entries with different `msgctxt` as separate translation units—so tools like Poedit, Weblate, or Crowdin will show them as distinct items. The important part: * Your source strings remain unchanged (`"banco"`, `"carta"`, `"cura"`) * The `.po` file gains the extra structure needed to translate correctly --- ## Tips for writing good context strings {#sec-b3510089df06} Context isn’t displayed to users, but it’s often the most useful clue translators get. 1. **Describe the UI role** * `"button label"` * `"menu item"` * `"tooltip"` * `"error message"` * `"form field label"` 2. **Name the concept/domain** * `"financial institution"` * `"seat: bench"` * `"restaurant menu"` * `"letter (mail)"` * `"religion: priest"` * `"medical: cure"` 3. **Don’t put the translation in the context** * Keep context as a short explanation of role/meaning, not a translated result. --- ## When should you use Contextual Markers? {#sec-a19665010859} Use `context` / `pgettext` when any of these apply: 1. **Short strings (1–2 words)** reused across the UI Buttons, tabs, menu items, labels. 2. **The same source text is used with different meanings** Classic cases like `"banco"`, `"carta"`, `"cura"`. 3. **Translators work without seeing the UI** Translation platforms, external vendors, or `.po`-only workflows. --- ## Summary {#sec-479f5dedffa9} * By default, Django/gettext maps a single `msgid` to a single translation. * Ambiguous strings like **“banco”**, **“carta”**, and **“cura”** can’t be translated correctly without extra clues. * Instead of changing the source strings, add context: * Templates: `{% translate "…" context "…" %}` * Python: `pgettext`, `pgettext_lazy`, `npgettext` * Django records context as `msgctxt` in `.po` files, enabling distinct translations for identical source text. Contextual Markers let you keep your code clean while dramatically improving translation quality and maintainability. --- **Related Articles** - [Getting Rid of gettext vs gettext_lazy Confusion: Understanding Evaluation Time](/ko/whitedec/2026/1/5/django-gettext-vs-gettext-lazy/) - [Problems When Using gettext_lazy as JSON Keys and How to Fix Them](/ko/whitedec/2025/4/26/gettext-lazy-json-key-problem-solution/)