sanity-plugin-intl-input
sanity-plugin-intl-input copied to clipboard
Singleton with translations
I'm trying to combine this plugin with the setup for singleton document described here. When I do this the translation tab disappears across all documents. I'm using document level translations.
Assuming the settings document needs to be translated too, how do I rewrite this to make it work?
import S from "@sanity/desk-tool/structure-builder";
export default () =>
S.list()
.title('Content')
.items([
S.listItem()
.title('Site settings')
.child(
S.document()
.schemaType('siteSettings')
.documentId('siteSettings')
),
// Add a visual divider (optional)
S.divider(),
// List out the rest of the document types, but filter out the config type
...S.documentTypeListItems()
.filter(listItem => !['siteSettings'].includes(listItem.getId()))
])
So technically the problem is that when translating documents you are creating new ones, which conflicts with the whole singleton thing. However I did find a way around it as follows:
import S from '@sanity/desk-tool/structure-builder';
import homepage from './schemas/homepage';
import * as Structure from 'sanity-plugin-intl-input/lib/structure';
export const getDefaultDocumentNode = (props) => {
return Structure.getDefaultDocumentNode(props);
};
export default () => {
return S.list()
.id('__root__')
.title('Content')
.items([
S.listItem()
.id('siteSettings')
.title('Site Settings')
.schemaType('siteSettings')
.child(
S.documentList()
.id('siteSettings')
.title('Site Settings')
.schemaType('siteSettings')
.filter('_id == $id && _type == $type')
.params({
id: 'siteSettings',
type: 'siteSettings',
})
.menuItems([
{
title: 'Create new',
intent: {
type: 'create',
params: {
id: 'siteSettings',
type: 'siteSettings',
template: 'siteSettings'
}
}
}
])
),
...Structure.getFilteredDocumentTypeListItems().filter(l => !['siteSettings'].includes(l.getId())),
])
};
Some explanation to this:
Basically we are keeping the same structure as usual so Sanity can resolve the structure for non-singleton's (which means a url like /desk/documentType/documentId)
But in the document list we are only showing the document with id siteSettings (hence we will always only show a single document). Now when this document does not exist yet, we will need a way to create it, but only the one with the fixed id. To do this we can create a menu item with the intent to create a document with a fixed id. This way everything should work as expected and you will still keep the singleton-like behavior.
Let me know if this works for you!
Example below

