@sanity/assist 1.2.15 → 2.0.0

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 (52) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +551 -30
  3. package/dist/index.cjs.mjs +1 -0
  4. package/dist/index.d.ts +253 -11
  5. package/dist/index.esm.js +2405 -392
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +2399 -385
  8. package/dist/index.js.map +1 -1
  9. package/package.json +15 -14
  10. package/src/_lib/form/DocumentForm.tsx +3 -2
  11. package/src/_lib/form/constants.ts +1 -0
  12. package/src/assistDocument/AssistDocumentInput.tsx +24 -4
  13. package/src/assistDocument/RequestRunInstructionProvider.tsx +37 -21
  14. package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
  15. package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
  16. package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
  17. package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
  18. package/src/assistFormComponents/AssistField.tsx +11 -5
  19. package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
  20. package/src/assistFormComponents/validation/listItem.tsx +2 -2
  21. package/src/assistInspector/AssistInspector.tsx +6 -0
  22. package/src/assistInspector/FieldAutocomplete.tsx +1 -0
  23. package/src/assistInspector/InstructionTaskHistoryButton.tsx +2 -3
  24. package/src/assistInspector/helpers.ts +9 -11
  25. package/src/assistLayout/AssistLayout.tsx +9 -9
  26. package/src/components/ImageContext.tsx +19 -9
  27. package/src/components/SafeValueInput.tsx +4 -1
  28. package/src/fieldActions/assistFieldActions.tsx +42 -13
  29. package/src/fieldActions/generateCaptionActions.tsx +2 -2
  30. package/src/fieldActions/generateImageActions.tsx +57 -0
  31. package/src/helpers/assistSupported.ts +10 -16
  32. package/src/helpers/conditionalMembers.test.ts +200 -0
  33. package/src/helpers/conditionalMembers.ts +127 -0
  34. package/src/helpers/typeUtils.ts +19 -5
  35. package/src/index.ts +3 -0
  36. package/src/plugin.tsx +14 -5
  37. package/src/presence/AssistAvatar.tsx +1 -1
  38. package/src/schemas/assistDocumentSchema.tsx +40 -1
  39. package/src/schemas/serialize/serializeSchema.test.ts +239 -8
  40. package/src/schemas/serialize/serializeSchema.ts +77 -10
  41. package/src/schemas/typeDefExtensions.ts +89 -5
  42. package/src/translate/FieldTranslationProvider.tsx +360 -0
  43. package/src/translate/getLanguageParams.ts +26 -0
  44. package/src/translate/languageStore.ts +18 -0
  45. package/src/translate/paths.test.ts +133 -0
  46. package/src/translate/paths.ts +175 -0
  47. package/src/translate/translateActions.tsx +188 -0
  48. package/src/translate/types.ts +160 -0
  49. package/src/types.ts +33 -12
  50. package/src/useApiClient.ts +130 -2
  51. package/src/assistLayout/AlphaMigration.tsx +0 -310
  52. package/src/legacy-types.ts +0 -72
