How to Add Context to Translations with msgctxt

A practical guide to the PO format's most misunderstood feature, executed against GNU gettext 1.0 with a live Python runtime. Opens with the collision gettext cannot solve on its own — the English source string is the identifier, so one "Open" cannot be a menu verb and a ticket status at the same time — then shows what a context physically is by dumping the compiled MO's key table: the context is concatenated into the lookup key as context + EOT + msgid, with a NUL separating plural forms. Every surprising behaviour follows from that one fact, and each is demonstrated. Two identical msgids with different contexts are not duplicates and compile silently, while the same pair twice is a fatal error naming both lines. msgctxt "" is not the same as no msgctxt — it produces a third distinct entry keyed on EOT alone that no ordinary gettext call can ever reach, which is how spreadsheet-to-PO converters quietly break strings. pgettext never falls back to the context-free translation, so wrapping an already-translated string reverts it to English in every language while the English build looks perfect. And because a context re-keys the entry, msgmerge marks every affected string fuzzy — a merge that left two finished translations in place compiled to "0 translated messages" and an MO containing zero entries. Plus the extraction failure that costs an afternoon: with default keywords xgettext dropped all seven contextual calls and emitted zero msgctxt lines without a single warning, alongside the keyword specs that fix it for pgettext, Django, and WordPress's _x and _nx where the context sits in a different argument position. Closes with the decision that actually matters — a #. translator comment costs nothing and explains, a context costs a fuzzy pass in every language and disambiguates — plus naming conventions that survive a rename, msggrep --msgctxt, why msgcat and the MO sort contexts differently, and a nine-row symptom table.

Back to the PO-File blog