“1 file, 2 files” is one plural rule. Russian has more, Arabic has six, and Japanese makes no distinction at all. Every localisation format needs a way to carry those forms, and XLIFF, the format built for carrying translations, did not have one until very recently. What you find in exported files is a set of conventions layered on top, and each needs handling differently.
The categories every format is mapping to
Unicode’s CLDR names six plural
categories: zero, one, two, few, many and other. Every language needs
other. The categories are defined by how a sentence changes when the number changes,
not by traditional grammar, so one is not always exactly 1. Arabic uses all six; some
languages, such as Japanese, Korean, Vietnamese and Thai, use a single form.
That is why plural forms cannot be translated one to one. An English string has two forms; a Polish gettext file has three, and a Japanese one has one. Any format that fixes the number of forms to the source language’s count is wrong for most target languages.
XLIFF 1.2: no plural element
The XLIFF 1.2 standard defines no plural element or attribute; the word “plural” does
not appear in it. It does let tools invent their own restype values, as long as they
start with x-, and that is where the best-known convention comes from.
A separate OASIS committee guide for mapping gettext PO files to XLIFF, which only reached Committee Draft stage in 2006, turns each PO plural entry into a group with one unit per plural form of the target language. Simplified, for Polish:
<group restype="x-gettext-plurals">
<trans-unit id="1[0]">
<source>%d file</source>
<target>%d plik</target>
</trans-unit>
<trans-unit id="1[1]">
<source>%d files</source>
<target>%d pliki</target>
</trans-unit>
<trans-unit id="1[2]">
<source>%d files</source>
<target>%d plików</target>
</trans-unit>
</group>
It is widely used and still not part of the standard, so a tool that knows XLIFF 1.2 perfectly may treat those units as three unrelated strings.
XLIFF 2.0 and 2.1: still none
Neither 2.0 nor 2.1 has a plural module; their module lists contain no plural feature.
Plural support arrives in XLIFF 2.2
as the Plural, Gender, and Select Module. At the time of writing, the latest 2.2
publication is a Committee Specification dated 13 March 2025, not yet a full OASIS
Standard. It adds two attributes in the namespace urn:oasis:names:tc:xliff:pgs:1.0:
pgs:switch on a <unit> names the variable, and pgs:case on each <segment> names
the plural category or number that segment covers. In the module’s own example, an ICU
plural message becomes one unit with one segment per case.
Angular: ICU inside the segment
Angular keeps the whole ICU plural expression inside a single unit’s source and target:
{count, plural, =0 {no files} one {one file} other {{{count}} files}}
Translators translate the text inside each branch and may add or remove plural cases
for the target language. The keywords, count, plural, one, other, must stay as
they are. Angular’s ng extract-i18n writes XLIFF 1.2 or XLIFF 2, both as .xlf. When a
select or nested expression is extracted as a unit of its own, the surrounding unit keeps
a placeholder for it, and deleting that placeholder removes the expression from the app.
Xcode: one unit per category
When Xcode exports localisations,
it adds the right plural variants for each target language to the XLIFF file.
Translators fill in one translation per plural category, and Xcode rebuilds the plural
resource on import. From Xcode 15, string catalogs (.xcstrings) replace .stringsdict
files for plurals; for Russian, Xcode adds One, Few, Many and Other variants. Apple’s
older .stringsdict page lists a different set for Russian, which is a reminder to trust
the variants the export actually contains, not a table in a document. The
Xcode page covers translating these files.
gettext PO: forms counted by the header
A PO
plural entry has msgid (singular), msgid_plural (plural) and one msgstr[n] line per
plural form of the target language. How many forms there are, and the formula that picks
one, live in the header’s Plural-Forms line, where nplurals gives the count. A
language with a single form has nplurals=1.
What a translator has to check
- The number of forms matches the target language, not the source. Two English forms becoming two Polish forms means one form is missing.
- ICU keywords and selectors are never translated.
pluralstayspluralandonestaysone, and placeholders inside a branch stay exactly as written. Only the words inside the branches change. - Each form reads correctly with its numbers. Read
fewwith 2, 3 or 4, andmanywith 5 or 11, in the languages that have them. - No form is left empty. In gettext an empty translation falls back to the source text at runtime, which is the bug users see.
How xliff.ai handles plurals
Being precise about this is more useful than a feature list:
- PO files get one row per
msgstr[n]form the entry has, each labelled with its form number in the details. The first form’s source ismsgid; the others showmsgid_plural. The editor readsnpluralsfrom the header and warns when an entry has fewer forms than it says, but it cannot add forms. A.pottemplate always gives two forms, because a template does not know its language yet; to translate into Russian, Polish or Arabic, start from a.powhose header already declares the rightnplurals, not from a renamed.pot. See PO translation. - XLIFF plural conventions (gettext groups, Xcode variants) open as ordinary rows, one per unit. They are not grouped or labelled as plural categories, so the unit IDs are your guide to which row is which form.
- ICU messages are not parsed. QA does not check that the selector keywords survived,
so check those by eye; the placeholder check still catches a
missing
{count}inside a branch.
Plurals are where “the file imported fine” and “the app reads correctly” part company. Ten minutes reading each form against real numbers is cheaper than the bug report.