Translation & Localization8 min read

How to Integrate Localize with Your Headless CMS

Connect Localize to your headless CMS workflow so published Sanity content is sent to translators as structured source strings, then served in the right language across web and app experiences.

Published April 29, 2026
01Overview

What is Localize?

Localize is a translation and localization platform for websites, web apps, and product experiences. Teams use Localize to detect source text, manage translations, provide in-context editing, and serve translated copy through Localize.js and its API. It's commonly used by marketing, product, and localization teams that need to ship multiple languages without rebuilding their front end for every locale.


02The case for integration

Why integrate Localize with a headless CMS?

If your source content lives in one place and translations live somewhere else, localization gets messy fast. A product description changes in English, but the French, German, and Japanese versions don't get flagged. A campaign page launches with one untranslated CTA. A developer exports JSON manually, sends it to a localization team, waits for a file back, and hopes the keys still match.

Connecting Localize to a headless CMS workflow fixes the handoff. When content is published, updated, or deleted, the integration can send only the changed fields to Localize with stable keys and useful context. For a Sanity setup, structured content in the Content Lake means you're not scraping HTML or parsing page blobs. GROQ can pull exactly the fields Localize needs, including referenced author names, product titles, disclaimers, or Portable Text spans, while webhooks or Functions trigger the sync the moment content changes.

The trade-off is that you need to define your localization strategy up front. Localize.js can auto-detect strings from rendered pages, which is quick for many websites. A structured integration takes more setup, but it gives translators clearer source keys, better context, and fewer surprises when the same phrase appears in a navigation label, a checkout step, and a legal disclaimer.


03Architecture

Architecture overview

A typical Sanity and Localize integration starts with a publish event. When an editor publishes a page, product, article, or campaign in Sanity Studio, a Sanity webhook fires with the document ID, or a Sanity Function runs directly on the content mutation. The handler uses @sanity/client and GROQ to fetch the canonical document from the Content Lake, selecting only fields that should be translated, such as title, SEO description, CTA text, Portable Text spans, product descriptions, and referenced category names. The handler then flattens those fields into stable key-value pairs, for example page.home.heroTitle or product.sku-123.description, and calls the Localize REST API with the Localize project key and API key. In Localize, translators can review the source strings, add target-language translations, use the in-context editor, and keep translation memory aligned with the source text. On the front end, your app still fetches structured content from Sanity for layout, routing, references, and metadata. Localize.js runs in the browser with your Localize project key, detects the visitor's language or uses a language picker, and serves translated strings from Localize's delivery layer. For server-rendered or statically generated pages, you'll want to test crawl behavior, caching, and fallback language rules carefully, especially for SEO-sensitive pages.


04Use cases

Common use cases

🌐

Marketing site localization

Send Sanity-authored hero copy, navigation labels, CTAs, FAQs, and footer text to Localize whenever a page is published.

🛍️

Regional product pages

Sync product names, feature bullets, sizing notes, and legal disclaimers to Localize while Sanity keeps product structure and references intact.

📱

Web app text updates

Use Sanity for editable onboarding messages, empty states, and help text, then push those strings into Localize for app-wide translation.

🚀

Campaign launches

Trigger Localize sync from Sanity publish events so translators see new campaign strings before traffic hits the localized pages.


05Implementation

Step-by-step integration

  1. 1

    Set up Localize

    Create a Localize project, set your source language, add target languages, and copy the project key. In Localize's developer settings, create an API key for server-side imports. Add the Localize.js snippet to your website or app so translations can be delivered at runtime.

  2. 2

    Model translatable content in Sanity Studio

    Define clear Sanity schemas for the content types you want to localize, such as page, product, article, and navigationItem. Mark fields that should not be translated, keep stable slugs or IDs, and consider fields like localizeKey, translationNotes, or marketAvailability when translators need context.

  3. 3

    Create the publish trigger

    Use a Sanity webhook or Function that runs on publish for selected document types. A common webhook filter is _type in ['page', 'product', 'article'] && !(_id in path('drafts.**')). Send the document ID to your handler instead of sending the whole document.

  4. 4

    Fetch the exact fields with GROQ

    In the handler, use @sanity/client and GROQ to fetch only the strings Localize needs. Include referenced content when it appears on the page, such as category titles, author names, related product labels, and Portable Text span text.

  5. 5

    Push source strings to Localize

    Flatten the GROQ result into stable keys and call the Localize REST API with your project key and API key. Start with a small set of fields, handle rate limits and retries, and log the number of strings sent for each document.

  6. 6

    Test the frontend experience

    Load Localize.js in your Next.js, Nuxt, Remix, Astro, or SvelteKit app, switch languages, and verify that translated strings match the Sanity-authored layout. Test fallbacks, cache behavior, draft previews, and SEO-critical pages before rolling out to every locale.


06Code

Code example

typescriptapi/sanity-to-localize.ts
import {createClient} from '@sanity/client'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-02-19',
  token: process.env.SANITY_READ_TOKEN,
  useCdn: false,
})

export default async function handler(req: any, res: any) {
  const id = String(req.body?._id || '').replace(/^drafts\./, '')

  const doc = await sanity.fetch(`*[_id == $id][0]{
    _id, _type, title, subtitle,
    body[]{children[]{text}}
  }`, {id})

  const strings = Object.fromEntries([
    [`${doc._type}.${doc._id}.title`, doc.title],
    [`${doc._type}.${doc._id}.subtitle`, doc.subtitle],
    ...(doc.body || []).flatMap((b: any, i: number) =>
      (b.children || []).map((c: any, j: number) =>
        [`${doc._id}.body.${i}.${j}`, c.text]
      )
    ),
  ].filter(([, value]) => Boolean(value)))

  const response = await fetch(
    `https://api.localizejs.com/v2.0/projects/${process.env.LOCALIZE_PROJECT_KEY}/phrases`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.LOCALIZE_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({locale: 'en', phrases: strings}),
    }
  )

  if (!response.ok) {
    throw new Error(`Localize sync failed: ${response.status} ${await response.text()}`)
  }

  res.status(200).json({synced: Object.keys(strings).length})
}

07Why Sanity

How Sanity + Localize works

Build your Localize integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs you need to connect Localize to your translation workflow.

Start building free →

08Comparison

CMS approaches to Localize

CapabilityTraditional CMSSanity
Source string extractionOften tied to rendered pages or theme files, so teams export HTML, copy text manually, or rely on page scanning.Content Lake stores typed JSON, and GROQ can extract exactly the fields Localize needs, including Portable Text and references.
Sync timingTranslation updates often depend on manual exports, scheduled jobs, or plugin-specific queues.Webhooks or Functions can run on publish events, fetch fresh content, and call Localize without polling.
Translator contextTranslators may see long page blobs with limited field-level context.GROQ can send stable keys plus notes, document type, slug, market, and referenced labels in one payload.
Rich text handlingRich text is often HTML, which can mix copy, layout, links, and formatting in one string.Portable Text keeps rich text structured, so you can sync text spans to Localize while preserving marks, links, and annotations in Sanity.
Multi-channel deliveryLocalized copy is usually bound to one website or template system.One structured back end can feed web, mobile, Localize, and AI agents, with the same source documents and references.
Developer controlPlugins can be quick, but behavior is often tied to the vendor's admin UI and release cycle.Schema-as-code and a customizable Sanity Studio let you define the fields, validation, and editorial cues that make Localize sync predictable.

09Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Localize and 200+ other tools.