@sanity/document-internationalization 1.0.0 → 1.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.
Files changed (114) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +89 -47
  3. package/lib/index.esm.js +2 -0
  4. package/lib/index.esm.js.map +1 -0
  5. package/lib/index.js +2 -0
  6. package/lib/index.js.map +1 -0
  7. package/lib/src/index.d.ts +200 -0
  8. package/package.json +100 -43
  9. package/src/actions/DeleteWithi18nAction.tsx +118 -0
  10. package/src/actions/DuplicateWithi18nAction.tsx +77 -0
  11. package/src/actions/PublishWithi18nAction.ts +118 -0
  12. package/src/actions/index.ts +42 -0
  13. package/src/badges/LanguageBadge.tsx +22 -0
  14. package/src/constants/I18nDelimiter.ts +1 -0
  15. package/src/constants/I18nPrefix.ts +1 -0
  16. package/src/constants/IdStructure.ts +4 -0
  17. package/src/constants/LanguageCultures.ts +902 -0
  18. package/src/constants/ReferenceBehavior.ts +5 -0
  19. package/src/constants/SupportedEmojiFlagCodes.ts +251 -0
  20. package/src/constants/UiMessages.ts +56 -0
  21. package/src/constants/index.ts +7 -0
  22. package/src/documentI18n.tsx +33 -0
  23. package/src/hooks/index.ts +2 -0
  24. package/src/hooks/useConfig.ts +22 -0
  25. package/src/hooks/useDelayedFlag.ts +31 -0
  26. package/src/index.ts +6 -1
  27. package/src/language-select/components/LanguageSelect/LanguageConfigContext.tsx +4 -0
  28. package/src/language-select/components/LanguageSelect/LanguageSelect.tsx +188 -0
  29. package/src/language-select/components/LanguageSelect/LanguageSelectContext.ts +16 -0
  30. package/src/language-select/components/LanguageSelect/LanguageSelectLabel.tsx +12 -0
  31. package/src/language-select/components/LanguageSelect/LanguageSelectList.tsx +84 -0
  32. package/src/language-select/components/LanguageSelect/LanguageSelectListItem.tsx +251 -0
  33. package/src/language-select/components/LanguageSelect/index.tsx +25 -0
  34. package/src/language-select/components/SingleFlag/SingleFlag.tsx +44 -0
  35. package/src/language-select/components/SingleFlag/allEmojiFlagCodes.ts +251 -0
  36. package/src/language-select/components/SingleFlag/index.ts +1 -0
  37. package/src/language-select/components/SplitPaneIcon/SplitPaneIcon.tsx +25 -0
  38. package/src/language-select/components/SplitPaneIcon/index.ts +1 -0
  39. package/src/language-select/components/index.ts +2 -0
  40. package/src/language-select/hooks/index.ts +1 -0
  41. package/src/language-select/hooks/useLanguages.ts +37 -0
  42. package/src/language-select/hooks/useListeningQuery.ts +66 -0
  43. package/src/structure/IDefaultDocumentNodeStructureProps.ts +4 -0
  44. package/src/structure/components/MaintenanceTab/MaintenanceTab.tsx +13 -0
  45. package/src/structure/components/MaintenanceTab/MaintenanceTabContent.tsx +259 -0
  46. package/src/structure/components/MaintenanceTab/index.ts +1 -0
  47. package/src/structure/components/MaintenanceTabResult/MaintenanceTabResult.tsx +44 -0
  48. package/src/structure/components/MaintenanceTabResult/index.ts +1 -0
  49. package/src/structure/components/MaintenanceTabTypeSelector/MaintenanceTabTypeSelector.tsx +56 -0
  50. package/src/structure/components/MaintenanceTabTypeSelector/index.ts +1 -0
  51. package/src/structure/hooks/index.ts +1 -0
  52. package/src/structure/hooks/useDocumentsInformation.ts +110 -0
  53. package/src/structure/index.tsx +120 -0
  54. package/src/structure/utils/fixBaseDocumentRefs.ts +30 -0
  55. package/src/structure/utils/fixBaseLanguageMismatch.ts +21 -0
  56. package/src/structure/utils/fixIdStructureMismatchDocuments.ts +72 -0
  57. package/src/structure/utils/fixLanguageFields.ts +32 -0
  58. package/src/structure/utils/fixOrphanedDocuments.ts +22 -0
  59. package/src/structure/utils/fixTranslationRefs.ts +50 -0
  60. package/src/structure/utils/index.ts +6 -0
  61. package/src/types/IEditState.ts +6 -0
  62. package/src/types/IExtendedLanguageObject.ts +6 -0
  63. package/src/types/ILanguageObject.ts +4 -0
  64. package/src/types/ILanguageQuery.ts +13 -0
  65. package/src/types/IResolverProps.ts +10 -0
  66. package/src/types/ITranslationRef.ts +6 -0
  67. package/src/types/IType.ts +40 -0
  68. package/src/types/IUseDocumentOperationResult.ts +11 -0
  69. package/src/types/TFieldNamesConfig.ts +5 -0
  70. package/src/types/TLanguage.ts +3 -0
  71. package/src/types/TLanguagesOption.ts +4 -0
  72. package/src/types/TSchema.ts +6 -0
  73. package/src/types/Ti18nConfig.ts +33 -0
  74. package/src/types/Ti18nDocument.ts +14 -0
  75. package/src/types/Ti18nSchema.ts +13 -0
  76. package/src/types/index.ts +15 -0
  77. package/src/utils/applyConfig.ts +43 -0
  78. package/src/utils/baseToTop.ts +5 -0
  79. package/src/utils/buildDocId.ts +8 -0
  80. package/src/utils/createSanityReference.ts +11 -0
  81. package/src/utils/getBaseIdFromId.ts +20 -0
  82. package/src/utils/getBaseLanguage.ts +6 -0
  83. package/src/utils/getFlag.ts +38 -0
  84. package/src/utils/getLanguageFromDocument.ts +9 -0
  85. package/src/utils/getLanguageFromId.ts +13 -0
  86. package/src/utils/getLanguagesFromOption.ts +40 -0
  87. package/src/utils/getTranslationsForId.ts +39 -0
  88. package/src/utils/index.ts +14 -0
  89. package/src/utils/makeObjectKey.ts +3 -0
  90. package/src/utils/normalizeLanguageList.ts +26 -0
  91. package/src/utils/serializePath.ts +11 -0
  92. package/src/utils/updateIntlFieldsForDocument.ts +79 -0
  93. package/src/utils/useSanityClient.ts +6 -0
  94. package/src/validators/index.ts +1 -0
  95. package/src/validators/isSlugUnique.ts +56 -0
  96. package/src/withDocumentI18nPlugin.ts +24 -0
  97. package/v2-incompatible.js +1 -1
  98. package/lib/cjs/index.js +0 -539
  99. package/lib/cjs/index.js.map +0 -1
  100. package/lib/esm/index.js +0 -532
  101. package/lib/esm/index.js.map +0 -1
  102. package/lib/types/index.d.ts +0 -12
  103. package/lib/types/index.d.ts.map +0 -1
  104. package/src/LanguageManage.tsx +0 -25
  105. package/src/LanguageOption.tsx +0 -158
  106. package/src/LanguagePatch.tsx +0 -61
  107. package/src/MenuButton.tsx +0 -101
  108. package/src/badges/index.tsx +0 -23
  109. package/src/constants.ts +0 -1
  110. package/src/hooks/useLanguageMetadata.tsx +0 -21
  111. package/src/hooks/useOpenInNewPane.tsx +0 -27
  112. package/src/plugin.tsx +0 -104
  113. package/src/schema/translation/metadata.ts +0 -42
  114. package/src/types.ts +0 -24
