@sanity/assist 1.2.13 → 1.2.15-lang.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +392 -6
  2. package/dist/index.d.ts +170 -3
  3. package/dist/index.esm.js +1986 -111
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +1980 -105
  6. package/dist/index.js.map +1 -1
  7. package/package.json +15 -14
  8. package/src/_lib/form/DocumentForm.tsx +1 -1
  9. package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
  10. package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
  11. package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
  12. package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
  13. package/src/assistFormComponents/AssistField.tsx +5 -4
  14. package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
  15. package/src/assistFormComponents/validation/listItem.tsx +2 -2
  16. package/src/assistInspector/FieldAutocomplete.tsx +1 -0
  17. package/src/assistInspector/InstructionTaskHistoryButton.tsx +2 -3
  18. package/src/assistInspector/helpers.ts +7 -9
  19. package/src/assistLayout/AssistLayout.tsx +9 -6
  20. package/src/fieldActions/assistFieldActions.tsx +14 -8
  21. package/src/fieldActions/translateActions.tsx +118 -0
  22. package/src/helpers/assistSupported.ts +1 -1
  23. package/src/node_modules/.vitest/results.json +1 -0
  24. package/src/plugin.tsx +12 -2
  25. package/src/presence/AssistAvatar.tsx +1 -1
  26. package/src/schemas/assistDocumentSchema.tsx +39 -0
  27. package/src/schemas/serialize/serializeSchema.test.ts +15 -2
  28. package/src/schemas/serialize/serializeSchema.ts +8 -7
  29. package/src/schemas/typeDefExtensions.ts +12 -1
  30. package/src/translate/FieldTranslationProvider.tsx +254 -0
  31. package/src/translate/getLanguageParams.ts +26 -0
  32. package/src/translate/paths.test.ts +87 -0
  33. package/src/translate/paths.ts +151 -0
  34. package/src/translate/types.ts +159 -0
  35. package/src/types.ts +21 -2
  36. package/src/useApiClient.ts +63 -0