@@ -0,0 +1,133 @@
1
+ import {describe, expect, test} from 'vitest'
2
+ import {Schema} from '@sanity/schema'
3
+ import {defineType, ObjectSchemaType, pathToString, SanityDocumentLike, typed} from 'sanity'
4
+ import {
5
+ defaultLanguageOutputs,
6
+ FieldLanguageMap,
7
+ getDocumentMembersFlat,
8
+ getFieldLanguageMap,
9
+ } from './paths'
10
+
11
+ describe('paths', () => {
12
+ test('should return internationalizedArrayString paths and find translation mappings', () => {
13
+ const docSchema: ObjectSchemaType = Schema.compile({
14
+ name: 'test',
15
+ types: [
16
+ defineType({
17
+ type: 'document',
18
+ name: 'article',
19
+ fields: [
20
+ {type: 'string', name: 'title'},
21
+ {
22
+ type: 'object',
23
+ name: 'localeTitle',
24
+ fields: [
25
+ {type: 'string', name: 'en'},
26
+ {type: 'string', name: 'no'},
27
+ ],
28
+ },
29
+ {
30
+ type: 'array',
31
+ name: 'translations',
32
+ of: [
33
+ {
34
+ type: 'object',
35
+ name: 'internationalizedArrayString',
36
+ fields: [{type: 'string', name: 'value'}],
37
+ },
38
+ ],
39
+ },
40
+ ],
41
+ }),
42
+ ],
43
+ }).get('article')
44
+
45
+ const doc: SanityDocumentLike = {
46
+ _id: 'na',
47
+ _type: 'article',
48
+ title: 'some title',
49
+ localeTitle: {
50
+ en: 'en string',
51
+ },
52
+ translations: [
53
+ {
54
+ _type: 'internationalizedArrayString',
55
+ _key: 'en',
56
+ value: 'some string',
57
+ },
58
+ ],
59
+ }
60
+
61
+ const members = getDocumentMembersFlat(doc, docSchema)
62
+ expect(members.map((p) => pathToString(p.path))).toEqual([
63
+ 'title',
64
+ 'localeTitle',
65
+ 'localeTitle.en',
66
+ // this path has no value in the document, so are not included
67
+ //'localeTitle.no',
68
+ 'translations',
69
+ 'translations[_key=="en"]',
70
+ 'translations[_key=="en"].value',
71
+ // these path has no value in the document, so are not included
72
+ //'translations[_key=="nb"]',
73
+ //'translations[_key=="nb"].value',
74
+ ])
75
+
76
+ const transMap = getFieldLanguageMap(docSchema, members, 'en', ['nb'], defaultLanguageOutputs)
77
+
78
+ expect(transMap).toEqual(
79
+ typed<FieldLanguageMap[]>([
80
+ {
81
+ inputLanguageId: 'en',
82
+ inputPath: ['translations', {_key: 'en'}],
83
+ outputs: [{id: 'nb', outputPath: ['translations', {_key: 'nb'}]}],
84
+ },
85
+ ])
86
+ )
87
+ })
88
+
89
+ test('should use first type in array when array item is missing _type', () => {
90
+ const docSchema: ObjectSchemaType = Schema.compile({
91
+ name: 'test',
92
+ types: [
93
+ defineType({
94
+ type: 'document',
95
+ name: 'article',
96
+ fields: [
97
+ {
98
+ type: 'array',
99
+ name: 'translations',
100
+ of: [
101
+ {
102
+ type: 'object',
103
+ name: 'internationalizedArrayString',
104
+ fields: [{type: 'string', name: 'value'}],
105
+ },
106
+ ],
107
+ },
108
+ ],
109
+ }),
110
+ ],
111
+ }).get('article')
112
+
113
+ const doc: SanityDocumentLike = {
114
+ _id: 'na',
115
+ _type: 'article',
116
+ translations: [
117
+ {
118
+ //assume type is missing in the data for some reason
119
+ //_type: 'internationalizedArrayString',
120
+ _key: 'en',
121
+ value: 'some string',
122
+ },
123
+ ],
124
+ }
125
+
126
+ const members = getDocumentMembersFlat(doc, docSchema)
127
+ expect(members.map((p) => pathToString(p.path))).toEqual([
128
+ 'translations',
129
+ 'translations[_key=="en"]',
130
+ 'translations[_key=="en"].value',
131
+ ])
132
+ })
133
+ })
@@ -0,0 +1,175 @@
1
+ import {
2
+ isDocumentSchemaType,
3
+ isKeySegment,
4
+ ObjectSchemaType,
5
+ Path,
6
+ pathToString,
7
+ SanityDocumentLike,
8
+ } from 'sanity'
9
+ import {extractWithPath} from '@sanity/mutator'
10
+ import {DocumentMember, TranslationOutput, TranslationOutputsFunction} from './types'
11
+
12
+ export interface FieldLanguageMap {
13
+ inputLanguageId: string
14
+ inputPath: Path
15
+ outputs: TranslationOutput[]
16
+ }
17
+
18
+ const MAX_DEPTH = 6
19
+
20
+ export function getDocumentMembersFlat(doc: SanityDocumentLike, schemaType: ObjectSchemaType) {
21
+ if (!isDocumentSchemaType(schemaType)) {
22
+ console.error(`Schema type is not a document`)
23
+ return []
24
+ }
25
+
26
+ return extractPaths(doc, schemaType, [], MAX_DEPTH)
27
+ }
28
+
29
+ function extractPaths(
30
+ doc: SanityDocumentLike,
31
+ schemaType: ObjectSchemaType,
32
+ path: Path,
33
+ maxDepth: number
34
+ ): DocumentMember[] {
35
+ if (path.length >= maxDepth) {
36
+ return []
37
+ }
38
+
39
+ return schemaType.fields.reduce<DocumentMember[]>((acc, field) => {
40
+ const fieldPath = [...path, field.name]
41
+ const fieldSchema = field.type
42
+ const {value} = extractWithPath(pathToString(fieldPath), doc)[0] ?? {}
43
+ if (!value) {
44
+ return acc
45
+ }
46
+
47
+ const thisFieldWithPath: DocumentMember = {
48
+ path: fieldPath,
49
+ name: field.name,
50
+ schemaType: fieldSchema,
51
+ value,
52
+ }
53
+
54
+ if (fieldSchema.jsonType === 'object') {
55
+ const innerFields = extractPaths(doc, fieldSchema, fieldPath, maxDepth)
56
+
57
+ return [...acc, thisFieldWithPath, ...innerFields]
58
+ } else if (
59
+ fieldSchema.jsonType === 'array' &&
60
+ fieldSchema.of.length &&
61
+ fieldSchema.of.some((item) => 'fields' in item)
62
+ ) {
63
+ const {value: arrayValue} = extractWithPath(pathToString(fieldPath), doc)[0] ?? {}
64
+
65
+ let arrayPaths: DocumentMember[] = []
66
+ if ((arrayValue as any)?.length) {
67
+ for (const item of arrayValue as any[]) {
68
+ const itemPath = [...fieldPath, {_key: item._key}]
69
+ let itemSchema = fieldSchema.of.find((t) => t.name === item._type)
70
+ if (!item._type) {
71
+ itemSchema = fieldSchema.of[0]
72
+ console.warn(
73
+ 'Array item is missing _type - using the first defined type in the array.of schema',
74
+ {
75
+ itemPath,
76
+ item,
77
+ itemSchema,
78
+ }
79
+ )
80
+ }
81
+ if (item._key && itemSchema) {
82
+ const innerFields = extractPaths(
83
+ doc,
84
+ itemSchema as ObjectSchemaType,
85
+ itemPath,
86
+ maxDepth
87
+ )
88
+ const arrayMember = {
89
+ path: itemPath,
90
+ name: item._key,
91
+ schemaType: itemSchema,
92
+ value: item,
93
+ }
94
+ arrayPaths = [...arrayPaths, arrayMember, ...innerFields]
95
+ }
96
+ }
97
+ }
98
+
99
+ return [...acc, thisFieldWithPath, ...arrayPaths]
100
+ }
101
+
102
+ return [...acc, thisFieldWithPath]
103
+ }, [])
104
+ }
105
+
106
+ /**
107
+ * Default implementation for plugin config `translate.field.translationOutputs`
108
+ *
109
+ * @see FieldTranslationConfig#translationOutputs
110
+ */
111
+ export const defaultLanguageOutputs: TranslationOutputsFunction = function (
112
+ member,
113
+ enclosingType,
114
+ translateFromLanguageId,
115
+ translateToLanguageIds
116
+ ) {
117
+ if (
118
+ member.schemaType.jsonType === 'object' &&
119
+ member.schemaType.name.startsWith('internationalizedArray')
120
+ ) {
121
+ const pathEnd = member.path.slice(-1)
122
+
123
+ const language = isKeySegment(pathEnd[0]) ? pathEnd[0]._key : null
124
+ return language === translateFromLanguageId
125
+ ? translateToLanguageIds.map((translateToId) => ({
126
+ id: translateToId,
127
+ outputPath: [...member.path.slice(0, -1), {_key: translateToId}],
128
+ }))
129
+ : undefined
130
+ }
131
+
132
+ if (enclosingType.jsonType === 'object' && enclosingType.name.startsWith('locale')) {
133
+ return translateFromLanguageId === member.name
134
+ ? translateToLanguageIds.map((translateToId) => ({
135
+ id: translateToId,
136
+ outputPath: [...member.path.slice(0, -1), translateToId],
137
+ }))
138
+ : undefined
139
+ }
140
+
141
+ return undefined
142
+ }
143
+
144
+ export function getFieldLanguageMap(
145
+ documentSchema: ObjectSchemaType,
146
+ documentMembers: DocumentMember[],
147
+ translateFromLanguageId: string,
148
+ outputLanguageIds: string[],
149
+ langFn: TranslationOutputsFunction
150
+ ): FieldLanguageMap[] {
151
+ const translationMaps: FieldLanguageMap[] = []
152
+ for (const member of documentMembers) {
153
+ const parentPath = member.path.slice(0, -1)
154
+ const enclosingType =
155
+ documentMembers.find((m) => pathToString(m.path) === pathToString(parentPath))?.schemaType ??
156
+ documentSchema
157
+
158
+ const translations = langFn(
159
+ member,
160
+ enclosingType,
161
+ translateFromLanguageId,
162
+ outputLanguageIds
163
+ )?.filter((translation) => translation.id !== translateFromLanguageId)
164
+
165
+ if (translations) {
166
+ translationMaps.push({
167
+ inputLanguageId: translateFromLanguageId,
168
+ inputPath: member.path,
169
+ outputs: translations,
170
+ })
171
+ }
172
+ }
173
+
174
+ return translationMaps
175
+ }
@@ -0,0 +1,188 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+ import {
3
+ DocumentFieldAction,
4
+ DocumentFieldActionGroup,
5
+ DocumentFieldActionItem,
6
+ DocumentFieldActionProps,
7
+ ObjectSchemaType,
8
+ } from 'sanity'
9
+ import {TranslateIcon} from '@sanity/icons'
10
+ import {useMemo, useRef} from 'react'
11
+ import {useApiClient, useTranslate} from '../useApiClient'
12
+ import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
13
+ import {Box, Spinner} from '@sanity/ui'
14
+ import {isAssistSupported} from '../helpers/assistSupported'
15
+ import {useDocumentPane} from 'sanity/desk'
16
+ import {useFieldTranslation} from './FieldTranslationProvider'
17
+ import {useDraftDelayedTask} from '../assistDocument/RequestRunInstructionProvider'
18
+ import {AssistOptions} from '../schemas/typeDefExtensions'
19
+ import {getConditionalMembers} from '../helpers/conditionalMembers'
20
+
21
+ function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
22
+ return node
23
+ }
24
+
25
+ export type TranslateProps = DocumentFieldActionProps & {
26
+ documentIsAssistable?: boolean
27
+ documentSchemaType?: ObjectSchemaType
28
+ }
29
+ export const translateActions: DocumentFieldAction = {
30
+ name: 'sanity-assist-translate',
31
+ useAction(props: TranslateProps) {
32
+ const {config, status} = useAiAssistanceConfig()
33
+ const apiClient = useApiClient(config?.__customApiClient)
34
+
35
+ const {
36
+ schemaType: fieldSchemaType,
37
+ path,
38
+ documentId,
39
+ documentSchemaType,
40
+ documentIsAssistable,
41
+ } = props
42
+ const isDocumentLevel = path.length === 0
43
+ const readOnly = fieldSchemaType.readOnly === true
44
+
45
+ const docTransTypes = config.translate?.document?.documentTypes
46
+ const options = fieldSchemaType?.options as AssistOptions | undefined
47
+ const addFieldAction = isDocumentLevel || options?.aiAssist?.translateAction
48
+
49
+ //All props used here MUST have the same value always, or we break the rules of hooks (conditional hook usage)
50
+ const fieldTransEnabled =
51
+ addFieldAction &&
52
+ documentSchemaType &&
53
+ config.translate?.field?.documentTypes?.includes(documentSchemaType.name)
54
+ const documentTranslationEnabled =
55
+ addFieldAction &&
56
+ documentSchemaType &&
57
+ ((!docTransTypes && isAssistSupported(fieldSchemaType)) ||
58
+ docTransTypes?.includes(documentSchemaType.name))
59
+
60
+ // these checks are stable (ie, does not change after mount), so not breaking rules of hooks
61
+ if (documentSchemaType && (documentTranslationEnabled || fieldTransEnabled)) {
62
+ const {value: documentValue, onChange: documentOnChange, formState} = useDocumentPane()
63
+ const docRef = useRef(documentValue)
64
+ docRef.current = documentValue
65
+ const formStateRef = useRef(formState)
66
+ formStateRef.current = formState
67
+
68
+ const translationApi = useTranslate(apiClient)
69
+ const translate = useDraftDelayedTask({
70
+ documentOnChange,
71
+ isDocAssistable: documentIsAssistable ?? false,
72
+ task: translationApi.translate,
73
+ })
74
+
75
+ const languagePath = config.translate?.document?.languageField
76
+
77
+ // if this is true, it is stable, and not breaking rules of hooks
78
+ const translateDocumentAction = useMemo(() => {
79
+ if (!languagePath || !documentTranslationEnabled) {
80
+ return undefined
81
+ }
82
+ const title = path.length ? `Translate` : `Translate document`
83
+ return node({
84
+ type: 'action',
85
+ icon: translationApi.loading
86
+ ? () => (
87
+ <Box style={{height: 17}}>
88
+ <Spinner style={{transform: 'translateY(6px)'}} />
89
+ </Box>
90
+ )
91
+ : TranslateIcon,
92
+ title,
93
+ onAction: () => {
94
+ if (translationApi.loading || !languagePath || !documentId) {
95
+ return
96
+ }
97
+ translate({
98
+ languagePath,
99
+ translatePath: path,
100
+ documentId: documentId ?? '',
101
+ conditionalMembers: formStateRef.current
102
+ ? getConditionalMembers(formStateRef.current)
103
+ : [],
104
+ })
105
+ },
106
+ renderAsButton: true,
107
+ disabled: translationApi.loading || readOnly,
108
+ })
109
+ }, [
110
+ languagePath,
111
+ translate,
112
+ documentId,
113
+ translationApi.loading,
114
+ documentTranslationEnabled,
115
+ path,
116
+ readOnly,
117
+ ])
118
+ const fieldTranslate = useFieldTranslation()
119
+ const openFieldTranslation = useDraftDelayedTask({
120
+ documentOnChange,
121
+ isDocAssistable: documentIsAssistable ?? false,
122
+ task: fieldTranslate.openFieldTranslation,
123
+ })
124
+
125
+ const translateFieldsAction = useMemo(
126
+ () =>
127
+ fieldTransEnabled
128
+ ? node({
129
+ type: 'action',
130
+ icon: fieldTranslate.translationLoading
131
+ ? () => (
132
+ <Box style={{height: 17}}>
133
+ <Spinner style={{transform: 'translateY(6px)'}} />
134
+ </Box>
135
+ )
136
+ : TranslateIcon,
137
+ title: `Translate fields...`,
138
+ onAction: () => {
139
+ if (fieldTranslate.translationLoading || !documentId) {
140
+ return
141
+ }
142
+ if (formStateRef.current) {
143
+ getConditionalMembers(formStateRef.current)
144
+ }
145
+ openFieldTranslation({
146
+ document: docRef.current,
147
+ documentSchema: documentSchemaType,
148
+ translatePath: path,
149
+ conditionalMembers: formStateRef.current
150
+ ? getConditionalMembers(formStateRef.current)
151
+ : [],
152
+ })
153
+ },
154
+ renderAsButton: true,
155
+ disabled: fieldTranslate.translationLoading || readOnly,
156
+ })
157
+ : undefined,
158
+ [
159
+ openFieldTranslation,
160
+ documentSchemaType,
161
+ documentId,
162
+ fieldTranslate.translationLoading,
163
+ fieldTransEnabled,
164
+ path,
165
+ readOnly,
166
+ ]
167
+ )
168
+
169
+ // eslint-disable-next-line react-hooks/rules-of-hooks
170
+ return useMemo(() => {
171
+ if (!status?.initialized) {
172
+ return undefined as unknown as DocumentFieldActionItem
173
+ }
174
+ return node({
175
+ type: 'group',
176
+ icon: () => null,
177
+ title: 'Translation',
178
+ children: [translateDocumentAction, translateFieldsAction].filter(
179
+ (c): c is DocumentFieldActionItem => !!c
180
+ ),
181
+ expanded: true,
182
+ })
183
+ }, [translateDocumentAction, translateFieldsAction, status])
184
+ }
185
+ // works but not supported by types
186
+ return undefined as unknown as DocumentFieldActionItem
187
+ },
188
+ }
@@ -0,0 +1,160 @@
1
+ import {Path, SanityClient, SchemaType} from 'sanity'
2
+
3
+ export interface Language {
4
+ id: string
5
+ title?: string
6
+ }
7
+
8
+ export interface DocumentMember {
9
+ schemaType: SchemaType
10
+ path: Path
11
+ name: string
12
+ value: unknown
13
+ }
14
+
15
+ export interface TranslationOutput {
16
+ /** Language id */
17
+ id: string
18
+ outputPath: Path
19
+ }
20
+
21
+ export type TranslationOutputsFunction = (
22
+ documentMember: DocumentMember,
23
+ enclosingType: SchemaType,
24
+ translateFromLanguageId: string,
25
+ translateToLanguageIds: string[]
26
+ ) => TranslationOutput[] | undefined
27
+
28
+ export type LanguageCallback = (
29
+ client: SanityClient,
30
+ selectedLanguageParams: Record<string, unknown>
31
+ ) => Promise<Language[]>
32
+
33
+ export interface FieldTranslationConfig {
34
+ /**
35
+ * `documentTypes` should be an array of strings where each entry must match a name from your document schemas.
36
+ *
37
+ * If defined, matching document will get a "Translate fields" instruction added.
38
+ **/
39
+ documentTypes?: string[]
40
+
41
+ /**
42
+ *
43
+ * Used for display strings in the Studio, and to determine languages for field level translations
44
+ *
45
+ * If the studio is using the sanity-plugin-internationalized-array plugin, this
46
+ * should be set to the same configuration.
47
+ */
48
+ languages: Language[] | LanguageCallback
49
+
50
+ /**
51
+ * API version for client passed to LanguageCallback for languages
52
+ * https://www.sanity.io/docs/api-versioning
53
+ * @defaultValue '2022-11-27'
54
+ */
55
+ apiVersion?: string
56
+
57
+ /**
58
+ * Specify fields that should be available in the languages callback:
59
+ * ```tsx
60
+ * {
61
+ * select: {
62
+ * markets: 'markets'
63
+ * },
64
+ * languages: (client, {markets}) =>
65
+ * client.fetch('*[_type == "language" && market in $markets]{id,title}', {markets})
66
+ * }
67
+ * ```
68
+ *
69
+ * If the studio is using the sanity-plugin-internationalized-array plugin, this
70
+ * should be set to the same configuration.
71
+ */
72
+ selectLanguageParams?: Record<string, string>
73
+
74
+ /**
75
+ * `translationOutputs` is used when the "Translate fields" instruction is started by a Studio user.
76
+ *
77
+ * It determines the relationships between document paths: Given a document path and a language, into which
78
+ * sibling paths should translations be output.
79
+
80
+ *
81
+ * `translationOutputs` is invoked once per path in the document (limited to a depth of 6), with the following:
82
+ *
83
+ * * `documentMember` - the field or array item for a given path; contains the path and its schemaType,
84
+ * * `enclosingType` - the schema type of parent holding the member
85
+ * * `translateFromLanguageId` - the languageId for the language the user want to translate from
86
+ * * `translateToLanguageIds` - all languageIds the user can translate to
87
+ *
88
+ * The function should return a `TranslationOutput[]` array that contains all the paths where translations from
89
+ * documentMember language (translateFromLanguageId) should be output.
90
+ *
91
+ * The function should return `undefined` for all documentMembers that should not be directly translated,
92
+ * or are nested fields under a translated path.
93
+ *
94
+ * ## Default function
95
+ *
96
+ * The default function for `translationOutputs` is configured to be automatically compatible with sanity-plugin-internationalized-array
97
+ * and object types prefixed with "locale".
98
+ *
99
+ * See <link to source for defaultTranslationOutputs> implementation details.
100
+ *
101
+ * ## Example
102
+ * A document has the following document members:
103
+ * * `{path: 'localeObject.en', schemaType: ObjectSchemaType}`
104
+ * * `{path: 'localeObject.en.title', schemaType: StringSchemaType}`
105
+ * * `{path: 'localeObject.de', schemaType: ObjectSchemaType}`,
106
+ * * `{path: 'localeObject.de.title', schemaType: StringSchemaType}`
107
+ *
108
+ * `translationOutputs` for invoked with `translateFromLanguageId` `en`,
109
+ * should only return [{id: 'de', outputPath: 'localeObject.de'}] for the `'localeObject.en'` path,
110
+ * and undefined for all the other members.
111
+ *
112
+ * ### Example implementation
113
+ * ```ts
114
+ * function translationOutputs(member, enclosingType, translateFromLanguageId, translateToLanguageIds)
115
+ * if (enclosingType.jsonType === 'object' && enclosingType.name.startsWith('locale') && translateFromLanguageId === member.name) {
116
+ * return translateToLanguageIds.map((translateToId) => ({
117
+ * id: translateToId,
118
+ * outputPath: [...member.path.slice(0, -1), translateToId],
119
+ * }))
120
+ * }
121
+ * return undefined
122
+ * }
123
+ * ```
124
+ **/
125
+ translationOutputs?: TranslationOutputsFunction
126
+ }
127
+
128
+ export interface DocumentTranslationConfig {
129
+ /**
130
+ * Path to language field in documents. Can be a hidden field.
131
+ * For instance: 'config.language'
132
+ *
133
+ * For projects that use the `@sanity/document-internationalization` plugin,
134
+ * this should be the same as `languageField` config for that plugin.
135
+ *
136
+ * Default: 'language'
137
+ */
138
+ languageField: string
139
+
140
+ /**
141
+ * `documentTypes` should be an array of strings where each entry must match a name from your document schemas.
142
+ *
143
+ * If defined, this property will add a translate instruction to these document types.
144
+ * If undefined, the instruction will be added to all documents with aiAssistance enabled and a field matching `documentLanguageField` config.
145
+ *
146
+ * Documents with translation support will get a "Translate document>" instruction added.
147
+ **/
148
+ documentTypes?: string[]
149
+ }
150
+
151
+ export interface TranslationConfig {
152
+ /**
153
+ * Config for document types with fields in multiple languages in the same document.
154
+ */
155
+ field?: FieldTranslationConfig
156
+ /**
157
+ * Config for document types with a single language field that determines the language for the whole document.
158
+ */
159
+ document?: DocumentTranslationConfig
160
+ }