Skip to content

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.

  • Each page has a locale field (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=es returns only Spanish pages
  • Single page responses include a translations array linking to other language versions
  • Slugs are unique per locale — /about can exist in both en and es

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.

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.

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.

GET /api/content/pages?type=article&locale=es

If locale is omitted, the site’s defaultLocale is used. Pages are filtered to only return the requested locale.

GET /api/content/pages/about?locale=es

Response 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 /api/content/config

Returns defaultLocale and supportedLocales alongside other site config.

WollyCMS handles the content and translation linking, but your Astro frontend needs to be configured to serve multi-language pages. This means:

  1. Route structure — Add locale-prefixed routes so each language has its own URL (e.g., /en/about, /es/about)
  2. Locale parameter — Pass ?locale= when fetching content from the CMS API
  3. Language switcher — Build a component that lets visitors switch between available languages using the translations array from the page response
  4. Site config — Fetch supportedLocales and defaultLocale from /api/content/config to 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.

// Fetch Spanish articles
const articles = await client.pages.list({ type: 'article', locale: 'es' });
// Fetch a page in a specific locale
const page = await client.pages.getBySlug('about', { locale: 'es' });
// Access translations for a language switcher
const translations = page.translations;
// [{ id: 10, locale: 'en', slug: 'about', title: 'About Us' }, ...]

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');
---
src/components/LanguageSwitcher.astro
---
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>
)}
  • 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 translationGroupId simply 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).
EndpointMethodDescription
/api/admin/pagesGETFilter by ?locale=
/api/admin/pagesPOSTInclude locale in body
/api/admin/pages/:id/translatePOSTCreate translation: { locale: "es" }
/api/admin/pages/:id/translationsGETList all translations in the group