Thanks for getting back to me on this issue.
The solution you propose is essentially the same as the default option. It is still possible to create multiple of the singleton doc and the user has to click through a secondary document list of one item. I'm trying to avoid the list view completely so clicking the content type leads directly to the page.
I understand why this tricky with the intl plugin, but singletons are a very standard part of all our projects so I'll need to keep looking for a solution.
Thanks again for the help
@jdwillemse No you can still only create the one with the fixed ID (aka singleton) because the "Create new" action is fixed to the ID with this structure. However I am aware it is slightly more cumbersome. I am talking to some guys at Sanity regarding conflicts with custom desk structure but have not been able to find a resolution yet.
@LiamMartens I'm trying to use it as you suggest up there (even though the extra pane for the single document is really weird/ugly), but I'm actually running into the issue of editor/translations-tabs not showing up on the singleton, even with your suggested solution.
This is my deskStructure (the commented out part is how I would have included the singleton without the intl-plugin, ignore the base settings, these are non-i18n):
import S from '@sanity/desk-tool/structure-builder'
import * as Structure from 'sanity-plugin-intl-input/lib/structure'
export const getDefaultDocumentNode = (props) => {
if (props.schemaType === 'page') {
return S.document().views(Structure.getDocumentNodeViewsForSchemaType(props.schemaType))
}
return S.document()
}
export default () => {
const items = Structure.getFilteredDocumentTypeListItems()
return S.list()
.title('Content')
.items([
S.listItem().title('Base Settings').icon(GlobalIcon).child(S.document().schemaType('base').documentId('base')),
// S.listItem()
// .title('Global Settings')
// .icon(HomeIcon)
// .child(S.document().schemaType('global').documentId('global')),
S.listItem()
.id('global')
.title('Global Settings')
.icon(HomeIcon)
.schemaType('global')
.child(
S.documentList()
.id('global')
.title('Global Settings')
.schemaType('global')
.filter('_id == $id && _type == $type')
.params({
id: 'global',
type: 'global'
})
.menuItems([
{
title: 'Create new',
intent: {
type: 'create',
params: {
id: 'global',
type: 'global',
template: 'global'
}
}
}
])
),
S.divider(),
...items.filter((item) => !['base', 'global'].includes(item.spec.id))
])
}
Since i18n works on other schemas in the same project and I'm using a global config with i18n: true, I'm kinda confident the config is fine (it's using GROQ for the languages property if that makes a difference for the singleton?).
I guess the other way to do it is to nest all document fields into one object and throwing options: { i18n: true } on it. Although it seems like it kills fieldset borders and does some padding/margin-weirdness (https://www.dropbox.com/s/g5bp6u1aodnqpej/Screenshot%202020-07-28%2016.11.10.png?dl=0)
@LiamMartens @tom2strobl I am facing same issue where in, Translation tab disappears for article documents appearing 2 levels deep.
Any pointers would be really helpful.
PS: I have tried the approach given above but it somehow it didn't work for me.
export default () => {
return S.list()
.id('__root__')
.title('Content')
.items([
S.divider(),
S.listItem()
.title('Articles')
.icon(FaNewspaper)
.child(
S.list()
.title('Article Type')
.items([
getArticlePreviews('featureArticle').title('Feature'),
getArticlePreviews('galleryArticle').title('Gallery'),
getArticlePreviews('howToArticle').title('How To')
])
)]
)
}
const getArticlePreviews = type =>
S.documentTypeListItem(type).child(
S.documentTypeList(type).child(docId =>
S.document()
.id(docId)
.schemaType(type)
.views([
S.view.form()
])
)
)
Although it seems like it kills fieldset borders and does some padding/margin-weirdness (https://www.dropbox.com/s/g5bp6u1aodnqpej/Screenshot%202020-07-28%2016.11.10.png?dl=0)
I'm experiencing the same issue and I'm wondering what the solution is? Do we need to write (or overwrite) a custom component for nested objects like this?
Any help pointing us in the right direction would be great @LiamMartens :+1:
@saulhardman Sorry I have not been able to look into this deeper. I might have to update the component that's being used as it is most likely outdated according to updates Sanity has received now. I try to look at it this week.
@ylokesh I need to have a look at this :grimacing: combining desk structures is a bit "hacky" at the moment Imo
@LiamMartens no need to apologise – thanks for taking a look and let us know if there's any way we can help out.
@ylokesh you also have the getDefaultDocumentNode export in your deskStructure?
@tom2strobl very late answer but in your case I believe it is because of your getDefaultDocumentNode export. You are limiting it to the "page" schemaType, but your document has a "global" schemaType
@saulhardman I published a beta version 5.1.0-beta.0 where I updated the component to be in line with the latest Sanity updates. I need to do some more testing before releasing but it's there
@saulhardman I published a beta version
5.1.0-beta.0where I updated the component to be in line with the latest Sanity updates. I need to do some more testing before releasing but it's there
Hi @LiamMartens, thank you very much 👍 I'll try that version out and get back to you.
Hey @LiamMartens, just following up to confirm that the updates you made in 5.1.0-beta.0 are working for me – thanks again 😊
Good to know @saulhardman that it works well for you! Can you share your solution in this thread please?
Also, @LiamMartens it would be great to have some documentation on this use case in the documentation, as I feel it is a very common use case.
Good to know @saulhardman that it works well for you! Can you share your solution in this thread please?
This isn't exactly what you asked for but here's what my deskStructure.js looks like after setting i18n up a couple weeks ago:
// deskStructure.js
import S from '@sanity/desk-tool/structure-builder'
import * as Structure from 'sanity-plugin-intl-input/lib/structure'
import SocialPreview from 'part:social-preview/component'
// import { toPlainText } from 'part:social-preview/utils'
import { BiCookie } from '@hacknug/react-icons/bi'
import {
RiTranslate, RiFileLine, RiFolderShieldLine, RiFileDownloadLine,
RiBriefcaseLine, RiArticleLine, RiUserLine, RiPriceTagLine, RiLayoutBottomLine, RiSettingsLine,
} from '@hacknug/react-icons/ri'
const pagesItems = [
{ title: 'Home', id: 'frontpage', schema: 'frontpage' },
{ title: 'About Us', id: 'about', schema: 'about' },
{ title: 'Blog', id: 'blog', schema: 'blog' },
{ title: 'Contact', id: 'contact', schema: 'contact' },
]
export const getDefaultDocumentNode = (props) => {
return (![
...pagesItems.map(({ schema }) => schema),
'blogPost', 'blogAuthor', 'blogTag',
].includes(props.schemaType))
? S.document()
: S.document().views([
...Structure.getDocumentNodeViewsForSchemaType(props.schemaType),
S.view.component(SocialPreview({
prepareFunction: ({ seo }) => ({
title: seo?.seo_text,
description: seo?.seo_desc,
ogImage: seo?.seo_image,
siteUrl: 'https://www.example.com',
}),
})).title('Social & SEO'),
])
}
export default () => {
const items = Structure.getFilteredDocumentTypeListItems()
const getLocalizedPosts = (type) => items.find((item) => [type].includes(item.spec.id)).getChild()
return S.list().title('Content').items([
...pagesItems.map(({ id, title, schema, icon = RiFileLine }) => {
return S.listItem().title(`${title} Page`).icon(icon).child(getLocalizedPosts(schema))
}),
S.divider(),
S.listItem().title('Blog Articles').icon(RiArticleLine).child(getLocalizedPosts('blogPost')),
S.listItem().title('Blog Authors').icon(RiUserLine).child(getLocalizedPosts('blogAuthor')),
S.listItem().title('Blog Tags').icon(RiPriceTagLine).child(getLocalizedPosts('blogTag')),
S.divider(),
S.listItem().title('Site Settings').icon(RiSettingsLine)
.child(S.document().schemaType('siteSettings').documentId('siteSettings').title('Site Settings')),
S.divider(),
items[0].title('Translation Manager').icon(RiTranslate),
])
}
It doesn't keep the whole singleton implementation but it is okay in my case. I also ended up leaving these pageItems ungrouped (they were all previously grouped under a Pages folder).
Only reason for this is I didn't manage to make everything work while keeping the same structure. The current implementation made it easy to keep everything I had in place pretty much the same so that's a win if you ask me.
Hi @LiamMartens is there any update on this one? I'm using version 5.2.1 to no avail.
There is a related issue on the sanity repo which seems relevant.
Thanks!