@sanity/assist 1.2.16 → 2.0.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.
- package/LICENSE +1 -1
- package/README.md +551 -30
- package/dist/index.cjs.mjs +1 -0
- package/dist/index.d.ts +333 -9
- package/dist/index.esm.js +2463 -390
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2457 -383
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
- package/src/_lib/form/DocumentForm.tsx +2 -1
- package/src/_lib/form/constants.ts +1 -0
- package/src/assistDocument/AssistDocumentInput.tsx +24 -4
- package/src/assistDocument/RequestRunInstructionProvider.tsx +37 -21
- package/src/assistDocument/components/AssistDocumentForm.tsx +65 -21
- package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
- package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
- package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
- package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
- package/src/assistFormComponents/AssistField.tsx +11 -5
- package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
- package/src/assistFormComponents/validation/listItem.tsx +2 -2
- package/src/assistInspector/AssistInspector.tsx +6 -0
- package/src/assistInspector/FieldAutocomplete.tsx +1 -0
- package/src/assistInspector/helpers.ts +9 -11
- package/src/assistLayout/AssistLayout.tsx +9 -9
- package/src/components/ImageContext.tsx +30 -13
- package/src/components/SafeValueInput.tsx +4 -1
- package/src/fieldActions/assistFieldActions.tsx +42 -13
- package/src/fieldActions/generateCaptionActions.tsx +17 -6
- package/src/fieldActions/generateImageActions.tsx +57 -0
- package/src/helpers/assistSupported.ts +10 -16
- package/src/helpers/conditionalMembers.test.ts +200 -0
- package/src/helpers/conditionalMembers.ts +127 -0
- package/src/helpers/misc.ts +8 -4
- package/src/helpers/typeUtils.ts +19 -5
- package/src/index.ts +3 -0
- package/src/plugin.tsx +18 -4
- package/src/schemas/assistDocumentSchema.tsx +40 -1
- package/src/schemas/serialize/serializeSchema.test.ts +239 -8
- package/src/schemas/serialize/serializeSchema.ts +77 -10
- package/src/schemas/typeDefExtensions.ts +89 -5
- package/src/translate/FieldTranslationProvider.tsx +360 -0
- package/src/translate/getLanguageParams.ts +26 -0
- package/src/translate/languageStore.ts +18 -0
- package/src/translate/paths.test.ts +133 -0
- package/src/translate/paths.ts +175 -0
- package/src/translate/translateActions.tsx +188 -0
- package/src/translate/types.ts +160 -0
- package/src/types.ts +67 -15
- package/src/useApiClient.ts +134 -2
- package/src/assistLayout/AlphaMigration.tsx +0 -310
- package/src/legacy-types.ts +0 -72
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
PropsWithChildren,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useId,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import {ObjectSchemaType, Path, pathToString, 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
|
+
FieldLanguageMap,
|
|
17
|
+
getDocumentMembersFlat,
|
|
18
|
+
getFieldLanguageMap,
|
|
19
|
+
} from './paths'
|
|
20
|
+
import {PlayIcon} from '@sanity/icons'
|
|
21
|
+
import {Language} from './types'
|
|
22
|
+
import {getLanguageParams} from './getLanguageParams'
|
|
23
|
+
import {getPreferredToFieldLanguages, setPreferredToFieldLanguages} from './languageStore'
|
|
24
|
+
import {ConditionalMemberState} from '../helpers/conditionalMembers'
|
|
25
|
+
|
|
26
|
+
interface FieldTranslationParams {
|
|
27
|
+
document: SanityDocumentLike
|
|
28
|
+
documentSchema: ObjectSchemaType
|
|
29
|
+
translatePath: Path
|
|
30
|
+
conditionalMembers: ConditionalMemberState[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FieldTranslationContextValue {
|
|
34
|
+
openFieldTranslation: (args: FieldTranslationParams) => void
|
|
35
|
+
translationLoading: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const FieldTranslationContext = createContext<FieldTranslationContextValue>({
|
|
39
|
+
openFieldTranslation: () => {},
|
|
40
|
+
translationLoading: false,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export function useFieldTranslation() {
|
|
44
|
+
return useContext(FieldTranslationContext)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasValuesToTranslate(
|
|
48
|
+
fieldLanguageMaps: FieldLanguageMap[] | undefined,
|
|
49
|
+
fromLanguage: Language | undefined,
|
|
50
|
+
basePath: Path
|
|
51
|
+
) {
|
|
52
|
+
return fieldLanguageMaps?.some(
|
|
53
|
+
(map) =>
|
|
54
|
+
map.inputLanguageId === fromLanguage?.id &&
|
|
55
|
+
map.inputPath &&
|
|
56
|
+
pathToString(map.inputPath).startsWith(pathToString(basePath))
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function FieldTranslationProvider(props: PropsWithChildren<{}>) {
|
|
61
|
+
const {config: assistConfig} = useAiAssistanceConfig()
|
|
62
|
+
const apiClient = useApiClient(assistConfig.__customApiClient)
|
|
63
|
+
const config = assistConfig.translate?.field
|
|
64
|
+
const {translate: runTranslate} = useTranslate(apiClient)
|
|
65
|
+
|
|
66
|
+
const [dialogOpen, setDialogOpen] = useState(false)
|
|
67
|
+
|
|
68
|
+
const [fieldTranslationParams, setFieldTranslationParams] = useState<
|
|
69
|
+
FieldTranslationParams | undefined
|
|
70
|
+
>()
|
|
71
|
+
const [languages, setLanguages] = useState<Language[] | undefined>()
|
|
72
|
+
const [fromLanguage, setFromLanguage] = useState<Language | undefined>(undefined)
|
|
73
|
+
const [toLanguages, setToLanguages] = useState<Language[] | undefined>(undefined)
|
|
74
|
+
const [fieldLanguageMaps, setFieldLanguageMaps] = useState<FieldLanguageMap[] | undefined>()
|
|
75
|
+
|
|
76
|
+
const close = useCallback(() => {
|
|
77
|
+
setDialogOpen(false)
|
|
78
|
+
setLanguages(undefined)
|
|
79
|
+
setFieldTranslationParams(undefined)
|
|
80
|
+
}, [])
|
|
81
|
+
const languageClient = useClient({apiVersion: config?.apiVersion ?? '2022-11-27'})
|
|
82
|
+
const documentId = fieldTranslationParams?.document?._id
|
|
83
|
+
const id = useId()
|
|
84
|
+
|
|
85
|
+
const selectFromLanguage = useCallback(
|
|
86
|
+
(
|
|
87
|
+
from: Language,
|
|
88
|
+
languages: Language[] | undefined,
|
|
89
|
+
params: FieldTranslationParams | undefined
|
|
90
|
+
) => {
|
|
91
|
+
const {document, documentSchema} = params ?? {}
|
|
92
|
+
setFromLanguage(from)
|
|
93
|
+
if (!document || !documentSchema || !params || !languages) {
|
|
94
|
+
setFieldLanguageMaps(undefined)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const preferred = getPreferredToFieldLanguages(from.id)
|
|
99
|
+
const allToLanguages = languages.filter((l) => l.id !== from?.id)
|
|
100
|
+
const filteredToLanguages = allToLanguages.filter(
|
|
101
|
+
(l) => !preferred.length || preferred.includes(l.id)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
setToLanguages(filteredToLanguages)
|
|
105
|
+
const fromId = from?.id
|
|
106
|
+
const allToIds = allToLanguages?.map((l) => l.id) ?? []
|
|
107
|
+
const docMembers = getDocumentMembersFlat(document, documentSchema)
|
|
108
|
+
if (fromId && allToIds?.length) {
|
|
109
|
+
const transMap = getFieldLanguageMap(
|
|
110
|
+
documentSchema,
|
|
111
|
+
docMembers,
|
|
112
|
+
fromId,
|
|
113
|
+
allToIds.filter((toId) => fromId !== toId),
|
|
114
|
+
config?.translationOutputs ?? defaultLanguageOutputs
|
|
115
|
+
)
|
|
116
|
+
setFieldLanguageMaps(transMap)
|
|
117
|
+
} else {
|
|
118
|
+
setFieldLanguageMaps(undefined)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
[config]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
const toggleToLanguage = useCallback(
|
|
125
|
+
(
|
|
126
|
+
toggledLang: Language,
|
|
127
|
+
toLanguages: Language[] | undefined,
|
|
128
|
+
languages: Language[] | undefined
|
|
129
|
+
) => {
|
|
130
|
+
if (!languages || !fromLanguage) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const wasSelected = !!toLanguages?.find((l) => l.id === toggledLang.id)
|
|
134
|
+
const newToLangs = languages.filter(
|
|
135
|
+
(anyLang) =>
|
|
136
|
+
!!toLanguages?.find(
|
|
137
|
+
(selectedLang) => toggledLang.id !== selectedLang.id && selectedLang.id === anyLang.id
|
|
138
|
+
) ||
|
|
139
|
+
(toggledLang.id === anyLang.id && !wasSelected)
|
|
140
|
+
)
|
|
141
|
+
setToLanguages(newToLangs)
|
|
142
|
+
setPreferredToFieldLanguages(
|
|
143
|
+
fromLanguage.id,
|
|
144
|
+
newToLangs.map((l) => l.id)
|
|
145
|
+
)
|
|
146
|
+
},
|
|
147
|
+
[fromLanguage]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const openFieldTranslation = useCallback(
|
|
151
|
+
async (params: FieldTranslationParams) => {
|
|
152
|
+
setDialogOpen(true)
|
|
153
|
+
const languageParams = getLanguageParams(config?.selectLanguageParams, params.document)
|
|
154
|
+
const languages: Language[] | undefined = await (typeof config?.languages === 'function'
|
|
155
|
+
? config?.languages(languageClient, languageParams)
|
|
156
|
+
: Promise.resolve(config?.languages))
|
|
157
|
+
setLanguages(languages)
|
|
158
|
+
setFieldTranslationParams(params)
|
|
159
|
+
const fromLanguage = languages?.[0]
|
|
160
|
+
if (fromLanguage) {
|
|
161
|
+
selectFromLanguage(fromLanguage, languages, params)
|
|
162
|
+
} else {
|
|
163
|
+
console.error('No languages available for selected language params', languageParams)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
[selectFromLanguage, config, languageClient]
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
const contextValue: FieldTranslationContextValue = useMemo(() => {
|
|
170
|
+
return {
|
|
171
|
+
openFieldTranslation,
|
|
172
|
+
translationLoading: false,
|
|
173
|
+
}
|
|
174
|
+
}, [openFieldTranslation])
|
|
175
|
+
|
|
176
|
+
const runDisabled =
|
|
177
|
+
!fromLanguage ||
|
|
178
|
+
!toLanguages?.length ||
|
|
179
|
+
!fieldLanguageMaps?.length ||
|
|
180
|
+
!documentId ||
|
|
181
|
+
!hasValuesToTranslate(fieldLanguageMaps, fromLanguage, fieldTranslationParams.translatePath)
|
|
182
|
+
|
|
183
|
+
const onRunTranslation = useCallback(() => {
|
|
184
|
+
const translatePath = fieldTranslationParams?.translatePath
|
|
185
|
+
if (fieldLanguageMaps && documentId && translatePath) {
|
|
186
|
+
runTranslate({
|
|
187
|
+
documentId,
|
|
188
|
+
translatePath: translatePath,
|
|
189
|
+
fieldLanguageMap: fieldLanguageMaps.map((map) => ({
|
|
190
|
+
...map,
|
|
191
|
+
// eslint-disable-next-line max-nested-callbacks
|
|
192
|
+
outputs: map.outputs.filter((out) => !!toLanguages?.find((l) => l.id === out.id)),
|
|
193
|
+
})),
|
|
194
|
+
conditionalMembers: fieldTranslationParams?.conditionalMembers,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
close()
|
|
198
|
+
}, [
|
|
199
|
+
fieldLanguageMaps,
|
|
200
|
+
documentId,
|
|
201
|
+
runTranslate,
|
|
202
|
+
close,
|
|
203
|
+
toLanguages,
|
|
204
|
+
fieldTranslationParams?.translatePath,
|
|
205
|
+
fieldTranslationParams?.conditionalMembers,
|
|
206
|
+
])
|
|
207
|
+
|
|
208
|
+
const runButton = (
|
|
209
|
+
<Button
|
|
210
|
+
text={`Translate`}
|
|
211
|
+
tone="primary"
|
|
212
|
+
icon={PlayIcon}
|
|
213
|
+
style={{width: '100%'}}
|
|
214
|
+
disabled={runDisabled}
|
|
215
|
+
onClick={onRunTranslation}
|
|
216
|
+
/>
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<FieldTranslationContext.Provider value={contextValue}>
|
|
221
|
+
{dialogOpen ? (
|
|
222
|
+
<Dialog
|
|
223
|
+
id={id}
|
|
224
|
+
width={1}
|
|
225
|
+
open={dialogOpen}
|
|
226
|
+
onClose={close}
|
|
227
|
+
header="Translate fields"
|
|
228
|
+
footer={
|
|
229
|
+
<Flex justify="space-between" padding={2} flex={1}>
|
|
230
|
+
{runDisabled ? (
|
|
231
|
+
<Tooltip
|
|
232
|
+
content={
|
|
233
|
+
<Flex padding={2}>
|
|
234
|
+
<Text>There is nothing to translate in the selected from-language.</Text>
|
|
235
|
+
</Flex>
|
|
236
|
+
}
|
|
237
|
+
placement="top"
|
|
238
|
+
>
|
|
239
|
+
<Flex flex={1}>{runButton}</Flex>
|
|
240
|
+
</Tooltip>
|
|
241
|
+
) : (
|
|
242
|
+
runButton
|
|
243
|
+
)}
|
|
244
|
+
</Flex>
|
|
245
|
+
}
|
|
246
|
+
>
|
|
247
|
+
{languages ? (
|
|
248
|
+
<Flex padding={4} gap={5} align="flex-start" justify="center">
|
|
249
|
+
<Stack space={2}>
|
|
250
|
+
<Box marginBottom={2}>
|
|
251
|
+
<Text weight="semibold">From</Text>
|
|
252
|
+
</Box>
|
|
253
|
+
{languages?.map((radioLanguage) => (
|
|
254
|
+
<FromLanguageRadio
|
|
255
|
+
key={radioLanguage.id}
|
|
256
|
+
{...{
|
|
257
|
+
radioLanguage,
|
|
258
|
+
fromLanguage,
|
|
259
|
+
selectFromLanguage,
|
|
260
|
+
languages,
|
|
261
|
+
fieldTranslationParams,
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
))}
|
|
265
|
+
</Stack>
|
|
266
|
+
|
|
267
|
+
<Stack space={2}>
|
|
268
|
+
<Box marginBottom={2}>
|
|
269
|
+
<Text weight="semibold">To</Text>
|
|
270
|
+
</Box>
|
|
271
|
+
{languages.map((checkboxLanguage) => (
|
|
272
|
+
<ToLanguageCheckbox
|
|
273
|
+
key={checkboxLanguage.id}
|
|
274
|
+
{...{checkboxLanguage, fromLanguage, toLanguages, toggleToLanguage, languages}}
|
|
275
|
+
/>
|
|
276
|
+
))}
|
|
277
|
+
</Stack>
|
|
278
|
+
</Flex>
|
|
279
|
+
) : (
|
|
280
|
+
<Flex padding={4} gap={2} align="flex-start" justify="center">
|
|
281
|
+
<Box>
|
|
282
|
+
<Spinner />
|
|
283
|
+
</Box>
|
|
284
|
+
<Text>Loading languages...</Text>
|
|
285
|
+
</Flex>
|
|
286
|
+
)}
|
|
287
|
+
</Dialog>
|
|
288
|
+
) : null}
|
|
289
|
+
{props.children}
|
|
290
|
+
</FieldTranslationContext.Provider>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function ToLanguageCheckbox(props: {
|
|
295
|
+
checkboxLanguage: Language
|
|
296
|
+
fromLanguage: Language | undefined
|
|
297
|
+
toLanguages: Language[] | undefined
|
|
298
|
+
toggleToLanguage: (
|
|
299
|
+
toggledLang: Language,
|
|
300
|
+
toLanguages: Language[] | undefined,
|
|
301
|
+
languages: Language[] | undefined
|
|
302
|
+
) => void
|
|
303
|
+
languages: Language[]
|
|
304
|
+
}) {
|
|
305
|
+
const {checkboxLanguage, fromLanguage, toLanguages, toggleToLanguage, languages} = props
|
|
306
|
+
const langId = checkboxLanguage.id
|
|
307
|
+
const onChange = useCallback(
|
|
308
|
+
() => toggleToLanguage(checkboxLanguage, toLanguages, languages),
|
|
309
|
+
[toggleToLanguage, checkboxLanguage, toLanguages, languages]
|
|
310
|
+
)
|
|
311
|
+
return (
|
|
312
|
+
<Flex
|
|
313
|
+
key={langId}
|
|
314
|
+
gap={3}
|
|
315
|
+
align="center"
|
|
316
|
+
as={'label'}
|
|
317
|
+
style={langId === fromLanguage?.id ? {opacity: 0.5} : undefined}
|
|
318
|
+
>
|
|
319
|
+
<Checkbox
|
|
320
|
+
name="toLang"
|
|
321
|
+
value={langId}
|
|
322
|
+
checked={langId !== fromLanguage?.id && !!toLanguages?.find((tl) => tl.id === langId)}
|
|
323
|
+
onChange={onChange}
|
|
324
|
+
disabled={langId === fromLanguage?.id}
|
|
325
|
+
/>
|
|
326
|
+
<Text muted={langId === fromLanguage?.id}>{checkboxLanguage.title ?? langId}</Text>
|
|
327
|
+
</Flex>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function FromLanguageRadio(props: {
|
|
332
|
+
radioLanguage: Language
|
|
333
|
+
fromLanguage: Language | undefined
|
|
334
|
+
selectFromLanguage: (
|
|
335
|
+
from: Language,
|
|
336
|
+
languages: Language[] | undefined,
|
|
337
|
+
params: FieldTranslationParams | undefined
|
|
338
|
+
) => void
|
|
339
|
+
languages: Language[] | undefined
|
|
340
|
+
fieldTranslationParams: FieldTranslationParams | undefined
|
|
341
|
+
}) {
|
|
342
|
+
const {languages, radioLanguage, selectFromLanguage, fromLanguage, fieldTranslationParams} = props
|
|
343
|
+
const langId = radioLanguage.id
|
|
344
|
+
|
|
345
|
+
const onChange = useCallback(
|
|
346
|
+
() => selectFromLanguage(radioLanguage, languages, fieldTranslationParams),
|
|
347
|
+
[selectFromLanguage, radioLanguage, languages, fieldTranslationParams]
|
|
348
|
+
)
|
|
349
|
+
return (
|
|
350
|
+
<Flex key={langId} gap={3} align="center" as={'label'}>
|
|
351
|
+
<Radio
|
|
352
|
+
name="fromLang"
|
|
353
|
+
value={langId}
|
|
354
|
+
checked={langId === fromLanguage?.id}
|
|
355
|
+
onChange={onChange}
|
|
356
|
+
/>
|
|
357
|
+
<Text>{radioLanguage.title ?? radioLanguage.id}</Text>
|
|
358
|
+
</Flex>
|
|
359
|
+
)
|
|
360
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const toFieldLanguagesKeyPrefix = 'sanityStudio:assist:field-languages:from:'
|
|
2
|
+
|
|
3
|
+
export function getPreferredToFieldLanguages(fromLanguageId: string): string[] {
|
|
4
|
+
if (typeof localStorage === 'undefined') {
|
|
5
|
+
return []
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const value = localStorage.getItem(`${toFieldLanguagesKeyPrefix}${fromLanguageId}`)
|
|
9
|
+
return value ? (JSON.parse(value) as string[]) : []
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function setPreferredToFieldLanguages(fromLanguageId: string, languageIds: string[]) {
|
|
13
|
+
if (typeof localStorage === 'undefined') {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
localStorage.setItem(`${toFieldLanguagesKeyPrefix}${fromLanguageId}`, JSON.stringify(languageIds))
|
|
18
|
+
}
|
|
@@ -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
|
+
})
|