sanity-plugin-ai-translation
v0.2.79
Published
A Sanity Studio plugin that adds an AI-powered **Translate** document action for internationalized content.
Readme
@braze/sanity-plugin-ai-translation
A Sanity Studio plugin that adds an AI-powered Translate document action for internationalized content.
- Default provider: Sanity Agent Actions (
client.agent.action.translate()) - Optional provider: OpenAI GPT-5 (direct API) for large documents / long style guides
- Output: Translations are created as versioned documents inside Content Releases (staged publishing)
- Extras: translate referenced documents, glossary terms, protected phrases, debug logging, field exclusion, and field transforms (Portable Text + captions)
For editors: see the standalone user guide at docs/user-guide.md.
Table of contents
- What this plugin does
- Prerequisites
- Install
- Configure (Studio)
- Optional: OpenAI mode
- User guide (for editors)
- Content configuration (settings + glossary)
- Advanced configuration
- Debugging & troubleshooting
- Development (this repo)
- License
What this plugin does
When you open a document in Sanity Studio (for a configured schema type), the plugin adds a Translate action. You can:
- Select one or more target languages
- Choose whether to:
- overwrite existing translations
- translate only diffs (when applicable)
- translate referenced documents (with depth control)
- Run translation using:
- Sanity Agent Actions (default), or
- OpenAI (optional)
Translations are written as version documents in a Content Release, so you can review and publish them on your schedule.
Prerequisites
Your Studio must already support internationalized documents.
Required:
- Sanity Studio v4 (plugin is built for Sanity Studio v4)
@sanity/document-internationalizationconfiguredsanity-plugin-internationalized-arrayinstalled and configured (used for glossary term translations)@sanity/client7.1.0+ (required for Agent Actions)
Also required for Agent Actions:
- Your schema must be deployed to Sanity so Agent Actions can access it (see Deploy schema)
- Important API version requirement: the client must use
apiVersion: 'vX'(not date-based versions). Date-based versions can cause Agent Actions to fail.
Install
Use your Studio package manager (examples use pnpm):
pnpm add @braze/sanity-plugin-ai-translationYou’ll also need the prerequisites if you haven’t installed them yet:
pnpm add @sanity/document-internationalization sanity-plugin-internationalized-array
pnpm add @sanity/client@^7.1.0Configure (Studio)
1) Configure document internationalization
This plugin is designed to work with @sanity/document-internationalization. Configure it first.
You’ll typically define your supported languages as:
const SUPPORTED_LANGUAGES = [
{ id: "en-us", title: "English" },
{ id: "es", title: "Spanish" },
{ id: "fr", title: "French" },
];Then enable internationalization for the schema types you want to translate:
documentInternationalization({
supportedLanguages: SUPPORTED_LANGUAGES,
schemaTypes: ["post", "page"],
languageField: "language", // your language field name (example)
});2) Add the AI translation plugin
Add aiTranslationPlugin() after documentInternationalization():
import { defineConfig } from "sanity";
import { documentInternationalization } from "@sanity/document-internationalization";
import { aiTranslationPlugin } from "@braze/sanity-plugin-ai-translation";
const SUPPORTED_LANGUAGES = [
{ id: "en", title: "English" },
{ id: "es", title: "Spanish" },
{ id: "fr", title: "French" },
];
export default defineConfig({
// ...your config...
plugins: [
documentInternationalization({
supportedLanguages: SUPPORTED_LANGUAGES,
schemaTypes: ["post", "page"],
languageField: "language",
}),
aiTranslationPlugin({
supportedLanguages: SUPPORTED_LANGUAGES,
translatableSchemaTypes: ["post", "page"],
languageFieldName: "language",
schemaId: "_.schemas.default",
maxConcurrentTranslationJobs: 5,
maxReferenceDepth: 3,
}),
],
});Notes
schemaIdis usually'_.schemas.default'for most projects.languageFieldNamemust match the field your documents use (oftenlanguage).
3) Add translation history metadata
This plugin can store translation history via a metadata schema you attach to documentInternationalization.
Add translationHistorySchema:
import { translationHistorySchema } from "@braze/sanity-plugin-ai-translation";
documentInternationalization({
supportedLanguages: SUPPORTED_LANGUAGES,
schemaTypes: ["post", "page"],
languageField: "language",
metadataFields: [translationHistorySchema],
});4) Deploy schema for Agent Actions
Agent Actions need access to your deployed schema.
From your Studio project directory:
npx sanity deployIf Agent Actions fail with schema-related errors, re-check:
- schema deployment
schemaId- client
apiVersionis set to'vX'(not date-based)
Optional: OpenAI mode
OpenAI mode is optional. If enabled, users can choose OpenAI as the translation provider inside the translation dialog.
Example configuration:
aiTranslationPlugin({
supportedLanguages: SUPPORTED_LANGUAGES,
translatableSchemaTypes: ["post", "page"],
languageFieldName: "language",
schemaId: "_.schemas.default",
openai: {
enabled: true,
apiKey: process.env.OPENAI_API_KEY!,
model: "gpt-5",
reasoningEffort: "medium", // 'minimal' | 'low' | 'medium' | 'high'
verbosity: "medium", // 'low' | 'medium' | 'high' (if supported by model)
defaultMode: "openai-only", // 'sanity-only' | 'openai-only'
},
});Security
- Do not hardcode API keys.
- Use environment variables (e.g.
.env/.env.local) and your deployment platform’s secret manager.
Why use OpenAI mode?
- Larger effective context window for long documents and long style guides
- Deterministic serialization/deserialization to preserve structure
- Strict structure validation to prevent data corruption
Costs
- OpenAI calls incur API costs based on token usage.
User guide (for editors)
Translate a document
- Open a document in Studio whose type is included in
translatableSchemaTypes. - Open the document actions menu and click Translate.
- Select one or more target languages.
- Configure translation options (see below).
- Click Translate to start.
While translation runs, you’ll see job statuses for each target language (and for any referenced documents, if enabled).
Choose a Content Release
Translations are always created inside a Content Release.
In the dialog you can typically:
- create a new release, or
- add the translations to an existing active release
Use releases when you want:
- staged review
- coordinated publishing of multiple translations at once
Translation options
The exact UI labels may vary, but the workflow supports:
- Overwrite fresh translations
- Forces re-translation even if the existing translation is considered up-to-date.
- Translate only diffs
- For stale translations, translate only the changed fields where supported.
- Translate references
- Also translate referenced documents and update references to point at the localized versions.
- Max reference depth
- How far to traverse reference graphs (depth
0means no traversal).
- How far to traverse reference graphs (depth
Translate referenced documents
If you enable reference translation:
- The plugin traverses references (up to the configured depth).
- It translates referenced documents that are eligible (type is included in
translatableSchemaTypes). - It updates references in the translated document to point to localized versions.
This is useful for content like:
- posts referencing authors
- pages referencing shared sections
- documents referencing related content
What gets created
For each selected target language, the plugin creates a versioned document inside the selected Content Release.
You can then:
- review and edit translated versions
- publish the release when ready
Content configuration (settings + glossary)
The plugin introduces two document types for configuration:
translationSettings
Create a document with _id == "translation-settings" to configure:
- Global prompt (applies to all translations)
- Locale prompts (per-target-language instructions)
- Protected phrases (never translate these terms; e.g. brand names)
Example shape:
{
_type: 'translationSettings',
_id: 'translation-settings',
globalPrompt: 'Maintain a professional tone. Preserve formatting and links.',
localePrompts: [
{_key: 'es', value: 'Use formal Spanish (usted).'},
{_key: 'fr', value: 'Use formal French.'},
],
protectedPhrases: ['Braze', 'iOS', 'Android', 'SDK']
}Notes:
localePromptsuses_keyfor the locale id (must match yoursupportedLanguages[].id).- Protected phrases may be filtered to only those that appear in the source document (to keep prompts smaller).
translationGlossaryTerm
Create glossary term documents to enforce consistent translations of key terms.
This schema uses internationalizedArrayString so you can provide one value per locale.
Conceptual example:
{
_type: 'translationGlossaryTerm',
translations: [
{_key: 'en', value: 'dashboard'},
{_key: 'es', value: 'panel de control'},
{_key: 'fr', value: 'tableau de bord'},
]
}Glossary terms are used to build locale-specific translation instructions.
Advanced configuration
Exclude fields from translation
Use excludeFields to prevent certain fields from being translated (slugs, enums, flags, etc.).
Supports:
- field name matches anywhere (e.g.
'status') - exact paths (e.g.
'seo.title') - array paths / wildcards (e.g.
'body[*].imageType')
Example:
aiTranslationPlugin({
// ...
excludeFields: ["slug", "status", "seo.canonicalUrl", "body[*].variant"],
});Important behavior:
- The plugin will not allow the
languageFieldNameto be excluded. If you include it, it will be removed to prevent translation errors.
Field transforms
Some fields (like Portable Text or caption formats) benefit from transformation before translation.
Transforms run in this sequence:
- Filter excluded fields
- Apply transforms (to produce a translatable representation)
- Translate (Sanity Agent or OpenAI)
- Restore transforms (back into original structure)
- Restore excluded fields (except language)
- Create version document in the release
Built-in transforms include:
richTextTransformfor Portable TextvttCaptionTransformfor VTT captionswistiaCaptionTransformfor Wistia captions (if used by your schema)
Example setup:
import {
aiTranslationPlugin,
richTextTransform,
vttCaptionTransform,
wistiaCaptionTransform,
} from "@braze/sanity-plugin-ai-translation";
aiTranslationPlugin({
// ...
fieldTransforms: [
{
...vttCaptionTransform,
matcher: { schemaType: "video", fieldName: "captions" },
},
{
...wistiaCaptionTransform,
matcher: { schemaType: "videoDoc", fieldName: "wistiaCaptions" },
},
richTextTransform,
],
});Order matters: first match wins.
Concurrency and depth
maxConcurrentTranslationJobscontrols how many translations run at the same time (default behavior is conservative).maxReferenceDepthcontrols how deeply references are traversed (set to0to disable traversal).
Debugging & troubleshooting
Enable debug logging
This plugin uses a centralized debug logger. To enable debug output in your browser:
localStorage.setItem("sanity-ai-translation:debug", "true");To disable:
localStorage.removeItem("sanity-ai-translation:debug");Common issues
Agent Actions fail immediately
- Confirm
@sanity/clientis 7.1.0+ - Confirm your client API version is
apiVersion: 'vX'(not date-based) - Confirm schema is deployed:
npx sanity deploy - Confirm
schemaIdmatches your deployed schema (commonly'_.schemas.default')
Translation completes but content didn’t change
- Ensure the document type is included in
translatableSchemaTypes - Check whether excluded fields or transforms are preventing updates
- If using OpenAI mode, structure validation can reject translations that modify non-text fields
Reference translation is slow
- Reduce
maxReferenceDepth - Reduce
maxConcurrentTranslationJobs - Ensure you’re not translating large reference graphs unnecessarily
Development (this repo)
This repository is a pnpm monorepo with:
package/— the plugin packagedemo-nextjs/— demo Next.js app with embedded Studio
Common commands:
pnpm dev
pnpm build
cd package
pnpm test
pnpm typecheck
pnpm lintLicense
MIT
