Build a Python Translation Template with pygettext3.12

Inherit an old localisation pipeline and pygettext3.12 is often the only tool that still knows how it was built. This guide extracts marked Python strings into a gettext Portable Object Template file, checks the entries, and leaves the source tree untouched. It targets the pygettext3.12 executable installed with Python 3.12.3 on this machine. Allow about fifteen minutes for a small project.

You need a shell, Python source files, and permission to write the output directory. Nothing here needs elevated privileges. The tool calls itself deprecated in its own help text and manual: use GNU xgettext for a new project when it covers your workflow, and reach for this guide only when you must reproduce an existing pygettext-based build.

1. Check the executable and its version

Start read-only, so you are not accidentally testing a different Python installation:

$ command -v pygettext3.12
/usr/bin/pygettext3.12
$ pygettext3.12 --version
pygettext.py (xgettext for Python) 1.5
$ dpkg-query -W -f='${Package} ${Version}\n' python3.12
python3.12 3.12.3-1ubuntu0.17

Both local manpages describe the interface as pygettext 1.4, while the installed script reports 1.5. Trust what the executable actually does: do not assume a newer online manual or a different distribution's package shares its defaults.

Checkpoint: if command -v finds a different path, stop and decide whether that is really the interpreter you want before generating anything.

2. Mark strings the way pygettext expects

By default it looks for calls to _(), and the call must contain a literal string. Build or inspect source that follows this convention:

from gettext import gettext as _

title = _('Account settings')
message = _('Your export is ready.')

def greet(name):
    return _('Hello, %s') % name

3. Generate a named template

Run the extractor from the project directory, with a clear domain name; -d app writes app.pot:

$ pygettext3.12 --default-domain=app src/messages.py

There is normally no success message, so check the file yourself before opening it:

$ test -s app.pot && echo 'template created'
template created
$ sed -n '1,80p' app.pot
# SOME DESCRIPTIVE TITLE.
...
#: src/messages.py:3
msgid "Account settings"
msgstr ""

Location comments use GNU style by default and point back to the source line. Header timestamps and entry ordering can shift between runs, so review the message entries rather than diffing the whole file as a fixture.

4. Process more than one file safely

List the files explicitly when the project is small:

$ pygettext3.12 -d app src/messages.py src/errors.py src/cli.py
$ test -s app.pot && grep -n '^msgid ' app.pot
5:msgid ""
12:msgid "Account settings"
16:msgid "Your export is ready."

For a bigger tree, use the output-directory option with a file list your build system generates, and check that list before you run pygettext against it. A shell glob can quietly pull in tests, generated files or a previous output file, and generated Python can add messages you never meant to translate:

$ mkdir -p build/locale
$ find src -type f -name '*.py' -print
src/messages.py
src/errors.py
$ pygettext3.12 --output-dir=build/locale --default-domain=app src/messages.py src/errors.py
$ test -s build/locale/app.pot && echo 'checked output path'
checked output path

The only state change here is mkdir creating the build directory. If a temporary output file outlives its usefulness, remove that specific file once you have checked it; never a broad recursive delete inside a project tree.

5. Match the keyword convention your project actually uses

Projects calling gettext() rather than _() need an explicit keyword, given with -k or --keyword, repeatable as needed:

$ pygettext3.12 --keyword=gettext --keyword=ngettext -d app src/messages.py

The default keyword on this installed script is _. -K or --no-default-keywords drops it, while anything you supplied explicitly stays active:

$ pygettext3.12 --no-default-keywords --keyword=gettext -d app src/messages.py

Use -K only when you are deliberately enforcing one naming convention. A common mistake: add it while the source still calls _(), then assume extraction is broken when the expected entries vanish.

6. Add docstrings or control location comments deliberately

Use -D when the project treats docstrings as translatable content:

$ pygettext3.12 --docstrings -d app src/messages.py

Keep this run separate from the normal marked-string one until you have reviewed the result; a documentation-heavy module can produce a far bigger template than you expected. Use --no-location when output must carry no filename or line comments, or keep the default --add-location when reviewers and translators benefit from source references.

-E replaces non-ASCII characters with octal escapes. It can help an old pipeline but makes a template harder to read, so leave it off unless the consuming tool actually needs it. -o - sends the template to standard output for inspection; do not confuse that with a saved catalogue:

$ pygettext3.12 --no-location -o - src/messages.py | sed -n '/^msgid /,/^$/p'
msgid ""
msgid "Account settings"
msgid "Your export is ready."

7. Diagnose an empty or unexpected template

Check the command status and the input path first:

$ test -r src/messages.py && echo 'input is readable'
input is readable
$ pygettext3.12 -v -d app src/messages.py
src/messages.py

Verbose mode reports which files were processed; it does not print every extracted string. If the template has nothing but its empty header, check the source for the selected keyword and literal arguments, confirm -K was not used by accident, and check that a previous run did not write the file somewhere else with a different domain name or output path.

Do not treat a non-zero exit status as a reason to run this as root. Fix the source path, output permission, option spelling or Python syntax first. To keep a known-good template safe, write the new one to app.pot.new, inspect it, then replace the old file explicitly: redirection and output options can silently overwrite an existing destination, so never point a first test at your only copy.

Done means