In a Django-generated .po file, what do the msgid, msgstr, msgctxt, #., #: and #, lines of an entry mean?
answer
- source text, translation, disambiguator
- comments with different prefixes
- Translators: in the code
- flags like fuzzy and python-format
basics
~20 smsgid is the source string and msgstr its translation; msgctxt, from pgettext, separates identical msgids. #. lines carry Translators: comments from the code, #: lines give source locations, and #, lines hold flags such as fuzzy or python-format.
solid answer
~40 sEvery entry is keyed by its `msgid`, the exact source string, which translators must never edit, and holds the translation in `msgstr`, empty until translated. Plurals use `msgid_plural` with `msgstr[0]`, `msgstr[1]` and so on. A `msgctxt` line appears when the code used `pgettext()` or a template `context` keyword. It makes the entry distinct from another with the same `msgid`, so "Room" as a gallery and as a free seat can differ. Comment lines differ by prefix. `#.` holds **extracted comments**: a `# Translators:` comment in Python or `{# Translators: … #}` in a template, placed right before the string. `#:` lists source locations, controlled by `makemessages --add-location` or `--no-location`. `#,` carries flags: `fuzzy` for unreviewed guesses, which `compilemessages` skips, and `python-format` for `%`-placeholders that `msgfmt --check-format` verifies.
code
python · 9 linesfrom django.utils.translation import gettext as _, pgettext
def room_labels(room):
# Translators: name of an exhibition hall, e.g. on the floor plan
hall = pgettext("gallery", "Room")
# Translators: %(count)s is how many audio-guide seats are free
seats = _("%(count)s seats free") % {"count": room.free_seats}
return hall, seatsgo deeper
Recall that msgid is the source key you never edit and msgstr is the translation you fill in.
Explain msgctxt, the three comment prefixes, and what the fuzzy and python-format flags change at compile time.
Use translator comments and contexts to prevent mistranslations, and tune --add-location to keep catalog diffs reviewable.
Set conventions for translator comments on ambiguous or placeholder-bearing strings as part of code review.
## An entry, line by line `makemessages` writes one entry per distinct source string (and context). A typical entry from a museum guide: ```po #. Translators: shown under a painting; %(year)s is a four-digit year #: exhibits/templates/exhibits/detail.html:22 #, python-format msgctxt "painting caption" msgid "Painted in %(year)s" msgstr "Gemalt im Jahr %(year)s" ``` | Line | Written by | Meaning | |---|---|---| | `#.` | extracted from your source | a note for translators, from a `Translators:` comment | | `#:` | `makemessages` | file and line where the string was found | | `#,` | gettext tools or translators | flags: `fuzzy`, `python-format`, … | | `msgctxt` | extracted from `pgettext`/`context` | disambiguates identical msgids | | `msgid` | extracted | the exact source string, the lookup key | | `msgstr` | the translator | the translation, empty until filled | A translator's own notes use `# ` with a space and no other marker, and `makemessages` keeps them across runs. ## `msgid` and `msgstr` - The **`msgid`** is the lookup key: exact text, including placeholders, punctuation and whitespace. **Never edit it** in the `.po` file. Change the source code and re-run `makemessages`. - The **`msgstr`** is the translation. An empty `msgstr` means "untranslated": the runtime falls through to lower-precedence catalogs, then to the `LANGUAGE_CODE` translation, and finally shows the source text. - Long strings are split into several quoted lines that are concatenated, so trailing spaces inside each piece matter. - **Plurals** from `ngettext` or `blocktranslate count` use `msgid` plus `msgid_plural`, and one `msgstr[n]` per plural form of the target language, as declared in the file's `Plural-Forms` header. ## `msgctxt`: same text, different meaning When code uses `pgettext("gallery", "Room")` and `pgettext("seat availability", "Room")`, the file gets **two** entries with the same `msgid`, told apart by `msgctxt`. German can then say "Saal" for one and "Platz" for the other. Without context both calls would share one entry and one translation. How to mark context in code is a separate topic. In the catalog, the context is simply part of the entry's identity. ## `#.`: comments for translators Notes you write in code become extracted comments: - in Python, a comment starting with `Translators` on the line just before the marked string, e.g. `# Translators: museum room label`; - in templates, `{# Translators: … #}` or `{% comment %}Translators: …{% endcomment %}` placed right before the tag. They are the cheapest way to prevent mistranslations of short or ambiguous strings, and to explain what a placeholder holds. ## `#:`: source locations `#:` lines list every place the string was found. They help translators see context and help developers find the code. `makemessages --add-location=file` drops line numbers, which reduces diff noise when code moves. `--add-location=never` or `--no-location` removes them entirely, at the cost of context for translators. ## The header entry The first entry in every `.po` file has an **empty `msgid`**. Its `msgstr` is not a translation but the catalog's metadata: - `Content-Type: text/plain; charset=UTF-8`, which `makemessages` sets because Django requires UTF-8 catalogs; - `Language:`, the locale the file is for; - `Plural-Forms:`, the number of plural forms and the expression that picks one for a count. For German that is `nplurals=2; plural=(n != 1);`, while Django's own French catalog declares three forms. Translators or their tools fill in the header. A wrong `Plural-Forms` line makes every counted message pick the wrong form. ## `#,`: flags - **`fuzzy`**: gettext guessed this translation, usually from a similar old string after the source was reworded, and a human hasn't confirmed it. `compilemessages` **leaves fuzzy entries out** unless given `--use-fuzzy`. The translator reviews the entry and deletes the flag. - **`python-format`**: the string contains Python `%`-style placeholders. `compilemessages` runs `msgfmt --check-format`, which rejects a translation whose placeholders don't match, for example a dropped `%(year)s`. ## Entries you should ignore Entries whose lines start with `#~` are **obsolete**: their `msgid` no longer appears in the source. They are kept so an old translation can be recovered, but they are never compiled. `makemessages --no-obsolete` removes them.
- A translator changed a msgid in the .po file to fix a typo in the English. What happens?The runtime looks up the source string from the code, which still has the typo, so the edited entry no longer matches. On the next `makemessages` it becomes obsolete (`#~`), and a fresh untranslated entry appears for the original text. Typos in the source language must be fixed in the code, then `makemessages` re-run.
- What happens at compile time if the German msgstr drops the %(year)s placeholder?`makemessages` flags the entry `python-format` because the msgid contains a `%`-placeholder. `compilemessages` runs `msgfmt --check-format`, which reports the mismatch, and the command ends with "compilemessages generated one or more errors". Without that check, the translated string would silently lose the year at runtime, or raise `KeyError` if a translator had renamed the placeholder.
saying these in an interview costs you the question
- You can fix an English typo by editing the msgid in the .po file.
- The #: location lines are what makes the translation apply to that file only.
- A fuzzy translation is used at runtime, just highlighted for review.
- msgctxt is a translator note and does not affect lookups.
- An empty msgstr makes Django show an empty string.