Markdown in, stunning docs out. No JavaScript toolchain, no config. ⭐ Star us on GitHub

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:

  1. The default locale builds from content.dir into its path (usually the site root).
  2. Each other locale's translations live in <content.dir>/<code> (e.g. docs/es/) and build into that locale's path (e.g. /es/).
  3. 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.
  4. 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:

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:

html
<!-- 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:

html
<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:

yaml
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
yaml
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/ }
bash
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:

yaml
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:

css
[dir="rtl"] .my-custom-component {
  text-align: right;
}

See Theme Configuration — CSS Classes Reference for the full list of locale and RTL classes.

6 min readLast updated 2026-07-26Samuel Abada