@sanity/document-internationalization
This is the all-new, Sanity Studio v3 exclusive version of @sanity/document-internationalization released as v2.0.0
A complete rewrite of the original Document Internationalization plugin, exclusively for Sanity Studio v3. Major benefits from the previous versions include:
- Create new documents in any language and link translated references later
- Translation references are written to a separate "meta" document
- Updates to one translation no longer affect the change history of others
- Does not require custom - nor modify the built-in - Document Actions
- Changes made to one translation do not patch changes to other documents
- Configurable "language" field on documents
- Built-in static and parameterized initial value templates for new documents
Upgrading
If this is your first time installing Document Internationalization, skip to the Install section.
I'm on Sanity Studio v3 and upgrading from plugin v1.0.0 and above
I'm on Sanity Studio v3 but will stay with the older plugin for now
- Please refer to the v2 branch
- Install from
v1.0.0
and above - This version of the plugin will not be updated with new features
I'm on Sanity Studio v2
- Please refer to the studio-v2 branch
- Install from
v0.0.0
and above - This version of the plugin will not be updated with new features
- You will not need to perform a content migration to move to Sanity Studio v3, if you install the v1 plugin.
Install
npm install --save @sanity/document-internationalization@studio-v3-plugin-v2
or
yarn add @sanity/document-internationalization@studio-v3-plugin-v2
Usage
Add it as a plugin in sanity.config.ts
(or .js):
Basic configuration
The only required configuration is the supportedLanguages
array and the schemaTypes
array.
import {createConfig} from 'sanity'
import {documentInternationalization} from '@sanity/document-internationalization'
export const createConfig({
plugins: [
documentInternationalization({
supportedLanguages: [
{id: 'es', title: 'Spanish'},
{id: 'en', title: 'English'}
],
schemaTypes: ['lesson'],
})
]
})
Advanced configuration
The plugin also supports asynchronously retrieving languages from the dataset, modifying the language field, adding a bulk publishing feature and adding additional fields to the metadata document.
import {createConfig} from 'sanity'
import {documentInternationalization} from '@sanity/document-internationalization'
export const createConfig({
plugins: [
documentInternationalization({
supportedLanguages: [
{id: 'nb', title: 'Norwegian (Bokmål)'},
{id: 'nn', title: 'Norwegian (Nynorsk)'},
{id: 'en', title: 'English'}
],
schemaTypes: ['lesson'],
languageField: `language`
weakReferences: true
bulkPublish: true
metadataFields: [
defineField({ name: 'slug', type: 'slug' })
],
apiVersion: '2023-05-22'
})
]
})
Language field
The schema types that use document internationalization must also have a string
field type with the same name configured in the languageField
setting. Unless you want content creators to be able to change the language of a document, you may hide this field since the plugin will handle writing patches to it.
defineField({
name: 'language',
type: 'string',
readOnly: true,
hidden: true,
})
Importing plugin components
Some components and functions from the plugin are exported for you to use throughout the Studio. For example, a custom Tool that lists documents but also needs to pull from available languages, create new translations, show the language of an existing document and link to the metadata document.
useDocumentInternationalizationContext
The useDocumentInternationalizationContext
hook can be used to access all plugin configuration values, including the result of supportedLanguages
if it is an async function.
import {useDocumentInternationalizationContext} from '@sanity/document-internationalization'
export function MyComponent({doc}: {doc: SanityDocument}) {
const {languageField} = useDocumentInternationalizationContext()
return <Badge>{doc[languageField] ?? `No Language`}</Badge>
}
The menu button shown at the top of documents can be imported anywhere and requires the published document ID of a document and its schema type to set the language of the document and handle creating new translations and the metadata document.
import {DocumentInternationalizationMenu} from '@sanity/document-internationalization'
export function MyComponent({_id, _type}) {
return (
<DocumentInternationalizationMenu
documentId={_id.replace(`drafts.`, ``)}
schemaType={_type}
/>
)
}
Code examples
Querying with GROQ
To query a single document and all its translations, we use the references()
function in GROQ.
// All `lesson` documents of a single language
*[_type == "lesson" && language == $language]{
// Just these fields
title,
slug,
language,
// Get the translations metadata
// And resolve the `value` field in each array item
"_translations": *[_type == "translation.metadata" && references(^._id)].translations[].value->{
title,
slug,
language
},
}
Querying with GraphQL
Fortunately, the Sanity GraphQL API contains a similar filter for document references.
query GetLesson($language: String!, $slug: String!) {
allLesson(
limit: 1
where: {language: {eq: $language}, slug: {current: {eq: $slug}}}
) {
_id
title
language
slug {
current
}
}
}
query GetTranslations($id: ID!) {
allTranslationMetadata(where: {_: {references: $id}}) {
translations {
_key
value {
title
slug {
current
}
}
}
}
}
Allowing the same slug on different language versions
Often your translated documents will share the same slug. You might wish to move this into the metadata document itself using the metadataFields
option in the plugin. Alternatively, you can customize the isUnique
function on a slug type field.
defineField({
name: 'slug',
type: 'slug',
options: {
isUnique: isUniqueOtherThanLanguage
},
}),
export async function isUniqueOtherThanLanguage(slug: string, context: SlugValidationContext) {
const {document, getClient} = context
if (!document?.language) {
return true
}
const client = getClient({apiVersion: '2023-04-24'})
const id = document._id.replace(/^drafts\./, '')
const params = {
draft: `drafts.${id}`,
published: id,
language: document.language,
slug,
}
const query = `!defined(*[
!(_id in [$draft, $published]) &&
slug.current == $slug &&
language == $language
][0]._id)`
const result = await client.fetch(query, params)
return result
}
Deleting documents
Deleting a single translated document
By default, this plugin creates a strong reference between a document and its connected translation metadata document. Because reference integrity is maintained by the API, you cannot delete a document that has a strong reference to it. To offset this difficulty, the plugin exports a document action that will allow you to remove the translation reference from the action, before proceeding to delete the document. It is not added by default to your schema types.
Import into your Studio's config file
import {
documentInternationalization,
DeleteTranslationAction,
} from '@sanity/document-internationalization'
export default defineConfig({
document: {
actions: (prev, {schemaType}) => {
if (['page'].includes(schemaType)) {
return [...prev, DeleteTranslationAction]
}
return prev
},
},
})
Deleting all translations
The metadata document also contains a "Delete all translations" document action which is queued by default for only that schema type. It will delete all of the documents in the translations
array of references, as well as the metadata document itself.
Note on document quotas
In previous versions of this plugin, translations were stored as an array of references on the actual documents. This required a base language, lead to messy transaction histories and made deleting documents difficult.
In this version of the plugin, translations of a document are stored as an array of references in a separate document of the type translation.metadata
, and one is created for every document that has translations. A document with no translations will not have a metadata document.
This means if you have 100 documents and they are all translated into 3 languages, you will have 400 documents. Keep this in mind for extremely high-volume datasets.
License
MIT © Sanity.io
Develop & test
This plugin uses @sanity/plugin-kit
with default configuration for build & watch scripts.
See Testing a plugin in Sanity Studio
on how to run this plugin with hot reload in the studio.
Release new version
Run "CI & Release" workflow.
Make sure to select the main branch and check "Release new version".
Semantic release will only release on configured branches, so it is safe to run the release on any branch.