webhouse.appwebhouse.appdocs

Multi-language content with automatic AI translation, locale routing, and hreflang.

Configure locales

typescript
export default defineConfig({
  defaultLocale: 'en',
  locales: ['en', 'da'],

  collections: [
    defineCollection({
      name: 'posts',
      sourceLocale: 'en',
      locales: ['en', 'da'],
      translatable: true,
      fields: [
        { name: 'title', type: 'text', required: true },
        { name: 'content', type: 'richtext' },
      ],
    }),
  ],
});

How translations work

Each translation is a separate document linked via translationGroup — a shared UUID connecting all language versions:

json
// content/posts/hello-world.json (English)
{
  "slug": "hello-world",
  "locale": "en",
  "translationGroup": "abc-123",
  "data": { "title": "Hello, World!" }
}

// content/posts/hello-world-da.json (Danish)
{
  "slug": "hello-world-da",
  "locale": "da",
  "translationGroup": "abc-123",
  "data": { "title": "Hej, Verden!" }
}

Admin UI translation workflow

In the admin UI:

  1. Open a document — see the locale badge showing current language
  2. Click "+ Add translation" to create a new locale version
  3. The AI translator auto-translates all fields
  4. Review and publish the translation

Translations appear grouped in the document list and the editor shows a locale switcher.

AI translation

typescript
import { createAi } from '@webhouse/cms-ai';

const ai = await createAi();
const result = await ai.content.translate(
  sourceDoc.data,
  'da',
  { collection: collectionConfig },
);
// result.fields contains translated data

Locale routing in Next.js

Use a [locale] route segment:

app/
  [locale]/
    blog/
      [slug]/page.tsx
    page.tsx
  layout.tsx

With a middleware for locale detection:

typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

const LOCALES = ['en', 'da'];
const DEFAULT = 'en';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const hasLocale = LOCALES.some(l => pathname.startsWith(`/${l}/`) || pathname === `/${l}`);
  if (hasLocale) return;

  const preferred = request.headers.get('accept-language')?.split(',')[0]?.split('-')[0] ?? DEFAULT;
  const locale = LOCALES.includes(preferred) ? preferred : DEFAULT;
  return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
}
Previous
Next.js Patterns
Next
SEO & Visibility