Sanity Localization

Flexible multilingual content management through document-level and field-level translation strategies with official Sanity plugins.

Sanity's Flexible Localization Approach

Sanity offers a flexible, plugin-driven approach to content localization that adapts to your content model rather than forcing a one-size-fits-all solution. Unlike traditional CMS platforms that impose rigid localization structures, Sanity empowers developers to choose between document-level and field-level translation strategies based on their specific use case, giving teams the flexibility to optimize for content editor workflows, publishing independence, or attribute efficiency.

This guide covers both localization approaches, the official plugins that simplify implementation, and practical patterns for querying and displaying localized content in your frontend applications. For teams just getting started with Sanity, we recommend reviewing the Sanity Getting Started guide to understand the fundamentals before diving into localization.

Understanding Localization Approaches in Sanity

Sanity provides two fundamental approaches to content localization. Understanding the differences between these approaches is essential for choosing the right strategy for your project.

Document-Level Translation

Document-level translation creates a separate document for each language version, connected through reference relationships. Each document has a language field value that identifies its locale, and a reference document serves as the hub that connects all translations together.

Key Characteristics:

  • Each language has its own document in the Content Lake
  • Reference documents connect translations into a translation set
  • Enables independent publishing of each language version
  • Best for content with significant structural differences per language
  • Ideal when different teams manage different languages

Field-Level Translation

Field-level translation stores all language variants within a single document using either objects or arrays. This approach keeps translations together, making it easier to maintain consistency across languages.

Key Characteristics:

  • All translations stored in one document
  • Two schema patterns: objects (title.en, title.fr) vs arrays (title[].language, title[].value)
  • Object pattern creates more unique attributes per language
  • Array pattern scales better for many languages
  • Simpler content management when fields need consistent translation

Sanity's official localization documentation provides comprehensive guidance on both approaches and their use cases.

Document-Level vs Field-Level Translation Comparison
AspectDocument-LevelField-Level
PublishingIndependent per languageAll languages together
Content StructureCan vary per languageConsistent across languages
Team WorkflowSeparate teams per languageSingle team approach
Query ComplexityReference expansion neededDirect field access
Attribute EfficiencyHigh (per document)Varies by schema pattern
Best ForEnterprise multilingual sitesSimple to medium localization

Official Localization Plugins

Sanity provides official plugins that simplify implementing both localization approaches. These plugins handle the complex work of managing references, language fields, and Studio UI integration.

Document Internationalization Plugin

The @sanity/document-internationalization plugin automates document-level translation workflows. It creates reference relationships between translations and manages the language field across your documents.

Plugin Features:

  • Automatic creation of translation documents
  • Language field assignment and management
  • Reference relationship handling between translations
  • Integration with Sanity Studio desk tool
  • Support for weak and strong references

Internationalized Array Plugin

The sanity-plugin-internationalized-array provides a custom UI for array-based field-level translations. This plugin makes it easy for editors to manage translations without complex popup dialogs.

Plugin Features:

  • Custom inline editing interface
  • Supports all field types (string, text, Portable Text)
  • Scales efficiently for many languages
  • Reduces unique attribute count
  • Clean editor experience

Language Filter Plugin

The @sanity/language-filter plugin improves the Studio experience for editors working with multilingual content by allowing them to show or hide specific languages.

Plugin Features:

  • Per-document-type language visibility control
  • Reduces cognitive load in the Studio
  • Integrates with other localization plugins
  • Improves editor productivity

The document-internationalization plugin on GitHub provides detailed documentation on installation, configuration, and best practices for document-level localization workflows.

Localization Plugin Comparison

Choose the right plugin based on your localization approach and project requirements

document-internationalization

Official plugin for document-level translations. Creates reference relationships and manages language fields across separate documents.

internationalized-array

Custom UI plugin for array-based field translations. Supports any field type and scales efficiently across many languages.

language-filter

Studio UI plugin for filtering visible languages. Reduces editor complexity when working with multilingual content.

