FeaturesPluginPricingResources
Change Language
ResourcesHow to Translate XLIFF Files (Drupal, Symfony, Angular, iOS)

How to Translate XLIFF Files (Drupal, Symfony, Angular, iOS)

SimplePoTranslate TeamApril 16, 2026
How to Translate XLIFF Files (Drupal, Symfony, Angular, iOS)

A Drupal developer posted to Stack Overflow last week with a story that plays out thousands of times a year across enterprise localization teams. Her team exported a 40MB XLIFF file from their Drupal site. She opened it in a text editor, pasted chunks into Google Translate, reassembled the file, imported back into Drupal - and half the pages refused to render because translated strings contained malformed XML, escaped characters in the wrong places, and broken <g> tags where inline formatting had been destroyed.

XLIFF is the enterprise localization standard for a reason. It is XML-based, tool-agnostic, and supports the rich metadata that serious translation projects need: translation states, alternate translations, notes between translators and developers, and structured inline markup. But its very flexibility makes it easy to corrupt, and the tools that handle .po files safely often fall apart on XLIFF.

This guide walks through what makes XLIFF distinct, why generic translation approaches break it, and the right way to translate XLIFF files for Drupal, Symfony, Angular, and iOS - the four platforms where XLIFF is most commonly used in 2026.

What Is XLIFF (and Why Enterprise Teams Use It)

XLIFF stands for XML Localization Interchange File Format. It is an OASIS standard designed specifically for moving translation content between tools - content management systems, translation memory databases, CAT tools, and localization platforms. Unlike Gettext .po files (which store flat key-value pairs) or JSON locale files (which nest arbitrarily), XLIFF is a structured XML document with a standardized schema.

XLIFF 1.2 vs XLIFF 2.0

Two versions exist in the wild, and they are not fully compatible.

XLIFF 1.2 is the older, more widely deployed version. It uses <trans-unit> elements to wrap translatable content, with <source> and <target> children. Inline formatting uses <g>, <x>, and <bpt> / <ept> paired tags. Drupal's Translation Management Tool and many older platforms still export 1.2.

XLIFF 2.0 is the 2014 revision, simpler and cleaner. It uses <unit> and <segment> with <source> and <target>. Inline markup uses <pc> (paired code) and <ph> (placeholder). Symfony's translator component and modern iOS exports default to 2.0.

A translation tool that handles 1.2 does not automatically handle 2.0. The tag vocabularies are different, and the escaping rules differ slightly. Always check which version your platform exports before picking a translation pipeline.

The Anatomy of an XLIFF Unit

A minimal XLIFF 1.2 trans-unit looks like this:

<trans-unit id="msg_welcome" datatype="plaintext">
  <source>Welcome, <g id="1">%name%</g>!</source>
  <target state="needs-translation">Welcome, <g id="1">%name%</g>!</target>
  <note>Displayed on the homepage after login</note>
</trans-unit>

The <g id="1"> wraps a placeholder variable. The state attribute tells the platform this string needs translation. The <note> is a developer hint. A translator who understands XLIFF should produce:

<target state="translated">¡Bienvenido, <g id="1">%name%</g>!</target>

A translator who treats the file as plain text might produce any of these broken variants:

<target>¡Bienvenido, <g id="1">%nombre%</g>!</target>
<target>¡Bienvenido, &lt;g id="1"&gt;%name%&lt;/g&gt;!</target>
<target>¡Bienvenido, %name%!</target>

Each of these breaks the import differently. The first renames the variable. The second escapes the XML. The third drops the formatting tag entirely.

