Internationalization
Reaching a global audience means serving the right language at the right URL, with sensible fallbacks and clean, crawlable routes. Astro ships first-class internationalization (i18n) support built directly into the router, so you can map locales to URL prefixes, redirect visitors based on their browser preferences, and generate localized paths without bolting on a third-party library. Because Astro renders on the server by default, all of this happens before any JavaScript reaches the browser — your localized pages stay fast and SEO-friendly.
Enabling i18n routing
You configure internationalization in astro.config.mjs under the i18n key. At minimum you declare your supported locales and a defaultLocale. Astro uses this to build locale-prefixed routes and to expose a set of helper utilities throughout your project.
import { defineConfig } from "astro/config";
export default defineConfig({
i18n: {
defaultLocale: "en",
locales: ["en", "es", "fr"],
routing: {
prefixDefaultLocale: false,
},
},
});
With this configuration, en is the default and its pages live at the root (/about), while other locales are prefixed (/es/about, /fr/about). Setting prefixDefaultLocale: true would instead force every locale to carry a prefix, including the default (/en/about).
The
localesarray can also contain grouped locale codes — for example{ path: "spanish", codes: ["es", "es-AR"] }maps multiple language codes to a single/spanish/URL segment. Match these codes carefully against your content folder names.
How routes map to folders
Astro derives localized routes from your src/pages directory. You create one subfolder per non-default locale and place the translated pages inside it. The default locale’s pages sit at the top level (unless you enable prefixDefaultLocale).
src/pages/
├── index.astro → /
├── about.astro → /about
├── es/
│ ├── index.astro → /es/
│ └── about.astro → /es/about
└── fr/
├── index.astro → /fr/
└── about.astro → /fr/about
This file-based approach keeps each translation as a real, editable page rather than a key in a giant JSON blob — ideal for marketing and content-heavy sites where translations diverge structurally.
Built-in helper functions
Astro exposes a virtual module, astro:i18n, with utilities for constructing locale-aware URLs and reading the current locale. These work in any .astro component script.
---
import { getRelativeLocaleUrl, getAbsoluteLocaleUrl } from "astro:i18n";
const spanishAbout = getRelativeLocaleUrl("es", "about");
const frenchHome = getAbsoluteLocaleUrl("fr", "");
---
<a href={spanishAbout}>Ver en español</a>
<a href={frenchHome}>Voir en français</a>
The current request’s locale is always available on the global Astro object via Astro.currentLocale, which Astro infers from the URL.
---
const locale = Astro.currentLocale ?? "en";
const greetings = { en: "Hello", es: "Hola", fr: "Bonjour" };
---
<p>{greetings[locale]}</p>
The key helpers are summarized below.
| Helper | Returns | Use for |
|---|---|---|
getRelativeLocaleUrl(locale, path) | A path like /es/about | In-app links |
getAbsoluteLocaleUrl(locale, path) | A full URL with your site origin | Canonical tags, sitemaps, feeds |
getRelativeLocaleUrlList(path) | An array of all locale URLs for a path | Building hreflang link sets |
Astro.currentLocale | The active locale string | Conditional rendering and translation lookups |
Locale detection and fallback
For server-rendered routes, Astro can redirect a visitor to their preferred language based on the Accept-Language header. Enable it with routing options and a fallback map that points missing translations back to a locale you do have.
import { defineConfig } from "astro/config";
export default defineConfig({
i18n: {
defaultLocale: "en",
locales: ["en", "es", "fr"],
fallback: {
fr: "en",
es: "en",
},
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: true,
},
},
});
With fallback, a request for /fr/pricing that has no src/pages/fr/pricing.astro will serve the English page instead of returning a 404. Browser-based redirection requires an on-demand rendered route, so add a server adapter and ensure the relevant pages are not prerendered.
Adding hreflang tags
Search engines need hreflang annotations to understand which URL serves which language. Generate them from the locale URL list so they stay in sync with your config.
---
import { getAbsoluteLocaleUrl } from "astro:i18n";
const locales = ["en", "es", "fr"];
const path = Astro.url.pathname.replace(/^\/(es|fr)/, "");
---
{locales.map((locale) => (
<link
rel="alternate"
hreflang={locale}
href={getAbsoluteLocaleUrl(locale, path)}
/>
))}
Best practices
- Declare every supported language in
localesand pick a stabledefaultLocalebefore building out content folders. - Keep
prefixDefaultLocaleconsistent across your site; switching it later changes every default-locale URL and breaks links. - Use
getRelativeLocaleUrlfor internal links instead of hard-coding prefixes, so your markup survives config changes. - Configure a
fallbackmap so partially translated sites never serve broken pages. - Emit
hreflangand canonical tags on every page to protect your multilingual SEO. - Reserve
Accept-Languageredirection for on-demand routes, and remember it requires a server adapter. - Store shared UI strings in typed translation dictionaries keyed by
Astro.currentLocaleto avoid scattering text across components.