@sanity/document-internationalization 2.0.0-studio-v3-plugin-v2.7 → 2.0.0-studio-v3-plugin-v2.8
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/README.md +6 -3
- package/lib/index.esm.js +407 -156
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +405 -154
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/BulkPublish/DocumentCheck.tsx +39 -16
- package/src/components/BulkPublish/index.tsx +86 -55
- package/src/components/LanguageManage.tsx +1 -2
- package/src/components/LanguageOption.tsx +78 -28
- package/src/components/LanguagePatch.tsx +16 -8
- package/src/components/MenuButton.tsx +76 -43
- package/src/components/OptimisticallyStrengthen/ReferencePatcher.tsx +49 -0
- package/src/components/OptimisticallyStrengthen/index.tsx +34 -0
- package/src/plugin.tsx +195 -147
- package/src/schema/translation/metadata.ts +1 -1
- package/src/types.ts +9 -7
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import {AddIcon, CheckmarkIcon, SplitVerticalIcon} from '@sanity/icons'
|
|
2
|
+
import {
|
|
3
|
+
Badge,
|
|
4
|
+
Box,
|
|
5
|
+
Button,
|
|
6
|
+
Flex,
|
|
7
|
+
Spinner,
|
|
8
|
+
Text,
|
|
9
|
+
Tooltip,
|
|
10
|
+
useToast,
|
|
11
|
+
} from '@sanity/ui'
|
|
4
12
|
import {uuid} from '@sanity/uuid'
|
|
5
|
-
import {
|
|
13
|
+
import {useCallback} from 'react'
|
|
14
|
+
import {useClient} from 'sanity'
|
|
6
15
|
|
|
7
|
-
import {Language, Metadata, TranslationReference} from '../types'
|
|
8
16
|
import {API_VERSION, METADATA_SCHEMA_NAME} from '../constants'
|
|
9
17
|
import {useOpenInNewPane} from '../hooks/useOpenInNewPane'
|
|
18
|
+
import {Language, Metadata, TranslationReference} from '../types'
|
|
10
19
|
|
|
11
20
|
type LanguageOptionProps = {
|
|
12
21
|
language: Language
|
|
@@ -23,9 +32,14 @@ type LanguageOptionProps = {
|
|
|
23
32
|
apiVersion?: string
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
function createReference(
|
|
35
|
+
function createReference(
|
|
36
|
+
key: string,
|
|
37
|
+
ref: string,
|
|
38
|
+
type: string
|
|
39
|
+
): TranslationReference {
|
|
27
40
|
return {
|
|
28
41
|
_key: key,
|
|
42
|
+
_type: 'internationalizedArrayReferenceValue',
|
|
29
43
|
value: {
|
|
30
44
|
_type: 'reference',
|
|
31
45
|
_ref: ref,
|
|
@@ -68,9 +82,12 @@ export default function LanguageOption(props: LanguageOptionProps) {
|
|
|
68
82
|
const documentIds = documentId.startsWith(`drafts.`)
|
|
69
83
|
? [documentId, documentId.replace(`drafts.`, ``)]
|
|
70
84
|
: [documentId, `drafts.${documentId}`]
|
|
71
|
-
const latestDocument = await client.fetch(
|
|
72
|
-
ids
|
|
73
|
-
|
|
85
|
+
const latestDocument = await client.fetch(
|
|
86
|
+
`*[_id in $ids]|order(_updatedAt desc)[0]`,
|
|
87
|
+
{
|
|
88
|
+
ids: documentIds,
|
|
89
|
+
}
|
|
90
|
+
)
|
|
74
91
|
const newTranslationDocument = {
|
|
75
92
|
...latestDocument,
|
|
76
93
|
_id: `drafts.${uuid()}`,
|
|
@@ -105,7 +122,9 @@ export default function LanguageOption(props: LanguageOptionProps) {
|
|
|
105
122
|
_id: metadataId,
|
|
106
123
|
_type: METADATA_SCHEMA_NAME,
|
|
107
124
|
schemaTypes: [schemaType],
|
|
108
|
-
translations: [newTranslationReference, sourceReference].filter(
|
|
125
|
+
translations: [newTranslationReference, sourceReference].filter(
|
|
126
|
+
Boolean
|
|
127
|
+
),
|
|
109
128
|
})
|
|
110
129
|
}
|
|
111
130
|
|
|
@@ -143,26 +162,57 @@ export default function LanguageOption(props: LanguageOptionProps) {
|
|
|
143
162
|
toast,
|
|
144
163
|
])
|
|
145
164
|
|
|
165
|
+
let message
|
|
166
|
+
|
|
167
|
+
if (current) {
|
|
168
|
+
message = `Current document`
|
|
169
|
+
} else if (translation) {
|
|
170
|
+
message = `Open ${language.title} translation`
|
|
171
|
+
} else if (!translation) {
|
|
172
|
+
message = `Create new ${language.title} translation`
|
|
173
|
+
}
|
|
174
|
+
|
|
146
175
|
return (
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<Flex gap={3} align="center">
|
|
153
|
-
{disabled ? (
|
|
154
|
-
<Spinner />
|
|
155
|
-
) : (
|
|
156
|
-
<Text size={2}>
|
|
157
|
-
{/* eslint-disable-next-line no-nested-ternary */}
|
|
158
|
-
{translation ? <SplitVerticalIcon /> : current ? <CheckmarkIcon /> : <AddIcon />}
|
|
176
|
+
<Tooltip
|
|
177
|
+
content={
|
|
178
|
+
<Box padding={2}>
|
|
179
|
+
<Text muted size={1}>
|
|
180
|
+
{message}
|
|
159
181
|
</Text>
|
|
160
|
-
)}
|
|
161
|
-
<Box flex={1}>
|
|
162
|
-
<Text>{language.title}</Text>
|
|
163
182
|
</Box>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
}
|
|
184
|
+
fallbackPlacements={['right', 'left']}
|
|
185
|
+
placement="top"
|
|
186
|
+
portal
|
|
187
|
+
>
|
|
188
|
+
<Button
|
|
189
|
+
onClick={translation ? handleOpen : handleCreate}
|
|
190
|
+
mode={current && disabled ? `default` : `bleed`}
|
|
191
|
+
disabled={disabled || current}
|
|
192
|
+
>
|
|
193
|
+
<Flex gap={3} align="center">
|
|
194
|
+
{disabled ? (
|
|
195
|
+
<Spinner />
|
|
196
|
+
) : (
|
|
197
|
+
<Text size={2}>
|
|
198
|
+
{/* eslint-disable-next-line no-nested-ternary */}
|
|
199
|
+
{translation ? (
|
|
200
|
+
<SplitVerticalIcon />
|
|
201
|
+
) : current ? (
|
|
202
|
+
<CheckmarkIcon />
|
|
203
|
+
) : (
|
|
204
|
+
<AddIcon />
|
|
205
|
+
)}
|
|
206
|
+
</Text>
|
|
207
|
+
)}
|
|
208
|
+
<Box flex={1}>
|
|
209
|
+
<Text>{language.title}</Text>
|
|
210
|
+
</Box>
|
|
211
|
+
<Badge tone={disabled || current ? `default` : `primary`}>
|
|
212
|
+
{language.id}
|
|
213
|
+
</Badge>
|
|
214
|
+
</Flex>
|
|
215
|
+
</Button>
|
|
216
|
+
</Tooltip>
|
|
167
217
|
)
|
|
168
218
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import {EditIcon} from '@sanity/icons'
|
|
2
|
+
import {Badge, Box, Button, Flex, Text, useToast} from '@sanity/ui'
|
|
1
3
|
import React from 'react'
|
|
2
|
-
import {ChevronRightIcon} from '@sanity/icons'
|
|
3
|
-
import {Button, useToast} from '@sanity/ui'
|
|
4
4
|
import {SanityDocument, useClient} from 'sanity'
|
|
5
5
|
|
|
6
|
-
import {Language} from '../types'
|
|
7
6
|
import {API_VERSION} from '../constants'
|
|
7
|
+
import {Language} from '../types'
|
|
8
8
|
|
|
9
9
|
type LanguagePatchProps = {
|
|
10
10
|
language: Language
|
|
@@ -63,12 +63,20 @@ export default function LanguagePatch(props: LanguagePatchProps) {
|
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
65
|
<Button
|
|
66
|
-
mode="
|
|
67
|
-
|
|
68
|
-
icon={ChevronRightIcon}
|
|
69
|
-
onClick={() => handleClick()}
|
|
66
|
+
mode="bleed"
|
|
67
|
+
onClick={handleClick}
|
|
70
68
|
disabled={disabled}
|
|
71
69
|
justify="flex-start"
|
|
72
|
-
|
|
70
|
+
>
|
|
71
|
+
<Flex gap={3} align="center">
|
|
72
|
+
<Text size={2}>
|
|
73
|
+
<EditIcon />
|
|
74
|
+
</Text>
|
|
75
|
+
<Box flex={1}>
|
|
76
|
+
<Text>{language.title}</Text>
|
|
77
|
+
</Box>
|
|
78
|
+
<Badge>{language.id}</Badge>
|
|
79
|
+
</Flex>
|
|
80
|
+
</Button>
|
|
73
81
|
)
|
|
74
82
|
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import {suspend} from 'suspend-react'
|
|
2
|
-
import React, {useCallback, useState} from 'react'
|
|
3
|
-
import {Text, Card, useClickOutside, Stack, Popover, Button, Box} from '@sanity/ui'
|
|
4
1
|
import {TranslateIcon} from '@sanity/icons'
|
|
5
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
Popover,
|
|
7
|
+
Stack,
|
|
8
|
+
Text,
|
|
9
|
+
useClickOutside,
|
|
10
|
+
} from '@sanity/ui'
|
|
11
|
+
import React, {useCallback, useState} from 'react'
|
|
12
|
+
import {useClient, useEditState} from 'sanity'
|
|
13
|
+
import {suspend} from 'suspend-react'
|
|
6
14
|
|
|
7
|
-
import {
|
|
8
|
-
import LanguageOption from './LanguageOption'
|
|
15
|
+
import {API_VERSION} from '../constants'
|
|
9
16
|
import {useTranslationMetadata} from '../hooks/useLanguageMetadata'
|
|
17
|
+
import {SupportedLanguages} from '../types'
|
|
10
18
|
import LanguageManage from './LanguageManage'
|
|
19
|
+
import LanguageOption from './LanguageOption'
|
|
11
20
|
import LanguagePatch from './LanguagePatch'
|
|
12
|
-
import {API_VERSION} from '../constants'
|
|
13
21
|
|
|
14
22
|
type MenuButtonProps = {
|
|
15
23
|
supportedLanguages: SupportedLanguages
|
|
@@ -20,7 +28,12 @@ type MenuButtonProps = {
|
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
export default function MenuButton(props: MenuButtonProps) {
|
|
23
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
apiVersion = API_VERSION,
|
|
33
|
+
schemaType,
|
|
34
|
+
documentId,
|
|
35
|
+
languageField,
|
|
36
|
+
} = props
|
|
24
37
|
|
|
25
38
|
const client = useClient({apiVersion})
|
|
26
39
|
const supportedLanguages = Array.isArray(props.supportedLanguages)
|
|
@@ -39,12 +52,18 @@ export default function MenuButton(props: MenuButtonProps) {
|
|
|
39
52
|
const [popover, setPopover] = useState<HTMLElement | null>(null)
|
|
40
53
|
const handleClickOutside = useCallback(() => setOpen(false), [])
|
|
41
54
|
useClickOutside(handleClickOutside, [button, popover])
|
|
42
|
-
const {
|
|
55
|
+
const {
|
|
56
|
+
data: metadata,
|
|
57
|
+
loading,
|
|
58
|
+
error,
|
|
59
|
+
} = useTranslationMetadata(documentId, schemaType)
|
|
43
60
|
const {draft, published} = useEditState(documentId, schemaType)
|
|
44
61
|
const source = draft || published
|
|
45
62
|
|
|
46
63
|
const sourceLanguageId = source?.[languageField] as string | undefined
|
|
47
|
-
const sourceLanguageIsValid = supportedLanguages.some(
|
|
64
|
+
const sourceLanguageIsValid = supportedLanguages.some(
|
|
65
|
+
(l) => l.id === sourceLanguageId
|
|
66
|
+
)
|
|
48
67
|
const allLanguagesAreValid = React.useMemo(() => {
|
|
49
68
|
const valid = supportedLanguages.every((l) => l.id && l.title)
|
|
50
69
|
if (!valid) {
|
|
@@ -67,6 +86,38 @@ export default function MenuButton(props: MenuButtonProps) {
|
|
|
67
86
|
<Stack padding={1} space={1}>
|
|
68
87
|
{supportedLanguages.length > 0 ? (
|
|
69
88
|
<>
|
|
89
|
+
{/* Once metadata is loaded, there may be issues */}
|
|
90
|
+
{loading ? null : (
|
|
91
|
+
<>
|
|
92
|
+
{/* Not all languages are valid */}
|
|
93
|
+
{allLanguagesAreValid ? null : (
|
|
94
|
+
<Card tone="caution" padding={3}>
|
|
95
|
+
<Text size={1}>
|
|
96
|
+
Not all language objects are valid. See the console.
|
|
97
|
+
</Text>
|
|
98
|
+
</Card>
|
|
99
|
+
)}
|
|
100
|
+
{/* Current document has no language field */}
|
|
101
|
+
{sourceLanguageId ? null : (
|
|
102
|
+
<Card tone="caution" padding={3}>
|
|
103
|
+
<Text size={1}>
|
|
104
|
+
Choose a language to <br />
|
|
105
|
+
apply to <strong>this Document</strong>
|
|
106
|
+
</Text>
|
|
107
|
+
</Card>
|
|
108
|
+
)}
|
|
109
|
+
{/* Current document has an invalid language field */}
|
|
110
|
+
{sourceLanguageId && !sourceLanguageIsValid ? (
|
|
111
|
+
<Card tone="caution" padding={3}>
|
|
112
|
+
<Text size={1}>
|
|
113
|
+
Select a supported language.
|
|
114
|
+
<br />
|
|
115
|
+
Current language value: <code>{sourceLanguageId}</code>
|
|
116
|
+
</Text>
|
|
117
|
+
</Card>
|
|
118
|
+
) : null}
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
70
121
|
{supportedLanguages.map((language, langIndex) =>
|
|
71
122
|
!loading && sourceLanguageId && sourceLanguageIsValid ? (
|
|
72
123
|
// Button to duplicate this document to a new translation
|
|
@@ -83,7 +134,9 @@ export default function MenuButton(props: MenuButtonProps) {
|
|
|
83
134
|
metadata={metadata}
|
|
84
135
|
sourceId={documentId}
|
|
85
136
|
sourceLanguageId={sourceLanguageId}
|
|
86
|
-
translation={metadata?.translations.find(
|
|
137
|
+
translation={metadata?.translations.find(
|
|
138
|
+
(t) => t._key === language.id
|
|
139
|
+
)}
|
|
87
140
|
/>
|
|
88
141
|
) : (
|
|
89
142
|
// Button to set a language field on *this* document
|
|
@@ -107,36 +160,6 @@ export default function MenuButton(props: MenuButtonProps) {
|
|
|
107
160
|
/>
|
|
108
161
|
)
|
|
109
162
|
)}
|
|
110
|
-
{/* Once metadata is loaded, there may be issues */}
|
|
111
|
-
{loading ? null : (
|
|
112
|
-
<>
|
|
113
|
-
{/* Not all languages are valid */}
|
|
114
|
-
{allLanguagesAreValid ? null : (
|
|
115
|
-
<Card tone="caution" padding={3}>
|
|
116
|
-
<Text size={1}>Not all language objects are valid. See the console.</Text>
|
|
117
|
-
</Card>
|
|
118
|
-
)}
|
|
119
|
-
{/* Current document has no language field */}
|
|
120
|
-
{sourceLanguageId ? null : (
|
|
121
|
-
<Card tone="caution" padding={3}>
|
|
122
|
-
<Text size={1}>
|
|
123
|
-
Choose a language to <br />
|
|
124
|
-
apply to <strong>this Document</strong>
|
|
125
|
-
</Text>
|
|
126
|
-
</Card>
|
|
127
|
-
)}
|
|
128
|
-
{/* Current document has an invalid language field */}
|
|
129
|
-
{sourceLanguageId && !sourceLanguageIsValid ? (
|
|
130
|
-
<Card tone="caution" padding={3}>
|
|
131
|
-
<Text size={1}>
|
|
132
|
-
Change the current language value <code>{sourceLanguageId}</code>
|
|
133
|
-
<br />
|
|
134
|
-
to one of the supported languages
|
|
135
|
-
</Text>
|
|
136
|
-
</Card>
|
|
137
|
-
) : null}
|
|
138
|
-
</>
|
|
139
|
-
)}
|
|
140
163
|
</>
|
|
141
164
|
) : null}
|
|
142
165
|
<LanguageManage id={metadata?._id} />
|
|
@@ -145,14 +168,24 @@ export default function MenuButton(props: MenuButtonProps) {
|
|
|
145
168
|
</Box>
|
|
146
169
|
)
|
|
147
170
|
|
|
171
|
+
const issueWithTranslations =
|
|
172
|
+
!loading && sourceLanguageId && !sourceLanguageIsValid
|
|
173
|
+
|
|
148
174
|
return (
|
|
149
|
-
<Popover
|
|
175
|
+
<Popover
|
|
176
|
+
constrainSize
|
|
177
|
+
content={content}
|
|
178
|
+
open={open}
|
|
179
|
+
portal
|
|
180
|
+
ref={setPopover}
|
|
181
|
+
overflow="auto"
|
|
182
|
+
>
|
|
150
183
|
<Button
|
|
151
184
|
text="Translations"
|
|
152
185
|
mode="bleed"
|
|
153
186
|
disabled={!source}
|
|
154
187
|
tone={
|
|
155
|
-
!source ||
|
|
188
|
+
!source || loading || !issueWithTranslations ? undefined : `caution`
|
|
156
189
|
}
|
|
157
190
|
icon={TranslateIcon}
|
|
158
191
|
onClick={handleClick}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {PatchEvent, unset, useClient, useEditState} from 'sanity'
|
|
3
|
+
import {useDocumentPane} from 'sanity/desk'
|
|
4
|
+
|
|
5
|
+
import {API_VERSION} from '../../constants'
|
|
6
|
+
import {TranslationReference} from '../../types'
|
|
7
|
+
|
|
8
|
+
type ReferencePatcherProps = {
|
|
9
|
+
translation: TranslationReference
|
|
10
|
+
documentType: string
|
|
11
|
+
metadataId: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// For every reference, check if it is published, and if so, strengthen the reference
|
|
15
|
+
export default function ReferencePatcher(props: ReferencePatcherProps) {
|
|
16
|
+
const {translation, documentType, metadataId} = props
|
|
17
|
+
const editState = useEditState(translation.value._ref, documentType)
|
|
18
|
+
const client = useClient({apiVersion: API_VERSION})
|
|
19
|
+
const {onChange} = useDocumentPane()
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (
|
|
23
|
+
// We have a reference
|
|
24
|
+
translation.value._ref &&
|
|
25
|
+
// It's still weak and not-yet-strengthened
|
|
26
|
+
translation.value._weak &&
|
|
27
|
+
translation.value._strengthenOnPublish &&
|
|
28
|
+
// The referenced document has just been published
|
|
29
|
+
!editState.draft &&
|
|
30
|
+
editState.published &&
|
|
31
|
+
editState.ready
|
|
32
|
+
) {
|
|
33
|
+
const referencePathBase = [
|
|
34
|
+
'translations',
|
|
35
|
+
{_key: translation._key},
|
|
36
|
+
'value',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
onChange(
|
|
40
|
+
new PatchEvent([
|
|
41
|
+
unset([...referencePathBase, '_weak']),
|
|
42
|
+
unset([...referencePathBase, '_strengthenOnPublish']),
|
|
43
|
+
])
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}, [translation, editState, metadataId, client, onChange])
|
|
47
|
+
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {TranslationReference} from '../../types'
|
|
2
|
+
import ReferencePatcher from './ReferencePatcher'
|
|
3
|
+
|
|
4
|
+
type OptimisticallyStrengthenProps = {
|
|
5
|
+
translations: TranslationReference[]
|
|
6
|
+
metadataId: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// There's no good reason to leave published references as weak
|
|
10
|
+
// So this component will run on every render and strengthen them
|
|
11
|
+
export default function OptimisticallyStrengthen(
|
|
12
|
+
props: OptimisticallyStrengthenProps
|
|
13
|
+
) {
|
|
14
|
+
const {translations = [], metadataId} = props
|
|
15
|
+
|
|
16
|
+
if (!translations.length) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
{translations.map((translation) =>
|
|
23
|
+
translation.value._strengthenOnPublish?.type ? (
|
|
24
|
+
<ReferencePatcher
|
|
25
|
+
key={translation._key}
|
|
26
|
+
translation={translation}
|
|
27
|
+
documentType={translation.value._strengthenOnPublish.type}
|
|
28
|
+
metadataId={metadataId}
|
|
29
|
+
/>
|
|
30
|
+
) : null
|
|
31
|
+
)}
|
|
32
|
+
</>
|
|
33
|
+
)
|
|
34
|
+
}
|