Multi-Language Sites
At a glance
Sites can publish parallel content in 43+ FIPJP languages, with per-page translation, a language switcher in navigation, a configurable default language per site, a translation workflow that flags pages needing work with source diffs, and hreflang tags plus xhtml:link sitemap alternates emitted automatically for SEO. Editors translate page-by-page, partial coverage falls back politely, and visitors land in their language via URL prefix, Accept-Language header or remembered preference.
How it works
Multi-language support is built into the CmsPage model. Each page has a base language (`source_language`) and a translations map keyed by ISO locale (`sv`, `fr`, `de`, …) containing localized title, slug, meta and a translated copy of the block array. The CmsSite declares supported_languages and default_language; pages can be translated into any subset of the site's supported list, and partial coverage is allowed — pages without a target translation fall back to default with a polite notice for visitors who chose another language.
Localized URLs follow a `/<locale>/...` pattern except the default language, which uses clean root paths to keep canonical URLs stable. The renderer detects visitor language via the URL prefix first, then Accept-Language headers, then a sessionStorage preference; the language switcher in the navigation lets visitors change explicitly and remembers the choice across pages. The translation workflow surfaces a queue per site: when a source page is updated, every translation is automatically flagged 'needs review' with a diff against the previous source version, so translators see exactly what changed.
Translators can be different OrgNodeMembers from editors and have a `cms:translate` capability that lets them edit translations without unlocking the source. SEO hreflang tags are emitted for every available translation plus an `x-default` pointing to the site default; the sitemap includes `xhtml:link` hreflang alternates so Google indexes each language correctly. CMS-search respects the active locale and only returns results from the matching language version, with a setting to fall back to default-language matches when no localized hit exists.
Key capabilities
- Parallel page versions per ISO locale with localized title, slug, meta and blocks
- Per-site default and supported language configuration
- Localized `/<locale>/...` routing with clean root paths for default language
- Language switcher with sessionStorage memory and Accept-Language fallback
- Translation queue with source-diff to show what changed since last review
- Separate `cms:translate` capability for translator roles
- hreflang tags and sitemap alternates for SEO across all languages
In practice
Anneli at the Finnish Federation maintains the federation site in Finnish, Swedish and English. She updates the membership-fees page in Finnish to reflect new prices. The translation queue immediately flags the Swedish and English copies as 'needs review' with a side-by-side diff showing exactly the three numbers and one paragraph that changed.
She forwards the queue link to the federation's volunteer translator who edits the Swedish version that evening; the English version gets done two days later. Until then, visitors who land on the English page see the Finnish content with a 'translation in progress' badge, and Google indexes only the Finnish version because the English hreflang is suppressed for unfinished pages.
Features in this subsystem
5| ID | Status | Features |
|---|---|---|
| F18.09.01 | Shipped | Multi-language page content (parallel versions per language) PL-F1809 | ✅ byggd, annan modell — innehållet är {lang→text} i sidans title och i varje blockfält blockkatalogen deklarerar som i18nField (aldrig parallella sidrader). cms_page.translations fick sin FORM i 00216: en post per språk med status, translator_ref, reviewed_by/reviewed_at, source_version, source_fingerprint, notes. Båda identitetsreferenserna står i båda identitetsmanifesten (erasure: actor · merge: actor-repoint) |
| F18.09.02 | Shipped | Language switcher in navigation PL-F1809 | ✅ byggd, FLYTTAD nyckel — legacys boolean header_layout.show_language_switcher är ersatt av language_config.switcher_placement med FYRA lägen (header|footer|header_footer|hidden). Headern avvisar den gamla nyckeln med ett pekande 422 (D-3), migrationens datasteg bevarade varje sajts placering. Växlarens data kommer ur den publika projektionen GET /v1/public/cms/sites/{id}/languages — renderarens ENDA språkkälla |
| F18.09.03 | Shipped | Default language per site PL-F1809 | ✅ byggd med EN skrivare — PUT /v1/cms/sites/{id}/languages är enda vägen till default_language, supported_languages och language_config; sajt-PATCH:ens språkfält ⇒ 422 som pekar hit (D-2). Varje tagg kommer ur plattformens EGET register (GET /v1/cms/language-catalog, cms:view, stark ETag) — D-CMS listar inga språk själv. Att ta bort ett språk raderar ingenting: innehållet ligger kvar och svaret bokför content_languages_dropped[] |
| F18.09.04 | Shipped | Translation workflow (flag pages needing translation) PL-F1809 | ✅ byggd, utan flaggan — fyra lagrade ord (not_started|in_progress|needs_review|approved) med sluten kantlista och mandat per kant (cms:edit · cms:approve · cms:publish:immediate), If-Match på SIDANS ETag och EN audit-rad per kant. Föråldrat är HÄRLETT ur källavtrycket, aldrig lagrat — legacys flag-outdated och DELETE …/translations/{lang} är konsoliderade bort (Ö-2). Täckningen är MÄTT av EN funktion som matar matrisen, …/translations/needed och sidlistans languages_with_content |
| F18.09.05 | Shipped | hreflang tags for SEO PL-F1809 | ✅ byggd som EN härledning — GET /v1/public/cms/sites/{id}/pages/{slug}/hreflang ger en post per språk sidan faktiskt är TILLGÄNGLIG i (policyn respekterad) plus x-default. URL-formen är default-språket på ren väg och övriga under /{lang}, absolut över sajtens publika värd inklusive monteringen. Saknar sajten publik värd är mängden tom och base_url null — en påhittad adress vore fabricerad data i ett SEO-huvud. m-11:s seo-head/canonical/sitemap KONSUMERAR mängden |