Internationalizing Starlight Docs
Starlight, the Astro-based documentation framework, treats multiple languages as a first-class feature: it has locale configuration, translated interface strings for dozens of languages, sidebar label translations, a built-in fallback for untranslated pages, and per-language search indexes. That makes it one of the quickest ways to take documentation from one language to several. The parts it leaves to you are the same as everywhere — organising translated content, keeping it in sync, and deciding what readers see when a translation is missing.
This guide adds German, French and Japanese to an English Starlight site of 480 pages, keeping English URLs unchanged, translating interface strings and sidebar groups, handling untranslated pages, and measuring what four languages cost in build time and output size. It is part of Docs Frameworks: Docusaurus, Starlight and VitePress, and builds on the general approach in Internationalization for Static Sites.
Prerequisites
- A Starlight 0.29+ site with content in
src/content/docs/. - Translations available or planned for at least part of the content.
- Decisions on URL structure (here English stays at the root) and on which locale codes to use.
Step 1: Configure Locales
// astro.config.mjs
import starlight from '@astrojs/starlight';
export default defineConfig({
site: 'https://docs.example.com',
integrations: [
starlight({
title: { en: 'Example Docs', de: 'Example Dokumentation', ja: 'Example ドキュメント' },
defaultLocale: 'root',
locales: {
root: { label: 'English', lang: 'en' },
de: { label: 'Deutsch' },
fr: { label: 'Français' },
ja: { label: '日本語' }
}
})
]
});
defaultLocale: 'root' with a root locale keeps English content directly in src/content/docs/ with unprefixed URLs, so every existing URL survives. The other locales live in src/content/docs/de/, fr/ and ja/, mirroring the English structure. Starlight adds a language picker to the header automatically and generates hreflang alternates for pages that exist in several languages, matched by their path relative to the locale directory.
Step 2: Mirror the Content Structure
Translations must keep the same relative path as the English page for Starlight to link them:
src/content/docs/
guide/install.md → /guide/install/
de/guide/install.md → /de/guide/install/
fr/guide/install.md → /fr/guide/install/
ja/guide/install.md → /ja/guide/install/
That path matching is simpler than the translation keys described in hreflang and Language Alternates on Static Sites, and it means slugs are not localized. Most documentation teams accept English slugs in all languages; it keeps URLs predictable and makes support links work across languages. If localized slugs are required, set slug in frontmatter and accept that the automatic alternates will no longer match.
Step 3: Translate Interface Strings and Sidebar Labels
Starlight's own UI is already translated for German, French and Japanese. Custom strings — and overrides of built-in ones — go in src/content/i18n/<locale>.json:
// src/content/i18n/de.json
{
"search.label": "Dokumentation durchsuchen",
"custom.betaBadge": "Beta",
"custom.feedback.question": "War diese Seite hilfreich?"
}
Custom components read them with Astro.locals.t('custom.feedback.question'). Sidebar group labels are translated in the sidebar configuration:
sidebar: [
{
label: 'Guides',
translations: { de: 'Anleitungen', fr: 'Guides', ja: 'ガイド' },
autogenerate: { directory: 'guide' }
}
]
Autogenerated entries use each page's translated title (or sidebar.label) from frontmatter, so translated pages appear with translated names and untranslated ones with the English name.
Step 4: Decide How Missing Pages Behave
Starlight's default fallback renders the English content at /de/guide/troubleshooting/ with a banner saying the page is not yet available in German, and marks the content with lang="en". That keeps every sidebar link working in every language, which readers appreciate, and the lang attribute keeps screen readers correct.
For search engines, fallback pages are English content at German URLs. Starlight excludes them from the hreflang group, and on this site they were also excluded from the sitemap with a small filter, so search engines index only real translations. At launch, German had 100% coverage, French 72% and Japanese 58%; the fallback count per locale went into a build report that the translation team used as its backlog.
Step 5: Search per Language
Starlight's Pagefind integration indexes each page under its lang, so the search dialog on a German page only loads and searches the German index. Fallback pages carry lang="en" and therefore land in the English index, which keeps German results clean. Japanese needs no special handling in current Pagefind versions, which segment CJK text; see Multilingual Search on Static Sites for the details and index sizes.
Build Time and Output Size
Every locale adds pages — real translations plus fallbacks — so build time grows with the number of locales, not with the number of translations:
| Configuration | Pages built | Build time | Output size | Pagefind index total |
|---|---|---|---|---|
| English only | 480 | 24 s | 21 MB | 3.1 MB |
| + German | 960 | 41 s | 38 MB | 6.0 MB |
| + French, Japanese | 1,920 | 79 s | 74 MB | 11.8 MB |
Measured with hyperfine, five runs, cold content-layer cache, on an 8-vCPU runner. Readers are unaffected by the total — each downloads only its language's pages and search index — but CI time roughly tripled. Persisting node_modules/.astro between CI runs brought warm builds of the four-language site to 52 s, as in Incremental Builds in Astro with the Content Layer.
Keeping Translations Current
Starlight does not track whether a translation is out of date. Add the source-commit approach from Translation Workflows with Git and Crowdin: each translated file records the commit of the English file it was translated from, and CI lists translations whose source has changed since. A small component override can show a "the English version was updated more recently" notice on stale pages, alongside Starlight's own fallback notice for missing ones. Translators then see two queues — missing pages and stale pages — and readers are told the truth about both.
Right-to-Left Locales
Starlight supports right-to-left languages with a dir: 'rtl' option per locale, and its own layout uses logical CSS properties, so the sidebar, table of contents and navigation mirror correctly out of the box. Custom components and CSS overrides are where problems appear: any margin-left or left: in a site's custom CSS will not mirror. Before adding Arabic or Hebrew, run the forced-RTL check described in Right-to-Left Layouts on Static Sites against the custom components only; on this site it found six physical properties in two component overrides, fixed in an afternoon. Code blocks stay left-to-right automatically because Starlight's Expressive Code integration sets their direction explicitly.
Date formats, number formats and "last updated" labels follow the page locale automatically in Starlight, because it formats them with Intl using the current language; custom components should do the same rather than hard-coding English formats, so a German page shows "25. September 2026" instead of "September 25, 2026".
Measured Impact
| Measure | English only | Four locales |
|---|---|---|
| Custom i18n code | — | 3 JSON files, 1 sidebar config block, 12-line sitemap filter |
| English URLs changed | — | 0 |
| Broken language-picker links (Playwright crawl) | — | 0 |
| Build time, warm cache | 17 s | 52 s |
| Organic sessions from DE, FR, JP after 12 weeks | 7% of total | 31% of total |
Pitfalls & Rollback
- Moving English under
/en/late. Use therootlocale to keep URLs stable. - Localized slugs without planning. Path matching breaks; decide before translating.
- Fallback pages in the sitemap. English text at localized URLs should not be submitted.
- Overriding UI strings in components. Use the i18n JSON so translators see them.
- Rollback: removing a locale from the config stops building it; its directory can stay in the repository until translations are ready again.
Conclusion
Starlight makes a multilingual documentation site mostly configuration: locales in one block, translated interface strings built in, sidebar labels with a translations map, fallbacks that keep navigation whole, and search that stays per language. Keep the default language at the root, mirror paths exactly, keep fallback pages out of the sitemap, and add stale-translation tracking yourself.
FAQ
Does Starlight translate its own interface?
Yes. Starlight ships translations of its built-in UI strings, such as search, table of contents and pagination labels, for many languages. You can override any string, and add strings for custom components, through i18n JSON files in the content directory.
What happens when a page is missing in a locale?
Starlight renders the default-language page at the localized URL and shows a notice that the content is not yet translated. That keeps navigation complete while translations catch up. The page uses the default language's lang attribute so screen readers pronounce it correctly.
Is Starlight search per language?
Yes. Starlight's Pagefind integration builds a separate index per language based on each page's lang attribute, so readers searching in German only get German results and only download the German index.
Should the root locale have a URL prefix?
Use the root locale option to keep the default language unprefixed if the site already exists in one language, so existing URLs do not change. New sites can prefix every language for symmetry.
Related
- Parent: Docs Frameworks: Docusaurus, Starlight and VitePress — framework comparison.
- i18n Routing in Astro — the Astro layer under Starlight.
- Customizing Starlight Without Forking the Theme — component overrides for notices.
- Comparing Docs Framework Build Times — how locales affect build time.
- Picking an SSG for a Multi-Language Documentation Site — the selection view.