Schema Design for Localized Content

Designing effective schemas for multilingual content requires understanding the available patterns and choosing the right approach for your use case. Proper schema design is crucial for maintainable localization workflows, and the patterns shown here complement the approaches covered in our Sanity Schema Design guide.

Object-Based Field Localization

The object-based approach creates a separate field for each language within an object type. This pattern is straightforward but creates more unique attributes in your dataset.

Implementation Pattern:

// Define supported languages
const supportedLanguages = [
 { id: 'en', title: 'English', isDefault: true },
 { id: 'es', title: 'Spanish' },
 { id: 'fr', title: 'French' }
];

// Create locale string type
export const localeString = defineType({
 title: 'Localized String',
 name: 'localeString',
 type: 'object',
 fieldsets: [
 { title: 'Translations', name: 'translations', options: { collapsible: true } }
 ],
 fields: supportedLanguages.map(lang => ({
 title: lang.title,
 name: lang.id,
 type: 'string',
 fieldset: lang.isDefault ? undefined : 'translations'
 }))
});

Array-Based Field Localization

The array-based approach stores translations in an array of objects, each containing a language identifier and the translated value. This pattern is more attribute-efficient.

Implementation Pattern:

defineField({
 name: 'title',
 type: 'internationalizedArrayString',
 languages: ['en', 'es', 'fr', 'de', 'it', 'pt']
});

Document-Level Configuration

For document-level localization, configure the document-internationalization plugin in your Sanity configuration:

import { documentInternationalization } from '@sanity/document-internationalization';

export default defineConfig({
 plugins: [
 deskTool(),
 documentInternationalization({
 schemaTypes: ['post', 'page', 'product'],
 languageField: 'language',
 referenceBehavior: 'weak',
 defaultLanguages: ['en'],
 withReferenceProblems: true
 })
 ]
});

The Sanity localization documentation provides additional schema patterns and best practices for implementing localization in your project.

Querying Localized Content with GROQ

GROQ provides powerful patterns for querying localized content, including language selection, fallback strategies, and dynamic language handling. For a comprehensive guide to GROQ syntax and advanced querying patterns, see our Sanity GROQ Queries guide.

Basic Language Selection

Query specific language variants using direct property access for object-based localization:

// Fetch English title
*[_type == "presenter"][0] {
 name,
 "title": title.en
}

Fallback with Coalesce

Use the coalesce function to provide fallback values when a translation is missing:

// Fetch with language fallback
*[_type == "presenter"][0] {
 "title": coalesce(title[$language], title.en, "Missing translation")
}

Dynamic Language Queries

Pass the language as a query parameter for flexible language selection:

// Query with dynamic language parameter
*[_type == "post" && slug.current == $slug][0] {
 title,
 "localizedTitle": coalesce(title[$language], title.en),
 body
}

Filtering by Language

For document-level localization, filter and expand references by language:

// Get all translations of a document
*[_type == "post" && _id == $id][0] {
 _id,
 language,
 title,
 "translations": *[_type == "post" && references(^._id)] {
 _id,
 language,
 title
 }
}

The Sanity GROQ documentation includes additional query patterns and optimization strategies for multilingual content retrieval.

Complete GROQ Query Examples
1// Query all posts with localized titles for a specific language2const localizedPostsQuery = `*[_type == "post"] | order(publishedAt desc) {3 _id,4 title,5 "localizedTitle": coalesce(title[$language], title.en),6 slug,7 publishedAt,8 "author": author->name9}`;10 11// Query single post with all translations12const postWithTranslationsQuery = `*[_type == "post" && slug.current == $slug][0] {13 _id,14 title,15 "localizedTitle": coalesce(title[$language], title.en),16 body,17 "localizedBody": body[$language],18 language,19 "allTranslations": *[_type == "post" && references(^._id)] {20 _id,21 language,22 title,23 slug24 }25}`;26 27// Query with dynamic language from request28const dynamicQuery = `*[_type == "product" && $language in supportedLanguages][0] {29 name,30 "description": coalesce(description[$language], description.en),31 "specifications": specifications[$language]32}`;

