Add a language
Themes marked i18n-ready on their theme page are built so a second language is a
configuration task, not a rewrite. English stays the complete source of truth in
src/config/site.ts. Every other language is an override file that only holds the keys you
translate. Anything you leave out falls back to English.
What the theme already does
Section titled “What the theme already does”- Every component reads its copy through
getSiteConfig(Astro.url)insrc/config/localized.ts, so the same component renders English on/about/and French on/fr/about/. - Every route has a twin under
src/pages/[locale]/. With only English registered those twins build zero pages. Register a language and they light up. - Internal links inside a localized page carry the language prefix. Config values whose key
ends in
urlorhrefare prefixed automatically, and components uselocalizePath()for the links they build themselves. <html lang>,og:localeandhreflangalternates follow the language of the page.
Adding a language in three steps
Section titled “Adding a language in three steps”1. Create the translation file
Section titled “1. Create the translation file”Copy the template and rename it to the language code you want:
src/i18n/locales/template.ts -> src/i18n/locales/fr.tsThe file exports one object that mirrors src/config/site.ts key for key. Every key is
optional. Translate what you need:
import type { SiteConfig } from "~/config/site";import type { DeepPartial } from "~/i18n/config";
const overrides: DeepPartial<SiteConfig> = { navigation: { links: [ { label: "Accueil", url: "/" }, { label: "Tarifs", url: "/pricing/" }, ], }, hero: { heading: "Des factures claires, des clients sereins.", },};
export default overrides;Keep internal URLs exactly as they are in English. The theme adds the /fr/ prefix for you.
2. Register it
Section titled “2. Register it”Open src/i18n/config.ts and add the language to the locales map:
import fr from "./locales/fr";
export const locales = { en: { label: "English", htmlLang: "en", ogLocale: "en_US", overrides: {} }, fr: { label: "Français", htmlLang: "fr", ogLocale: "fr_FR", overrides: fr },} satisfies Record<string, LocaleDefinition>;htmlLang goes into <html lang>, ogLocale into the Open Graph tags.
3. Build
Section titled “3. Build”pnpm buildEvery route now has a /fr/... twin. The page count roughly doubles. Open /fr/ and check
the navigation, then open one detail page and check its links stay under /fr/.
Markdown content
Section titled “Markdown content”Blog posts, case studies and other Markdown collections are shared across languages. The
listing pages and detail routes exist under /fr/, but the Markdown body is whatever you
wrote. Two common approaches:
- Keep long-form content in one language and translate only the interface and the marketing pages. This is what most small teams ship first.
- Duplicate the collection per language and point the localized listing at the right one.
This is a small code change in the
[locale]mirror of that route.
Adding a new page later
Section titled “Adding a new page later”When you add a page under src/pages/, add its twin under src/pages/[locale]/. Copy the
sibling that already exists there. Static pages are three lines:
---import Page from "../about.astro";import { getLocaleStaticPaths } from "~/i18n/config";export const getStaticPaths = getLocaleStaticPaths;---<Page />Dynamic routes ([slug], [...page]) map every entry to every registered language.
The existing mirrors show the pattern for collections, config arrays and paginated lists.
Two themes with a slightly different layout
Section titled “Two themes with a slightly different layout”- Sable keeps the override objects inline in
src/i18n/config.tsinstead of separate files. Same shape, same rules. - Moxie keeps the English copy in
src/i18n/locales/en.ts.src/config/site.tsis the runtime facade there. Createsrc/i18n/locales/fr.tsnext to it.
Checklist before you ship a language
Section titled “Checklist before you ship a language”- Every visible string you care about is in the override file. Grep the built
/fr/pages for English words to find leftovers. - Links on the localized pages start with
/fr/. Grepdist/frforhref="/without the prefix. <html lang="fr">is on the localized pages.- The language switcher, if you add one, links with
localizePath(Astro.url.pathname, code).