@@ -0,0 +1,37 @@
1
+ import {SanityDocument} from '@sanity/client'
2
+ import {useCallback, useEffect, useState} from 'react'
3
+ import {useEditState} from 'sanity'
4
+ import {ILanguageObject} from '../../types'
5
+ import {getLanguagesFromOption, useSanityClient, ApplyConfigResult} from '../../utils'
6
+
7
+ export function useLanguages(
8
+ config: ApplyConfigResult,
9
+ document: SanityDocument
10
+ ): [boolean, ILanguageObject[]] {
11
+ const client = useSanityClient()
12
+ const {draft, published} = useEditState(document._id.replace(/^drafts\./, ''), document._type)
13
+ const [pending, setPending] = useState(false)
14
+ const [languages, setLanguages] = useState<ILanguageObject[]>([])
15
+
16
+ const loadOrReloadLanguages = useCallback(async () => {
17
+ const shouldReload =
18
+ languages.length === 0 || (config.shouldReload && config.shouldReload(draft ?? published))
19
+ if (shouldReload) {
20
+ setPending(true)
21
+ const languageObjects = await getLanguagesFromOption(
22
+ client,
23
+ config,
24
+ config.languages,
25
+ draft ?? published
26
+ )
27
+ setLanguages(languageObjects)
28
+ setPending(false)
29
+ }
30
+ }, [client, config, draft, published, languages])
31
+
32
+ useEffect(() => {
33
+ loadOrReloadLanguages()
34
+ }, [draft, published, languages, config, loadOrReloadLanguages])
35
+
36
+ return [pending, languages]
37
+ }
@@ -0,0 +1,66 @@
1
+ import {useEffect, useState, useRef} from 'react'
2
+ import {catchError, distinctUntilChanged} from 'rxjs/operators'
3
+ import isEqual from 'react-fast-compare'
4
+ import {useDocumentStore} from 'sanity'
5
+
6
+ type Params = Record<string, string | number | boolean | string[]>
7
+
8
+ interface ListenQueryOptions {
9
+ tag?: string
10
+ apiVersion?: string
11
+ }
12
+
13
+ type ReturnShape = {
14
+ loading: boolean
15
+ error: boolean
16
+ data: any
17
+ }
18
+
19
+ type Observable = {
20
+ unsubscribe: () => void
21
+ }
22
+
23
+ const DEFAULT_PARAMS = {}
24
+ const DEFAULT_OPTIONS = {apiVersion: `v2022-05-09`}
25
+
26
+ export function useListeningQuery(
27
+ query: string,
28
+ params: Params = DEFAULT_PARAMS,
29
+ options: ListenQueryOptions = DEFAULT_OPTIONS
30
+ ): ReturnShape {
31
+ const [loading, setLoading] = useState(true)
32
+ const [error, setError] = useState(false)
33
+ const [data, setData] = useState(null)
34
+ const subscription = useRef<null | Observable>(null)
35
+
36
+ const documentStore = useDocumentStore()
37
+
38
+ useEffect(() => {
39
+ if (query) {
40
+ subscription.current = documentStore
41
+ .listenQuery(query, params, options)
42
+ .pipe(
43
+ distinctUntilChanged(isEqual),
44
+ catchError((err) => {
45
+ console.error(err)
46
+ setError(err)
47
+ setLoading(false)
48
+ setData(null)
49
+
50
+ return err
51
+ })
52
+ )
53
+ .subscribe((documents) => {
54
+ setData((current) => (isEqual(current, documents) ? current : documents))
55
+ setLoading(false)
56
+ setError(false)
57
+ })
58
+ }
59
+
60
+ return () => {
61
+ return subscription.current ? subscription.current.unsubscribe() : undefined
62
+ }
63
+ }, [query, params, options])
64
+
65
+ return {loading, error, data}
66
+ }
@@ -0,0 +1,4 @@
1
+ export interface IDefaultDocumentNodeStructureProps {
2
+ documentId: string
3
+ schemaType: string
4
+ }
@@ -0,0 +1,13 @@
1
+ import React, {forwardRef, Ref, VoidFunctionComponent} from 'react'
2
+ import {Ti18nConfig} from '../../../types'
3
+ import {MaintenanceTabContent} from './MaintenanceTabContent'
4
+
5
+ export interface MaintenanceTabProps {
6
+ config: Ti18nConfig
7
+ }
8
+
9
+ export function createMaintenanceTabComponent(props: MaintenanceTabProps): VoidFunctionComponent {
10
+ return forwardRef(function MaintenanceTab(p: any, ref: Ref<HTMLInputElement>) {
11
+ return <MaintenanceTabContent {...props} ref={ref} />
12
+ })
13
+ }
@@ -0,0 +1,259 @@
1
+ import React, {forwardRef, Ref, useCallback, useState} from 'react'
2
+ import {useSchema} from 'sanity'
3
+ import {Box, Button, Card, Code, Container, Dialog, Flex, Grid, Stack, Text} from '@sanity/ui'
4
+ import {Transaction} from '@sanity/client'
5
+ import styled from 'styled-components'
6
+ import {UiMessages} from '../../../constants'
7
+ import {MaintenanceTabTypeSelector} from '../MaintenanceTabTypeSelector'
8
+ import {useDocumentsInformation} from '../../hooks'
9
+ import {MaintenanceTabResult} from '../MaintenanceTabResult'
10
+ import {
11
+ fixBaseDocumentRefs,
12
+ fixBaseLanguageMismatch,
13
+ fixIdStructureMismatchDocuments,
14
+ fixLanguageFields,
15
+ fixOrphanedDocuments,
16
+ fixTranslationRefs,
17
+ } from '../../utils'
18
+ import {Ti18nConfig} from '../../../types'
19
+ import {useSanityClient} from '../../../utils'
20
+ import {useConfig} from '../../../hooks'
21
+
22
+ const StyledCodeCard = styled(Card)`
23
+ grid-column-start: 1;
24
+ grid-row-start: 1;
25
+ `
26
+
27
+ const StyledCode = styled(Code)`
28
+ word-break: break-word;
29
+ white-space: break-spaces;
30
+ `
31
+
32
+ const StyledDownloadCodeFlex = styled(Flex)`
33
+ grid-column-start: 1;
34
+ grid-row-start: 1;
35
+ `
36
+
37
+ export interface MaintenanceTabContentProps {
38
+ config: Ti18nConfig
39
+ }
40
+
41
+ export const MaintenanceTabContent = forwardRef(function MaintenanceTabContent(
42
+ {config: pluginConfig}: MaintenanceTabContentProps,
43
+ ref: Ref<HTMLInputElement>
44
+ ) {
45
+ const [selectedSchema, setSelectedSchema] = useState('')
46
+ const client = useSanityClient()
47
+ const {
48
+ pending,
49
+ setPending,
50
+ documents,
51
+ baseDocuments,
52
+ translatedDocuments,
53
+ documentsSummaryInformation,
54
+ fetchInformation,
55
+ } = useDocumentsInformation(pluginConfig, selectedSchema)
56
+ const [pendingTransactions, setPendingTransactions] = useState<Transaction[] | null>(null)
57
+ const schemaRegistry = useSchema()
58
+ const config = useConfig(pluginConfig, selectedSchema)
59
+
60
+ const onSchemaTypeChange = useCallback((schemaName: string) => setSelectedSchema(schemaName), [])
61
+
62
+ const handleOpen = useCallback(() => setSelectedSchema(''), [])
63
+
64
+ const handleFixIdStructureMismatchDocuments = useCallback(() => {
65
+ setPendingTransactions(
66
+ fixIdStructureMismatchDocuments(client, config, selectedSchema, documents)
67
+ )
68
+ }, [client, config, selectedSchema, documents])
69
+
70
+ const handleFixMissingLanguageFields = useCallback(() => {
71
+ setPendingTransactions([fixLanguageFields(client, config, schemaRegistry, documents)])
72
+ }, [client, schemaRegistry, config, documents])
73
+
74
+ const handleFixTranslationRefs = useCallback(() => {
75
+ setPendingTransactions(fixTranslationRefs(client, config, baseDocuments, translatedDocuments))
76
+ }, [client, config, baseDocuments, translatedDocuments])
77
+
78
+ const handleFixBaseDocumntRefs = useCallback(() => {
79
+ setPendingTransactions([fixBaseDocumentRefs(client, config, translatedDocuments)])
80
+ }, [client, config, translatedDocuments])
81
+
82
+ const handleFixOrphanDocuments = useCallback(() => {
83
+ setPendingTransactions([
84
+ fixOrphanedDocuments(client, config, baseDocuments, translatedDocuments),
85
+ ])
86
+ }, [client, config, baseDocuments, translatedDocuments])
87
+
88
+ const handleFixReferenceBehaviorMismatch = useCallback(() => {
89
+ setPendingTransactions(fixTranslationRefs(client, config, baseDocuments, translatedDocuments))
90
+ }, [client, config, baseDocuments, translatedDocuments])
91
+
92
+ const handleFixBaseLanguageMismatch = useCallback(async () => {
93
+ setPendingTransactions([await fixBaseLanguageMismatch(client, config, baseDocuments)])
94
+ }, [client, config, baseDocuments])
95
+
96
+ const handleDownloadCode = useCallback(() => {
97
+ if (pendingTransactions) {
98
+ const json = JSON.stringify(
99
+ pendingTransactions.map((transaction) => transaction.toJSON()),
100
+ null,
101
+ 2
102
+ )
103
+ const blob = new Blob([json], {
104
+ type: 'application/json',
105
+ })
106
+
107
+ const url = URL.createObjectURL(blob)
108
+ const a = document.createElement('a')
109
+ a.href = url
110
+ a.download = 'transactions.json'
111
+ a.style.display = 'none'
112
+ document.body.appendChild(a)
113
+ a.click()
114
+ URL.revokeObjectURL(url)
115
+ }
116
+ }, [pendingTransactions])
117
+
118
+ const handleCancelPendingTransaction = useCallback(() => {
119
+ setPendingTransactions(null)
120
+ }, [setPendingTransactions])
121
+
122
+ const handleConfirmPendingTransaction = useCallback(async () => {
123
+ setPending(true)
124
+ try {
125
+ // run all transactions
126
+ if (pendingTransactions) {
127
+ await pendingTransactions.reduce<Promise<void>>(async (prev, transaction) => {
128
+ await prev
129
+ await transaction.commit()
130
+ }, Promise.resolve())
131
+ await fetchInformation(selectedSchema)
132
+ setPendingTransactions(null)
133
+ }
134
+ } catch (err) {
135
+ console.error(err)
136
+ // @TODO show error
137
+ } finally {
138
+ setPending(false)
139
+ }
140
+ }, [setPending, fetchInformation, selectedSchema, pendingTransactions])
141
+
142
+ return (
143
+ <Container width={1}>
144
+ <Stack space={2}>
145
+ <Box padding={4}>
146
+ <MaintenanceTabTypeSelector
147
+ value={selectedSchema}
148
+ onChange={onSchemaTypeChange}
149
+ onOpen={handleOpen}
150
+ ref={ref}
151
+ />
152
+ </Box>
153
+ {!!selectedSchema && (
154
+ <Box paddingX={4}>
155
+ <Stack space={2}>
156
+ <MaintenanceTabResult
157
+ pending={pending}
158
+ count={documentsSummaryInformation.idStructureMismatch.length}
159
+ labelName="idStructureMismatch"
160
+ onClick={handleFixIdStructureMismatchDocuments}
161
+ />
162
+ <MaintenanceTabResult
163
+ pending={pending}
164
+ count={documentsSummaryInformation.missingLanguageField.length}
165
+ labelName="missingLanguageField"
166
+ onClick={handleFixMissingLanguageFields}
167
+ />
168
+ <MaintenanceTabResult
169
+ pending={pending}
170
+ count={documentsSummaryInformation.missingDocumentRefs.length}
171
+ labelName="missingDocumentRefs"
172
+ onClick={handleFixTranslationRefs}
173
+ />
174
+ <MaintenanceTabResult
175
+ pending={pending}
176
+ count={documentsSummaryInformation.missingBaseDocumentRefs.length}
177
+ labelName="missingBaseDocumentRefs"
178
+ onClick={handleFixBaseDocumntRefs}
179
+ />
180
+ <MaintenanceTabResult
181
+ pending={pending}
182
+ count={documentsSummaryInformation.orphanDocuments.length}
183
+ labelName="orphanDocuments"
184
+ onClick={handleFixOrphanDocuments}
185
+ />
186
+ <MaintenanceTabResult
187
+ pending={pending}
188
+ count={documentsSummaryInformation.referenceBehaviorMismatch.length}
189
+ labelName="referenceBehaviorMismatch"
190
+ onClick={handleFixReferenceBehaviorMismatch}
191
+ />
192
+ <MaintenanceTabResult
193
+ pending={pending}
194
+ count={documentsSummaryInformation.baseLanguageMismatch.length}
195
+ labelName="baseLanguageMismatch"
196
+ onClick={handleFixBaseLanguageMismatch}
197
+ />
198
+ </Stack>
199
+ </Box>
200
+ )}
201
+ </Stack>
202
+ {!!pendingTransactions?.length && (
203
+ <Dialog
204
+ id="confirm-pending-transactions"
205
+ width={2}
206
+ header={UiMessages.translationsMaintenance.pendingTransactionDialog.header}
207
+ onClose={handleCancelPendingTransaction}
208
+ footer={
209
+ <Flex padding={3} justify="flex-end">
210
+ <Grid autoFlow="column" autoCols="auto" gap={2}>
211
+ <Button
212
+ tone="default"
213
+ loading={pending}
214
+ disabled={pending}
215
+ text={UiMessages.translationsMaintenance.pendingTransactionDialog.cancel}
216
+ onClick={handleCancelPendingTransaction}
217
+ />
218
+ <Button
219
+ tone="critical"
220
+ loading={pending}
221
+ disabled={pending}
222
+ text={UiMessages.translationsMaintenance.pendingTransactionDialog.confirm}
223
+ onClick={handleConfirmPendingTransaction}
224
+ />
225
+ </Grid>
226
+ </Flex>
227
+ }
228
+ >
229
+ <Stack padding={3} space={3}>
230
+ <Card padding={3} radius={2} shadow={1} tone="caution">
231
+ <Text size={2}>
232
+ {UiMessages.translationsMaintenance.pendingTransactionDialog.caution}
233
+ </Text>
234
+ </Card>
235
+ <Grid cols={1}>
236
+ <StyledCodeCard padding={3} radius={2} shadow={1} tone="default">
237
+ <StyledCode language="json">
238
+ {JSON.stringify(
239
+ pendingTransactions.map((transaction) => transaction.toJSON()),
240
+ null,
241
+ 2
242
+ )}
243
+ </StyledCode>
244
+ </StyledCodeCard>
245
+ <StyledDownloadCodeFlex
246
+ padding={2}
247
+ justify="flex-end"
248
+ align="flex-start"
249
+ onClick={handleDownloadCode}
250
+ >
251
+ <Button text="Download" />
252
+ </StyledDownloadCodeFlex>
253
+ </Grid>
254
+ </Stack>
255
+ </Dialog>
256
+ )}
257
+ </Container>
258
+ )
259
+ })
@@ -0,0 +1 @@
1
+ export * from './MaintenanceTab'
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import {Box, Button, Card, Flex, Text} from '@sanity/ui'
3
+ import {UiMessages} from '../../../constants/UiMessages'
4
+
5
+ type Props = React.PropsWithChildren<{
6
+ pending?: boolean
7
+ count: number
8
+ labelName?: keyof typeof UiMessages['translationsMaintenance']
9
+ onClick?: (event: React.SyntheticEvent<HTMLButtonElement, Event>) => void
10
+ }>
11
+
12
+ export const MaintenanceTabResult: React.FC<Props> = ({
13
+ pending,
14
+ count,
15
+ labelName,
16
+ children,
17
+ onClick,
18
+ }) => {
19
+ return (
20
+ <Card padding={3} radius={2} shadow={1} tone={count > 0 ? `caution` : `default`}>
21
+ <Flex align="center">
22
+ <Box flex={1}>
23
+ <Text muted={count <= 0}>
24
+ <span>{count}</span>
25
+ <span>&nbsp;</span>
26
+ <span>
27
+ {labelName ? String(UiMessages.translationsMaintenance[labelName]) : children}
28
+ </span>
29
+ </Text>
30
+ </Box>
31
+
32
+ {count > 0 && (
33
+ <Button
34
+ padding={2}
35
+ fontSize={2}
36
+ disabled={pending}
37
+ onClick={onClick}
38
+ text={UiMessages.translationsMaintenance.fix}
39
+ />
40
+ )}
41
+ </Flex>
42
+ </Card>
43
+ )
44
+ }
@@ -0,0 +1 @@
1
+ export * from './MaintenanceTabResult'
@@ -0,0 +1,56 @@
1
+ import React, {forwardRef, Ref, useMemo} from 'react'
2
+ import {Autocomplete, Card, Stack, Text} from '@sanity/ui'
3
+ import {EarthGlobeIcon} from '@sanity/icons'
4
+ import {useSchema, DefaultPreview as Preview} from 'sanity'
5
+ import {Ti18nSchema} from '../../../types'
6
+ import {UiMessages} from '../../../constants'
7
+
8
+ type PreviewMedia = React.ComponentProps<typeof Preview>['media']
9
+ type Props = {
10
+ value: string
11
+ onChange: (value: string) => void
12
+ onOpen: () => void
13
+ }
14
+
15
+ export const MaintenanceTabTypeSelector = forwardRef(function MaintenanceTabTypeSelector(
16
+ {value, onChange, onOpen}: Props,
17
+ ref: Ref<HTMLInputElement>
18
+ ) {
19
+ const schema = useSchema()
20
+ const i18nSchemas = useMemo(
21
+ () => schema._original?.types.filter((s: unknown) => !!(s as Ti18nSchema).i18n),
22
+ [schema]
23
+ )
24
+
25
+ return (
26
+ <Stack space={4}>
27
+ <Text>{UiMessages.translationsMaintenance.selectSchemaPlaceholder}</Text>
28
+ <Card>
29
+ <Autocomplete
30
+ ref={ref}
31
+ fontSize={[2, 2, 3]}
32
+ icon={EarthGlobeIcon}
33
+ id="i18n-schema-selector"
34
+ options={i18nSchemas?.map((option) => ({
35
+ value: option.name,
36
+ payload: option,
37
+ }))}
38
+ value={value}
39
+ placeholder="Search"
40
+ openButton={{onClick: () => onOpen}}
41
+ onChange={onChange}
42
+ renderValue={(v, option) => option?.payload.title || v}
43
+ renderOption={({payload}) => (
44
+ <Card padding={2} radius={2} as="button">
45
+ <Preview
46
+ layout="default"
47
+ title={payload.title}
48
+ media={payload.icon as PreviewMedia}
49
+ />
50
+ </Card>
51
+ )}
52
+ />
53
+ </Card>
54
+ </Stack>
55
+ )
56
+ })
@@ -0,0 +1 @@
1
+ export * from './MaintenanceTabTypeSelector'
@@ -0,0 +1 @@
1
+ export * from './useDocumentsInformation'
@@ -0,0 +1,110 @@
1
+ import React from 'react'
2
+ import {useConfig} from '../../hooks'
3
+ import {I18nDelimiter, I18nPrefix, IdStructure, ReferenceBehavior} from '../../constants'
4
+ import {ILanguageObject, ITranslationRef, Ti18nConfig, Ti18nDocument} from '../../types'
5
+ import {
6
+ getBaseIdFromId,
7
+ getBaseLanguage,
8
+ getLanguagesFromOption,
9
+ useSanityClient,
10
+ } from '../../utils'
11
+
12
+ export const useDocumentsInformation = (pluginConfig: Ti18nConfig, schema: string) => {
13
+ const client = useSanityClient()
14
+ const config = useConfig(pluginConfig, schema)
15
+ const [pending, setPending] = React.useState(false)
16
+ const [documents, setDocuments] = React.useState<Ti18nDocument[]>([])
17
+ const [languages, setLanguages] = React.useState<ILanguageObject[]>([])
18
+ const baseDocuments = React.useMemo(() => {
19
+ if (config.idStructure === IdStructure.DELIMITER)
20
+ return documents.filter((d) => !d._id.includes(I18nDelimiter))
21
+ return documents.filter((d) => !d._id.startsWith(I18nPrefix))
22
+ }, [config, documents])
23
+ const translatedDocuments = React.useMemo(() => {
24
+ if (config.idStructure === IdStructure.DELIMITER)
25
+ return documents.filter((d) => d._id.includes(I18nDelimiter))
26
+ return documents.filter((d) => d._id.startsWith(I18nPrefix))
27
+ }, [config, documents])
28
+ const idStructureMismatchDocuments = React.useMemo(() => {
29
+ if (config.idStructure === IdStructure.DELIMITER)
30
+ return documents.filter((d) => d._id.startsWith(I18nPrefix))
31
+ return documents.filter((d) => d._id.includes(I18nDelimiter))
32
+ }, [config, documents])
33
+
34
+ const fetchInformation = React.useCallback(
35
+ async (selectedSchema: string) => {
36
+ setPending(true)
37
+ const [langs, result] = await Promise.all([
38
+ getLanguagesFromOption(client, config, config.languages),
39
+ client.fetch<Ti18nDocument[]>(`*[_type == $type && !(_id in path('drafts.**'))]`, {
40
+ type: selectedSchema,
41
+ }),
42
+ ])
43
+ setLanguages(langs)
44
+ setDocuments(result)
45
+ setPending(false)
46
+ },
47
+ [config, client]
48
+ )
49
+
50
+ const documentsSummaryInformation = React.useMemo(() => {
51
+ const base = getBaseLanguage(languages, config.base)
52
+ const basedocuments = baseDocuments
53
+ const translateddocuments = translatedDocuments
54
+ const langFieldName = config.fieldNames?.lang
55
+ const refsFieldName = config.fieldNames?.references
56
+ const baseRefFieldName = config.fieldNames?.baseReference
57
+ return {
58
+ idStructureMismatch: idStructureMismatchDocuments,
59
+ missingLanguageField: documents.filter((d) => !d[langFieldName]),
60
+ missingDocumentRefs: basedocuments.filter((d) => {
61
+ const docs = translateddocuments.filter((dx) => getBaseIdFromId(dx._id) === d._id)
62
+ const refsCount = (Object.values(d[refsFieldName] || []) as any[]).filter(
63
+ (ref) => ref._type === 'reference' && !!ref._ref
64
+ ).length
65
+ return refsCount != docs.length
66
+ }),
67
+ missingBaseDocumentRefs: translateddocuments.filter((d) => !d[baseRefFieldName]),
68
+ orphanDocuments: translateddocuments.filter((d) => {
69
+ const baseDoc = basedocuments.find((doc) => getBaseIdFromId(d._id) === doc._id)
70
+ if (baseDoc) return false
71
+ return true
72
+ }),
73
+ referenceBehaviorMismatch: basedocuments.filter((doc) => {
74
+ const refs: ITranslationRef[] = doc[refsFieldName] || []
75
+ if (config.referenceBehavior === ReferenceBehavior.DISABLED)
76
+ return Object.keys(refs).length > 0
77
+ if (config.referenceBehavior === ReferenceBehavior.WEAK)
78
+ return Object.values(refs).some((r) => !r._weak)
79
+ return Object.values(refs).some((r) => !!r._weak)
80
+ }),
81
+ baseLanguageMismatch: basedocuments.filter((doc) => {
82
+ return base?.id && doc[langFieldName] !== base.id
83
+ }),
84
+ }
85
+ }, [
86
+ config,
87
+ documents,
88
+ languages,
89
+ baseDocuments,
90
+ translatedDocuments,
91
+ idStructureMismatchDocuments,
92
+ ])
93
+
94
+ React.useEffect(() => {
95
+ if (schema) {
96
+ fetchInformation(schema)
97
+ }
98
+ }, [schema])
99
+
100
+ return {
101
+ pending,
102
+ setPending,
103
+ documents,
104
+ baseDocuments,
105
+ translatedDocuments,
106
+ idStructureMismatchDocuments,
107
+ documentsSummaryInformation,
108
+ fetchInformation,
109
+ }
110
+ }