Multilingual sites
Overview
Astropress provides locale-aware primitives without enforcing a routing scheme. The host application controls URL structure (path prefix, subdomain, or TLD strategy).
Configuring locales
Register the locales your site supports in registerCms():
import { registerCms } from "astropress";
registerCms({ siteUrl: "https://example.com", locales: ["en", "es", "fr", "de"], // first entry is the default locale templateKeys: ["content"], seedPages: [], archives: [], translationStatus: [],});Astropress exports two locale utilities:
| Function | Description |
|---|---|
localeFromPath(pathname) | Extracts a locale prefix from a URL path (e.g. /es/my-post/ → "es") |
localeFromAcceptLanguage(header) | Negotiates a locale from an Accept-Language header |
URL strategy
Path-prefix (recommended)
All content lives under a locale prefix:
/en/blog/my-post//es/blog/mi-articulo//fr/blog/mon-article/Configure Astro’s i18n routing in astro.config.mjs:
import { defineConfig } from "astro/config";
export default defineConfig({ i18n: { defaultLocale: "en", locales: ["en", "es", "fr", "de"], routing: { prefixDefaultLocale: false }, // /blog/... for EN, /es/blog/... for ES },});Subdomain or TLD
Use a CDN or reverse proxy to route traffic. The Astropress localeFromPath utility
can be replaced with a custom resolver that reads a cookie or subdomain header.
Content storage
Content records have a single slug and legacyUrl. For multilingual content:
- Separate slugs per locale — store
en/blog/my-postandes/blog/mi-articuloas distinct records. hreflanglinks — add alternate links to each page’s<head>pointing to the other locales.
Example using <AstropressSeoHead>:
---import { AstropressSeoHead } from "astropress/components/AstropressSeoHead.astro";---<head> <AstropressSeoHead record={pageRecord} siteUrl={siteUrl} /> <!-- hreflang for each locale --> <link rel="alternate" hreflang="en" href="https://example.com/blog/my-post/" /> <link rel="alternate" hreflang="es" href="https://example.com/es/blog/mi-articulo/" /> <link rel="alternate" hreflang="x-default" href="https://example.com/blog/my-post/" /></head>Translation workflow
- Create the primary content record in the default locale via the admin panel.
- Navigate to
/ap-admin/translationsto track translation state per route. - Create the translated record with the locale-prefixed slug (e.g.
es/blog/mi-articulo). - Update the translation status to
"translated"once the record is ready.
The translationStatus array in registerCms() seeds the initial translation dashboard
from a translation-status.json file in your host app.
Admin UI labels
Admin UI strings (login heading, nav labels, etc.) are currently English-only.
They can be customized via registerCms({ admin: { labels: { loginHeading: "..." } } }).
For full locale-switching in the admin panel, the host app can inject a ?locale=es
query parameter and pass it to registerCms on each request.
Locale-aware SEO
Use localeFromPath in your Astro layouts to set <html lang> and resolve alternate links:
---import { localeFromPath } from "astropress";const locale = localeFromPath(Astro.url.pathname);---<html lang={locale}> <head> <!-- ... --> </head></html>