Localization (i18n)
WollyCMS supports multi-language content through page-level localization. Each locale gets its own page with its own content, linked together by a translation group. This gives editors full control over each language version while keeping translations connected.
How it works
Section titled “How it works”- Each page has a
localefield (e.g.,en,es,fr) set at creation time - Translations of the same page share a
translationGroupId(UUID) - The content API filters by locale —
?locale=esreturns only Spanish pages - Single page responses include a
translationsarray linking to other language versions - Slugs are unique per locale —
/aboutcan exist in bothenandes
1. Configure supported locales
Section titled “1. Configure supported locales”Go to System → Settings in the admin. Under “Localization”:
- Supported Locales: Add the language codes you need (e.g.,
en,es,fr,de). Press Enter to add each one. - Default Locale: Select which locale is used when no
?locale=param is provided in API requests.
2. Create translated pages
Section titled “2. Create translated pages”On any page in the editor, the sidebar shows a Translations card:
- The current page’s locale is displayed as a badge
- Existing translations are listed with links to their editors
- “Translate to…” buttons appear for locales that don’t have a translation yet
Clicking “Translate to ES” creates a new draft page with the same slug and content type, linked to the original via the translation group. The new page starts as a draft so you can translate the content before publishing.
3. Filter pages by locale
Section titled “3. Filter pages by locale”The pages list in the admin has a locale filter dropdown (shown when multiple locales are configured). Use it to view pages in a specific language.
Content API
Section titled “Content API”List pages by locale
Section titled “List pages by locale”GET /api/content/pages?type=article&locale=esIf locale is omitted, the site’s defaultLocale is used. Pages are filtered to only return the requested locale.
Get a single page with translations
Section titled “Get a single page with translations”GET /api/content/pages/about?locale=esResponse includes the page data plus a translations array:
{ "data": { "id": 42, "type": "secondary_page", "title": "Sobre Nosotros", "slug": "about", "locale": "es", "translationGroupId": "550e8400-e29b-41d4-a716-446655440000", "translations": [ { "id": 10, "locale": "en", "slug": "about", "title": "About Us" }, { "id": 55, "locale": "fr", "slug": "about", "title": "À propos" } ], "regions": { ... }, "seo": { ... } }}Get site locale config
Section titled “Get site locale config”GET /api/content/configReturns defaultLocale and supportedLocales alongside other site config.
Astro frontend setup
Section titled “Astro frontend setup”WollyCMS handles the content and translation linking, but your Astro frontend needs to be configured to serve multi-language pages. This means:
- Route structure — Add locale-prefixed routes so each language has its own URL (e.g.,
/en/about,/es/about) - Locale parameter — Pass
?locale=when fetching content from the CMS API - Language switcher — Build a component that lets visitors switch between available languages using the
translationsarray from the page response - Site config — Fetch
supportedLocalesanddefaultLocalefrom/api/content/configto drive your routing and UI
None of this happens automatically — the CMS stores and serves the data, your frontend decides how to present it. The examples below show the common patterns.
Fetching localized content
Section titled “Fetching localized content”// Fetch Spanish articlesconst articles = await client.pages.list({ type: 'article', locale: 'es' });
// Fetch a page in a specific localeconst page = await client.pages.getBySlug('about', { locale: 'es' });
// Access translations for a language switcherconst translations = page.translations;// [{ id: 10, locale: 'en', slug: 'about', title: 'About Us' }, ...]Locale-prefixed routes
Section titled “Locale-prefixed routes”A common Astro pattern for multi-language sites:
src/pages/ [locale]/ [...slug].astro # /en/about, /es/about, /fr/about [...slug].astro # /about (redirects to default locale)In [locale]/[...slug].astro:
---const { locale, slug } = Astro.params;const page = await client.pages.getBySlug(slug, { locale });if (!page) return Astro.redirect('/404');---Language switcher component
Section titled “Language switcher component”---const { translations, currentLocale } = Astro.props;---{translations?.length > 0 && ( <nav aria-label="Language"> {translations.map(t => ( <a href={`/${t.locale}/${t.slug}`} aria-current={t.locale === currentLocale ? 'page' : undefined}> {t.locale.toUpperCase()} </a> ))} </nav>)}Design notes
Section titled “Design notes”- Page-level, not field-level: Each locale is a separate page with its own blocks, SEO fields, and content. This gives editors full flexibility per language rather than forcing identical page structure across locales.
- Slugs are per-locale: The same slug (
about) can exist in multiple locales. The content API disambiguates via the?locale=parameter. - Translation groups are optional: A page doesn’t need to be translated. Pages without a
translationGroupIdsimply exist in one locale. - No content is auto-translated: WollyCMS creates draft translation pages with the same slug and content type but leaves the actual translation to editors (or external tools).
Admin API
Section titled “Admin API”| Endpoint | Method | Description |
|---|---|---|
/api/admin/pages | GET | Filter by ?locale= |
/api/admin/pages | POST | Include locale in body |
/api/admin/pages/:id/translate | POST | Create translation: { locale: "es" } |
/api/admin/pages/:id/translations | GET | List all translations in the group |