There are two ways to carry markup, and they differ in what translation tools do with it: escape the HTML so it is ordinary text inside <source> and <target>, or represent each tag with an XLIFF inline element so tools treat it as a protected code. For line breaks, a literal newline is valid but only guaranteed to survive under xml:space="preserve"; is the explicit form.
Markup as text. XML requires < to be escaped in text, so <b> becomes <b>, or the whole value goes into a CDATA section:
<trans-unit id="save_hint">
<source>Click <b>Save</b> to continue</source>
<target><![CDATA[Klicken Sie auf <b>Speichern</b>, um fortzufahren]]></target>
</trans-unit>
Your code gets Click <b>Save</b> to continue back. To a CAT tool or TMS this is plain text, so the tags are counted, shown and editable, and a translator can change or drop them. In Twig, output is escaped by default, so {{ 'save_hint'|trans|raw }} is needed to render the tags.
Markup as inline elements. XLIFF 1.2 defines <g> for a paired code, <x/> for a standalone one, and <bpt>/<ept> and <ph> when the native code itself should travel inside the element. The XLIFF 1.2 representation guide for HTML maps <b> to <g ctype="bold"> (x-html- plus the element name for others) and <br> to <x ctype="lb"/>:
<trans-unit id="save_hint">
<source>Click <g id="1" ctype="bold">Save</g> to continue<x id="2" ctype="lb"/>or press Enter</source>
<target>Klicken Sie auf <g id="1" ctype="bold">Speichern</g><x id="2" ctype="lb"/>oder Enter drücken</target>
</trans-unit>
XLIFF 2.0 uses <pc> for paired and <ph> for standalone codes and keeps the original markup in <originalData>:
<unit id="save_hint">
<originalData>
<data id="d1"><b></data>
<data id="d2"></b></data>
<data id="d3"><br></data>
</originalData>
<segment>
<source>Click <pc id="1" dataRefStart="d1" dataRefEnd="d2">Save</pc> to continue<ph id="2" dataRef="d3"/>or press Enter</source>
</segment>
</unit>
Editors show these as locked tags that can be moved but not edited. The condition is that whatever reads the file must understand them. Angular's extractor writes its own placeholders (<x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/>) and resolves them at build time; a loader that takes the target as plain text will not, and there you escape instead.
Line breaks. Put the newline in the text and set xml:space="preserve", which in 1.2 goes on <file>, <group> or <trans-unit> (not on <source>), and in 2.0 may also go on <unit>, <source> or <target>:
<trans-unit id="address" xml:space="preserve">
<source>Line one
Line two</source>
</trans-unit>
Without it an XML parser still hands the newline to the application, but an XLIFF tool may normalize whitespace and return it as a space. is expanded to a line feed by every XML parser and survives editors that re-indent the file. Either way the parser turns CRLF into LF, so you get \n, not \r\n.
Published Oct 9, 2026 · Markdown version