XLIFF 1.2 is what most translation management systems hand back after a translation round. It is bilingual by design: every <trans-unit> has a <source> element with the original English (or whatever the source language is) and a <target> element with the translation. Your web app, on the other hand, consumes a single-language JSON bundle — typically the i18next-style flat map of keys to translated strings. Going from XLIFF to runtime JSON therefore involves a conscious choice: keep the source strings (for an English bundle) or keep the targets (for a localized bundle). Most ad-hoc scripts get this wrong by emitting whichever side appears first in the XML.
i18n-convert extracts the <target> text from each <trans-unit>, uses the id attribute as the JSON key, and sorts the output alphabetically for stable diffs. Entries whose target is missing — which happens for any unit that has not yet been translated — emit an empty string rather than being dropped, so your runtime keeps the keys and can fall back through your i18n library's resolution chain. Because XLIFF can carry source strings that have no corresponding target, the CLI surfaces a data-loss warning and requires --force to proceed; this acknowledges that the source-side text is being discarded by design.
Command
i18n-convert simple.xliff --to i18next --force -o messages.json
Input
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="de" datatype="plaintext" original="messages.properties">
<body>
<trans-unit id="greeting">
<source>Hello</source>
<target>Hallo</target>
</trans-unit>
<trans-unit id="farewell">
<source>Goodbye</source>
<target>Auf Wiedersehen</target>
</trans-unit>
<trans-unit id="untranslated">
<source>Not yet translated</source>
</trans-unit>
</body>
</file>
</xliff>
Output
{
"farewell": "Auf Wiedersehen",
"greeting": "Hallo",
"untranslated": ""
}