skip to content

A Django museum-guide site deploys an updated German .po file, yet several reworded exhibit texts still appear in English — what do you check, and in what order?

level: seniorimportance: should knowfreq 32%

answer

  1. is there a fresh .mo?
  2. flags on the changed entries
  3. does msgid still match the code?
  4. processes cache catalogs

basics

~20 s

Check that compilemessages produced a fresh .mo and that it was deployed, that the changed entries are not marked fuzzy, that each msgid still matches the current source string, that the catalog sits in a searched locale directory, and that workers were restarted.

solid answer

~40 s

Work from the file to the process. First, is there a `.mo` newer than the `.po`, and did it reach the servers? Django never reads `.po`. Second, look at the changed entries. Reworded source strings often come back from `makemessages` flagged `#, fuzzy` with the old translation pre-filled, and `compilemessages` leaves fuzzy entries out unless you pass `--use-fuzzy`, so the English source shows. Third, confirm the `msgid` matches the code exactly. A changed source string makes the old entry obsolete (`#~`), and whitespace or placeholder differences are enough to miss. Fourth, check the catalog is in a searched location (`LOCALE_PATHS` or an installed app's `locale/de/LC_MESSAGES/`) and not shadowed by a higher-precedence catalog. Finally, restart the workers: each process caches loaded catalogs, and only the development autoreloader clears that cache on `.mo` changes.

code

bash · 10 lines
bash
# Which reworded entries are still fuzzy or obsolete?
grep -n -A3 '#, fuzzy' exhibits/locale/de/LC_MESSAGES/django.po
grep -c '^#~ msgid' exhibits/locale/de/LC_MESSAGES/django.po

# Re-extract, then compile (fails on format errors)
python manage.py makemessages -l de
python manage.py compilemessages -l de

# Only if the fuzzy guesses were reviewed as acceptable:
python manage.py compilemessages -l de --use-fuzzy

go deeper

for a junior

Recall that .po edits only take effect after compilemessages produces a new .mo.

for a middle

Explain fuzzy and obsolete entries, why reworded strings become fuzzy, and what --use-fuzzy changes.

for a senior

Diagnose partial translation by working from file to process, and put fuzzy and format checks into CI.

for a principal

Define the translation release process so source rewording, translator review and deployment stay in step.

## Read the symptom first "Several reworded texts are English, the rest of the German site is fine" is a narrow symptom. It suggests that: - the German catalog **is** being found and loaded, since most strings work; - German **is** the active language for those pages, so language selection is not the problem; - something about **those particular entries** keeps them out of the compiled catalog, or they no longer match what the code asks for. If *everything* were English, you would look first at language activation, `USE_I18N`, or a catalog that was never found at all. ## Step 1: is there a current `.mo`, and did it ship? Django's runtime reads only binary `.mo` files. Check: 1. `compilemessages` ran **after** the `.po` edit. It skips files whose `.mo` is already at least as new as the `.po`, and reports them as "already compiled and up to date". 2. It succeeded. `msgfmt --check-format` fails on placeholder mismatches, such as a German `msgstr` missing `%(room)s`, and the command then ends with "compilemessages generated one or more errors". A `.po` saved with a BOM is also rejected. 3. The new `.mo` is actually in the deployed artefact, whether committed or built in CI. ## Step 2: are the changed entries fuzzy? When a curator rewords a source string, for example "Oil on canvas" becoming "Oil paint on canvas", the next `makemessages` run can't find the old msgid. `msgmerge` then often creates the new entry **pre-filled with the old translation and flagged `#, fuzzy`**, so that a translator can review it. By default `compilemessages` **excludes fuzzy entries**. They fall back to the source text (English, on a site whose `LANGUAGE_CODE` is English), which is exactly the "a few strings in English" pattern. Fixes, in order of preference: - have the translator review each fuzzy entry and delete the `fuzzy` flag; - only if the guesses are acceptable, compile with `compilemessages --use-fuzzy`. ## Step 3: does the `msgid` still match the code? Lookups are exact string matches: - If the code changed **after** the last `makemessages`, the catalog has no entry for the new wording. Re-run `makemessages --all` and translate the new entries. - The old entry is now **obsolete**, commented out with `#~`. Translating an obsolete entry has no effect. `--no-obsolete` removes them to avoid confusion. - Whitespace matters. A `{% blocktranslate %}` without `trimmed` carries newlines and indentation into the msgid, so re-indenting a template silently changes the msgid. - Placeholders are part of the msgid: `%(name)s` versus `%(title)s` is a different string. ## Step 4: is the catalog where Django looks, and not shadowed? - The path must be `<base>/de/LC_MESSAGES/django.mo`, using a locale name (`de`, `pt_BR`), under a directory in `LOCALE_PATHS` or an installed app's `locale/`. - A higher-precedence catalog can translate the same msgid differently, for example an old override in `LOCALE_PATHS` that still carries the previous wording. `LOCALE_PATHS` wins over apps, and earlier apps in `INSTALLED_APPS` win over later ones. - JavaScript strings live in `djangojs.mo`. If the untranslated texts come from `.js` files, check that domain instead. ## Step 5: did the processes reload? Each worker loads catalogs on first use and **caches** them for its lifetime. The development server's autoreloader watches `.mo` files and clears that cache on change, which is why "it works locally". Production application servers don't do this, so new catalogs need a worker restart or reload. ## A checklist to run | Check | Evidence of the problem | |---|---| | `.mo` newer than `.po`, present on the server | `.mo` missing, older, or not in the artefact | | `compilemessages` exit status | "generated one or more errors", format mismatch or BOM | | entry flags | `#, fuzzy` on the reworded entries | | msgid equals the source string | only an obsolete `#~` entry, or a whitespace or placeholder mismatch | | catalog location and precedence | wrong directory name, or a higher-ranked catalog with a different `msgstr` | | worker restart | pages correct on a fresh worker but not on old ones | Automating steps 1–3 in CI turns most of this into a failed build instead of a customer report. Run `compilemessages` in the pipeline so format errors fail the build, and fail on any remaining fuzzy entries in the release languages.

  • Why does the problem disappear on a developer's machine running runserver?
    The development server's autoreloader watches `.mo` files in `locale/`, the apps' `locale/` directories and `LOCALE_PATHS`, and clears Django's translation cache when one changes. So a local recompile shows up immediately. Production workers keep the catalogs they loaded until they are restarted. The local compile may also have used `--use-fuzzy` or picked up a newer `.po` than the one that was deployed.
  • How would you stop fuzzy entries from reaching production unnoticed?
    Make the release pipeline run `makemessages` and `compilemessages` for the release languages and fail if any `#, fuzzy` flag or empty `msgstr` remains, for example with a gettext statistics check such as `msgfmt --statistics`. Pair that with a rule that translators clear fuzzy flags as part of review. Then the gap shows as a failed build, not as English text in the German exhibit pages.

saying these in an interview costs you the question

  • Fuzzy entries are compiled and shown, just marked for review.
  • Translating the #~ obsolete entry fixes the reworded text.
  • Django rereads .po files on every request, so no restart is needed.
  • If most German strings work, the .mo must be up to date for all of them.
  • compilemessages always recompiles every .po file.