Multi-Language Document Generation: Supporting 6+ Languages in Your API

Published on March 31, 2026 · 9 min read

When your business operates across borders, every customer-facing document needs to speak the customer's language. An invoice sent to a German client should display "Rechnung", not "Invoice". A receipt printed in a Parisian shop should read "Total TTC", not "Total incl. VAT". Multi-language document generation is not a luxury; it is a fundamental requirement for any business with an international presence.

Yet implementing internationalization (i18n) in document generation is one of the most underestimated technical challenges. It involves more than translating static labels. Dates, numbers, currencies, and even page layouts need to adapt to each locale. This article explains how modern document APIs solve this problem and how Doxnex supports 6+ languages out of the box.

English
French
German
Spanish
Italian
Arabic

The Anatomy of a Localized Document

A truly localized document goes beyond translating the word "Invoice" into "Facture". Here are all the elements that change with locale:

How Template-Based i18n Works

The most maintainable approach to multi-language documents is the template + message bundle pattern. This is the approach used by Doxnex, built on the Thymeleaf template engine.

The Template

A single HTML template defines the structure and layout of the document. Instead of hardcoding text, it uses message expressions that act as placeholders:

<h1 th:text="#{invoice.title}">Invoice</h1>
<table>
  <thead>
    <tr>
      <th th:text="#{invoice.column.description}">Description</th>
      <th th:text="#{invoice.column.quantity}">Quantity</th>
      <th th:text="#{invoice.column.unitPrice}">Unit Price</th>
      <th th:text="#{invoice.column.total}">Total</th>
    </tr>
  </thead>
</table>
<p th:text="#{invoice.totalLabel}">Total</p>

The #{...} syntax tells Thymeleaf to look up the text from a message bundle based on the current locale.

Message Bundles

Each language has its own properties file containing the translations:

# messages_en.properties
invoice.title=Invoice
invoice.column.description=Description
invoice.column.quantity=Quantity
invoice.column.unitPrice=Unit Price
invoice.column.total=Total
invoice.totalLabel=Total Amount Due

# messages_fr.properties
invoice.title=Facture
invoice.column.description=Description
invoice.column.quantity=Quantit\u00e9
invoice.column.unitPrice=Prix Unitaire
invoice.column.total=Total
invoice.totalLabel=Montant Total TTC

# messages_de.properties
invoice.title=Rechnung
invoice.column.description=Beschreibung
invoice.column.quantity=Menge
invoice.column.unitPrice=Einzelpreis
invoice.column.total=Gesamt
invoice.totalLabel=Gesamtbetrag

The Message Resolver

The Thymeleaf message resolver is the component that connects templates to message bundles. When a document is generated with "locale": "fr", the resolver loads messages_fr.properties and replaces every #{...} expression with the French translation. If a key is missing from the French bundle, it falls back to the default (English) bundle.

Why Thymeleaf? Thymeleaf processes templates as valid HTML, which means designers can preview templates in a browser without running the application. The message expressions render as their fallback text (the content between the tags), making templates readable and testable by non-developers.

Locale-Aware Formatting

Translating labels is only half the challenge. Numbers, dates, and currencies must also be formatted according to locale conventions. Thymeleaf provides formatting utilities that handle this automatically:

<!-- Date formatting by locale -->
<span th:text="${#dates.format(invoiceDate, 'dd MMMM yyyy')}">31 March 2026</span>

<!-- Number formatting by locale -->
<span th:text="${#numbers.formatDecimal(total, 1, 2)}">1,234.56</span>

<!-- Currency formatting -->
<span th:text="${#numbers.formatCurrency(total)}">$1,234.56</span>

When the locale is set to fr, the date renders as "31 mars 2026", the number as "1 234,56", and the currency as "1 234,56 €". No conditional logic is needed in the template; the locale drives all formatting decisions.

Using Doxnex for Multi-Language Documents

With Doxnex, generating a document in any supported language requires a single parameter change in your API call:

# Generate invoice in French
curl -X POST https://api.doxnex.io/v1/documents/invoice \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "invoice-standard",
    "locale": "fr",
    "data": { ... }
  }'

# Same invoice in German - only the locale changes
curl -X POST https://api.doxnex.io/v1/documents/invoice \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "invoice-standard",
    "locale": "de",
    "data": { ... }
  }'

The same template, the same data, the same endpoint. Only the locale field changes, and the API produces a fully localized document with translated labels, locale-appropriate date and number formats, and the correct currency presentation.

Handling RTL Languages

Right-to-left languages like Arabic present additional challenges beyond translation. The entire document layout must mirror: text alignment flips from left to right, columns reorder, and the reading flow reverses.

Doxnex handles RTL automatically. When the locale is set to ar, the rendering engine applies dir="rtl" to the document root, loads Arabic-compatible fonts (such as Noto Sans Arabic), and mirrors CSS layout properties. You do not need to create separate RTL templates.

Adding Custom Languages

If Doxnex's built-in languages do not cover your needs, you can add custom message bundles through the API dashboard. Upload a properties file with your translations, and the new locale becomes immediately available in your API calls. This is useful for supporting regional dialects, less common languages, or industry-specific terminology.

Need documents in multiple languages without the complexity?

Explore Doxnex i18n →

Frequently Asked Questions

How does multi-language document generation work?

Multi-language document generation uses a template engine with externalized message bundles. The same template is used for all languages, but labels, headers, and static text are resolved from locale-specific property files at render time. When you request a document in French, the engine loads French translations; for German, it loads German translations. The data (names, amounts, items) comes from your API payload.

What languages does Doxnex support?

Doxnex supports French, English, German, Spanish, Italian, and Arabic out of the box. Additional languages can be added by providing custom message bundles. The system also handles right-to-left (RTL) scripts for Arabic and locale-specific formatting for dates, numbers, and currencies.

What is a Thymeleaf message resolver?

A Thymeleaf message resolver is a component in the Thymeleaf template engine that looks up translated text based on the current locale. When a template contains a message expression like #{invoice.title}, the resolver finds the corresponding value in the appropriate language file (e.g., messages_fr.properties for French). This allows a single template to produce documents in any supported language.

How do I handle right-to-left (RTL) languages in generated documents?

RTL language support requires both text direction changes and layout mirroring. In Doxnex, when you generate a document with an RTL locale like Arabic, the template engine automatically applies dir='rtl' to the document, mirrors the layout, and uses appropriate fonts that support Arabic glyphs. You do not need separate templates for RTL languages.