The first entry of a catalogue has an empty msgid. Its
msgstr is not a translation but a block of header fields, one per line,
each written Name: value and ended by \n. In the compiled
catalogue the same entry is the translation of the empty string, and the program that
loads the catalogue reads the encoding and the plural rule from it.
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-11-13 22:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
Fields
| Field | Meaning |
|---|---|
Project-Id-Version | The package name and version. Filled in by xgettext when it can, otherwise by the translator. |
Report-Msgid-Bugs-To | An address or URL for reporting problems in the original strings: a message that is not a sentence, an unclear term, a wrong assumption about dates or money, a plural problem, spelling or formatting. Filled in by xgettext. |
POT-Creation-Date | When the template was made. Filled in by xgettext. msgfmt 1.0 removes it from the header it writes to the MO file. |
PO-Revision-Date | When the catalogue was last revised, left for the PO editor to fill in when it saves. |
Last-Translator | The translator’s name and e-mail address, without double quotes. |
Language-Team | The English name of the language and the e-mail address or home page of the translation team. |
Language | The language code. |
MIME-Version | Written 1.0 in the headers the tools write. The manual’s chapter on the header does not describe it. |
Content-Type | text/plain; charset=CHARSET: the encoding of the file. Needed by msgmerge and msgfmt. The translator replaces CHARSET with the encoding of the language, or UTF-8. |
Content-Transfer-Encoding | Set to 8bit. |
Plural-Forms | Optional: needed only when the file has plural entries. nplurals=N; plural=EXPRESSION;: see below. |
Other fields exist, such as X-Generator that an editor adds to name
itself; the manual’s header chapter does not describe any beyond the table. An editor
keeps fields it does not know.
msgmerge and msgfmt read the header; msgfmt
--check-header checks that it is there and has the right contents.
Language codes
The Language field, and the language part of the file name, take one of
three forms: ll, the ISO 639 code in lower case; ll_CC, with
an upper-case ISO 3166 country code; or ll@variant (or
ll_CC@variant), with a lower-case variant such as a script or a dialect.
In the field, the code of the main dialect may be shortened, so de means
de_DE, and an encoding suffix or an irrelevant variant such as
@euro is left out.
The encoding
The charset in Content-Type is the encoding of every
string in the file, comments included. The manual advises checking the language’s
locale with locale charmap and asking the team when the answer is
C or ASCII, and recommends UTF-8 when the typographic quotation marks
are used with the ISO-8859 encodings. The value CHARSET of a template is
a placeholder, not an encoding.
Plural-Forms
The value is two assignments: nplurals=N; plural=EXPRESSION;.
N is the number of plural forms the language has, and the expression
gives the number, from 0 to N minus 1, of the form to use for the
number n. A program that asks for a plural message with
ngettext passes n, evaluates the expression and takes the
translation msgstr[that number].
"Plural-Forms: nplurals=2; plural=n != 1;\n"
The expression
It is an expression of C, with these parts only:
- the variable
nand numbers written as decimal digits (an unsigned integer; there is no sign, hexadecimal or octal); - the operators
?:,||,&&,==,!=,<,<=,>,>=,+,-,*,/,%and the unary!; - parentheses.
There is no unary minus and there are no bit operators. The operators bind as in C,
from the loosest to the tightest: ?: (taken from the right),
||, &&, == and !=, the
four comparisons, + and -, *
/ and %, and !. The reader skips spaces and
tabs, and ends the expression at a semicolon, a line break or the end of the string. A lone
=, & or | is an error.
How a program finds it
The loader looks for the text nplurals= and the text
plural= anywhere in the header entry. After nplurals= it
skips white space and reads a decimal number; the expression starts straight after
plural=. If either is missing or the expression does not parse, the
rule is the Germanic one, two forms and n != 1. When a program asks for
the translation of number n, a result that is not a number the language
can use, or an expression that fails to evaluate, selects form 0, and so does a value
of nplurals or more.
The rules the manual lists
| Languages | Plural-Forms |
|---|---|
| Japanese, Vietnamese, Korean, Thai | nplurals=1; plural=0; |
| English, German, Dutch, Swedish, Danish, Norwegian, Faroese, Spanish, Portuguese, Italian, Greek, Bulgarian, Finnish, Estonian, Hungarian, Hebrew, Indonesian, Turkish, Esperanto | nplurals=2; plural=n != 1; |
| French, Brazilian Portuguese | nplurals=2; plural=n>1; |
| Latvian | nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2; |
| Irish | nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2; |
| Romanian | nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2; |
| Lithuanian | nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2; |
| Russian, Ukrainian, Belarusian, Serbian, Croatian | nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; |
| Czech, Slovak | nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; |
| Polish | nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; |
| Slovenian | nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3; |
| Arabic | nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5; |
The manual also discusses large numbers and negative or fractional values, which the rules above are not written for. This page does not cover them.
Not described
Known, not yet covered
- Reference: how the manual treats large, negative and fractional
numbers with
ngettext, and the plural rules of the languages beyond the ones in the table (the manual’s list goes on; the part of the text read for this page ended before its end). - Reference: what the evaluator does when a division or a remainder has a zero divisor; the loader’s source shows a failed evaluation selecting form 0, but the part that fails was not read.