@@ -0,0 +1,118 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+ import {
3
+ DocumentFieldAction,
4
+ DocumentFieldActionGroup,
5
+ DocumentFieldActionItem,
6
+ ObjectSchemaType,
7
+ } from 'sanity'
8
+ import {TranslateIcon} from '@sanity/icons'
9
+ import {useMemo, useRef} from 'react'
10
+ import {useApiClient, useTranslate} from '../useApiClient'
11
+ import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
12
+ import {Box, Spinner} from '@sanity/ui'
13
+ import {isAssistSupported} from '../helpers/assistSupported'
14
+ import {useDocumentPane} from 'sanity/desk'
15
+ import {useFieldTranslation} from '../translate/FieldTranslationProvider'
16
+
17
+ function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
18
+ return node
19
+ }
20
+
21
+ export const translateActions: DocumentFieldAction = {
22
+ name: 'sanity-assist-translate',
23
+ useAction(props) {
24
+ const {config} = useAiAssistanceConfig()
25
+ const apiClient = useApiClient(config?.__customApiClient)
26
+ const {translate, loading} = useTranslate(apiClient)
27
+
28
+ const isDocumentLevel = props.path.length === 0
29
+ const {schemaType, documentId} = props
30
+
31
+ if (isDocumentLevel) {
32
+ const {value: documentValue} = useDocumentPane()
33
+ const docRef = useRef(documentValue)
34
+ docRef.current = documentValue
35
+
36
+ const docTransTypes = config.translate?.document?.documentTypes
37
+ const documentTranslation =
38
+ (!docTransTypes && isAssistSupported(schemaType)) ||
39
+ docTransTypes?.includes(schemaType.name)
40
+ const languagePath = config.translate?.document?.languageField
41
+
42
+ //const {value: languageId} = extractWithPath(languagePath, documentValue)[0] ?? {}
43
+ // if this is true, it is stable, and not breaking rules of hooks
44
+ const translateDocumentAction = useMemo(
45
+ () =>
46
+ languagePath && documentTranslation
47
+ ? node({
48
+ type: 'action',
49
+ icon: loading
50
+ ? () => (
51
+ <Box style={{height: 17}}>
52
+ <Spinner style={{transform: 'translateY(6px)'}} />
53
+ </Box>
54
+ )
55
+ : TranslateIcon,
56
+ title: `Translate document`,
57
+ onAction: () => {
58
+ if (loading || !languagePath || !documentId) {
59
+ return
60
+ }
61
+ translate({languagePath, documentId: documentId ?? ''})
62
+ },
63
+ renderAsButton: true,
64
+ disabled: loading,
65
+ })
66
+ : undefined,
67
+ [languagePath, translate, documentId, loading, documentTranslation]
68
+ )
69
+
70
+ const fieldTranslate = useFieldTranslation()
71
+
72
+ const fieldTransEnabled = config.translate?.field?.documentTypes?.includes(schemaType.name)
73
+ const translateFieldsAction = useMemo(
74
+ () =>
75
+ fieldTransEnabled
76
+ ? node({
77
+ type: 'action',
78
+ icon: loading
79
+ ? () => (
80
+ <Box style={{height: 17}}>
81
+ <Spinner style={{transform: 'translateY(6px)'}} />
82
+ </Box>
83
+ )
84
+ : TranslateIcon,
85
+ title: `Translate fields`,
86
+ onAction: () => {
87
+ if (loading || !documentId) {
88
+ return
89
+ }
90
+ fieldTranslate.openFieldTranslation(
91
+ docRef.current,
92
+ schemaType as ObjectSchemaType
93
+ )
94
+ },
95
+ renderAsButton: true,
96
+ disabled: loading,
97
+ })
98
+ : undefined,
99
+ [fieldTranslate, schemaType, documentId, loading, fieldTransEnabled]
100
+ )
101
+
102
+ // eslint-disable-next-line react-hooks/rules-of-hooks
103
+ return useMemo(() => {
104
+ return node({
105
+ type: 'group',
106
+ icon: () => null,
107
+ title: 'Translate',
108
+ children: [translateDocumentAction, translateFieldsAction].filter(
109
+ (c): c is DocumentFieldActionItem => !!c
110
+ ),
111
+ expanded: true,
112
+ })
113
+ }, [translateDocumentAction, translateFieldsAction])
114
+ }
115
+ // works but not supported by types
116
+ return undefined as unknown as DocumentFieldActionItem
117
+ },
118
+ }
@@ -43,7 +43,7 @@ function isUnsupportedType(type: SchemaType) {
43
43
  type.jsonType === 'number' ||
44
44
  type.name === 'sanity.imageCrop' ||
45
45
  type.name === 'sanity.imageHotspot' ||
46
- isType(type, 'reference') ||
46
+ (isType(type, 'reference') && !type?.options?.aiWritingAssistance?.embeddingsIndex) ||
47
47
  isType(type, 'crossDatasetReference') ||
48
48
  isType(type, 'slug') ||
49
49
  isType(type, 'url') ||
@@ -0,0 +1 @@
1
+ {"version":"0.32.2","results":[[":schemas/serialize/serializeSchema.test.ts",{"duration":12,"failed":false}],[":translate/paths.test.ts",{"duration":6,"failed":false}]]}
package/src/plugin.tsx CHANGED
@@ -15,8 +15,11 @@ import {createAssistDocumentPresence} from './presence/AssistDocumentPresence'
15
15
  import {isSchemaAssistEnabled} from './helpers/assistSupported'
16
16
  import {isImage} from './helpers/typeUtils'
17
17
  import {ImageContextProvider} from './components/ImageContext'
18
+ import {TranslationConfig} from './translate/types'
18
19
 
19
20
  export interface AssistPluginConfig {
21
+ translate?: TranslationConfig
22
+
20
23
  /**
21
24
  * Set this to false to disable model migration from the alpha version of this plugin
22
25
  */
@@ -36,6 +39,9 @@ export const assist = definePlugin<AssistPluginConfig | void>((config) => {
36
39
  schema: {
37
40
  types: schemaTypes,
38
41
  },
42
+ i18n: {
43
+ bundles: [{}],
44
+ },
39
45
 
40
46
  document: {
41
47
  inspectors: (prev, context) => {
@@ -45,8 +51,12 @@ export const assist = definePlugin<AssistPluginConfig | void>((config) => {
45
51
  }
46
52
  return prev
47
53
  },
48
- unstable_fieldActions: (prev) => {
49
- return [...prev, assistFieldActions]
54
+ unstable_fieldActions: (prev, {documentType, schema}) => {
55
+ const docSchema = schema.get(documentType)
56
+ if (docSchema && isSchemaAssistEnabled(docSchema)) {
57
+ return [...prev, assistFieldActions]
58
+ }
59
+ return prev
50
60
  },
51
61
  unstable_languageFilter: (prev, {documentId, schema, schemaType}) => {
52
62
  const docSchema = schema.get(schemaType)
@@ -88,7 +88,7 @@ export function AssistAvatar(props: {state?: 'present' | 'active'}) {
88
88
  </Outline>
89
89
  <IconDisc>
90
90
  <Text as="span" size={0} style={{color: 'inherit'}}>
91
- <SparklesIcon />
91
+ <SparklesIcon style={{color: 'inherit'}} />
92
92
  </Text>
93
93
  </IconDisc>
94
94
  </Root>
@@ -18,6 +18,8 @@ import {
18
18
  instructionContextTypeName,
19
19
  instructionTaskTypeName,
20
20
  instructionTypeName,
21
+ outputFieldTypeName,
22
+ outputTypeTypeName,
21
23
  promptTypeName,
22
24
  userInputTypeName,
23
25
  } from '../types'
@@ -37,6 +39,8 @@ import {PromptInput} from '../assistDocument/components/instruction/PromptInput'
37
39
  import {instructionGuideUrl} from '../constants'
38
40
  import {InstructionsArrayField} from '../assistDocument/components/InstructionsArrayField'
39
41
  import {getFieldRefsWithDocument} from '../assistInspector/helpers'
42
+ import {InstructionOutputField} from '../assistDocument/components/instruction/InstructionOutputField'
43
+ import {InstructionOutputInput} from '../assistDocument/components/instruction/InstructionOutputInput'
40
44
 
41
45
  export const fieldReference = defineType({
42
46
  type: 'object',
@@ -329,6 +333,41 @@ export const instruction = defineType({
329
333
  return context.currentUser?.id ?? ''
330
334
  },
331
335
  }),
336
+ defineField({
337
+ type: 'array',
338
+ name: 'output',
339
+ title: 'Output filter',
340
+ components: {
341
+ input: InstructionOutputInput,
342
+ field: InstructionOutputField,
343
+ },
344
+ of: [
345
+ defineArrayMember({
346
+ type: 'object' as const,
347
+ name: outputFieldTypeName,
348
+ title: 'Output field',
349
+ fields: [
350
+ {
351
+ type: 'string',
352
+ name: 'path',
353
+ title: 'Path',
354
+ },
355
+ ],
356
+ }),
357
+ defineArrayMember({
358
+ type: 'object' as const,
359
+ name: outputTypeTypeName,
360
+ title: 'Output type',
361
+ fields: [
362
+ {
363
+ type: 'string',
364
+ name: 'type',
365
+ title: 'Type',
366
+ },
367
+ ],
368
+ }),
369
+ ],
370
+ }),
332
371
  ],
333
372
  })
334
373
 
@@ -23,7 +23,7 @@ const mockStudioTypes = [
23
23
  ]
24
24
 
25
25
  describe('serializeSchema', () => {
26
- test('should not serialize excluded document schema', () => {
26
+ test('should serialize excluded document schema to support exclude: false overrides at the field level', () => {
27
27
  const schema = Schema.compile({
28
28
  name: 'test',
29
29
  types: [
@@ -42,7 +42,20 @@ describe('serializeSchema', () => {
42
42
 
43
43
  const serializedTypes = serializeSchema(schema, {leanFormat: true})
44
44
 
45
- expect(serializedTypes).toEqual([])
45
+ expect(serializedTypes).toEqual([
46
+ {
47
+ fields: [
48
+ {
49
+ name: 'title',
50
+ title: 'Title',
51
+ type: 'string',
52
+ },
53
+ ],
54
+ name: 'article',
55
+ title: 'Article',
56
+ type: 'document',
57
+ },
58
+ ])
46
59
  })
47
60
 
48
61
  test('should serialize simple schema', () => {
@@ -2,6 +2,7 @@ import {
2
2
  ArraySchemaType,
3
3
  ImageOptions,
4
4
  ObjectSchemaType,
5
+ ReferenceOptions,
5
6
  ReferenceSchemaType,
6
7
  Schema,
7
8
  SchemaType,
@@ -29,7 +30,8 @@ export function serializeSchema(schema: Schema, options?: Options): SerializedSc
29
30
  .filter((t) => !(hiddenTypes.includes(t) || t.startsWith('sanity.')))
30
31
  .map((t) => schema.get(t))
31
32
  .filter((t): t is SchemaType => !!t)
32
- .filter((t) => isAssistSupported(t))
33
+ // because a field can override exclude at the type level, we have to also serialize excluded types
34
+ // so don't do this: .filter((t) => isAssistSupported(t))
33
35
  .filter((t) => !t.hidden && !t.readOnly)
34
36
  .map((t) => getSchemaStub(t, schema, options))
35
37
  .filter((t) => {
@@ -77,13 +79,12 @@ function getBaseFields(
77
79
  typeName: string,
78
80
  options: Options | undefined
79
81
  ) {
80
- const imagePromptField = (type.options as ImageOptions)?.imagePromptField
82
+ const schemaOptions = removeUndef({
83
+ imagePromptField: (type.options as ImageOptions)?.imagePromptField,
84
+ embeddingsIndex: (type.options as ReferenceOptions)?.aiWritingAssistance?.embeddingsIndex,
85
+ })
81
86
  return removeUndef({
82
- options: imagePromptField
83
- ? {
84
- imagePromptField: imagePromptField,
85
- }
86
- : undefined,
87
+ options: Object.keys(schemaOptions).length ? schemaOptions : undefined,
87
88
  values: Array.isArray(type?.options?.list)
88
89
  ? type?.options?.list.map((v: string | {value: string; title: string}) =>
89
90
  typeof v === 'string' ? v : v.value ?? `${v.title}`
@@ -22,7 +22,18 @@ declare module 'sanity' {
22
22
  }
23
23
  interface NumberOptions extends AssistOptions {}
24
24
  interface ObjectOptions extends AssistOptions {}
25
- interface ReferenceBaseOptions extends AssistOptions {}
25
+ interface ReferenceBaseOptions {
26
+ aiWritingAssistance?: {
27
+ /** Set to true to disable assistance for this field or type */
28
+ exclude?: boolean
29
+
30
+ /**
31
+ * When set, the reference field will allow instructions to be added to it.
32
+ * Should be the name of the embeddings-index where assist will look for contextually relevant documents
33
+ * */
34
+ embeddingsIndex?: string
35
+ }
36
+ }
26
37
  interface SlugOptions extends AssistOptions {}
27
38
  interface StringOptions extends AssistOptions {}
28
39
  interface TextOptions extends AssistOptions {}
@@ -0,0 +1,254 @@
1
+ import {
2
+ createContext,
3
+ PropsWithChildren,
4
+ useCallback,
5
+ useContext,
6
+ useId,
7
+ useMemo,
8
+ useState,
9
+ } from 'react'
10
+ import {ObjectSchemaType, SanityDocumentLike, useClient} from 'sanity'
11
+ import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
12
+ import {useApiClient, useTranslate} from '../useApiClient'
13
+ import {Box, Button, Checkbox, Dialog, Flex, Radio, Spinner, Stack, Text, Tooltip} from '@sanity/ui'
14
+ import {
15
+ defaultLanguageOutputs,
16
+ getDocumentMembersFlat,
17
+ getTranslationMap,
18
+ TranslationMap,
19
+ } from './paths'
20
+ import {PlayIcon} from '@sanity/icons'
21
+ import {Language} from './types'
22
+ import {getLanguageParams} from './getLanguageParams'
23
+
24
+ export interface FieldTranslationContextValue {
25
+ openFieldTranslation: (document: SanityDocumentLike, documentSchema: ObjectSchemaType) => void
26
+ translationLoading: boolean
27
+ }
28
+
29
+ export const FieldTranslationContext = createContext<FieldTranslationContextValue>({
30
+ openFieldTranslation: () => {},
31
+ translationLoading: false,
32
+ })
33
+
34
+ export function useFieldTranslation() {
35
+ return useContext(FieldTranslationContext)
36
+ }
37
+
38
+ export function FieldTranslationProvider(props: PropsWithChildren<{}>) {
39
+ const {config: assistConfig} = useAiAssistanceConfig()
40
+ const apiClient = useApiClient(assistConfig.__customApiClient)
41
+ const config = assistConfig.translate?.field
42
+ const {translate: runTranslate} = useTranslate(apiClient)
43
+
44
+ const [dialogOpen, setDialogOpen] = useState(false)
45
+
46
+ const [document, setDocument] = useState<SanityDocumentLike | undefined>()
47
+ const [documentSchema, setDocumentSchema] = useState<ObjectSchemaType | undefined>()
48
+ const [languages, setLanguages] = useState<Language[] | undefined>()
49
+ const [fromLanguage, setFromLanguage] = useState<Language | undefined>(undefined)
50
+ const [toLanguages, setToLanguages] = useState<Language[] | undefined>(undefined)
51
+ const [translationMap, setTranslationMap] = useState<TranslationMap[] | undefined>()
52
+
53
+ const close = useCallback(() => {
54
+ setDialogOpen(false)
55
+ setLanguages(undefined)
56
+ setDocument(undefined)
57
+ setDocument(undefined)
58
+ }, [])
59
+ const languageClient = useClient({apiVersion: config?.apiVersion ?? '2022-11-27'})
60
+ const documentId = document?._id
61
+ const id = useId()
62
+
63
+ const selectFromLanguage = useCallback(
64
+ (
65
+ from: Language,
66
+ languages: Language[] | undefined,
67
+ document: SanityDocumentLike | undefined,
68
+ documentSchema: ObjectSchemaType | undefined
69
+ ) => {
70
+ setFromLanguage(from)
71
+ if (!document || !documentSchema || !languages) {
72
+ setTranslationMap(undefined)
73
+ return
74
+ }
75
+
76
+ const to = languages.filter((l) => l.id !== from?.id)
77
+ setToLanguages(to)
78
+ const fromId = from?.id
79
+ const toIds = to?.map((l) => l.id) ?? []
80
+ const docMembers = getDocumentMembersFlat(document, documentSchema)
81
+ if (fromId && toIds?.length) {
82
+ const transMap = getTranslationMap(
83
+ documentSchema,
84
+ docMembers,
85
+ fromId,
86
+ toIds,
87
+ config?.translationOutputs ?? defaultLanguageOutputs
88
+ )
89
+ setTranslationMap(transMap)
90
+ } else {
91
+ setTranslationMap(undefined)
92
+ }
93
+ },
94
+ [config]
95
+ )
96
+
97
+ const toggleToLanguage = useCallback(
98
+ (
99
+ toggledLang: Language,
100
+ toLanguages: Language[] | undefined,
101
+ languages: Language[] | undefined
102
+ ) => {
103
+ if (!languages) {
104
+ return
105
+ }
106
+ const wasSelected = !!toLanguages?.find((l) => l.id === toggledLang.id)
107
+ const newToLangs = languages.filter(
108
+ (anyLang) =>
109
+ !!toLanguages?.find(
110
+ (selectedLang) => toggledLang.id !== selectedLang.id && selectedLang.id === anyLang.id
111
+ ) ||
112
+ (toggledLang.id === anyLang.id && !wasSelected)
113
+ )
114
+ setToLanguages(newToLangs)
115
+ },
116
+ []
117
+ )
118
+
119
+ const contextValue: FieldTranslationContextValue = useMemo(() => {
120
+ return {
121
+ openFieldTranslation: async (
122
+ document: SanityDocumentLike,
123
+ documentSchema: ObjectSchemaType
124
+ ) => {
125
+ setDialogOpen(true)
126
+ const languageParams = getLanguageParams(config?.selectLanguageParams, document)
127
+ const languages: Language[] | undefined = await (typeof config?.languages === 'function'
128
+ ? config?.languages(languageClient, languageParams)
129
+ : Promise.resolve(config?.languages))
130
+ setLanguages(languages)
131
+ setDocument(document)
132
+ setDocumentSchema(documentSchema)
133
+ const fromLanguage = languages?.[0]
134
+ if (fromLanguage) {
135
+ selectFromLanguage(fromLanguage, languages, document, documentSchema)
136
+ } else {
137
+ console.error('No languages available for selected language params', languageParams)
138
+ }
139
+ },
140
+ translationLoading: false,
141
+ }
142
+ }, [selectFromLanguage, config, languageClient])
143
+
144
+ const runDisabled =
145
+ !fromLanguage || !toLanguages?.length || !translationMap?.length || !documentId
146
+
147
+ const onRunTranslation = useCallback(() => {
148
+ if (translationMap && documentId) {
149
+ runTranslate({
150
+ documentId,
151
+ fieldLanguageMap: translationMap.map((map) => ({
152
+ ...map,
153
+ // eslint-disable-next-line max-nested-callbacks
154
+ outputs: map.outputs.filter((out) => !!toLanguages?.find((l) => l.id === out.id)),
155
+ })),
156
+ })
157
+ }
158
+ close()
159
+ }, [translationMap, documentId, runTranslate, close, toLanguages])
160
+
161
+ const runButton = (
162
+ <Button
163
+ text={`Translate`}
164
+ tone="primary"
165
+ icon={PlayIcon}
166
+ style={{width: '100%'}}
167
+ disabled={runDisabled}
168
+ onClick={onRunTranslation}
169
+ />
170
+ )
171
+
172
+ return (
173
+ <FieldTranslationContext.Provider value={contextValue}>
174
+ {dialogOpen ? (
175
+ <Dialog
176
+ id={id}
177
+ width={1}
178
+ open={dialogOpen}
179
+ onClose={close}
180
+ header="Translate fields"
181
+ footer={
182
+ <Flex justify="space-between" padding={2} flex={1}>
183
+ {runDisabled ? (
184
+ <Tooltip
185
+ content={
186
+ <Flex padding={2}>
187
+ <Text>Nothing to translate.</Text>
188
+ </Flex>
189
+ }
190
+ placement="top"
191
+ >
192
+ <Flex flex={1}>{runButton}</Flex>
193
+ </Tooltip>
194
+ ) : (
195
+ runButton
196
+ )}
197
+ </Flex>
198
+ }
199
+ >
200
+ {languages ? (
201
+ <Flex padding={4} gap={5} align="flex-start" justify="center">
202
+ <Stack space={2}>
203
+ <Box marginBottom={2}>
204
+ <Text weight="semibold">From</Text>
205
+ </Box>
206
+ {languages?.map((l) => (
207
+ <Flex key={l.id} gap={3} align="center">
208
+ <Radio
209
+ name="fromLang"
210
+ value={l.id}
211
+ checked={l.id === fromLanguage?.id}
212
+ onClick={() => selectFromLanguage(l, languages, document, documentSchema)}
213
+ />
214
+ <Text>{l.title ?? l.id}</Text>
215
+ </Flex>
216
+ ))}
217
+ </Stack>
218
+
219
+ <Stack space={2}>
220
+ <Box marginBottom={2}>
221
+ <Text weight="semibold">To</Text>
222
+ </Box>
223
+ {languages
224
+ ?.filter((l) => l.id !== fromLanguage?.id)
225
+ .map((l) => (
226
+ <Flex key={l.id} gap={3} align="center">
227
+ <Checkbox
228
+ name="toLang"
229
+ value={l.id}
230
+ checked={!!toLanguages?.find((tl) => tl.id === l.id)}
231
+ onClick={() => toggleToLanguage(l, toLanguages, languages)}
232
+ disabled={
233
+ !translationMap?.find((tm) => tm.outputs.find((o) => o.id === l.id))
234
+ }
235
+ />
236
+ <Text>{l.title ?? l.id}</Text>
237
+ </Flex>
238
+ ))}
239
+ </Stack>
240
+ </Flex>
241
+ ) : (
242
+ <Flex padding={4} gap={2} align="flex-start" justify="center">
243
+ <Box>
244
+ <Spinner />
245
+ </Box>
246
+ <Text>Loading languages...</Text>
247
+ </Flex>
248
+ )}
249
+ </Dialog>
250
+ ) : null}
251
+ {props.children}
252
+ </FieldTranslationContext.Provider>
253
+ )
254
+ }
@@ -0,0 +1,26 @@
1
+ import {SanityDocumentLike} from 'sanity'
2
+ import get from 'lodash/get'
3
+
4
+ export const getLanguageParams = (
5
+ select: Record<string, string> | undefined,
6
+ document: SanityDocumentLike | undefined
7
+ ): Record<string, unknown> => {
8
+ if (!select || !document) {
9
+ return {}
10
+ }
11
+
12
+ const selection: Record<string, string> = select || {}
13
+ const selectedValue: Record<string, unknown> = {}
14
+ for (const [key, path] of Object.entries(selection)) {
15
+ let value = get(document, path)
16
+ if (Array.isArray(value)) {
17
+ // If there are references in the array, ensure they have `_ref` set, otherwise they are considered empty and can safely be ignored
18
+ value = value.filter((item) =>
19
+ typeof item === 'object' ? item?._type !== 'reference' || '_ref' in item : true
20
+ )
21
+ }
22
+ selectedValue[key] = value
23
+ }
24
+
25
+ return selectedValue
26
+ }