In Django, what problem does pgettext() solve, and how do you supply the same context marker in a template?
answer
- one English word, two meanings
- context as the first argument
- separate catalog entries
- context keyword on the tags
basics
~20 spgettext(context, message) separates identical source strings that need different translations, such as "Order" the noun and "Order" the button. Each context becomes its own catalog entry. Templates pass it with the context keyword on {% translate %} or {% blocktranslate %}.
solid answer
~30 sA catalog is keyed by the source text, so every `_("Order")` in a project shares one translation. On a storefront, "Order" is a noun in the order history (German "Bestellung", French "Commande") and a verb on the checkout button ("Bestellen", "Commander"). `pgettext("verb", "Order")` and `pgettext("noun", "Order")` become two separate entries, told apart by their context, so translators can give each its own text. In templates the equivalent is `{% translate "Order" context "verb" %}` or `{% blocktranslate context "greeting" %}…{% endblocktranslate %}`. `pgettext_lazy()` covers import-time strings, `npgettext()` adds plurals, and the context string is never shown to users.
code
python · 14 linesfrom django.db import models
from django.utils.translation import pgettext_lazy
class Order(models.Model):
class Status(models.TextChoices):
OPEN = "open", pgettext_lazy("order status", "Open")
SHIPPED = "shipped", pgettext_lazy("order status", "Shipped")
status = models.CharField(max_length=10, choices=Status)
class Meta:
verbose_name = pgettext_lazy("noun", "order")
verbose_name_plural = pgettext_lazy("noun", "orders")go deeper
Recall that pgettext takes a context first, and that templates use the context keyword.
Explain why identical msgids share one translation, and how context creates separate catalog entries.
Know that changing context invalidates translations, and choose between context and translator comments deliberately.
Define a small set of standard context labels for UI terms so products stay consistent across teams and locales.
## The problem: one msgid, one translation Django's catalogs map a source string (the **msgid**) to a translation. Two calls with the same English text share one entry, and therefore one translation. English reuses many words across meanings, and the languages you translate into often don't: | Source | Meaning on the storefront | German | French | |---|---|---|---| | "Order" | noun: an order in your history | Bestellung | Commande | | "Order" | verb: the checkout button | Bestellen | Commander | | "May" | month name | Mai | mai | | "May" | modal verb in "You may…" | darf / kann | pouvez | Without context, whichever translation the translator picks will be wrong in half the places. ## The Python API `django.utils.translation` has a context-aware version of each function: - **`pgettext(context, message)`**: eager, for use inside functions; - **`pgettext_lazy(context, message)`**: lazy, for model fields, form labels and other import-time strings; - **`npgettext(context, singular, plural, number)`** and **`npgettext_lazy(...)`**: context plus pluralization. ```python from django.utils.translation import pgettext, pgettext_lazy ORDER_TAB = pgettext_lazy("noun", "Order") # class or module scope button = pgettext("verb", "Order") # inside a view ``` The context string is free text written for translators, so make it descriptive: `"checkout button"` is more helpful than `"v"`. It never appears in the rendered page. `pgettext` cannot be aliased as `_`, because the extraction tool only treats `_` as a single-argument marker. ## The template API Both translation tags take a `context` keyword. The value can be a literal or a variable: - `{% translate "Order" context "checkout button" %}` - `{% translate "Order" context "order history heading" as heading %}` - `{% blocktranslate with name=user.first_name context "greeting" %}Hi {{ name }}{% endblocktranslate %}` - `{% blocktranslate count counter=n context "cart" %}…{% plural %}…{% endblocktranslate %}` becomes an `npgettext()` call. ## What changes in the catalog After extraction, each context produces its own entry. The context is recorded on a `msgctxt` line above the `msgid`, so a translation tool shows the two "Order" entries side by side with their contexts. The details of extraction and catalog files are a separate topic. The point here is that context is part of the entry's **identity**: 1. Adding a context to an existing string creates a **new** entry. The old translation doesn't carry over automatically, so the string shows in the source language until someone translates it again. 2. Removing a context has the same effect in reverse. 3. Two calls with the same context and message share an entry, which is what you want for consistent UI terms. ## When to use context, and when not to Use it when: - a short word or label is ambiguous out of context: buttons, column headers, status names, month and weekday names; - a term needs a different register in different places, such as a formal and an informal greeting; - your translators report that an entry "can't be translated both ways". Avoid it when: - the strings differ anyway. "Place order" and "Your orders" are already distinct msgids; - the translator only needs **a hint**, not a separate translation. That is what translator comments are for; - you would add a context to every string "just in case", which multiplies the work for translators. ## Context outside Python and templates The JavaScript catalog view exposes `pgettext(context, msgid)` and `npgettext(context, singular, plural, count)` in the browser, with the same semantics. A context used in a `.js` file must therefore match the one translators saw for that string. Keep the context vocabulary short and shared, for example `"noun"`, `"verb"`, `"order status"` and `"checkout button"`, so the same meaning always gets the same label across Python, templates and JavaScript. ## Common mistakes - Relying on `_("Order")` everywhere, then trying to fix the German button text in the `.po` file. That breaks the noun elsewhere. - Using `pgettext()` in a class body. Import-time strings need `pgettext_lazy()`, exactly as `gettext_lazy()` replaces `gettext()`. - Changing context wording casually, which throws away existing translations.
- You add a context to a string that was already translated. What happens to its translation?The context is part of the entry's identity, so the marked string becomes a new entry with no translation yet. It renders in the source language until translators fill it in. The old context-free entry stays in the catalog only as long as some code still uses it. Plan context changes together with a translation update.
- When is a translator comment better than a context marker?When the text needs one translation and the translator only needs to understand it, for example what a placeholder holds or where a button appears. A comment adds explanation without creating a separate entry. Context is for when the same source text needs different translations in different places.
saying these in an interview costs you the question
- The context string is displayed to users next to the translation.
- Adding a context keeps the existing translation automatically.
- pgettext() is safe in a model class body like any other call.
- Two identical English strings can already be translated differently without context.
- pgettext() can be imported as _ like gettext().