Translation System
What this is
ComStack has two translation systems. This page covers page translation — automatic translation of documentation and custom pages at publish time. For live voice agent translation (real-time language detection during visitor conversations), see the voice agent documentation.
How it works
When you publish, every promoted page is automatically translated to all locales configured for your project. The pipeline runs:
- Promote drafts to live
- Auto-translate each promoted page to all configured locales (in parallel)
- Promote the generated translation drafts to live
- Build and deploy the site
Both the publish-confirm MCP tool and the REST API publish endpoint run the same translation pipeline — there is no difference in translation behaviour between the two paths.
When to use it
The translation system runs automatically on every publish. Configure it by setting locales on your project modules:
- Knowledge pages:
modules.knowledge_base.locales - Custom pages:
modules.custom_pages.locales
Locale configuration
Knowledge pages vs custom pages
| Module | Config field | When translated |
|---|---|---|
| Knowledge pages (markdown docs) | project.knowledge_pages.locales | At publish time |
| Custom pages | project.custom_pages.locales | At publish time |
Custom page locales should always be a superset of knowledge page locales.
Locale config invariants
Three constraints are enforced at every write to default_locale or module locales:
| Rule | Constraint |
|---|---|
default_locale_in_custom_locales | default_locale ∈ modules.custom_pages.locales (when custom pages module is enabled) |
default_locale_in_knowledge_locales | default_locale ∈ modules.knowledge_base.locales (when knowledge base module is enabled) |
knowledge_subset_of_custom | modules.knowledge_base.locales ⊆ modules.custom_pages.locales (when both modules are enabled) |
Any violation returns 400 with { error: "Locale config drift", violations: [...] }.
Adding a locale
PUT /api/projects/:projectId/modules/knowledge_base (or /custom_pages) with a locales array that adds new codes triggers an automatic fan-out:
- For each new locale × each live source-locale page: generate a translated draft
- Drafts stay in draft state — run
publish+publish-confirmto promote them - Idempotent: pairs where a live or draft variant already exists for
(canonical_slug, locale)are skipped
A preview endpoint (POST /api/projects/:projectId/modules/knowledge_base/locales/preview) returns the expected manifest without writing anything.
Translated slugs and canonical_slug
Each locale gets its own native-language URL path. An English page at /docs/getting-started might translate to /es/docs/empezando.
The canonical_slug field groups all locale variants of the same page:
| Field | Purpose |
|---|---|
canonical_slug | Stable cross-locale grouping key — set once at promote time, never overwritten |
metadata.slug | Locale-specific URL path (different per locale) |
source_doc_id | Points to the source doc this translation was generated from |
canonical_slug is set once and preserved. This ensures:
- A translated variant keeps its source-locale canonical
- A renamed source page stays in the same hreflang group as its existing translations
- The promote step supersedes prior live docs by both
(metadata.slug, language)and(canonical_slug, language), so re-translated pages with a changed slug replace the old variant cleanly
Exception: slug === "index" is never translated — the homepage slug stays "index" in every locale.
What gets translated per page type
Knowledge pages (markdown)
| Field | Translation behaviour |
|---|---|
metadata.title | Translated |
metadata.description | Translated |
metadata.slug | Translated — native-language URL path |
content | Translated markdown |
metadata.sidebar.* | Copied unchanged (structural) |
Custom pages
Custom pages have a distinct pattern because the body source must never be modified:
| Field | Translation behaviour |
|---|---|
title, description | Translated |
body_source | Copied byte-identical — author code is never modified |
translatable_strings | The marker map { key: defaultText } is translated value-by-value; keys are preserved |
canonical_slug | Copied identically — custom pages never get per-locale slugs |
media_variants, og_image | Copied identically |
Field-level translate: false
Doc templates can declare individual fields with translate: false. Fields so declared are copied verbatim from the source locale to all target locales instead of being translated. Applies to title, description, slug, and content when declared.
Repairing missing translations
Use repair-translations (MCP tool, manager role) to backfill missing locale siblings on demand:
- For every live source-locale page, generate any active-locale variant that doesn’t yet exist
- Promote and deploy
- Idempotent: pairs with an existing variant are skipped
Options:
dry_run: true— preview the manifest without writing anythingdoc_id— scope to a single source pagelocale— scope to a single target locale
repair-translations includes a drift guard: a translated variant whose canonical_slug doesn’t match its source’s is flagged in drift_skipped[] and skipped rather than re-drafted as a duplicate. Drift is not healed by this tool — a targeted canonical_slug data-fix is needed first.
Common errors
| Error | Cause | Fix |
|---|---|---|
400: Locale config drift | A locale write violates one of the three invariants | Check which invariant fails; adjust default_locale or module locale arrays |
400: missing_source_index | Adding a locale but no live slug=index page exists in the default locale | Publish an index page in the default locale first |
| Translation drafts not visible | Fan-out generates drafts; they need a publish run to go live | Run publish + publish-confirm after adding a locale |
| Duplicate hreflang entries | Two source-locale docs share the same canonical_slug | Ensure each page has a unique canonical_slug |
Related
- Languages — all supported locales, BCP-47 codes, RTL languages
- Publish API — the publish pipeline that triggers translation
- Tool catalogue —
repair-translationsandpublish-confirm