# Django Internationalization: Avoiding Ambiguous Translations with Contextual Markers When you support multiple languages, you’ll run into a familiar problem: - A button labeled **“Polish”** (verb: refine UI) ends up translated as **“폴란드어”** (Polish language). - The word **“May”** in a date picker can be interpreted as a person’s name in some languages. - The menu item **“Book”** is translated only as “책” (book) and never as “예약하다” (to book). 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 {#sec-13190b2d4e0b} 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** ```po # django.po msgid "Polish" msgstr "폴란드어" ``` 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: ```html {% 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` {#sec-765a5e16aad9} 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) {#sec-5f3308498554} ```html {% load i18n %} {% translate "Polish" %} ``` In the `.po` file, both entries have `msgid "Polish"`, so only one translation is possible. ### 2) Improved Code (using context) {#sec-90b273a71c0b} ```html {% load i18n %} {% translate "Polish" context "language name" %} ``` 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 %}` {#sec-17523ff986e5} For longer sentences, you can still use `context` with `{% blocktranslate %}` (or `{% blocktrans %}`): ```html {% 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` {#sec-55a2981f11fa} 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 {#sec-1ece67649ba1} ```python 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 {#sec-eb83071193f9} ```python 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` {#sec-5d7922fe3a29} ```python 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 {#sec-f3b34b9cf8a6} After extracting messages with `python manage.py makemessages -l ko`, the `.po` file includes a `msgctxt` field: ```po # 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 {#sec-e32360a16a03} 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 {#sec-591fcd5e8f2a} 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 {#sec-bb5573d22803} - 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** - [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/)