Pick Singular or Plural Text with ngettext
English speakers hard-code "if count is 1" until a translated string needs three plural forms, which is the job ngettext was built for. This guide gets you to a shell command that selects singular or plural text from a gettext message catalogue, plus a reliable way to pick the catalogue and check the result. The examples use GNU gettext-runtime 0.21, supplied here by Ubuntu package gettext-base 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 the ngettext command. This guide only reads existing catalogues: it does not create, edit or install translations, and none of the examples need elevated privileges.
1. Check the installed command
Confirm which executable will run and record its version:
$ command -v ngettext
/usr/bin/ngettext
$ ngettext --version
ngettext (GNU gettext-runtime) 0.21
Checkpoint
If the command is missing, stop here and install the package through your normal system administration process. Do not work around a missing catalogue by changing system files.
2. Give ngettext both message forms and a count
The basic form is ngettext MSGID MSGID-PLURAL COUNT. The first two arguments are the source singular and plural messages, and the count tells gettext which grammatical form to request:
$ printf '%s\n' "$(ngettext 'one file' 'many files' 1)"
one file
$ printf '%s\n' "$(ngettext 'one file' 'many files' 2)"
many files
- No automatic newline. The command writes the selected message but does not add a newline; the surrounding
printfmakes terminal output and log lines readable. - Command substitution strips trailing newlines too. If a translated message ends in one, use a direct redirection instead when preserving exact output matters.
- No catalogue, no translation. With no matching catalogue, GNU gettext falls back to the message text on the command line. That makes the example reproducible in the default
Clocale, it is not evidence a translation was found.
3. Understand what the count actually does
Do not hard-code an English rule such as "only 1 is singular" around a translated message. The catalogue's language supplies the plural rules, and a language can have more than two plural forms, so the same count can select a different form elsewhere.
For a quick local check, these counts select the plural fallback in the installed command:
$ ngettext 'one file' 'many files' 0
many files$ ngettext 'one file' 'many files' -1
many files
The output runs together because ngettext never appends a newline. The important boundary is the argument COUNT, not text parsing. Pass the number you are actually describing and let the active catalogue apply its own language-specific rule.
Checkpoint
If a script needs to test success, save the status immediately after the command. A successful status only means the lookup completed, it does not guarantee a non-empty or translated string came back.
4. Select the message catalogue
A text domain identifies the catalogue, commonly by application or package. Set it explicitly with -d when a script should not depend on the caller's environment:
$ printf '%s\n' "$(ngettext --domain=my-app 'one report' 'many reports' 3)"
many reports
This example uses a domain name but no installed my-app catalogue, so the output is the untranslated fallback. The long option is useful in scripts because its purpose is visible right beside the value.
The positional form works too:
$ printf '%s\n' "$(ngettext my-app 'one report' 'many reports' 1)"
one report
If the domain is omitted, ngettext reads TEXTDOMAIN from the environment. Catalogue lookup normally uses /usr/share/locale; set TEXTDOMAINDIR when the catalogue lives elsewhere. Both variables only select where to look, they do not generate translations.
5. Add context when the same words mean different things
Use -c or --context when one source phrase needs separate translations in different situations. The context becomes part of the lookup key, so it has to match the context used when the catalogue was built:
$ printf '%s\n' "$(ngettext --context=menu 'Open' 'Open' 1)"
Open
This machine has no matching application catalogue for that example, so it prints the source singular text. Context does not select a translation by itself, and changing it will not repair a missing or badly installed catalogue.
Keep message IDs stable. Changing the singular text, plural text, domain or context can make an existing translation unreachable until the catalogue is regenerated.
6. Handle escape sequences on purpose
By default, backslash sequences come back as plain text. The -e option expands the escape sequences documented for this command, including \n for a newline:
$ ngettext -e 'one\nfile' 'many\nfiles' 1
one
file
Use -e only when escape expansion is genuinely part of the message contract, it is easy to make a translated backslash sequence change meaning unexpectedly. The -E option is accepted for compatibility and is ignored, so it does not turn expansion on.
7. Diagnose the common failures
- Output still in English? Check the domain, locale and catalogue directory first, then confirm the catalogue actually contains the exact singular, plural and context keys. A fallback message is not an error, it is the expected behaviour when no translation exists.
- Output joined to the next prompt or log line? Add a newline at the output boundary, for example with
printf '%s\n' "$(...)". Do not assumengettextsupplied one.
For syntax and version details, use the installed help and version output:
$ ngettext --help
$ ngettext --version
ngettext (GNU gettext-runtime) 0.21
There is no undo step for any of this. These commands read catalogues and environment variables without touching files, services or persistent system state.
Done means
- Argument order right: you supplied singular text, plural text and a count in that order.
- Plural logic understood: you know plural selection comes from the active language catalogue, not a shell-side English rule.
- Domain set deliberately: you set it explicitly when the caller's
TEXTDOMAINshould not matter. - Context used correctly: only when the catalogue was produced with the same context.
- Output handled cleanly: you dealt with the missing newline and used
-eonly on purpose.