How to Translate HTML with DeepL and Preserve Formatting

Vladimir Lomtev•
How to Translate HTML with DeepL and Preserve Formatting

To translate HTML with the DeepL API, pass the fragment to text, include tag_handling: "html" and explicitly set tag_handling_version: "v2". Before sending, mark any non-translatable elements, and after receiving the response, check links and protected text. This mode is described in DeepL's official HTML instructions.

A guide for an integration developer and an editor who need to localize a service description, help article, or CMS card. The source material already exists in HTML. This process does not recommend translating the entire app, moving the template engine, or automatically publishing the result.

The documentation was checked on November 3, 2026. The request below is an example based on the documentation, without an actual call to DeepL. JSON preparation, HTML extraction from the sample response, and comparison of structural markers were tested locally. The quality of the machine translation and the behavior of a specific CMS were not evaluated here.

Contents

What to prepare before translation

You will need access to the DeepL API, a server-side environment with Python 3 and curl, the source HTML fragment, and a separate draft in your CMS. API Free and API Pro use different endpoints: https://api-free.deepl.com and https://api.deepl.com. The key is obtained in the API Keys section and passed in the authorization header; it should not be placed in client-side JavaScript. These are requirements from DeepL's access documentation.

Start with one block that the editor can read in full. Save the source, its version, and its location in the CMS: for example, the body field of a service page. Work from a copy. For cloud processing, use material that the company has allowed you to send; do not add internal data or secrets to the example.

Split the content in advance:

Page section Action in this guide Stage result
Paragraphs, headings, link labels Translate together with the HTML context The reader gets coherent text
Exact plan name or command Mark as non-translatable The label can be compared with the source
Link and image URLs Keep for the first translation; change local routes separately A random translation does not determine navigation
alt, title, page metadata Move into a separate localization list Text outside visible paragraphs is not forgotten
Template variables and executable code Exclude from the first run The tutorial route remains ordinary HTML

This is a suggested CMS workflow, not a list of DeepL guarantees. If the export contains React components, templates, or XML, first identify the actual format. DeepL has a separate mode for XML and rules for handling structure.

Step 1. Select one complete HTML fragment

Save this sample English-language fragment in source.html :

<section id="service">
  <h2>Priority support</h2>
  <p>Activate <strong>priority support</strong> for your team.</p>
  <p>Plan: <span translate="no">TEAM-PRO</span>.</p>
  <p><a href="/support/contact">Contact support</a></p>
</section>

It includes a heading, emphasis, an exact plan name, and a link. TEAM-PRO is a fictional label used for demonstration. The address /support/contact is a sample relative path: before real work, specify a real address on your website.

Keep the full phrase together with the emphasis. In this example, there is no need to translate Activate, priority support and for your team: it is easier for the editor to review a finished sentence. At the same time, do not send the entire site HTML with menus and scripts just for one description. The scope of the task is the contents of the selected field.

Result: one small file with a clear purpose for each element. Open it in a code editor and make sure it is HTML, not a string in which angle brackets have already been replaced with &lt; and >.

Step 2. Mark what should remain untranslated

The attribute translate="no" indicates that the content of an element should be left unchanged. Its meaning is defined in the HTML standard. DeepL documents support for this attribute and the notranslate class for HTML. In our file, the protection applies to TEAM-PRO, while the surrounding phrase remains available for translation.

Do not disable translation for an entire paragraph if ordinary words in it still need to be translated. And do not assume that the line’s appearance, uppercase letters, or hyphens will convince the translator to preserve the label. Write down the exact expected value and compare it after you get the response.

Translation protection does not hide data from the cloud service. If a string cannot be sent, it must be excluded from the request, not just wrapped in span. Terms that need consistent translation require a different solution: they are covered in the DeepL glossary FAQ.

Result: only the unchanged fragments are marked. Checking the tariff name after translation remains a required part of this workflow.

Step 3. Build the JSON without manual escaping

For Linux/macOS or a server shell with Python 3, run the following in the folder with source.html:

import json
from pathlib import Path

source = Path("source.html").read_text(encoding="utf-8")
request = {
    "text": [source],
    "source_lang": "EN",
    "target_lang": "RU",
    "tag_handling": "html",
    "tag_handling_version": "v2"
}
Path("request.json").write_text(
    json.dumps(request, ensure_ascii=False), encoding="utf-8"
)

The code reads the HTML as a string and lets the JSON library correctly write quotes and line breaks. It does not translate the content. EN and RU are the languages used in this example; for a different source, specify the appropriate pair.

The parameters text, source_lang, target_lang and tag handling are listed in the Translate text reference. We set the processing version explicitly so the request result does not depend on the account’s default setting. The number v2 here refers to the tag algorithm, not the website or CMS version.

Result: request.json, which opens as JSON, and its first element text matches the source file. This step can be completed without a key and without a network request.

Step 4. Send the request from a server environment

The following example is designed for DeepL API Pro. Before running it, safely set the DEEPL_AUTH_KEY environment variable on the server; do not store the key in a public repository. For API Free, replace the domain with api-free.deepl.com.

curl --fail-with-body --silent --show-error \
  --request POST 'https://api.deepl.com/v2/translate' \
  --header "Authorization: DeepL-Auth-Key $DEEPL_AUTH_KEY" \
  --header 'Content-Type: application/json' \
  --data-binary @request.json \
  --output response.json

This is the documented way to make a request with a prepared body; the command in the article was not sent. The call itself may consume part of your subscription limit. It uses curl with --fail-with-body support: on an HTTP error, it returns a nonzero code while preserving the response body. The behavior of the flag is described in the official curl help

