Implementation of document internationalization and organization of pages in a list item discussed.
The URL you're trying to follow (known-caveats.md) doesn't actually exist in the document-internationalization repository—it returns a 404 error. However, the actual documentation for implementing singletons is in 01-singleton-documents.md.
The error "Expected List item, found Object" happens because you're trying to spread a single object directly into the items array. The fix is to use the spread operator (...) on the array returned by .map(), not on individual objects.
Here's the correct implementation from the actual singleton documentation:
import {StructureResolver} from 'sanity/structure'
// Define your singleton documents
const SINGLETONS = [
{id: 'home', _type: 'page', title: 'Home'},
{id: 'login', _type: 'page', title: 'Login'},
]
// Define your languages
const LANGUAGES = [
{id: 'en', title: 'English'},
{id: 'fr', title: 'French'},
]
export const structure: StructureResolver = (S) =>
S.list()
.title('Content')
.items([
// Spread the array returned by .map() - this is the key!
...SINGLETONS.map((singleton) =>
S.listItem()
.title(singleton.title)
.id(singleton.id)
.child(
S.list()
.title(singleton.title)
.id(singleton.id)
.items(
LANGUAGES.map((language) =>
S.documentListItem()
.schemaType('page')
.id(`${singleton.id}-${language.id}`)
.title(`${singleton.title} (${language.id.toLocaleUpperCase()})`)
)
)
.canHandleIntent(
(intentName, params) =>
intentName === 'edit' && params.id.startsWith(singleton.id)
)
)
),
S.divider(),
// ...other list items
])Why this works:
SINGLETONS.map()returns an array ofS.listItem()objects- The spread operator
...unpacks that array into individual items - Each singleton becomes a nested list showing all language versions
Before this will work, you need to:
Create the actual singleton documents using the createSingletons.ts script. This creates both the language-specific documents (like
home-en,home-fr) and thetranslation.metadatadocument that binds them together.Add a
languagefield to your schema (usually hidden):
defineField({
name: 'language',
type: 'string',
readOnly: true,
hidden: true,
})The canHandleIntent ensures that when you click on a singleton document from elsewhere in the Studio, it opens in the correct place in your structure.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.