Machine Translation
Dune can automatically translate pages using DeepL, Google Translate, or a self-hosted LibreTranslate instance. Once configured, a 🤖 button appears next to untranslated pages in the translation status dashboard.
Machine translation produces a draft — a translated copy of the page saved as a new language file — which editors can then review and refine.
Quick start
1. Get an API key
| Provider | Sign up | Free tier |
|---|---|---|
| DeepL | deepl.com | 500,000 chars/month |
| Google Translate | console.cloud.google.com | $0 up to 500k chars/month |
| LibreTranslate | Self-hosted or libretranslate.com | Self-hosted = free |
2. Configure in site.yaml
machine_translation:
provider: "deepl" # "deepl" | "google" | "libretranslate"
apiKey: "$DEEPL_API_KEY" # "$ENV_VAR" expansion supported
Store your key in an environment variable — never commit it to version control.
3. Restart the server
The 🤖 button will appear on the Admin → Translations dashboard for all missing language versions.
Providers
DeepL
Best quality for European languages. Supports Markdown-aware translation (formatting is preserved).
machine_translation:
provider: "deepl"
apiKey: "$DEEPL_API_KEY"
Free API keys end with :fx — Dune auto-detects which endpoint to use.
Google Translate
Widest language coverage. Uses the Cloud Translation API v2 (Basic).
machine_translation:
provider: "google"
apiKey: "$GOOGLE_TRANSLATE_API_KEY"
LibreTranslate
Self-hosted, open-source, no usage limits or vendor lock-in.
machine_translation:
provider: "libretranslate"
baseUrl: "https://translate.example.com" # your instance URL
apiKey: "$LIBRETRANSLATE_API_KEY" # optional (if your instance requires it)
How it works
When you click 🤖 on a missing translation:
- Dune reads the source page (default language)
- The page body and title are sent to the configured MT provider
- The translated content is written as a new language file:
content/blog/post/default.md→content/blog/post/default.de.md
- The page is indexed and immediately accessible via its translated URL
- The dashboard row updates from "Missing" to "Translated"
The API key never leaves the server — all MT requests are made server-side.
REST API
Check MT status
GET /admin/api/i18n/mt-status
Returns whether machine translation is configured:
{
"enabled": true,
"provider": "deepl"
}
Translate a page
POST /admin/api/i18n/translate-page
{
"sourcePath": "content/blog/my-post/default.md",
"targetLang": "de"
}
Translates the page body and title, writes the translated file, triggers a content rebuild.
Response:
{
"ok": true,
"targetPath": "content/blog/my-post/default.de.md"
}
Translate a segment
POST /admin/api/i18n/translate-segment
{
"text": "Hello world",
"from": "en",
"to": "de"
}
Response:
{
"ok": true,
"translation": "Hallo Welt"
}
Configuration reference
# site.yaml
machine_translation:
provider: "deepl" # string — "deepl" | "google" | "libretranslate"
apiKey: "$DEEPL_API_KEY" # string — API key ("$ENV_VAR" expansion supported)
baseUrl: "https://..." # string — LibreTranslate instance URL (libretranslate only)
enabled: true # boolean — Set to false to disable without removing config
Notes
- Machine translation is always a draft — review and proofread before publishing
- The translation engine preserves Markdown formatting (headings, bold, links, code blocks)
- Frontmatter other than
titleis copied unchanged from the source page - If a translated file already exists, clicking 🤖 overwrites it with a fresh translation
- MT calls time out after 30 seconds; a 🔴 indicator appears if the provider is unreachable