XLIFF 2 is the format translation vendors hand back; PO is the format your build pipeline actually compiles. The mismatch is structural rather than semantic: XLIFF 2 marks every segment with an explicit state attribute (initial, translated, reviewed, final) and groups them inside <unit> elements with stable ids, while PO has no first-class notion of translation state at all — an entry is "untranslated" simply because its msgstr is the empty string. Most XLIFF-to-PO converters either skip the untranslated units entirely or invent fake msgstr values from the source, both of which silently break downstream tooling that distinguishes "needs translation" from "translated to the same string as the source."
i18n-convert walks each XLIFF 2 unit, takes the <source> as the msgid, and emits the <target> as msgstr if it exists or an empty string if the segment state is initial. This is exactly the convention msgfmt and gettext-aware runtimes expect — an empty msgstr means "fall back to the source string at runtime." The fixture below is the canonical simple.xliff from the project's test suite: two translated units and one untranslated unit in state initial. Notice how "Not yet translated" round-trips with an empty msgstr, preserving the unfinished-work signal that an XLIFF reviewer originally set. The header msgid entry is emitted with empty fields because XLIFF 2 does not carry the PO header metadata.
Command
i18n-convert simple.xliff --to po -o messages.po
Input
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="de">
<file id="f1" original="messages.json">
<unit id="greeting">
<segment state="translated">
<source>Hello</source>
<target>Hallo</target>
</segment>
</unit>
<unit id="farewell">
<segment state="translated">
<source>Goodbye</source>
<target>Auf Wiedersehen</target>
</segment>
</unit>
<unit id="untranslated">
<segment state="initial">
<source>Not yet translated</source>
</segment>
</unit>
</file>
</xliff>
Output
msgid ""
msgstr ""
msgid "Hello"
msgstr "Hallo"
msgid "Goodbye"
msgstr "Auf Wiedersehen"
msgid "Not yet translated"
msgstr ""