How Modern Headless CMSes Handle Localisation Differently
Your product launches in Germany on Monday.
Your product launches in Germany on Monday. The German market team is blocked because their translator can only edit a field after English copy is locked, the `productName` field is a single string with no locale variant, and the reference to the shared spec sheet points at the English document with no way to fork it per market. So someone copies the entire document, translates it by hand, and now you have two documents that will drift apart the moment English changes. Multiply that by forty markets and you have a localization problem that is really a data-modeling problem wearing a translation costume.
Most headless CMSes bolt localization on after the fact, as a field-level toggle or a per-locale clone, and the seams show up exactly when you scale. Sanity treats localization as a modeling decision you make in your schema, not a plugin you install later. As the Content Operating System for the AI era, it lets you choose field-level, document-level, or hybrid localization per content type, query the right locale in a single GROQ round trip, and drive translation automation through Functions without cloning documents into drift.
This guide walks through how modern headless platforms actually differ on localization: where the locale lives, how references behave across markets, how translation workflows stay governed, and what that means when you are the engineer who has to build it.
Localization is a data-modeling decision, not a translation feature
The first fork in the road is where the locale lives in your content model, and most teams do not realize they are choosing until the choice has already cost them. There are two honest strategies. Field-level localization stores every translatable value as a keyed object, so `title` becomes `{en: 'Home', de: 'Startseite', fr: 'Accueil'}` on a single document. Document-level localization gives each locale its own document, linked to its siblings, so the German page is a separate record from the English one. Neither is universally correct. A marketing landing page that must stay structurally identical across markets wants field-level. A market where the German site has three sections the English site does not have wants document-level, because forcing structural parity onto genuinely divergent content is how you get empty fields and confused editors.
The failure mode is picking one strategy globally and discovering halfway through that a third of your content types wanted the other one. Legacy CMSes tend to stop at a single locale switch and make you work their way. Sanity adapts to yours: because your schema is code in `sanity.config.ts`, you decide per content type. You can model an internationalized array of localized strings for the field-level types, use separate documents joined by a shared reference for the document-level types, and mix both in the same dataset. There is no platform-imposed 'this is how localization works here' that you then fight for the next three years. The lens here is the first pillar, model your business, and localization is where that pillar earns its keep or fails you quietly.
The reference problem: what happens to links across markets
Localization rarely breaks on the visible text. It breaks on references. A product page references an author, a spec sheet, a set of legal disclaimers, and a pricing table. When you clone that page into a new market, what happens to those references? In naive systems, the clone points at the exact same referenced documents, so your French page shows English author bios and English disclaimers, or worse, someone edits the shared disclaimer for the French market and silently changes it for every other market that points at it.
This is the single most underestimated part of localization, and it is a query and modeling problem, not a UI problem. You need references that can be locale-aware: sometimes a reference should resolve to the localized variant of the target, sometimes it should stay global on purpose (a brand logo does not get translated), and you need to express that intent in the model rather than hoping editors remember. In Sanity, references are first-class and GROQ dereferences them with the `->` operator, so you can resolve a reference and, in the same projection, pull the correct locale off the referenced document: ask for exactly the shape you need, including the localized fields of a dereferenced spec sheet, in one round trip. That means a single query can return a fully assembled French page with French author, French disclaimers, and a deliberately global logo, without four client-side joins and without cloning the referenced documents. The counter-intuitive consequence: good localization means fewer copies, not more, because references stay live instead of being frozen into clones that drift.
Translation workflow: who edits, when, and how it stays governed
The modeling gets you a place to put translations. The workflow decides whether they ever ship on time. The classic breakdown looks like this: English is authored, then a translator is expected to work on the same document, but they can only start once English is final, and they have no safe place to stage their work, so everything happens in email attachments and the CMS becomes a paste target at the end. Governance is nonexistent because there is no record of who approved the German copy or when.
Governed localization needs three things: a staging surface where translations can be drafted and reviewed without touching production, a way to ship a whole locale's worth of changes together rather than field by field, and an audit trail. Sanity maps these to real surfaces. Content Releases let you bundle a market launch, the German translations for a campaign, into a single reviewable, schedulable unit, so the German site goes live as one governed change instead of a scatter of individual field saves. Functions can trigger machine translation on document creation to give translators a first-draft starting point rather than a blank field, running server-side without a human clicking a button. Roles & Permissions and Audit logs mean you can see who changed the French disclaimer and when, which is exactly the record a legal team asks for after the fact. The pillar this section maps to is automate everything: the translator's job becomes review and refinement, not retyping, and the platform scales output instead of forcing you to scale headcount market by market.
Querying the right locale without shipping four requests
Runtime is where localization strategy meets latency. Once your content is modeled and translated, the frontend has to ask for the right locale, and the shape of that request is where many stacks quietly bloat. With a REST API and field-level localization, you often fetch the whole localized object for every field and discard the locales you do not need, or you fetch the document, then fetch its references, then fetch their localized children, three round trips deep, and reassemble on the client. With GraphQL you can shape the response, but locale selection frequently becomes a pile of aliases or a resolver you maintain.
GROQ collapses this. Because a projection can filter, dereference, and reshape in one expression, you can write a query that selects the requested locale, follows references, and pulls the localized fields off those referenced documents, all server-side, returning exactly the payload the page renders. Operators like `->` for dereferencing and coalesce patterns for locale fallback (show German, fall back to English if the German field is empty) live in the query itself, so your frontend does not carry fallback logic. TypeGen then codegens TypeScript types from that query, so the localized shape you asked for is typed end to end, and a renamed locale field is a compile error rather than a runtime blank on your French homepage. One round trip, one typed shape, no client-side assembly. That is the practical difference between a localization layer that stays fast at forty markets and one that adds a network hop per locale.
Locale fallbacks, partial translations, and the empty-field problem
No large localized site is ever fully translated. A new campaign ships in English and three priority markets on day one; the other thirty-seven markets catch up over weeks. The question every localization system has to answer is what a visitor in an untranslated market sees. The wrong answers are a blank field, a broken layout, or a hard 404. The right answer is an explicit, modeled fallback chain: show Austrian German, fall back to standard German, fall back to English, and never render an empty string.
Where systems differ is whether fallback is a modeling primitive or a thing you hand-roll in application code. If your CMS gives you locale variants but no fallback semantics, every frontend that touches the content reimplements the same coalesce logic, and they drift, so the mobile app falls back differently from the website. Pushing fallback into the query is the durable fix. In Sanity, GROQ can express the fallback chain in the projection with a coalesce over the locale keys, so `coalesce(title.de_AT, title.de, title.en)` returns the best available string and the frontend never sees a gap. Because this lives in Content Lake and is queried the same way by every client, the website, the app, and any AI agent reading the content get identical fallback behavior. Portable Text keeps the rich-text fields structured through all of this, so a partially translated document degrades to a coherent fallback rather than a broken block of markup. The empty-field problem stops being an application bug and becomes a solved query pattern.
Compliance, residency, and the governance layer localization sits on
Localization is not only linguistic. The moment you operate in multiple markets, you inherit multiple regulatory regimes, and content is often the surface those regimes touch: consent language, regional disclaimers, and the data-residency expectations that follow personal data across borders. A localization architecture that ignores governance passes the problem downstream to whoever gets audited.
This is where the platform underneath the translations matters. Sanity is SOC 2 Type II compliant and GDPR compliant, offers regional hosting and data residency options, and publishes its sub-processor list, so a team localizing into the EU can make concrete statements about where content lives and who processes it rather than hand-waving. On the editorial side, Roles & Permissions scope who can edit which market's content, so your German legal reviewer can be the only role permitted to approve German disclaimers, and Audit logs record every change for the after-the-fact question of who changed what and when. Content Releases give the same governance to launches, so a regulated market goes live as a reviewed, attributable unit. The point is not that Sanity has features; it is that localization at scale is a governance problem as much as a translation problem, and a shared foundation, where modeling, workflow, permissions, and residency live in one system, beats a translation plugin stapled to a CMS that treats compliance as someone else's layer.
How headless platforms handle localization
| Feature | Sanity | Contentful | Storyblok | Strapi |
|---|---|---|---|---|
| Where the locale lives | Your choice per content type: field-level localized objects or document-level locale variants, both defined in `defineType` schemas as code. | Field-level localization configured per field in the model; a single locale strategy applied across the space. | Field-level and folder-level (per-locale folders) options; strategy set at the space and folder configuration level. | Field-level i18n plugin with locale variants; document-level handled by convention rather than a built-in mode. |
| Locale-aware references | GROQ `->` dereferences and pulls the correct localized field off the referenced document in the same projection; references stay live, not cloned. | References resolve to the linked entry; localized target fields require selecting the right locale per included entry via the API. | Resolved relations return the referenced story; localized target content depends on the story's own locale handling. | Relations return the related record; localized fields on the relation are fetched with populate plus locale params. |
| One-request locale query | Single GROQ query filters locale, dereferences, reshapes, and applies fallback server-side, returning exactly the page shape. | GraphQL or REST with a locale parameter; deep localized references can need aliases or multiple includes. | REST/GraphQL with a language parameter; nested resolved relations may add depth to the request. | REST/GraphQL with a locale query param plus populate for relations; deep localized graphs add populate depth. |
| Locale fallback | Expressed in the query with `coalesce(title.de_AT, title.de, title.en)`, so every client gets identical fallback, no app-side logic. | Fallback locale configurable at the locale level in the space settings; applied by the delivery API. | Fallback language configurable per space; delivery applies the configured fallback. | Fallback behavior handled in application code or via plugin configuration rather than a query primitive. |
| Governed market launch | Content Releases bundle a whole market's translations into one reviewable, schedulable, attributable change. | Scheduled publishing and releases available; governance depends on plan tier and configured workflows. | Pipelines and scheduling support staged releases; branching by environment on higher tiers. | Draft and publish states plus review workflows on the enterprise tier; release bundling is convention-driven. |
| Translation automation | Functions run server-side to seed machine-translation drafts on document creation; App SDK for in-Studio translation apps. | Marketplace apps and third-party translation integrations connect external TMS providers. | Translatable fields plus third-party and marketplace translation integrations. | Community and third-party translation plugins integrate external translation services. |
| Compliance and residency | SOC 2 Type II, GDPR, regional hosting and data-residency options, and a published sub-processor list. | SOC 2 and GDPR compliance with region options on enterprise plans. | GDPR compliance with EU and US hosting regions available. | Self-hosted deployments let you place data in your own region; SaaS Cloud offers managed regions. |