# 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/)