Internationalization
Organize translated content, routes, interface messages, search filters, and locale-aware SEO.
A multilingual site needs more than translated interface labels. In svedocs, every page belongs to a locale, so its route, navigation entry, search results, Ask AI citations, HTML language attributes, and SEO alternates stay in sync.
Configure locales
TypeScriptsvedocs.config.tsimport { defineConfig } from 'svedocs/config';export default defineConfig({ site: { name: 'Acme Docs', url: 'https://docs.acme.com' }, i18n: { defaultLocale: 'en-US', prefixDefaultLocale: false, locales: [ { code: 'en-US', label: 'English', path: 'en', hreflang: 'en-US', dir: 'ltr' }, { code: 'zh-Hans', label: '中文', path: 'zh', hreflang: 'zh-CN', dir: 'ltr' }, { code: 'ar', label: 'العربية', path: 'ar', hreflang: 'ar', dir: 'rtl' } ] }});
The fields separate the locale's internal identity from its public URL and language tag:
| Field | Purpose |
|---|---|
code | Stable internal identifier stored on pages, search records, and AI filters. |
label | Human-readable name shown by the locale switcher. Use the language's own name. |
path | One directory and URL segment used to discover and route localized content. Defaults to code. |
hreflang | BCP 47 language tag used by SEO metadata and the document lang attribute. Defaults to code. |
dir | Document direction, ltr or rtl. Defaults to ltr. |
Keeping code and path separate lets a stable identifier such as zh-Hans use the shorter /zh route. Search filters and stored data can then remain unchanged if the public URL changes later.
svedocs validates locale settings before discovering content. It rejects duplicate codes, paths, or language tags; an unknown defaultLocale; invalid or multi-segment paths; paths that conflict with /docs; and message catalogs for unconfigured locales. Duplicate checks are case-insensitive so the result does not depend on the host file system.
Organize content
Use the locale path as the first directory under both content roots:
Textcontent/├── docs/│ ├── en/│ │ ├── index.md│ │ └── guides/deploy.md│ ├── zh/│ │ ├── index.md│ │ └── guides/deploy.md│ └── ar/│ ├── index.md│ └── guides/deploy.md└── pages/ ├── en/index.md ├── zh/index.md └── ar/index.md
With defaultLocale: 'en-US' and prefixDefaultLocale: false, the routes are:
| Source | Route | Locale code | Translation group |
|---|---|---|---|
content/docs/en/index.md | /docs | en-US | /docs |
content/docs/zh/index.md | /docs/zh | zh-Hans | /docs |
content/docs/ar/guides/deploy.md | /docs/ar/guides/deploy | ar | /docs/guides/deploy |
content/pages/en/index.md | / | en-US | / |
content/pages/zh/index.md | /zh | zh-Hans | / |
Set prefixDefaultLocale: true when every public URL must include a locale. The English routes above then become /docs/en and /en.
Default-locale files may also live directly under content/docs and content/pages. Use either root files or a default-locale directory, but not both, because matching files would produce duplicate routes. Mirrored locale directories are usually easier to compare and make it simpler to change the URL-prefix strategy later.
Translate frontmatter along with the body. Titles, descriptions, navTitle, keywords, and meaningful image alt text appear in navigation, search, and page metadata. Keep matching files at the same relative path in each locale directory so svedocs can recognize them as translations of one another.
Localize the shell
i18n.messages overrides built-in theme strings per locale. Missing values fall back to the English base catalog.
TypeScriptsvedocs.config.tsexport default defineConfig({ theme: { nav: [ { label: 'Docs', labelKey: 'nav.docs', href: '/docs' }, { label: 'Guides', labelKey: 'nav.guides', href: '/docs/guides' } ], home: { primaryAction: { label: 'Read the guides', labelKey: 'home.readGuides', href: '/docs/guides' } } }, i18n: { defaultLocale: 'en', locales: ['en', 'zh'], messages: { en: { 'nav.guides': 'Guides', 'home.readGuides': 'Read the guides' }, zh: { 'nav.guides': '指南', 'home.readGuides': '阅读指南', 'search.placeholder': '搜索文档', 'ask.label': '问 AI' } } }});
Built-in message keys cover navigation, search, Ask AI, the table of contents, article controls, code tools, landing-page cards, errors, and rendering errors. You can add your own keys as well. Navigation, social, footer, and home-action items accept labelKey; brand.labelKey, home.kickerKey, and home.visual.altKey work the same way. Keep the plain text value as the default shown when a translation is unavailable.
Custom components can read both the internal locale code and the HTML language tag from SvedocsThemeContext:
Svelte<script lang="ts"> import type { SvedocsThemeContext } from 'svedocs/theme/types'; export let context: SvedocsThemeContext;</script><p lang={context.languageTag} dir={context.locale?.dir ?? 'ltr'}> {context.t('home.readGuides')}</p>
Use context.localeCode for filters and stored state. Use context.languageTag for HTML and locale-sensitive formatting.
Locale-aware links
For internal links in the brand, top navigation, social links, footer, home actions, and Markdown content, svedocs looks for the matching page in the active locale. A configured /docs/guides link therefore becomes /docs/zh/guides on a Chinese page when that translation exists.
The locale switcher uses the same translation mapping. If the current page is unavailable in a language, that option is disabled instead of claiming the translation exists. Other internal links fall back to the default-locale page when it exists. Direct requests for a missing localized route receive a temporary HTTP 307 redirect on edge builds. Static and SPA builds prerender an equivalent redirect document for every finite fallback route, so static hosts can send the browser to the canonical default-locale page even though they do not return the 307 status themselves. If neither page exists, the request remains a 404.
Relative Markdown links between mirrored files are resolved to generated routes, including links that retain a .md, .mdx, or .svx extension:
MarkdownRead the [deployment guide](../guides/deploy.md).
A locale-neutral absolute link such as /docs/guides/deploy is localized in the same way. An explicit locale route such as /docs/zh/guides/deploy keeps the language selected by the author. Query strings and heading fragments are preserved.
Custom Svelte and theme components can use LocalizedLink to apply the same behavior in the browser without duplicating route rules:
Svelte<script lang="ts"> import { LocalizedLink } from 'svedocs/theme'; import type { SvedocsThemeContext } from 'svedocs/theme/types'; export let context: SvedocsThemeContext;</script><LocalizedLink {context} href="/docs/guides/deploy">Deploy</LocalizedLink>
Search and Ask AI
Pages and section records carry metadata.locale = code. With the default scope: 'current', search and Ask AI requests include the active locale code, so results do not mix languages. Set a provider's scope to all only when you actually want cross-language results.
Hosted indexes must preserve locale as a filterable field. The built-in Algolia, Typesense, Cloudflare AI Search, and local adapters already pass this scope through.
SEO and document language
Set site.url to generate absolute canonical and alternate URLs. For every group of translations, svedocs emits:
- A canonical URL for the current route.
- Reciprocal
hreflanglinks for translations that actually exist. x-defaultpointing to the default-locale page when it exists.- Sitemap alternate links with the same mapping.
- Open Graph locale and available alternate locales.
- JSON-LD
inLanguage. <html lang>fromlanguageTagand<html dir>fromdir.
Missing translations are not advertised as Open Graph or hreflang alternates. Custom root layouts should call createPageMetadata(config, page, pages) so Open Graph alternates can be derived from the complete page set.
Check translation coverage
Enable the project check or request it from the CLI:
TypeScriptsvedocs.config.tsexport default defineConfig({ checks: { translations: true }});
Shellsvedocs check --translationssvedocs check --translations --strict
The check matches public docs and standalone pages by relative path, then reports any configured locale that is missing a translation. Hidden pages are excluded. With --strict, warnings cause the command to fail.
Run the check after adding, renaming, or removing localized files. Also test at least one route per locale, one missing translation in the switcher, and one localized 404 route before release.