Internationalization (i18n)
Stardust supports internationalized documentation with localized UI strings, a locale switcher dropdown, RTL layout support, and SEO-friendly hreflang tags.
How It Works
When i18n.enabled is set with more than one locale, a plain stardust build
builds every locale in one pass:
- The default locale builds from
content.dirinto itspath(usually the site root). - Each other locale's translations live in
<content.dir>/<code>(e.g.docs/es/) and build into that locale'spath(e.g./es/). - A page with no translation falls back to the default locale's content and shows a "not yet translated" notice, so every locale has the full page set and the switcher always lands on the same page.
- URLs, the search index,
<html lang>/dir, and hreflang are all set per locale; if the default locale isn't at the root, a root redirect points there.
A page's translation is resolved from a locale-suffixed sibling
(guide.es.md), then a file under the locale subdirectory (es/guide.md), then
the default page — so you can use either layout, or mix them:
docs/
index.md → / (en, default)
guide.md → /guide (en)
guide.es.md → /es/guide (es, suffix style)
es/
index.md → /es/ (es, subdirectory style)
(no api translation → /es/api falls back to docs/api.md + notice)
Configuration
Add an i18n section to your stardust.yaml:
i18n:
enabled: true
defaultLocale: en
locales:
- code: en
label: English
path: /en/
- code: ar
label: العربية
dir: rtl
path: /ar/
- code: fr
label: Français
path: /fr/
Options
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
false |
Enable i18n UI |
defaultLocale |
string |
"en" |
The canonical locale — builds from content.dir and is what untranslated pages fall back to |
locales |
array |
[] |
All available locales |
strings |
object |
{} |
UI string overrides (see UI Strings) |
Locale Entry
Each item in locales has:
| Key | Type | Required | Description |
|---|---|---|---|
code |
string |
Yes | Language code (e.g. "en", "ar", "fr") |
label |
string |
Yes | Display label in the locale dropdown |
path |
string |
Yes | URL path where this locale is deployed (/ for the site root) |
dir |
string |
No | Text direction — "ltr" (default) or "rtl" |
source |
string |
No | Directory holding this locale's translations. Defaults to <content.dir>/<code> |
sidebar |
array |
No | Sidebar for this locale, replacing the shared one — use it to translate group titles and page labels |
With versioning
i18n composes with versioning: stardust build --all-versions
builds every version in every locale, nesting the locale inside the version
prefix (/v1/es/guide). The version switcher keeps your current locale and the
locale switcher keeps your current version.
Locale Dropdown
When i18n is enabled with 2 or more locales, a locale switcher appears in the header. It shows the current locale's label and links to all other locales.
With a single locale, no dropdown is rendered.
HTML Attributes
Stardust automatically sets lang and dir on the <html> element based on the current locale:
<!-- English build -->
<html lang="en" dir="ltr">
<!-- Arabic build -->
<html lang="ar" dir="rtl">
When i18n is not configured, defaults to lang="en" and dir="ltr".
RTL Support
When a locale has dir: rtl, Stardust applies automatic layout adjustments:
- The sidebar moves to the right side
- The table of contents moves to the left
- Previous/next navigation respects RTL direction
- Sidebar chevrons are mirrored
No extra configuration is needed — RTL styles are applied via the [dir="rtl"] CSS selector.
Hreflang Tags
When i18n is enabled and a url is set in your config, Stardust emits <link rel="alternate" hreflang="..."> tags for each locale, plus an x-default entry pointing to the default locale:
<link rel="alternate" hreflang="en" href="https://docs.example.com/en/getting-started">
<link rel="alternate" hreflang="ar" href="https://docs.example.com/ar/getting-started">
<link rel="alternate" hreflang="x-default" href="https://docs.example.com/en/getting-started">
This tells search engines which page variants exist for each language.
UI Strings
All UI chrome text (buttons, labels, navigation) can be overridden per locale via the strings map. Use dot-notation keys:
i18n:
enabled: true
defaultLocale: ar
strings:
nav.previous: "→ السابق"
nav.next: "التالي ←"
footer.poweredBy: "مدعوم بواسطة"
theme.toggle: "تبديل الوضع الداكن"
menu.toggle: "القائمة"
menu.close: "إغلاق"
locale.select: "اختر اللغة"
Available String Keys
| Key | Default | Used In |
|---|---|---|
nav.previous |
"← Previous" |
Previous page link |
nav.next |
"Next →" |
Next page link |
footer.poweredBy |
"Powered by" |
Footer |
theme.toggle |
"Toggle dark mode" |
Theme button aria-label |
menu.toggle |
"Toggle menu" |
Mobile menu button aria-label |
menu.close |
"Close menu" |
Sidebar close button aria-label |
announcement.dismiss |
"Dismiss announcement" |
Announcement dismiss aria-label |
version.select |
"Select version" |
Version dropdown aria-label |
version.dismiss |
"Dismiss banner" |
Version banner dismiss aria-label |
search.noResults |
"No results found for \"%s\"" |
Search — no results (%s = query) |
search.oneResult |
"1 result" |
Search — single result |
search.manyResults |
"%s results" |
Search — multiple results (%s = count) |
search.searching |
"Searching..." |
Search — loading state |
locale.select |
"Select language" |
Locale dropdown aria-label |
locale.untranslated |
"This page has not been translated yet." |
Notice on pages that fell back to the default locale |
page.readingTime |
"%s min read" |
Reading-time label (%s = minutes) |
page.lastUpdated |
"Last updated %s" |
Last-updated label (%s = date) |
When no overrides are provided, English defaults are used.
Workflow
Keep the default locale in content.dir, then add translations either as
locale-suffixed siblings or in a per-locale subdirectory — whichever you prefer:
docs/
index.md # default (en)
guide.md
guide.es.md # Spanish, suffix style
es/
index.md # Spanish, subdirectory style
ar/
index.md # Arabic
content:
dir: docs
i18n:
enabled: true
defaultLocale: en
locales:
- { code: en, label: English, path: / }
- { code: es, label: Español, path: /es/ }
- { code: ar, label: العربية, dir: rtl, path: /ar/ }
stardust build
# dist/ → en (default)
# dist/es/ → es (untranslated pages fall back to en)
# dist/ar/ → ar
Translate pages incrementally — any page you haven't translated yet renders the default-locale content with a "not yet translated" notice, so links never break.
Styling
The locale dropdown can be customized with CSS class overrides via custom CSS:
theme:
custom:
css: |
.locale-dropdown-trigger {
background: var(--color-primary);
color: white;
border-color: var(--color-primary);
}
RTL layout overrides use the [dir="rtl"] selector. You can extend them in custom CSS:
[dir="rtl"] .my-custom-component {
text-align: right;
}
See Theme Configuration — CSS Classes Reference for the full list of locale and RTL classes.