Skip to content

Render multilingual documents

A template version can carry translations — a set of language keys, each mapping the template’s translatable strings to that language. Pass a language at render time and Pagr renders that variant. Omit it and you get the template’s base content.

  • A template version that defines translations. A version with none accepts no language at all.
  • The exact language key as defined on the version — fr, nl-BE, whatever the template author used. Matching is exact, not fuzzy.
  1. Find out which languages the version defines.

    Fetch the version and read its translations field — a JSON object keyed by language. null means the version has no translations at all.

  2. Pass the language key when you render.

    It’s a query parameter on the wire (?language=fr) and an option in every SDK. It works on single renders, synchronous batches and async jobs alike.

  3. Read the language back off the result.

    The rendered document’s language field echoes what was rendered, so a stored document is self-describing.

  4. Handle 400 as a data problem, not a bug.

    Catch it and surface the available languages — the message already lists them.

Terminal window
curl "https://pagr-prd-api-public.azurewebsites.net/v1/templates/8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90/versions/latest" \
-H "Authorization: Bearer pagr_prod_xxxxxxxx" | jq -r '.translations'
// translations is a JSON *string* on the wire — parse it, then read its keys
"{\"fr\":{\"invoice.title\":\"Facture\"},\"nl\":{\"invoice.title\":\"Factuur\"}}"
// ⇒ available languages: fr, nl
Terminal window
curl -X POST "https://pagr-prd-api-public.azurewebsites.net/v1/render/8bec66ff-6f3d-4c1e-9a2b-1f0e5d7c4a90?language=fr" \
-H "Authorization: Bearer pagr_prod_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"documents": [ { "Title": "Facture Acme T3", "Amount": 42 } ],
"includeDocument": true
}'

language is a query parameter, not a body field.

The same language option works on batches and async jobs — one language per request, applied to every document in it:

# One language per request — loop to produce several
for lang in ["fr", "nl", "de"]:
result = await client.render_batch(
TEMPLATE_ID, documents, language=lang, include_document=True,
)
result.save_all(f"out/{lang}/")

Variation: translations for a stateless render

Section titled “Variation: translations for a stateless render”

A stateless render has no stored version to read translations from, so you supply them inline alongside the template and data. The shape is an object keyed by language, each mapping translation keys to strings:

{
"template": { "…": "the template DSL" },
"data": { "Amount": 42 },
"translations": {
"fr": { "invoice.title": "Facture", "invoice.total": "Total" },
"nl": { "invoice.title": "Factuur", "invoice.total": "Totaal" }
}
}
Terminal window
curl -X POST "https://pagr-prd-api-public.azurewebsites.net/v1/render?language=fr" \
-H "Authorization: Bearer pagr_prod_xxxxxxxx" \
-H "Content-Type: application/json" \
-d @body.json --output facture.pdf
  • Matching is exact. fr and fr-FR are different keys. Requesting fr-FR against a version that defines only fr is a 400, not a fallback — the API uses the same lookup the renderer uses, so validation can never accept a language rendering would have to guess at.
  • One language per request. There’s no “render all languages” call. Loop over the languages you need; each is a separate request and a separate credit charge.
  • language is a query parameter, not a body field. If you’re calling the HTTP API directly, don’t put it in documents.
  • The stored document records the language. language on the rendered document and on Documents tells you which variant a stored PDF is — and it’s a filterable field, so you can list all French documents directly. See Listing & pagination.
  • language is null, not "", when the template has no translations or the render didn’t specify one.
  • Credit is charged per rendered page, so three languages of a 4-page invoice costs 12 pages, not 4.
  • The 400 message is useful — surface it. It enumerates the available languages, which is exactly what a caller who guessed wrong needs to see. Don’t swallow it into a generic “render failed”.