Bad Solutions (Why You Can't Just Translate XML)

Most teams start with the same three approaches and burn several days before giving up.

Feeding XLIFF to a Generic AI

Copy the file, paste into Claude or ChatGPT, ask for translation. The model usually does a reasonable job with the text but treats XLIFF tags inconsistently. Sometimes it preserves <g> tags, sometimes it translates the id attribute, sometimes it strips them entirely. Validation fails. Your import throws XML parse errors.

Using a CAT Tool Without XLIFF Support

Tools like Poedit are built for .po format. They may open XLIFF but treat it as a generic text container. Inline tags are not locked. Placeholders are not protected. You get a translation that looks fine in the editor but fails schema validation on import.

Writing a Custom Script

Your team writes a Node or Python script that parses XLIFF with xml2js, extracts source strings, calls Google Translate, and writes targets back. It works for 90% of strings. The other 10% - strings with nested formatting, plural groups, or special characters - break in ways that only show up after you have already shipped.

The same "flexible format meets naive translator" failure mode affects i18next JSON and Gettext .po files. Our guide on translating i18next JSON files for React and Next.js and our post on how to translate .po files without breaking code variables cover the parallel problems for those formats.

The Right Way: Syntax-Aware XLIFF Processing

A proper XLIFF translation pipeline follows the same principles as our PO engine, adapted for XML.

Parse, Don't Regex

Treat XLIFF as a structured document. Parse it with a real XML parser, build a tree, and walk the <trans-unit> (or <unit> for 2.0) elements. Attempting to regex-match source and target content is the fast path to corrupted files.

Lock Inline Tags Before Translation

Every <g>, <x>, <bpt>, <ept>, <ph>, <pc> inside a <source> must be preserved by position and id attribute. Replace them with numeric placeholders before sending text to the LLM, then reinsert the original tags with their attributes after translation returns.

Respect the State Machine

XLIFF units have state attributes: new, needs-translation, translated, reviewed, final, signed-off. A pipeline should only translate units in new or needs-translation state, and set the output state to translated (not final - a reviewer should still verify).

Preserve Structure Beyond Translation Units

XLIFF files contain headers, metadata, file-level attributes, notes, and alternate translations (<alt-trans>). These must survive unchanged through the round trip. Stripping or reordering them breaks round-trip compatibility with the source platform.

Validate Before Delivery

Before returning translated XLIFF, validate against the schema. XLIFF 1.2 has an official XSD. XLIFF 2.0 has its own. A tool that cannot self-validate is a tool that will ship you broken files.

Platform-Specific Notes

Each major platform that uses XLIFF has quirks worth knowing.

Drupal

Drupal's Translation Management Tool (TMGMT) exports XLIFF 1.2. Content types include nodes (pages, articles), taxonomy terms, and configuration. TMGMT wraps each translatable field in a separate <trans-unit> with a Drupal-specific ID format (fieldname:delta:format).

Gotcha: Drupal stores text format information (filtered HTML, full HTML, plain text) alongside content. Translation must preserve HTML markup when format allows it, strip to plain text when format does not. Your pipeline needs per-field awareness.

Symfony

Symfony's translation component uses XLIFF 2.0 by default (since Symfony 4). Strings live in translations/messages.xx.xliff. Symfony supports ICU message format inside XLIFF, meaning a single unit can contain {count, plural, one {...} other {...}} structures.

Gotcha: ICU plural categories inside XLIFF need double protection. The XML tags stay intact, AND the ICU keywords (plural, one, other, =0) must not be translated. Many XLIFF tools handle one layer but not both.

Angular i18n

Angular exports XLIFF 1.2 or 2.0 via the ng extract-i18n command. Files contain component template strings, with <x> tags representing Angular expressions and interpolations like {{ count }}.

Gotcha: Angular uses id hash collisions across identical source strings. A translation must maintain unit IDs exactly or Angular cannot match them on import. Renaming id attributes during processing is an instant break.

iOS (Xcode Export)

Xcode exports XLIFF 1.2 for app localization via Product > Export Localizations. Strings come from Localizable.strings, Info.plist entries, storyboards, and XIBs. iOS plural rules live in .stringsdict files exported as additional trans-units.

Gotcha: iOS storyboard strings reference UI element IDs. These must not be altered. Also, Xcode requires the target-language attribute to exactly match its expected locale format (es, not es-ES, in some contexts) or it silently ignores the import.

Translating XLIFF with SimplePoTranslate

SimplePoTranslate supports XLIFF on Pro and Lifetime plans. The workflow is the same as for .po files.

1. Export Your XLIFF

From your source platform, export one or more .xliff files. For Drupal, use TMGMT's export action. For Symfony, find translations/messages.en.xliff. For Angular, run ng extract-i18n --format=xlf2. For Xcode, use the Localization export.

2. Upload to SimplePoTranslate

Drag the file into the dashboard. The platform auto-detects XLIFF version (1.2 or 2.0), parses the structure, and identifies translatable units. Pick your target language and tone.

3. Syntax-Aware Translation

Inline tags, ICU parameters, placeholders, and unit IDs are locked before translation. The underlying AI engine sees only clean text with context. Translated text is reinserted into the exact original structure, states are updated, and the file is validated against the XLIFF schema before delivery.

4. Download and Import

Download the translated XLIFF (plus .po, .json, and .php equivalents if you need cross-platform). Import into your source platform. Validate by rendering a few translated pages or views before rolling out.

# Angular example
ng extract-i18n --format=xlf2 --output-path=src/locale
# upload src/locale/messages.xlf to SimplePoTranslate
# download messages.es.xlf
# reference in angular.json i18n configuration
ng build --localize

5. Integrate Into CI

Once you trust the pipeline, automate it. Export XLIFF on every release, submit via API, download translated files, commit to repo, deploy. This is the same CI-friendly pattern many agencies use for WordPress .po translation - see our post on cloud-based translation without broken sites for the architectural pattern.

Putting It All Together

XLIFF is the right tool for serious localization work - structured, tool-agnostic, and rich enough to carry project metadata across systems. But its XML structure is also brittle. Every tag, attribute, and state value has semantic weight, and a translator that does not understand XLIFF as a format will corrupt your file in ways that may not surface until import fails or a user reports broken UI.

The safe approach is syntax-aware: parse the XML, lock the structural elements, translate only text surfaces with context, validate against schema before delivery. This is true whether you are shipping a Drupal site, a Symfony API, an Angular SPA, or an iOS app. The platform differs, but the XLIFF discipline does not.

Ready to translate XLIFF files for Drupal, Symfony, Angular, or iOS without broken tags? Try SimplePoTranslate free - no credit card required. Upload .xliff, download safe translations, import into your platform.