Translate Shell Messages with gettext Without Guessing the Locale
You will finish with a small, testable workflow for translating a message from a GNU gettext catalogue, choosing a domain explicitly, and translating several messages from standard input. The examples use gettext-runtime 0.21 from the installed gettext-base package, version 0.21-14ubuntu2.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and a message catalogue that contains the message you want to translate. This guide only reads catalogues and writes to standard output. It does not install translations, change locale settings, or edit application files. No elevated privileges are needed.
1. Check the installed command
Confirm which executable is being used and record its version. These are ordinary read-only checks:
$ command -v gettext
/usr/bin/gettext
$ gettext --version
gettext (GNU gettext-runtime) 0.21
$ dpkg-query -W -f='${Package} ${Version}\n' gettext-base
gettext-base 0.21-14ubuntu2
The local manual describes two forms. The first translates one message, optionally with a text domain. The second, -s, translates each argument as though the command were an echo-like utility.
2. Translate one message with an explicit domain
Pass the domain with -d and put the original message after it. The domain is normally the application name, not the language name:
$ LANG=de_DE.UTF-8 gettext -d apt 'Reading package lists'
Reading package lists
This machine has the apt catalogue, but the displayed text can remain English when the selected locale is unavailable, the catalogue has no entry for that message, or the message is already untranslated. A successful exit status does not prove that a translation was found. Compare the output with the original message if that distinction matters.
Checkpoint: test the fallback deliberately:
$ gettext -d definitely-not-a-domain 'Message used for a fallback test'
Message used for a fallback test
$ printf 'exit status: %s\n' "$?"
exit status: 0
A missing catalogue is not a shell error in this invocation. Treat unchanged output as a possible missing translation, not as evidence that the requested language is active.
3. Let the environment select the domain
If you omit the domain argument, gettext reads the TEXTDOMAIN environment variable. Set it for the command rather than exporting it across an entire interactive session:
$ TEXTDOMAIN=apt LANG=de_DE.UTF-8 gettext 'Reading package lists'
Reading package lists
The domain is only one part of catalogue selection. The locale variables still determine which language is requested. If your application uses a non-standard catalogue directory, set TEXTDOMAINDIR to its parent directory:
$ TEXTDOMAINDIR=/path/to/locale TEXTDOMAIN=my-app \
LANG=fr_FR.UTF-8 gettext 'Welcome'
Welcome
Replace /path/to/locale and my-app with real values. Do not use sudo to compensate for an incorrect domain or directory. First check that the catalogue exists below the directory in the locale and domain layout used by your application.
4. Translate several fixed messages
Use -s when each message is a separate argument. This keeps the command simple for a short list:
$ TEXTDOMAIN=apt LANG=C gettext -s 'Reading package lists' 'Reading state information'
Reading package lists Reading state information
As with echo, the arguments are emitted on one line with spaces between them and a trailing newline. The output is still translated message by message; it is not a translation of one combined sentence. Use separate calls when the messages must appear on separate lines:
$ TEXTDOMAIN=apt gettext 'Reading package lists'
Reading package lists
$ TEXTDOMAIN=apt gettext 'Reading state information'
Reading state information
For a script, keep the original message stable. Catalogues use that message, called the message identifier, as the lookup key. Changing punctuation, capitalisation or whitespace can make a lookup miss even when a similar translation exists.
5. Preserve or expand escape sequences intentionally
By default, backslash escapes are not expanded. The -e option enables expansion of some escape sequences, while -n suppresses the final newline:
$ gettext -e 'first line\nsecond line'
first line
second line
$ gettext -n 'no trailing newline'
no trailing newline$
The prompt returns immediately after the final word in the second example, so the next shell prompt appears on the same line. That is useful when composing output, but awkward for normal status messages. Keep -n out of ordinary diagnostic commands unless the caller provides its own line ending.
Do not pass untrusted text as a shell fragment. Quote message identifiers, and remember that shell expansion happens before gettext sees the argument. If a message comes from a file or a pipeline, use the stream form only after checking what input the command accepts.
6. Add context only when the catalogue has it
The -c option supplies a context string for a message whose wording is shared by different parts of an application. It does not create a translation or make an ordinary entry more specific:
$ gettext -c 'menu' -d apt 'Reading package lists'
Reading package lists
A context lookup succeeds only when the catalogue was built with the same context and message identifier. If the catalogue contains an uncontexted entry instead, gettext can return the original identifier. Keep the context in the program and in the translation source exactly aligned.
7. Diagnose an unexpected result
Work through these checks in order:
- Run
command -v gettextandgettext --versionto confirm the binary. - Set
-d DOMAINexplicitly so an inheritedTEXTDOMAINcannot distract you. - Print the locale variables with
localeand check that the requested locale is generated on the host. - Check
TEXTDOMAINDIRand the catalogue path without changing files. - Compare the exact message identifier, including punctuation and whitespace, with the source used to build the catalogue.
Use gettext --help for the installed option set. The manual lists -E as ignored for compatibility; do not add it to new scripts. The standard search directory on this installation is /usr/share/locale.
Done means
- You confirmed the installed gettext-runtime and gettext-base versions.
- You can translate one identifier with an explicit domain.
- You know that unchanged output can mean a missing locale, catalogue or entry.
- You can select a domain with
TEXTDOMAINand a custom catalogue root withTEXTDOMAINDIR. - You use
-s,-e,-nand-conly when their output or lookup semantics are wanted. - No system files, catalogues or service configuration were changed.