webhouse.appwebhouse.appdocs

Edit source and translation simultaneously in a split-screen editor — the killer feature for multilingual content.

The problem with traditional translation workflows

Most CMS platforms treat translation as an afterthought. You write content in one language, export it, send it to a translator (or an AI), import the result, and hope nothing breaks. You can't see source and translation together. You can't compare paragraph by paragraph. And when you update the source, you have no idea which translations are stale.

Side-by-side editing

Side-by-side translation editing

@webhouse/cms solves this with a built-in split-screen editor. Open any document that has translations, click Side-by-side, and you see source and translation side by side — field by field, paragraph by paragraph.

What you see

The editor splits into two panels:

  • Left panel — the translation you're editing (e.g. Danish)
  • Right panel — the source document (e.g. English), read-only for reference

Both panels show the same fields in the same order: title, description, content, and all custom fields. You scroll them together. You compare them line by line.

How to use it

  1. Open any document in the editor
  2. If translations exist, you'll see a TRANSLATIONS bar at the top showing linked locale versions
  3. Click Side-by-side to enter split-screen mode
  4. The source language appears on the right, your translation on the left
  5. Edit the translation while reading the source — no tab switching, no copy-pasting

Translation groups make it work

The magic behind side-by-side editing is the translationGroup field. Every document that is a translation of another shares the same UUID:

json
// English source
{
  "slug": "getting-started",
  "locale": "en",
  "translationGroup": "a1b2c3d4-..."
}

// Danish translation
{
  "slug": "getting-started-da",
  "locale": "da",
  "translationGroup": "a1b2c3d4-..."
}

CMS admin reads the translationGroup and finds all documents that share it. That's how it knows which documents are translations of each other — and how it can show them side by side.

Creating translations

From the editor

  1. Open a document
  2. Click + Add translation in the translations bar
  3. Choose the target locale (e.g. Danish)
  4. The CMS creates a new document with:
- A slug based on the source (e.g. `getting-started-da`)

- The same translationGroup UUID

- AI-translated content (if an AI provider is configured)

  1. The new translation opens in the editor, ready for review

From a script

typescript
import { randomUUID } from 'crypto';

// Create the source document
const sourceDoc = {
  slug: "my-page",
  locale: "en",
  translationGroup: randomUUID(),
  status: "published",
  data: { title: "My Page", content: "Hello world" },
};

// Create the translation with the SAME translationGroup
const translationDoc = {
  slug: "my-page-da",
  locale: "da",
  translationGroup: sourceDoc.translationGroup, // ← same UUID!
  status: "published",
  data: { title: "Min side", content: "Hej verden" },
};

Via AI

bash
# The CMS AI agent can translate documents automatically
npx cms ai rewrite posts/hello-world "Translate to Danish"

Or use the built-in translation agent from the admin UI — it respects field types, preserves markdown formatting, and links the new document with the correct translationGroup.

Locale strategy

How locales appear in URLs depends on your site's localeStrategy setting:

StrategyEnglish URLDanish URLBest for
prefix-other/blog/my-post/da/blog/my-postMost sites
prefix-all/en/blog/my-post/da/blog/my-postExplicit locale
none/blog/my-post/blog/my-post-daLocale in slug

Configure in CMS admin → Site Settings → Language.

Why this matters

For content teams

  • No context switching between tabs or windows
  • See exactly what the source says while you translate
  • Catch mismatches instantly (missing paragraphs, wrong tone, outdated sections)

For developers

  • translationGroup is a simple UUID — no complex relational schema
  • Documents are independent files — no parent/child coupling
  • Works with any storage adapter (filesystem, GitHub, SQLite, Supabase)
  • Easy to script: just share the UUID between documents

For AI agents

  • AI translation agents create properly linked documents automatically
  • The translation agent reads the source via translationGroup, translates field by field
  • AI Lock ensures human-edited translations are never overwritten by AI

Configuration

cms.config.ts

typescript
defineCollection({
  name: 'posts',
  label: 'Blog Posts',
  sourceLocale: 'en',
  locales: ['en', 'da'],
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'content', type: 'richtext' },
  ],
})

Site settings

  • Default language: English (en)
  • Supported languages: add Danish (da), or any BCP 47 locale
  • Locale strategy: choose how URLs are structured

That's it. No plugins, no third-party translation management. Just translationGroup and the built-in editor.

Tags:i18nTranslationRichtext
Previous
Next.js SEO Helpers
Next
Chat with Your Site
JSON API →Edit on GitHub →