Fearless application of translations with xlf-stash

17.09.2025

No one wants to merge .xlf files.

Angular documentation explains well what to do with XLIFFs, but lacks an answer to when.

Now, the smartest move would be:

For ongoing development, of course it’s going to be a moving target, with some percentage of recently changed content that hasn’t undergone localization at a moment. But overall it should work just fine if done periodically, and basing on your production/release version guarantees the translation files won’t drift away from the rest of the code (perhaps with help from xliffmerge).

But, what if you work at less-than-smartest organization?

You may find yourself tasked with translating some feature on the respective branch, let’s say for proofreading or UAT. Copy from some lame Excel table to .xlf, commit and done.

Well, not quite. That early translations you meticulously entered into the XLIFFs scream conflict as soon as you try to integrate your changes with upstream. Overwhelmed with a messy diff, you see no sensible way to salvage the localized content.

Let me repeat subtitle of this post: don’t merge .xlf files.

They ought to be generated, edited or replaced (in case of delivery from translation bureau), and generally checked in to repository. They’re just very hard to merge, as they are compared by git as any other text file, without any notion of the process, ids, etc.

In such situations, all we want to avoid is redoing the string substitution. We’re developers after all, not some clerks.

That’s exactly where xlf-stash shines. It’s a CLI script you can use to set aside a bunch of translations and re-apply them to XLIFF files. Inspired by git, it shares the same concepts and actions, such as:

How to xlf-stash

The only prerequisite is to mark the translations with custom state of x-new. You do it only once, at the same time as the actual translation:

<!-- messages.pl.xlf on the feature branch -->
<trans-unit id="...">
  <source>Hello!</source>
  <target state="x-new">Cześć!</target>
</trans-unit>

International versions of the app will build normally. When you’ve got all the content in place and signed-off, still on the feature branch, run:

xlf-stash

Now, do the git merge. Dismiss all changes made to .xlf files - the entries you care about are stored in the stash. Resolve to the main branch version, and regenerate the translation files so they contain the strings you brought from the feature branch:

ng extract-i18n # or some variant involving xliffmerge - in the end, there are files for target languages

Your current state should consist of merged code, and the XLIFFs fully reflecting the i18n markings. The final touch is to put translations back:

xlf-stash pop

And you’re good to go, without the headache of moving the content manually.