- HTML to PDF APIConvert HTML and URLs into PDFsExtract PDF Form Data APIEasily extract data from PDFsWatermark PDF APIAdd custom watermarks to PDFsProtect PDF APISecure your PDFs with passwordCompress PDF APIReduce file size without losing qualityFlatten PDF APIFlatten PDFs to make form permanentDigital Signature APISend documents for signing
- API DocumentationAPI DocumentationFull REST API referenceNodeJS SDKClient library for Node.jsJava SDKClient library for JavaC# SDKClient library for C#PHP SDKClient library for PHPPython SDKClient library for Python
- Pricing
- Contact Us

How to Generate PDFs in Different Languages: Localized, Multilingual, and RTL PDFs
Capture web pages in German, Japanese, or Arabic with locale URLs, Accept-Language, cookies, or the language switcher, and generate multilingual and right-to-left PDFs from your own templates with the right fonts.
Generating a PDF in a specific language comes down to one question: how does the page decide which language to show? If you are capturing a live website, you need to make the site serve its German, Japanese, or Arabic version before the PDF is taken. If you are rendering your own templates, you control the language directly, and the work shifts to fonts, text direction, and formatting.
This guide covers both cases with PDFGate: capturing a localized web page, generating multilingual documents from your own HTML, handling non-Latin scripts and right-to-left layouts, and producing the same document in several languages in one batch.
Key takeaways
- For live pages, a locale URL (
/de/,de.example.com,?lang=de) is the simplest and most reliable way to capture a specific language. - If the site has no locale URLs, send
Accept-Languageor the language cookie throughhttpHeaders, or click the language switcher withclickSelectorChainSetup. - For your own templates, set
langanddiron thehtmlelement, load a font that covers the script, and format dates and numbers on your server. - One request per language makes batch generation of multilingual documents straightforward.
How websites decide which language to serve
Before you can capture a page in a given language, it helps to know which signal the site listens to. Most sites use one or more of these:
- Locale in the URL: a path prefix such as
/de/pricing, a subdomain such asja.example.com, a country domain such asexample.fr, or a query parameter such as?lang=es. The language is part of the address, so every request for that URL returns the same language. - The
Accept-Languageheader: the browser sends the visitor's preferred languages with every request, and the server picks the best match. Many sites use this only for the first visit and then redirect to a locale URL. - A language cookie: once a visitor picks a language, the site stores it in a cookie (for example
NEXT_LOCALEin Next.js apps) and reads it on later requests. - Client-side detection: JavaScript reads
navigator.languageor a value inlocalStorage, and an i18n library such as i18next swaps the text in the browser after the page loads.
The first three happen on the server, so you can influence them from the API request. The last one happens inside the rendering browser, which is why it needs a different approach.
Capturing a web page in a specific language with PDFGate
PDFGate's HTML to PDF API loads the page in a real Chromium browser and exports the rendered result. Pick the method below that matches how the target site chooses its language, starting with the simplest.
1. Use the localized URL
If the site exposes its languages in the URL, pass that URL directly. This is the most predictable option because it does not depend on headers, cookies, or detection logic, and it works for sites that detect language on the client as well, since the locale URL usually wins over detection.
1curl -H "Content-Type: application/json" \2 -H "Authorization: Bearer YOUR_API_KEY" \3 --request POST \4 --data '{"url":"https://example.com/de/pricing","pageSizeType":"a4"}' \5 https://api.pdfgate.com/generate/pdf \6 -o pricing-de.pdfIf you are not sure which URLs exist, check the page source for <link rel="alternate" hreflang="..."> tags. Sites that support several languages usually list every localized version there.
2. Send an Accept-Language header
For sites that negotiate the language on the server, send the header you want with the httpHeaders option. PDFGate attaches these headers to the page request before it loads.
1{2 "url": "https://example.com/pricing",3 "pageSizeType": "a4",4 "httpHeaders": {5 "Accept-Language": "ja-JP,ja;q=0.9"6 }7}This only changes what the server receives. It does not change the rendering browser's own locale, so a site that reads navigator.language in JavaScript will still see the browser default. If the header has no effect, the site is probably detecting the language on the client; use a locale URL or the language switcher instead.
3. Send the site's language cookie
If the site remembers the language in a cookie, you can send it as a Cookie header. Open the site in your own browser, switch languages, and look in your developer tools for the cookie that changed. Then pass it the same way:
1{2 "url": "https://example.com/pricing",3 "pageSizeType": "a4",4 "httpHeaders": {5 "Cookie": "NEXT_LOCALE=fr"6 }7}This works when the server reads the cookie to decide which HTML to return. Because the header is added to outgoing requests rather than stored in the browser, JavaScript that reads document.cookie will not see it, so client-side-only sites need one of the other methods.
4. Click the language switcher
When a site offers neither locale URLs nor server-side detection, do what a visitor would: click the language menu. The clickSelectorChainSetup option clicks a sequence of CSS selectors in order before the PDF is captured. Add a delay so the page has time to reload or re-render in the new language.
1{2 "url": "https://example.com/pricing",3 "pageSizeType": "a4",4 "clickSelectorChainSetup": {5 "ignoreFailingChains": false,6 "chains": [7 { "selectors": ["#language-menu", "a[hreflang='es']"] }8 ]9 },10 "delay": 200011}The same option dismisses cookie banners and popups, and you can add a separate chain for the consent dialog alongside the language chain. See the cookie banners and popups guide for how chains work. If the translated content loads asynchronously, the wait for dynamic content guide covers waiting for it reliably.
| How the site picks the language | What to send | Reliability |
|---|---|---|
| Locale in URL, subdomain, or query | The localized url | High |
Server reads Accept-Language | httpHeaders with Accept-Language | High, if the server honors it |
| Server reads a language cookie | httpHeaders with Cookie | High, if the server reads it |
| JavaScript detection only | Locale URL or clickSelectorChainSetup on the switcher | Depends on the site |
Generating localized documents from your own templates
For invoices, contracts, certificates, and reports, you usually render your own HTML and send it in the html field. Here you control the language completely, and a few details make the difference between a clean PDF and a broken one:
- Set
langon thehtmlelement. The browser uses it to pick the right glyph variants (Japanese and Chinese share characters that are drawn differently), apply hyphenation rules, and choose quotation marks. - Set
dir="rtl"for Arabic, Hebrew, Persian, and Urdu. This flips the base direction of the whole document, so text, tables, and lists start on the right. - Declare UTF-8. Include
<meta charset="utf-8">so accented and non-Latin characters are read correctly. - Format dates, numbers, and currencies on your server. Use
Intl.DateTimeFormatandIntl.NumberFormat, or your framework's i18n library, before building the HTML. That way 28. September 2026 and 12.500,00 € are already in the template, and the result does not depend on the locale of the rendering browser.
1<!DOCTYPE html>2<html lang="ar" dir="rtl">3 <head>4 <meta charset="utf-8" />5 <link href="https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic:wght@400;700&display=swap" rel="stylesheet" />6 <style>7 body { font-family: "Noto Naskh Arabic", sans-serif; }8 .total { text-align: end; padding-inline-start: 24px; }9 </style>10 </head>11 <body>12 <h1>فاتورة رقم 1042</h1>13 <p>التاريخ: ٢٨ سبتمبر ٢٠٢٦</p>14 <p class="total">الإجمالي: ١٢٬٥٠٠٫٠٠ ر.س.</p>15 </body>16</html>Fonts for non-Latin scripts and emoji
The most common problem with multilingual PDFs is missing glyphs: Chinese, Japanese, Korean, Arabic, Hebrew, or Devanagari text appears as empty boxes. It happens when no font available to the renderer covers those characters. Do not rely on system fonts; load a web font that covers the script you need.
- CJK: Noto Sans SC, TC, JP, or KR (pick the one that matches the
langof the document). - Arabic: Noto Naskh Arabic or Noto Sans Arabic. Hebrew: Noto Sans Hebrew.
- Cyrillic and Greek: Noto Sans and most major web fonts include them, but check the subset you load.
- Devanagari and other Indic scripts: Noto Sans Devanagari and its sibling families.
- Emoji: Noto Color Emoji.
List several families in font-family so each script falls back to a font that covers it. CJK fonts are large, so load only the weights you use, or embed a subset as base64 in your CSS to avoid depending on a network request at render time. The custom fonts and images guide walks through both loading options.
Right-to-left layout in print
Setting dir="rtl" is the start, but layouts built for left-to-right often break when mirrored. A few rules keep RTL PDFs clean:
- Use logical CSS properties. Replace
margin-left,padding-right, andtext-align: leftwithmargin-inline-start,padding-inline-end, andtext-align: start. The same stylesheet then works in both directions. - Isolate mixed-direction content. Order numbers, email addresses, and product codes inside Arabic or Hebrew text can reorder unexpectedly. Wrap them in
<bdi>or useunicode-bidi: isolate. - Mirror headers and footers too. PDFGate's
headerandfootertemplates are separate HTML fragments, so give their root elementdir="rtl"and a font that covers the script. See the header and footer guide for the available page number classes. - Check tables and icons. Tables follow the document direction automatically, but directional icons such as arrows do not; flip them with a transform when
dirisrtl.
Generating one document in multiple languages
A common requirement is to produce the same invoice, report, or certificate in every language a customer might need. Since each API request returns one PDF, the pattern is a loop over locales: render the template for each language, send it, and save the result. The example below formats dates and currencies per locale on the server, sets lang and dir, and generates all four PDFs in parallel.
1const fs = require("fs/promises");23const locales = [4 { code: "en-US", lang: "en", dir: "ltr" },5 { code: "de-DE", lang: "de", dir: "ltr" },6 { code: "ja-JP", lang: "ja", dir: "ltr" },7 { code: "ar-SA", lang: "ar", dir: "rtl" },8];910const invoice = { number: 1042, issuedAt: new Date("2026-09-28"), total: 12500, currency: "EUR" };1112function renderInvoice(locale, t) {13 // Format dates and numbers on your server, before the HTML is sent.14 const date = new Intl.DateTimeFormat(locale.code, { dateStyle: "long" }).format(invoice.issuedAt);15 const total = new Intl.NumberFormat(locale.code, { style: "currency", currency: invoice.currency }).format(invoice.total);1617 return `<!DOCTYPE html>18<html lang="${locale.lang}" dir="${locale.dir}">19 <head>20 <meta charset="utf-8" />21 <link href="https://fonts.googleapis.com/css2?family=Noto+Sans&family=Noto+Sans+JP&family=Noto+Naskh+Arabic&display=swap" rel="stylesheet" />22 <style>body { font-family: "Noto Sans", "Noto Sans JP", "Noto Naskh Arabic", sans-serif; }</style>23 </head>24 <body>25 <h1>${t.invoice} #${invoice.number}</h1>26 <p>${t.date}: ${date}</p>27 <p>${t.total}: ${total}</p>28 </body>29</html>`;30}3132async function generate(locale, t) {33 const response = await fetch("https://api.pdfgate.com/generate/pdf", {34 method: "POST",35 headers: {36 "Content-Type": "application/json",37 "Authorization": "Bearer YOUR_API_KEY",38 },39 body: JSON.stringify({ html: renderInvoice(locale, t), pageSizeType: "a4" }),40 });4142 if (response.status !== 201) {43 throw new Error(`${locale.code}: ${await response.text()}`);44 }4546 const pdf = Buffer.from(await response.arrayBuffer());47 await fs.writeFile(`invoice-${invoice.number}-${locale.code}.pdf`, pdf);48}4950const translations = {51 "en-US": { invoice: "Invoice", date: "Date", total: "Total" },52 "de-DE": { invoice: "Rechnung", date: "Datum", total: "Gesamtbetrag" },53 "ja-JP": { invoice: "請求書", date: "日付", total: "合計" },54 "ar-SA": { invoice: "فاتورة", date: "التاريخ", total: "الإجمالي" },55};5657Promise.all(locales.map((locale) => generate(locale, translations[locale.code])))58 .then(() => console.log("All languages generated"))59 .catch(console.error);For live pages, the loop is even simpler: swap the html field for the localized url of each language. Keep in mind that parallel requests count toward your plan's concurrency limit, so for large batches run the requests in groups rather than all at once. For more language examples, see the HTML to PDF API guide for Node.js, Python, and PHP.
Troubleshooting multilingual PDFs
- The PDF is still in English: the site ignores the signal you sent. Try the next method in the list above, starting with a locale URL.
- Characters render as boxes: the font does not cover the script. Load a Noto font for that script and add it to
font-family. - Japanese text looks like Chinese: the
langattribute is missing or wrong, so the browser picks the wrong glyph variants. - Numbers or punctuation jump to the wrong side in RTL text: isolate them with
<bdi>. - Dates or currencies use the wrong format: format them on your server for each locale instead of in the page's JavaScript.
Frequently asked questions
How do I generate a PDF of a web page in a specific language?
The most reliable way is to pass the localized URL, such as example.com/de/ or de.example.com, to the HTML to PDF API. If the site has no locale URLs, send an Accept-Language header or the site's language cookie with the request, or click the language switcher before the PDF is captured.
Does sending an Accept-Language header always change the page language?
No. It only works when the site's server reads Accept-Language and returns translated HTML. Sites that pick the language in the browser with JavaScript, or that always serve a default language, ignore it. In those cases use a locale URL or click the language switcher instead.
Why do Chinese, Japanese, or Arabic characters show up as empty boxes in my PDF?
The rendering environment has no font that covers those characters. Load a web font that supports the script, such as the matching Noto font, from a public URL or embed it as base64 in your CSS, and reference it in font-family.
Can I create right-to-left PDFs in Arabic or Hebrew?
Yes. Set dir="rtl" and the correct lang attribute on the html element, load a font that covers the script, and use logical CSS properties such as margin-inline-start so the layout mirrors correctly. The browser engine handles bidirectional text the same way it does on screen.
How do I format dates and currencies for each language?
When you render your own templates, format dates, numbers, and currencies on your server, for example with Intl.DateTimeFormat or your framework's i18n library, and send the finished strings in the HTML. This keeps the output identical regardless of the rendering browser's own locale.
Can I generate the same document in several languages at once?
Yes. Loop over your list of locales and send one API request per language, either with a localized URL or with HTML rendered from your template for that locale. Each request returns its own PDF, and the requests can run in parallel.
Learn more about how this works on our HTML to PDF API page.