When translation API calls silently corrupt an HTML tag, swallow a {variable} placeholder, or reorder ICU message tokens, the damage rarely surfaces during development. It surfaces in production, as broken UI layouts, crashed mobile apps, or garbled email templates that reach real customers. The core challenge is straightforward: machine translation engines are optimized to transform natural language, not to respect the structured markup and code tokens embedded within it. Every major translation API handles this differently, and misconfiguration is the norm rather than the exception. This guide walks through the concrete payload structures, API-specific parameters, and verification strategies you need to keep format and placeholder integrity intact when translating at scale.
If you're already dealing with broken markup in your localization pipeline, talk to our API integration team to see how Ollang enforces format safety across providers.
How Translation APIs Handle HTML and Markdown Tags
Ollang's integration layer normalizes tag handling differences across providers so you can apply a consistent payload pattern and verification flow.
DeepL: tag_handling, non_splitting_tags, and splitting_tags
DeepL provides the most granular tag-handling controls of any major translation API. The tag_handling parameter accepts "xml" or "html" and tells the engine to parse and protect markup rather than treating it as translatable text.
Once tag_handling is set to "html", DeepL automatically identifies most standard HTML elements. However, the real power lies in two additional parameters:
- non_splitting_tags, A comma-separated list of tag names that should not cause sentence segmentation. For example, <em> or <strong> tags mid-sentence should not split the surrounding text into separate translation units.
- splitting_tags, Tags that should force a sentence boundary, such as <br> or custom block-level elements.
A well-configured request looks like this:
{
"text": ["<p>Hello <em>{userName}</em>, your order <span translate=\"no\">#{{orderId}}</span> is ready.</p>"],
"target_lang": "DE",
"tag_handling": "html",
"non_splitting_tags": "em,strong,span",
"ignore_tags": "span"
}
The ignore_tags parameter tells DeepL to leave the content inside those tags untouched, critical for protecting placeholders wrapped in <span translate="no">. Without non_splitting_tags, DeepL may segment mid-sentence around inline elements, producing fragmented translations that lose grammatical coherence.
Google Cloud Translation: format=html and mime types
Google Cloud Translation's v2 API uses a format parameter that accepts "text" or "html". When set to "html", the API parses HTML entities and tags, preserving them in the output. The v3 (Advanced) API uses mime_type instead:
{
"contents": ["<p>Welcome back, <b>{displayName}</b>.</p>"],
"targetLanguageCode": "fr",
"mimeType": "text/html"
}
Setting mimeType to "text/html" tells the engine to treat angle-bracket content as markup rather than literal text. Without this, tags may be translated, escaped, or stripped entirely. In HTML mode, Google generally preserves non-translatable spans indicated by translate="no"; confirm this behavior with your content and language pairs. Google does not expose splitting or non-splitting tag controls. If you need fine-grained segmentation behavior, pre-segment your content before sending it to the API.
Amazon Translate: HTML content handling
Amazon Translate can detect and preserve HTML tags in well-formed input, but relying solely on auto-detection is risky. Amazon's documentation recommends sending valid, well-formed HTML; malformed tags can cause unpredictable behavior, including content being dropped from the output.
Amazon Translate does not consistently honor translate="no". A safer approach is to replace placeholders with XML-style custom tags before sending the request, then restore them afterward, for example, replacing {orderId} with <x id="orderId"/> during pre-processing.
Azure Translator: document types and textType
Azure's Translator API provides a textType parameter that accepts "plain" or "html". When set to "html", Azure preserves HTML structure and respects common non-translation patterns such as translate="no" and class="notranslate":
[
{
"Text": "<div>Your balance is <span class=\"notranslate\">${{amount}}</span>.</div>"
}
]
Azure also supports document translation endpoints that accept entire files (DOCX, PPTX, HTML, XLIFF) and preserve their native formatting. For API-level text translation, the textType parameter is the critical switch, omitting it defaults to "plain", which causes all tags to be treated as translatable text.
If you’re deciding whether to implement provider-specific HTML protection yourself or use a unified pre/post-processing layer, see how Ollang standardizes tag handling across DeepL, Google, Amazon, and Azure.
Protecting ICU MessageFormat Tokens
ICU MessageFormat is the standard for parameterized strings in internationalized applications. A typical ICU string looks like this:
{count, plural, =0 {No items} one {# item} other {# items}} in your cart.
Translation APIs have no built-in understanding of ICU syntax. If sent as plain text, engines will frequently translate keywords like plural, one, and other, or restructure the nested braces in ways that break parsing at runtime.
Pre-processing strategies
The safest approach is to extract ICU tokens before translation and reinsert them afterward:
- Parse the ICU message into an AST using a library such as @formatjs/icu-messageformat-parser.
- Replace each non-translatable token with a numbered placeholder (for example, <x1/>, <x2/>).
- Send the simplified string to the translation API with tag handling enabled.
- Map the placeholders back to the original ICU tokens in the response.
For simpler ICU patterns (just named variables like {userName}), wrapping them in <span translate="no">{userName}</span> and using HTML mode is often sufficient. But for nested plural, select, and selectordinal blocks, full pre-processing is the only reliable method.
Common failure modes
| Failure | Cause | Result |
|---|---|---|
| Translated keywords | plural rendered as pluriel in French | ICU parser throws at runtime |
| Reordered braces | Engine restructures nested {} | Mismatched brace count, crash |
| Escaped hash | # converted to # in HTML mode | Literal # displayed instead of count |
| Split segments | Engine breaks message across segments | Partial ICU blocks in each segment |
Always validate ICU output with a parser before committing translations to your resource files.
XLIFF Round-Tripping Without Data Loss
XLIFF (XML Localization Interchange File Format) is the industry standard for exchanging translation data between tools and systems. The specification, maintained by OASIS, defines inline elements like <x/>, <bx/>, <ex/>, <g>, and <ph> specifically to represent non-translatable code within translatable segments.
Preserving inline elements
When round-tripping XLIFF through a translation API, the critical requirement is that every inline element present in the source <seg-source> or <source> must appear in the target with the same id attributes and in a linguistically valid order. A source segment like:
<source>Click <g id="1">here</g> to view <x id="2"/> results.</source>
Must produce a target where <g id="1"> wraps the translated equivalent of "here" and <x id="2"/> remains present. If you send the inner text to a translation API, you need to either:
- Send the full XML segment with tag_handling set to XML (DeepL) or mimeType set to text/html (Google, with adaptation).
- Extract the text between tags, translate it, and reconstruct the XLIFF programmatically.
The second approach is more reliable because it gives you full control over element preservation, but it requires a proper XLIFF parser, not regex.
XLIFF 2.0 vs 1.2
XLIFF 2.0 replaced many inline elements with a unified <pc> (paired code) and <ph> (placeholder) model, which simplifies round-tripping. If you're starting a new integration, prefer XLIFF 2.0. If you're working with legacy 1.2 files, be aware that <bpt>/<ept> paired tags require careful tracking to avoid orphaned opening or closing elements in the translated output.
