Multilingual Content
Dune supports multilingual sites through a filename-based translation model. Each language variant of a page is a separate file living in the same content folder.
Enabling i18n
Add supported languages to config/system.yaml:
languages:
supported: ["en", "de", "fr"]
default: "en"
include_default_in_url: false # /page (not /en/page) for the default language
Once supported contains more than one language, Dune activates multilingual routing.
File naming convention
Create language variants by adding a language code before the file extension:
content/02.about/
├── default.md # English (default language)
├── default.de.md # German
└── default.fr.md # French
The pattern is {template}.{lang}.{ext} where {lang} is one of the codes in languages.supported.
A .tsx content page cannot use the language suffix — use separate folders for TSX pages in different languages.
URL structure
With include_default_in_url: false (the default):
| File | URL |
|---|---|
default.md |
/about (English — no prefix) |
default.de.md |
/de/about |
default.fr.md |
/fr/about |
With include_default_in_url: true:
| File | URL |
|---|---|
default.md |
/en/about |
default.de.md |
/de/about |
default.fr.md |
/fr/about |
The home page works the same way: /de serves the German home page.
Language fallback
If a visitor requests /de/about but default.de.md doesn't exist, Dune serves the default-language version (default.md) rather than returning a 404.
Internal links
Links written in Markdown content are automatically rewritten to include the current language prefix when rendering non-default-language pages.
For example, in a German page:
[Contact us](/contact)
…is rewritten to:
<a href="/de/contact">Contact us</a>
The following URL patterns are never rewritten: /themes/, /content-media/, /api/, /admin/.
Navigation
Each language has its own navigation tree. Templates receive the current language's nav via the nav prop. Pages are filtered to only show content for the active language.
Sitemap
The sitemap (/sitemap.xml) includes xhtml:link hreflang alternates for all multilingual pages, including an x-default entry pointing to the default-language version.
Checking translation status
dune content:i18n-status
Reports translation coverage across all configured languages — which pages have translations, which are missing, and which are outdated (the default-language version has been updated since the translation was written).
RTL languages
Dune automatically detects right-to-left languages (Arabic, Hebrew, Persian, Urdu, and others) and applies the correct dir attribute.
Theme templates
The TemplateProps object passed to TSX templates includes a dir field:
export default function Layout({ dir, title, content }: TemplateProps) {
return (
<html dir={dir} lang={props.page.language ?? "en"}>
<head>...</head>
<body>{content}</body>
</html>
);
}
dir is "rtl" for Arabic, Hebrew, Persian, Urdu, and similar languages; "ltr" for everything else.
Automatic injection
If your theme template does not include a dir attribute on <html>, Dune injects it automatically on the server when serving RTL-language pages. This ensures correct text direction even with themes that predate RTL support.
Admin panel
The admin panel mirrors its layout when the site's default language (or the language of the page being edited) is RTL:
- Sidebar shifts to the right side
- Text inputs are right-aligned
- Navigation items right-align
- Breadcrumbs reverse direction
Extending the RTL language list
Add languages to the built-in RTL set via system.yaml:
languages:
supported: ["en", "ar", "ku-Latn"]
default: "en"
rtl_override: ["ku-Latn"] # Force Kurdish Latin script to RTL (unusual, example only)
rtl_override entries are checked after the built-in list, so you can add rare scripts or regional variants not covered by the default detection.