Docs

Multilingual (i18n)

Per-entry locale variants using a slug--locale convention. No separate tables, no routing configuration.

Setup

In the admin, go to Settings → Site and set:

  • Primary locale — e.g. en
  • Additional locales — comma-separated, e.g. en,de,fr

The locale switcher appears automatically in the entry editor once additional locales are configured.

The slug--locale convention

my-post          ← base / primary entry (e.g. English)
my-post--de      ← German variant
my-post--fr      ← French variant

Locale variants are regular entries with a special slug format. They appear in the editor as tabs. Creating a variant duplicates the base entry and appends the locale suffix.

Reading locale content

import { getLocaleCollection, getLocaleEntry, locale, locales } from 'orbiter:collections';

// All German posts
const posts = await getLocaleCollection('posts', 'de');

// A specific German post, with fallback to the base entry
const post = await getLocaleEntry('posts', 'my-post', 'de');

Locale-aware static paths

---
import { getCollection, locales } from 'orbiter:collections';

export async function getStaticPaths() {
  const posts = await getCollection('posts');
  const base  = posts.filter(p => !p.slug.includes('--'));

  return base.flatMap(post =>
    locales.map(loc => ({
      params: { lang: loc, slug: post.slug },
    }))
  );
}

const { lang, slug } = Astro.params;
const post = await getLocaleEntry('posts', slug, lang);
---

<html lang={lang}>
  <h1>{post.data.title}</h1>
</html>

Fallback behaviour

getLocaleEntry() tries to find slug--locale first. If it doesn't exist, it returns the base entry (slug). This means you can translate incrementally — untranslated entries gracefully fall back to the primary language.

Accessing locale config

import { locale, locales } from 'orbiter:collections';
// locale  → 'en'            (the primary locale)
// locales → ['en', 'de', 'fr']

Use locales to build language switchers or generate <link rel="alternate"> tags.