Frontend Language Switcher Implementation

Implementing language switchers in frontend applications requires URL routing strategies and content adaptation patterns. For detailed integration patterns with Next.js, which is commonly used with Sanity, see our Sanity Next.js Integration guide.

URL-Based Language Routing

Structure your URLs to include language prefixes for SEO and user clarity:

/example-page (default language)
/es/ejemplo-pagina (Spanish)
/fr/page-exemple (French)
/de/beispielseite (German)

Next.js Implementation

Implement dynamic routing with locale parameters:

// app/[locale]/products/[slug]/page.tsx
import { client } from '@/lib/sanity';

const productQuery = `*[_type == "product" && slug.current == $slug][0] {
 name,
 "localizedName": coalesce(name[$locale], name.en),
 description,
 "localizedDescription": coalesce(description[$locale], description.en)
}`;

export default async function ProductPage({
 params
}: {
 params: { locale: string; slug: string }
}) {
 const product = await client.fetch(productQuery, {
 slug: params.slug,
 locale: params.locale
 });

 return (
 <article>
 <h1>{product.localizedName}</h1>
 <div>{product.localizedDescription}</div>
 </article>
 );
}

Language Switcher Component

Create a language switcher that preserves the current page:

// components/LanguageSwitcher.tsx
export default function LanguageSwitcher({ currentLocale, availableLocales }) {
 return (
 <select
 value={currentLocale}
 onChange={(e) => window.location.href = `/${e.target.value}${currentPath}`}
 >
 {availableLocales.map(locale => (
 <option key={locale.code} value={locale.code}>
 {locale.name}
 </option>
 ))}
 </select>
 );
}

Best Practices and Decision Framework

Choosing the Right Approach

Use Document-Level Translation When:

  • Different teams manage different languages independently
  • Content structure varies significantly between languages
  • Publishing schedules differ by language
  • Content volume differs substantially between languages
  • You need granular permission control per language

Use Field-Level Translation When:

  • All languages publish simultaneously
  • Content model is consistent across languages
  • You prefer simpler content management
  • You have a single team managing all languages
  • Attribute efficiency is a concern for many languages

Performance Optimization

  • Query Efficiency: Use projection to fetch only needed language fields
  • Caching: Implement CDN caching per language variant
  • Attribute Limits: Be mindful of Content Lake attribute limits when using object-based field localization
  • GROQ Optimization: Use query parameters instead of string concatenation for language selection

Editor Experience

  • Use the language filter plugin to reduce clutter in the Studio
  • Organize fields with fieldsets to distinguish default from translated content
  • Configure previews that show the correct language variant
  • Provide documentation for content editors on localization workflows

Sanity's localization best practices provide additional guidance on optimizing your localization implementation for both performance and editor experience.

Summary

Sanity's localization system stands out for its flexibility and developer-friendly approach. By offering both document-level and field-level translation strategies through official plugins, teams can choose the implementation that best fits their content model and workflow requirements.

The document-internationalization plugin provides robust support for managing separate translation documents with reference relationships, ideal for enterprise multilingual sites. The internationalized-array plugin offers an efficient and editor-friendly approach for field-level translations, scaling well across many languages.

The GROQ query language provides powerful patterns for fetching localized content with intelligent fallback handling using the coalesce function. Combined with customizable URL routing and content adaptation strategies, Sanity enables building sophisticated multilingual experiences.

Whether building a simple bilingual site or a complex multilingual platform serving dozens of languages, Sanity provides the tools and patterns needed for professional-grade content localization.

Frequently Asked Questions

Ready to Implement Multilingual Content in Sanity?

Our team specializes in building multilingual web applications with Sanity CMS. From schema design to frontend implementation, we can help you create a localization strategy that scales.