. Continue only after the command completes successfully. If the request failed, do not treat the existing response.json

as a translation: it may contain a server message or the result of a previous attempt. First analyze the error, then run a new request deliberately. Result: a successful JSON response with a translations

array. An HTTP success does not yet mean the resulting text is ready to publish.

Step 5. Extract the HTML and compare the key elements

For a single fragment from the example, save the translation like this:

import json from pathlib import Path response = json.loads(Path("response.json").read_text(encoding="utf-8")) translations = response.get("translations") if not isinstance(translations, list) or len(translations) != 1: raise ValueError("Expected one translation") text = translations[0].get("text") if not isinstance(text, str) or not text.strip(): raise ValueError("Missing translated HTML") Path("translated.html").write_text(text, encoding="utf-8") In the CMS HTML field, pass the value translations[0].text

, not the entire JSON or a string with outer JSON quotes. The correspondence between the translation array and the input array is described in the DeepL reference. If you later expand the request to multiple blocks, preserve the mapping to the source fields; this short extractor intentionally accepts only one result. Compare source.html and translated.html

  • designation inside the protected span remained TEAM-PRO;
  • the link points to the previous /support/contact;
  • the heading and paragraphs kept their roles;
  • the emphasis applies to the correct words in the translated phrase;
  • the Russian text conveys the original meaning without added conditions.

Do not require byte-for-byte HTML matching: words and their order change during translation. At the same time, a changed target URL, a missing protected designation, or the loss of a heading is a reason to stop the transfer and find out why.

The local check for this article was done on the source excerpt and a manually prepared sample Russian answer, not on DeepL’s result. The JSON preparation and text extraction preserved the data; the comparison found a deliberately changed link and plan name. This tests the helper path, but it proves nothing about the quality or reliability of cloud translation.

Result: an individual HTML file and parsed differences. If the helper script fails in any way, do not use the previous translated.html as the result of the current run.

Step 6. Transfer the translation into the CMS draft

Open the source field in HTML mode and paste the translation into a separate language version. Use your CMS’s built-in preview. Check the heading, emphasis, and link behavior on the page itself: the editor may convert the inserted markup.

The link address was intentionally left as the original. If the Russian version has a different route, replace it using the site’s URL mapping table and test the navigation. Translating the link label and choosing its URL are separate actions.

Then process the text outside the excerpt: the page title, search description, image captions, and interface elements. Do not assume they are ready based on the translated paragraph. Attributes that can be translated are described separately in the HTML standard; test the actual behavior of the chosen API and integration on your own fields.

Result: a language draft with working navigation and a full set of visible text. Publish it after editorial review of the meaning and a page check. Such a CMS review was not performed as part of this article.

Common mistakes when translating HTML

Symptom What to check What to do
Tag brackets are visible on the page Was escaped text inserted into the visual editor Use the correct HTML mode and the right escaping level
The plan name was translated Is there narrow protection in the source Add translate="no" and compare the exact string again
The link is labeled in Russian but opens the old language page Is there a local route for it Apply the confirmed URL mapping
The translation is there, but part of the page stayed in the source language Were metadata and attributes included in the task Translate the missing fields separately
The page looks different after insertion Did the CMS transform the HTML itself Compare the translation file with the saved field and the preview
Script error, but the output file exists Did a file from a previous run remain Stop the transfer; get a successful result from the current attempt

These are diagnostic directions, not the results of testing all CMS platforms. If the task involves subtitles, a different route is needed: SRT translation with timecodes preserved checks the video’s timing structure.

Frequently asked questions

Can I just ask a chatbot to translate HTML?

You can prepare a draft that way, but a request to “keep the tags” does not verify the result. For the workflow described here, an API with documented HTML mode and a clear separation between translation and CMS transfer was chosen. There is no comparison here between the quality of chatbots and DeepL.

Do I need to convert HTML to XML?

For standard HTML, this guide uses tag_handling: "html". XML is a different format with separate rules. In particular, DeepL documents strict XML parsing in the v2 algorithm; that rule should not be applied to HTML.

Does a non-translatable span preserve confidentiality?

No. It defines translation behavior. The fragment itself is still included in the request body being sent; data that must not be transmitted should be excluded before sending.

How Vladimir Lomtev’s AI studio helps connect translation with a CMS

Vladimir Lomtev’s AI studio works on data preparation, integrations, and workflow automation. For localization work, this can include extracting translatable fields from an export, integrating the processing stage with a business system, and testing the return of the result into drafts. Support for a specific CMS and translator needs to be determined by the structure of the project.

The first step is to choose one field on one page, describe the current manual process, the sources, data transfer constraints, and the readiness criterion: correct text, correct links, and preserved designations. Discuss the task.

Bottom line: keep a controlled path from HTML to the page

Start with a small fragment, explicitly enable HTML mode, and protect exact designations. Extract the resulting text from JSON, compare the important elements, and transfer it into the CMS language draft. The finished result is a page that the editor has read and checked together with navigation; a successful request is only one step in this path.

Request an audit

Share your contact details and we will follow up.

← All articles

Comments (0)

Loading comments…

Leave a comment
No registration required

Book a strategy call
for agentic operations

Tell us which workflow you want to improve. We will map feasibility, risks, and the fastest MVP path.

By submitting, you agree to our privacy policy

Contacts

Global Operations

Serving U.S. clients remotely
with private cloud and on-prem options

Strategy calls by request

We respond after reviewing your workflow context.

info@agentsunrise.pro

For partnership inquiries

Have a proposal?

Write to us in messengers

© 2026 AgentSunrise