name: sanity-l10n description: 'Work with a Sanity starter that uses structured content (glossaries, style guides, locale metadata) to make AI translation enterprise-grade. Covers prompt assembly, automated stale detection via Sanity Functions, translation quality evals, and the Agent Actions Translate API. Trigger on: customize glossary, add terminology, translation style guide, run evals, deploy functions, prompt assembly, debug translation, extend l10n plugin, Agent Actions Translate, blueprint deploy, stale detection, pre-translation, field-level translation, field translation, internationalizedArray, field workflow, publish gate, field matrix, per-field translate, field approve, review workflow, translate field action. Complements sanity-best-practices (general i18n) and add-l10n-frontend (frontend rendering).'
Sanity Agentic Localization
Core Principles
Enterprise translation quality comes from structured metadata, not better
engines. This starter closes the gap between enterprise TMS capabilities and AI
translation APIs. See docs/I18N_RESEARCH.md for the full gap analysis.
Structured content is the medium — Glossaries, style guides, and locale rules are Sanity documents. Content teams maintain them in the Studio. Code queries them at translation time. Every job reuses them.
Content-aware assembly — Don't dump all glossary terms into every prompt.
filterGlossaryByContent()prunes to terms that actually appear in the source document. Always include do-not-translate and forbidden terms as guardrails.Prove it — Translate WITH and WITHOUT context. Measure the delta. If glossaries don't measurably improve quality, the terms need work. The strongest entries are brand names that look like common English words (e.g., "Releases", "Perspectives", "Portable Text").
Automate the lifecycle — Stale detection, change analysis, and pre-translation happen automatically via Sanity Functions. Editors review results, not initiate workflows.
Lean on the platform — Use Sanity exports, generated types (TypeGen), and
@sanity/*utilities. Don't reinvent what the SDK provides.
Orientation
Read references/architecture.md for the full project map. Key entry points:
packages/l10n/— Unified plugin: schemas, prompt assembly, translation UI, hooks, evals. Sub-path exports keep serverless functions React-free.functions/— Two Sanity Functions: mark translations stale on publish, analyze changes + pre-translate.studio/— Studio workspace with article, person, topic, tag types.apps/translations-dashboard/— Real-time translation overview (App SDK).apps/frontend/— Next.js frontend with path-based i18n routing.sanity.blueprint.ts— Infrastructure-as-code: dataset, robot token, functions.- Start here:
packages/l10n/src/promptAssembly.ts— the core bridge between structured metadata and the Translate API.
Two-Tier Architecture
The starter supports two complementary translation approaches:
- Document-level (
@sanity/document-internationalization) — one document per locale. Used for content types where the entire document is translated (e.g., articles). Configured vialocalizedSchemaTypesincreateL10n(). - Field-level (
sanity-plugin-internationalized-array) — inlineinternationalizedArray*fields on a single document. Used for types where only specific fields need translation (e.g., person bios). Auto-detected by the inspector via schema walk.
Key field-level entry points:
packages/l10n/src/translations/FieldTranslationContent.tsx— field x locale matrix inspectorpackages/l10n/src/translations/deriveFieldCellStates.ts— pure state derivation (6 rules)packages/l10n/src/translations/useFieldTranslateActions.ts— translation orchestration (translate, approve, dismiss)packages/l10n/src/core/types.ts—FieldWorkflowStateEntry,FieldCellStatetypes
Jobs to Be Done
1. Customize glossaries for my domain
Replace the Sanity product terms with your own brand terminology. The strongest glossary entries are brand names that look like common English words — generic terms like "Dashboard" add little value because models translate them correctly without help.
- Read
packages/l10n/evals/fixtures.tsto see the example glossary entries - Read
packages/l10n/src/schemas/glossaryEntry.tsfor the 7-field anatomy - Load
references/customization-guide.mdfor detailed guidance - Do NOT remove fields from glossary entries — each drives branching logic in prompt assembly
2. Add a content type to the l10n system
- Add the type name to
localizedSchemaTypesinstudio/sanity.config.ts - Run
pnpm exec sanity schema deployfromstudio/ - Update the function event filter in
sanity.blueprint.tsif the new type should trigger stale detection - Redeploy functions:
pnpm exec sanity blueprints deploy
3. Create or modify style guides
Style guides are per-locale: formality level, tone adjectives, and free-form instructions in Portable Text.
- Read
packages/l10n/src/schemas/translationStyleGuide.tsfor the schema - Read
packages/l10n/evals/fixtures.tsfor example style guides (DE, FR, JA) - Load
references/customization-guide.mdfor best practices - Style guides are fetched by locale code via
STYLE_GUIDE_FOR_LOCALE_QUERY
4. Run and understand evals
Two test suites live in packages/l10n/:
pnpm --filter l10n test # Unit tests: schema, prompt assembly, locale utils
pnpm --filter l10n eval # Model evals: translate with/without context, score delta
- Evals require
sanity loginand consume AI credits - Two-layer scoring: deterministic checks (term presence/absence/patterns) + LLM judge (4 dimensions, 3 trials averaged)
- Pass = deterministic.pass AND judge.overall >= 3.5
- Load
references/customization-guide.mdfor writing new eval cases
5. Deploy functions and infrastructure
pnpm exec sanity blueprints deploy
- Read
sanity.blueprint.tsfor the resource definitions - Two functions:
mark-translations-stale(15s timeout, triggers on publish) andanalyze-stale-translations(120s timeout, 1GB memory, triggers on metadata update) - Both use a shared robot token with editor role
- Load
references/customization-guide.mdfor modifying function filters and env vars
6. Understand prompt assembly
The pipeline in packages/l10n/src/promptAssembly.ts:
extractDocumentText()— recursively extract text from a Sanity documentfilterGlossaryByContent()— prune glossary to terms in the sourcebuildGlossarySection()— format entries as Approved / DNT / ForbiddenbuildStyleGuideSection()— format formality, tone, instructionsassembleStyleGuide()— combine glossary + style guide into a single stringextractProtectedPhrases()— pull DNT terms for the API's protectedPhrasesbuildTranslateParams()— package everything for Agent Actions Translate
Read the source file directly — it's ~250 lines and well-commented.
7. Debug a translation issue
Load references/troubleshooting.md for common issues:
- Agent Actions errors (schema not deployed, token missing)
- Style guide too large (>12,000 chars warning)
- Eval failures (sourceText/fieldPath mismatch, auth token resolution)
- Functions issues (pnpm dep resolution, env var loading in jiti)
8. Understand the field-level translation architecture
- When to use document-level vs field-level: see "Two-Tier Architecture" above
- The
fieldTranslation.metadataschema usesliveEdit: true(patches write directly, no draft),hidden: true, and deterministic IDs viagetFieldTranslationMetadataId() - The 6 derivation rules in
deriveFieldCellStates.tsmerge document state with metadata to produce theFieldCellStatematrix - Inspector auto-detection:
useInternationalizedFieldswalks the compiled schema to find allinternationalizedArray*fields — no manual registration - Load
references/field-level-patterns.mdfor the full data flow, hook API, and translation pipeline details
9. Add field-level translations to a document type
- Change the field type to
internationalizedArrayText(orinternationalizedArrayString) in your schema definition - The inspector auto-discovers it — no registration needed
- Deploy schema:
cd studio && pnpm exec sanity schema deploy
Example: studio/schemaTypes/person.ts uses internationalizedArrayText for
the bio field. The field-level inspector automatically detects it and shows
the field x locale matrix.
10. Customize the field-level review workflow
Load references/field-level-patterns.md for details on:
- Publish gate behavior —
createFieldTranslationPublishGatewraps PublishAction, disables whenneedsRevieworstaleentries exist - Concurrency limits —
MAX_CONCURRENT = 5inuseFieldTranslateActions.ts - Stale detection sensitivity —
sourceSnapshotcomparison usesJSON.stringify, debounce is 500ms inuseStaleSyncEffect - Load
references/customization-guide.mdfor modification guidance
11. Debug field-level translation issues
Load references/troubleshooting.md for common issues:
- Metadata not created — needs write permission (
liveEdit: true) - Publish blocked but translations look fine — stale metadata
- Fields not appearing in matrix — must use
internationalizedArray*type - Stale not detected — client-side only, runs when inspector is open
- Translations missing from matrix — check source content exists
Anti-Patterns
- Do NOT duplicate l10n schema types — the plugin registers them via
createL10n(). Adding them tostudio/schemaTypes/causes conflicts. - Do NOT hardcode locale lists — query
l10n.localedocuments. The seed migration creates them. - Do NOT inject all glossary terms — use
filterGlossaryByContent()to keep prompts focused. - Do NOT remove glossary entry fields — all 7 (term, status,
doNotTranslate, partOfSpeech, definition, context, translations) drive
branching logic in
buildGlossarySection(). - Do NOT use
getCliClientoutside CLI — it won't resolve auth tokens. Passtokenexplicitly. Seepackages/l10n/evals/authToken.ts. - Do NOT skip
sanity schema deploy— Agent Actions requires deployed schema. Schema ID is_.schemas.default. - Do NOT manually create
fieldTranslation.metadatadocuments — deterministic IDs viagetFieldTranslationMetadataId(). The hooks and actions create them automatically withcreateIfNotExists. - Do NOT use
useFormValuefor field translation data — the inspector renders outside form context. UsedocumentStore.listenQuery()instead (seeuseFieldTranslationData.ts). - Do NOT skip the publish gate —
createFieldTranslationPublishGatewraps PublishAction. Don't remove it or bypass it without understanding the consequences for translation review workflows.
Reference Files
| File | Load when... |
|---|---|
references/architecture.md |
You need the full project map, data flow, or schema overview |
references/customization-guide.md |
Customizing glossaries, style guides, evals, or functions |
references/troubleshooting.md |
Debugging translation, eval, or deployment issues |
references/field-level-patterns.md |
Understanding or customizing the field-level translation workflow |
Companion Skills
- sanity-best-practices — General i18n patterns: document-level vs
field-level,
@sanity/document-internationalization - add-l10n-frontend — Frontend rendering of localized content (Next.js reference implementation, patterns for other frameworks)