This plugin integrates with AI providers and provides on-demand AI-powered translations for your fields. You can also translate entire records or perform bulk translations across multiple records and models.
See the CHANGELOG.md file for details about all the latest features and improvements.
On the plugin's Settings screen:
gemini-2.5-flash (fast/cost‑effective default). For the highest fidelity, use gemini-2.5-pro. For very large or budget batches, consider gemini-2.5-flash-lite.{fieldValue}, {fromLocale}, {toLocale}, and {recordContext}.Models
gemini-1.5-flash and gemini-1.5-pro).Save your changes. The plugin is now ready.
For each translatable field:
You can also pull content from a different locale by choosing "Translate from" to copy and translate that locale's content into your current locale.
If enabled:
Translate multiple records at once from any table view:

The plugin includes a dedicated page for translating multiple models at once:
The plugin now supports context-aware translations through the {recordContext} placeholder:
The plugin supports ICU Message Format strings, ensuring that complex pluralization and selection logic is preserved during translation.
{name} are masked to protect them, while ICU structures like {count, plural, ...} are passed to the AI.Example:
You have {count, plural, one {# message} other {# messages}}Becomes:
Você tem {count, plural, one {# mensagem} other {# mensagens}}You can customize the translation prompt template in the plugin settings:
{fieldValue} to represent the content to translate{fromLocale} and {toLocale} to specify languages{recordContext} to include the automatically generated record contextDeepL’s API does not support browser‑origin requests (no CORS). If you call DeepL directly from the plugin (which runs in the browser), the preflight request fails and you’ll see network/CORS errors. To use DeepL in this plugin, you must route requests through a small server you control (a “proxy”).
What the proxy must do
/v2/translate input. The plugin sends bodies like:
{ "text": ["Hello"], "target_lang": "DE", ... }Access-Control-Allow-Origin: * for testing; you can restrict later).Authorization: DeepL-Auth-Key <YOUR_KEY>:fx) → https://api-free.deepl.comhttps://api.deepl.comPlugin settings to use with a proxy
POST <proxy>/v2/translate.:fx, also enable “Use DeepL Free endpoint (api-free.deepl.com)” so validation and error messages match your plan.Security notes
https://admin.datocms.com and your own preview domains instead of *.Environment vars
DEEPL_AUTH_KEY — your DeepL keyDEEPL_BASE_URL — https://api-free.deepl.com (Free) or https://api.deepl.com (Pro)Worker (wrangler.toml configured; minimal example)
export default { async fetch(req, env) { const cors = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', }; if (req.method === 'OPTIONS') return new Response(null, { headers: cors });
const url = new URL(req.url); const isFree = url.searchParams.get('endpoint') === 'free'; const baseUrl = isFree ? 'https://api-free.deepl.com' : 'https://api.deepl.com'; const upstream = new URL('/v2/translate', baseUrl);
const body = await req.text(); // passthrough JSON body
const resp = await fetch(upstream, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `DeepL-Auth-Key ${env.DEEPL_AUTH_KEY}`, }, body, });
const text = await resp.text(); return new Response(text, { status: resp.status, headers: { ...cors, 'Content-Type': 'application/json' } }); }}Deploy
wrangler deployDEEPL_AUTH_KEY and DEEPL_BASE_URL in your Worker’s environment.https://your-worker.yourname.workers.dev) into the plugin’s “Proxy URL”.Environment vars (Project Settings → Environment Variables):
DEEPL_AUTH_KEY — your keyDEEPL_BASE_URL — https://api-free.deepl.com or https://api.deepl.comCreate pages/api/deepl.ts (or app/api/deepl/route.ts for App Router):
export default async function handler(req, res) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', '*'); if (req.method === 'OPTIONS') return res.status(204).end();
const isFree = req.query.endpoint === 'free'; const baseUrl = isFree ? 'https://api-free.deepl.com' : 'https://api.deepl.com'; const upstream = `${baseUrl}/v2/translate`;
const r = await fetch(upstream, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `DeepL-Auth-Key ${process.env.DEEPL_AUTH_KEY}`, }, body: JSON.stringify(req.body ?? {}), }); const text = await r.text(); res.status(r.status).setHeader('Content-Type', 'application/json').send(text);}Deploy
vercel deploy (or push to GitHub with Vercel connected).https://your-app.vercel.app/api/deepl as the “Proxy URL”.Note on Vercel Deployment Protection
Environment vars (Netlify dashboard → Site settings → Environment):
DEEPL_AUTH_KEY, DEEPL_BASE_URLCreate netlify/functions/deepl-proxy.ts:
export const handler = async (event) => { if (event.httpMethod === 'OPTIONS') { return { statusCode: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*' } }; }
const isFree = event.queryStringParameters?.endpoint === 'free'; const baseUrl = isFree ? 'https://api-free.deepl.com' : 'https://api.deepl.com'; const upstream = `${baseUrl}/v2/translate`;
const r = await fetch(upstream, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `DeepL-Auth-Key ${process.env.DEEPL_AUTH_KEY}`, }, body: event.body || '{}', }); const text = await r.text(); return { statusCode: r.status, headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, body: text, };};Deploy
netlify deploy (or via the Netlify app/CLI). Use /.netlify/functions/deepl-proxy as your “Proxy URL”.…:fx) must target api-free.deepl.com. Pro keys must target api.deepl.com.DEEPL_BASE_URL accordingly (and/or toggle “Use DeepL Free endpoint” in the plugin).Access-Control-Allow-Origin.Endpoint selection (Free vs Pro)
DEEPL_BASE_URL.DEEPL_BASE_URL to https://api-free.deepl.com if your key ends with :fx (DeepL Free), otherwise to https://api.deepl.com (Pro).DEEPL_BASE_URL so errors and validations are consistent. A mismatch will surface as a “Wrong endpoint for your API key” error.That’s it — once your proxy passes the test, DeepL translations (including large Structured Text fields) will work end‑to‑end.
The plugin supports DeepL glossaries to enforce preferred terminology. You can set a default glossary ID and/or map specific language pairs to specific glossary IDs. This works for all field types, including Structured Text.
POST <proxy>/v2/translate with an extra glossary_id in the JSON body.gls-abc123).You can use either DatoCMS locales (e.g., en-US, pt-BR) or DeepL codes (e.g., EN, PT-BR). The plugin normalizes both to DeepL codes internally.
Scenario A: Single Language Pair If you only translate from English to German, you only need one glossary.
gls-12345 (Your EN->DE glossary)Scenario B: Multiple Language Pairs If you translate to multiple languages, map each one specifically.
EN->DE=gls-german123EN->FR=gls-french456Scenario C: Fallback Strategy Use specific glossaries for main languages, and a default for everything else.
gls-fallback999EN->DE=gls-german123One entry per line. Supported forms:
EN->DE=gls-abc123en-US->pt-BR=gls-xyz789fr→it gls-123 # alt arrow and delimiter*->pt-BR=gls-777 # wildcard: any source to targetEN->*=gls-555 # wildcard: source to any targetpt-BR=gls-777 # shorthand for *->pt-BRDelimiters: =, :, or whitespace. Arrows: ->, →, ⇒ (all treated the same). Case is ignored.
When translating from fromLocale → toLocale, the plugin picks a glossary ID using this precedence:
EN:PT-BR).en-US:pt-BR).*:PT-BR or *:pt-BR).EN:* or en-US:*).If DeepL returns a glossary mismatch (e.g., glossary languages don’t match the current pair) or a missing glossary, the plugin automatically retries the same request once without a glossary so your translation continues. A brief hint is surfaced in the UI logs.
The plugin only needs the glossary_id string. You can create and list glossaries with the DeepL API from your own machine or server. Examples with cURL:
List glossaries
curl -H "Authorization: DeepL-Auth-Key $DEEPL_AUTH_KEY" \ https://api.deepl.com/v2/glossariesCreate a small glossary inline (tab-separated entries)
curl -X POST https://api.deepl.com/v2/glossaries \ -H "Authorization: DeepL-Auth-Key $DEEPL_AUTH_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Marketing-EN-DE", "source_lang": "EN", "target_lang": "DE", "entries_format": "tsv", "entries": "CTA\tCall-to-Action\nlead magnet\tLeadmagnet" }'Note: If your account uses the Free endpoint, replace the host with https://api-free.deepl.com.
You do not need to expose /v2/glossaries through your proxy for the plugin to work — it only calls /v2/translate. Manage glossaries from your server/CLI, then paste the resulting IDs into the plugin settings.
notranslate, ph, etc.). Glossaries will not alter those tokens.EN->DE=... mapping.apiKey and gptModel remain valid.This project is licensed under the MIT License - see the LICENSE file for details.