gt-sanity 0.0.5 → 1.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 (108) hide show
  1. package/LICENSE.md +1 -8
  2. package/README.md +5 -5
  3. package/dist/index.d.mts +122 -95
  4. package/dist/index.d.ts +122 -95
  5. package/dist/index.js +9089 -1119
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +9099 -1100
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +11 -4
  10. package/src/adapter/core.ts +111 -9
  11. package/src/adapter/createTask.ts +1 -1
  12. package/src/adapter/getLocales.ts +2 -2
  13. package/src/adapter/types.ts +9 -0
  14. package/src/components/TranslationsProvider.tsx +942 -0
  15. package/src/components/page/BatchProgress.tsx +27 -0
  16. package/src/components/page/ImportAllDialog.tsx +51 -0
  17. package/src/components/page/ImportMissingDialog.tsx +55 -0
  18. package/src/components/page/TranslateAllDialog.tsx +55 -0
  19. package/src/components/page/TranslationsTable.tsx +81 -0
  20. package/src/components/page/TranslationsTool.tsx +338 -0
  21. package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
  22. package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +2 -0
  23. package/src/components/shared/LocaleCheckbox.tsx +47 -0
  24. package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +2 -0
  25. package/src/components/shared/SingleDocumentView.tsx +108 -0
  26. package/src/components/tab/TranslationView.tsx +379 -0
  27. package/src/components/tab/TranslationsTab.tsx +25 -0
  28. package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +21 -11
  29. package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +57 -23
  30. package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +2 -0
  31. package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +2 -0
  32. package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +2 -0
  33. package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +31 -8
  34. package/src/configuration/baseDocumentLevelConfig/index.ts +18 -101
  35. package/src/configuration/baseFieldLevelConfig.ts +19 -51
  36. package/src/configuration/utils/checkSerializationVersion.ts +2 -0
  37. package/src/configuration/utils/findDocumentAtRevision.ts +2 -0
  38. package/src/configuration/utils/findLatestDraft.ts +2 -0
  39. package/src/hooks/useClient.ts +3 -1
  40. package/src/hooks/useSecrets.ts +2 -0
  41. package/src/index.ts +91 -67
  42. package/src/sanity-api/findDocuments.ts +44 -0
  43. package/src/sanity-api/publishDocuments.ts +49 -0
  44. package/src/sanity-api/resolveRefs.ts +146 -0
  45. package/src/serialization/BaseDocumentMerger.ts +138 -0
  46. package/src/serialization/BaseSerializationConfig.ts +220 -0
  47. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +189 -0
  48. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +107 -0
  49. package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +397 -0
  50. package/src/serialization/__tests__/BaseDocumentDeserializer/documentLevelDeserialization.test.ts +107 -0
  51. package/src/serialization/__tests__/BaseDocumentDeserializer/fieldLevelDeserialization.test.ts +107 -0
  52. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +193 -0
  53. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +97 -0
  54. package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +36 -0
  55. package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +96 -0
  56. package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +142 -0
  57. package/src/serialization/__tests__/BaseDocumentMerger/utils.ts +52 -0
  58. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +39 -0
  59. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +8 -0
  60. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +8 -0
  61. package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +345 -0
  62. package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +53 -0
  63. package/src/serialization/__tests__/BaseDocumentSerializer/documentLevelSerialization.test.ts +120 -0
  64. package/src/serialization/__tests__/BaseDocumentSerializer/fieldLevelSerialization.test.ts +153 -0
  65. package/src/serialization/__tests__/BaseDocumentSerializer/utils.ts +27 -0
  66. package/src/serialization/__tests__/README +2 -0
  67. package/src/serialization/__tests__/__fixtures__/annotationAndInlineBlocks.json +140 -0
  68. package/src/serialization/__tests__/__fixtures__/customStyles.json +62 -0
  69. package/src/serialization/__tests__/__fixtures__/documentInlineMarks.json +70 -0
  70. package/src/serialization/__tests__/__fixtures__/documentLevelArticle.json +185 -0
  71. package/src/serialization/__tests__/__fixtures__/fieldLevelArticle.json +107 -0
  72. package/src/serialization/__tests__/__fixtures__/inlineDocumentLevelArticle.json +134 -0
  73. package/src/serialization/__tests__/__fixtures__/inlineSchema.ts +270 -0
  74. package/src/serialization/__tests__/__fixtures__/messy-html.html +26 -0
  75. package/src/serialization/__tests__/__fixtures__/nestedLanguageFields.json +54 -0
  76. package/src/serialization/__tests__/__fixtures__/schema.ts +310 -0
  77. package/src/serialization/__tests__/global.setup.ts +40 -0
  78. package/src/serialization/__tests__/helpers.ts +132 -0
  79. package/src/serialization/data.ts +82 -0
  80. package/src/serialization/deserialize/BaseDocumentDeserializer.ts +171 -0
  81. package/src/serialization/deserialize/helpers.ts +42 -0
  82. package/src/serialization/helpers.ts +18 -0
  83. package/src/serialization/index.ts +11 -0
  84. package/src/serialization/serialize/fieldFilters.ts +124 -0
  85. package/src/serialization/serialize/index.ts +284 -0
  86. package/src/serialization/types.ts +41 -0
  87. package/src/translation/checkTranslationStatus.ts +42 -0
  88. package/src/translation/createJobs.ts +16 -0
  89. package/src/translation/downloadTranslations.ts +68 -0
  90. package/src/translation/importDocument.ts +23 -0
  91. package/src/translation/initProject.ts +61 -0
  92. package/src/translation/uploadFiles.ts +32 -0
  93. package/src/types.ts +7 -20
  94. package/src/utils/applyDocuments.ts +72 -0
  95. package/src/utils/batchProcessor.ts +111 -0
  96. package/src/utils/importUtils.ts +95 -0
  97. package/src/utils/serialize.ts +52 -0
  98. package/src/utils/shared.ts +1 -0
  99. package/src/adapter/index.ts +0 -13
  100. package/src/components/NewTask.tsx +0 -249
  101. package/src/components/TaskView.tsx +0 -255
  102. package/src/components/TranslationContext.tsx +0 -19
  103. package/src/components/TranslationView.tsx +0 -82
  104. package/src/components/TranslationsTab.tsx +0 -177
  105. package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +0 -5
  106. package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +0 -69
  107. package/src/configuration/index.ts +0 -18
  108. package/src/configuration/utils/index.ts +0 -3
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { Flex, Text, Spinner } from '@sanity/ui';
3
+
4
+ interface BatchProgressProps {
5
+ isActive: boolean;
6
+ current: number;
7
+ total: number;
8
+ operationName: string;
9
+ }
10
+
11
+ export const BatchProgress: React.FC<BatchProgressProps> = ({
12
+ isActive,
13
+ current,
14
+ total,
15
+ operationName,
16
+ }) => {
17
+ if (!isActive) return null;
18
+
19
+ return (
20
+ <Flex justify='center' align='center' gap={3}>
21
+ <Spinner size={1} />
22
+ <Text size={1}>
23
+ {operationName} {current} of {total}...
24
+ </Text>
25
+ </Flex>
26
+ );
27
+ };
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import { Box, Button, Dialog, Flex, Stack, Text } from '@sanity/ui';
3
+ import { useTranslations } from '../TranslationsProvider';
4
+
5
+ interface ImportAllDialogProps {
6
+ isOpen: boolean;
7
+ onClose: () => void;
8
+ }
9
+
10
+ export const ImportAllDialog: React.FC<ImportAllDialogProps> = ({
11
+ isOpen,
12
+ onClose,
13
+ }) => {
14
+ const { documents, handleImportAll } = useTranslations();
15
+
16
+ const handleConfirm = async () => {
17
+ onClose();
18
+ await handleImportAll();
19
+ };
20
+
21
+ if (!isOpen) return null;
22
+
23
+ return (
24
+ <Dialog
25
+ header='Confirm Import'
26
+ id='import-all-dialog'
27
+ onClose={onClose}
28
+ footer={
29
+ <Box padding={3}>
30
+ <Flex gap={2}>
31
+ <Button text='Cancel' mode='ghost' onClick={onClose} />
32
+ <Button text='Import All' tone='critical' onClick={handleConfirm} />
33
+ </Flex>
34
+ </Box>
35
+ }
36
+ >
37
+ <Box padding={4}>
38
+ <Stack space={3}>
39
+ <Text>
40
+ Are you sure you want to import translations for all{' '}
41
+ {documents.length} documents?
42
+ </Text>
43
+ <Text size={1} muted>
44
+ This will download and apply translated content to your documents.
45
+ Note that this will overwrite any existing translations!
46
+ </Text>
47
+ </Stack>
48
+ </Box>
49
+ </Dialog>
50
+ );
51
+ };
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { Box, Button, Dialog, Flex, Stack, Text } from '@sanity/ui';
3
+ import { useTranslations } from '../TranslationsProvider';
4
+
5
+ interface ImportMissingDialogProps {
6
+ isOpen: boolean;
7
+ onClose: () => void;
8
+ }
9
+
10
+ export const ImportMissingDialog: React.FC<ImportMissingDialogProps> = ({
11
+ isOpen,
12
+ onClose,
13
+ }) => {
14
+ const { handleImportMissing } = useTranslations();
15
+
16
+ const handleConfirm = async () => {
17
+ onClose();
18
+ await handleImportMissing();
19
+ };
20
+
21
+ if (!isOpen) return null;
22
+
23
+ return (
24
+ <Dialog
25
+ header='Confirm Import Missing'
26
+ id='import-missing-dialog'
27
+ onClose={onClose}
28
+ footer={
29
+ <Box padding={3}>
30
+ <Flex gap={2}>
31
+ <Button text='Cancel' mode='ghost' onClick={onClose} />
32
+ <Button
33
+ text='Import Missing'
34
+ tone='primary'
35
+ onClick={handleConfirm}
36
+ />
37
+ </Flex>
38
+ </Box>
39
+ }
40
+ >
41
+ <Box padding={4}>
42
+ <Stack space={3}>
43
+ <Text>
44
+ Import only the missing translations (translations that are ready
45
+ but haven't been imported to Sanity yet)?
46
+ </Text>
47
+ <Text size={1} muted>
48
+ This will check existing translation metadata and only import
49
+ translations that don't already exist in your dataset.
50
+ </Text>
51
+ </Stack>
52
+ </Box>
53
+ </Dialog>
54
+ );
55
+ };
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { Box, Button, Dialog, Flex, Stack, Text } from '@sanity/ui';
3
+ import { useTranslations } from '../TranslationsProvider';
4
+
5
+ interface TranslateAllDialogProps {
6
+ isOpen: boolean;
7
+ onClose: () => void;
8
+ }
9
+
10
+ export const TranslateAllDialog: React.FC<TranslateAllDialogProps> = ({
11
+ isOpen,
12
+ onClose,
13
+ }) => {
14
+ const { documents, handleTranslateAll } = useTranslations();
15
+
16
+ const handleConfirm = async () => {
17
+ onClose();
18
+ await handleTranslateAll();
19
+ };
20
+
21
+ if (!isOpen) return null;
22
+
23
+ return (
24
+ <Dialog
25
+ header='Confirm Translation'
26
+ id='translate-all-dialog'
27
+ onClose={onClose}
28
+ footer={
29
+ <Box padding={3}>
30
+ <Flex gap={2}>
31
+ <Button text='Cancel' mode='ghost' onClick={onClose} />
32
+ <Button
33
+ text='Translate All'
34
+ tone='critical'
35
+ onClick={handleConfirm}
36
+ />
37
+ </Flex>
38
+ </Box>
39
+ }
40
+ >
41
+ <Box padding={4}>
42
+ <Stack space={3}>
43
+ <Text>
44
+ Are you sure you want to create translations for all{' '}
45
+ {documents.length} documents?
46
+ </Text>
47
+ <Text size={1} muted>
48
+ This will submit all documents to General Translation for
49
+ processing.
50
+ </Text>
51
+ </Stack>
52
+ </Box>
53
+ </Dialog>
54
+ );
55
+ };
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import { Box, Card, Stack, Text, Flex, Spinner } from '@sanity/ui';
3
+ import { LanguageStatus } from '../shared/LanguageStatus';
4
+ import { useTranslations } from '../TranslationsProvider';
5
+
6
+ export const TranslationsTable: React.FC = () => {
7
+ const {
8
+ documents,
9
+ locales,
10
+ loadingDocuments,
11
+ translationStatuses,
12
+ downloadStatus,
13
+ importedTranslations,
14
+ handleImportDocument,
15
+ } = useTranslations();
16
+
17
+ if (loadingDocuments) {
18
+ return (
19
+ <Flex align='center' justify='center' padding={4}>
20
+ <Spinner />
21
+ </Flex>
22
+ );
23
+ }
24
+
25
+ return (
26
+ <Box style={{ maxHeight: '60vh', overflowY: 'auto' }}>
27
+ <Stack space={2}>
28
+ {documents.map((document) => (
29
+ <Card key={document._id} shadow={1} padding={3}>
30
+ <Stack space={3}>
31
+ <Flex justify='space-between' align='flex-start'>
32
+ <Box flex={1}>
33
+ <Text weight='semibold' size={1}>
34
+ {document._id?.replace('drafts.', '') || document._id}
35
+ </Text>
36
+ <Text size={0} muted style={{ marginTop: '2px' }}>
37
+ {document._type}
38
+ </Text>
39
+ </Box>
40
+ </Flex>
41
+
42
+ <Stack space={2}>
43
+ {locales.length > 0 ? (
44
+ locales
45
+ .filter((locale) => locale.enabled !== false)
46
+ .map((locale) => {
47
+ const documentId =
48
+ document._id?.replace('drafts.', '') || document._id;
49
+ const key = `${documentId}:${locale.localeId}`;
50
+ const status = translationStatuses.get(key);
51
+ const isDownloaded = downloadStatus.downloaded.has(key);
52
+ const isImported = importedTranslations.has(key);
53
+
54
+ return (
55
+ <LanguageStatus
56
+ key={`${document._id}-${locale.localeId}`}
57
+ title={locale.description || locale.localeId}
58
+ progress={status?.progress || 0}
59
+ isImported={isImported || isDownloaded}
60
+ importFile={async () => {
61
+ await handleImportDocument(
62
+ documentId,
63
+ locale.localeId
64
+ );
65
+ }}
66
+ />
67
+ );
68
+ })
69
+ ) : (
70
+ <Text size={1} muted>
71
+ No locales configured
72
+ </Text>
73
+ )}
74
+ </Stack>
75
+ </Stack>
76
+ </Card>
77
+ ))}
78
+ </Stack>
79
+ </Box>
80
+ );
81
+ };
@@ -0,0 +1,338 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Box,
4
+ Button,
5
+ Container,
6
+ Flex,
7
+ Heading,
8
+ Stack,
9
+ Switch,
10
+ Text,
11
+ Spinner,
12
+ Tooltip,
13
+ } from '@sanity/ui';
14
+ import {
15
+ DownloadIcon,
16
+ CheckmarkCircleIcon,
17
+ LinkIcon,
18
+ PublishIcon,
19
+ } from '@sanity/icons';
20
+ import { Link } from 'sanity/router';
21
+ import { BaseTranslationWrapper } from '../shared/BaseTranslationWrapper';
22
+ import { TranslationsProvider, useTranslations } from '../TranslationsProvider';
23
+ import { TranslationsTable } from './TranslationsTable';
24
+ import { TranslateAllDialog } from './TranslateAllDialog';
25
+ import { ImportAllDialog } from './ImportAllDialog';
26
+ import { ImportMissingDialog } from './ImportMissingDialog';
27
+ import { BatchProgress } from './BatchProgress';
28
+
29
+ const TranslationsToolContent: React.FC = () => {
30
+ const [isTranslateAllDialogOpen, setIsTranslateAllDialogOpen] =
31
+ useState(false);
32
+ const [isImportAllDialogOpen, setIsImportAllDialogOpen] = useState(false);
33
+ const [isImportMissingDialogOpen, setIsImportMissingDialogOpen] =
34
+ useState(false);
35
+
36
+ const {
37
+ isBusy,
38
+ documents,
39
+ locales,
40
+ autoRefresh,
41
+ loadingDocuments,
42
+ importProgress,
43
+ importedTranslations,
44
+ isRefreshing,
45
+ setAutoRefresh,
46
+ handleRefreshAll,
47
+ handlePatchDocumentReferences,
48
+ handlePublishAllTranslations,
49
+ } = useTranslations();
50
+
51
+ // Track which specific operation is running
52
+ const [currentOperation, setCurrentOperation] = useState<string | null>(null);
53
+
54
+ const getOperationText = (
55
+ operationName: string | null,
56
+ isProcessing: boolean
57
+ ) => {
58
+ if (!isProcessing || !operationName) return operationName;
59
+
60
+ switch (operationName) {
61
+ case 'Translate All':
62
+ return 'Translating...';
63
+ case 'Import All':
64
+ return 'Importing...';
65
+ case 'Import Missing':
66
+ return 'Importing...';
67
+ case 'Patch Document References':
68
+ return 'Patching...';
69
+ case 'Publish Translations':
70
+ return 'Publishing...';
71
+ default:
72
+ return 'Processing...';
73
+ }
74
+ };
75
+
76
+ const getProgressOperationName = () => {
77
+ switch (currentOperation) {
78
+ case 'Import All':
79
+ return 'Importing';
80
+ case 'Import Missing':
81
+ return 'Importing missing';
82
+ case 'Patch Document References':
83
+ return 'Patching';
84
+ case 'Publish Translations':
85
+ return 'Publishing';
86
+ default:
87
+ return 'Processing';
88
+ }
89
+ };
90
+
91
+ // Reset current operation when operation completes
92
+ React.useEffect(() => {
93
+ if (!isBusy && !importProgress.isImporting) {
94
+ setCurrentOperation(null);
95
+ }
96
+ }, [isBusy, importProgress.isImporting]);
97
+
98
+ return (
99
+ <Container width={2}>
100
+ <Box padding={4} marginTop={5}>
101
+ <Stack space={4}>
102
+ <Flex align='center' justify='space-between'>
103
+ <Stack space={2}>
104
+ <Heading as='h2' size={3}>
105
+ Translations
106
+ </Heading>
107
+ <Text size={2}>
108
+ Manage your document translations from this centralized
109
+ location.
110
+ </Text>
111
+ </Stack>
112
+
113
+ <Flex gap={3} align='center'>
114
+ <Flex gap={2} align='center'>
115
+ <Text size={1}>Auto-refresh</Text>
116
+ <Switch
117
+ checked={autoRefresh}
118
+ onChange={() => setAutoRefresh(!autoRefresh)}
119
+ />
120
+ </Flex>
121
+ <Button
122
+ fontSize={1}
123
+ padding={2}
124
+ text={isRefreshing ? 'Refreshing...' : 'Refresh Status'}
125
+ onClick={handleRefreshAll}
126
+ disabled={
127
+ isRefreshing ||
128
+ isBusy ||
129
+ loadingDocuments ||
130
+ documents.length === 0
131
+ }
132
+ />
133
+ </Flex>
134
+ </Flex>
135
+
136
+ <Stack space={4}>
137
+ <Box>
138
+ <Text size={1} muted>
139
+ {loadingDocuments
140
+ ? 'Loading documents...'
141
+ : `Found ${documents.length} documents available for translation`}
142
+ </Text>
143
+ </Box>
144
+
145
+ <Flex justify='center'>
146
+ <Button
147
+ style={{ width: '200px' }}
148
+ tone='critical'
149
+ text={getOperationText(
150
+ 'Translate All',
151
+ isBusy && currentOperation === 'Translate All'
152
+ )}
153
+ onClick={() => {
154
+ setCurrentOperation('Translate All');
155
+ setIsTranslateAllDialogOpen(true);
156
+ }}
157
+ disabled={isBusy || loadingDocuments || documents.length === 0}
158
+ />
159
+ </Flex>
160
+
161
+ <TranslationsTable />
162
+
163
+ <Stack space={3}>
164
+ <Flex gap={3} align='center' justify='space-between'>
165
+ <Flex gap={2} align='center'>
166
+ <Tooltip
167
+ placement='top'
168
+ content='Imports and overrides all translations'
169
+ >
170
+ <Button
171
+ mode='ghost'
172
+ onClick={() => {
173
+ setCurrentOperation('Import All');
174
+ setIsImportAllDialogOpen(true);
175
+ }}
176
+ text={getOperationText(
177
+ 'Import All',
178
+ isBusy && currentOperation === 'Import All'
179
+ )}
180
+ icon={
181
+ isBusy && currentOperation === 'Import All'
182
+ ? null
183
+ : DownloadIcon
184
+ }
185
+ disabled={
186
+ isBusy || loadingDocuments || documents.length === 0
187
+ }
188
+ />
189
+ </Tooltip>
190
+ <Tooltip
191
+ placement='top'
192
+ content="Imports all translations that are not yet imported (according to the source document's translation.metadata document)"
193
+ >
194
+ <Button
195
+ mode='ghost'
196
+ tone='primary'
197
+ onClick={() => {
198
+ setCurrentOperation('Import Missing');
199
+ setIsImportMissingDialogOpen(true);
200
+ }}
201
+ text={getOperationText(
202
+ 'Import Missing',
203
+ isBusy && currentOperation === 'Import Missing'
204
+ )}
205
+ icon={
206
+ isBusy && currentOperation === 'Import Missing'
207
+ ? null
208
+ : DownloadIcon
209
+ }
210
+ disabled={
211
+ isBusy || loadingDocuments || documents.length === 0
212
+ }
213
+ />
214
+ </Tooltip>
215
+ <Tooltip
216
+ placement='top'
217
+ content='Replaces references in documents with the corresponding translated document reference'
218
+ >
219
+ <Button
220
+ mode='ghost'
221
+ tone='caution'
222
+ onClick={() => {
223
+ setCurrentOperation('Patch Document References');
224
+ handlePatchDocumentReferences();
225
+ }}
226
+ text={getOperationText(
227
+ 'Patch Document References',
228
+ isBusy &&
229
+ currentOperation === 'Patch Document References'
230
+ )}
231
+ icon={
232
+ isBusy &&
233
+ currentOperation === 'Patch Document References'
234
+ ? null
235
+ : LinkIcon
236
+ }
237
+ disabled={
238
+ isBusy || loadingDocuments || documents.length === 0
239
+ }
240
+ />
241
+ </Tooltip>
242
+ <Tooltip
243
+ placement='top'
244
+ content='Publishes all translations whose source document is published'
245
+ >
246
+ <Button
247
+ mode='ghost'
248
+ tone='positive'
249
+ onClick={() => {
250
+ setCurrentOperation('Publish Translations');
251
+ handlePublishAllTranslations();
252
+ }}
253
+ text={getOperationText(
254
+ 'Publish Translations',
255
+ isBusy && currentOperation === 'Publish Translations'
256
+ )}
257
+ icon={
258
+ isBusy && currentOperation === 'Publish Translations'
259
+ ? null
260
+ : PublishIcon
261
+ }
262
+ disabled={
263
+ isBusy || loadingDocuments || documents.length === 0
264
+ }
265
+ />
266
+ </Tooltip>
267
+ {importedTranslations.size ===
268
+ documents.length *
269
+ locales.filter((l) => l.enabled !== false).length &&
270
+ documents.length > 0 &&
271
+ locales.length > 0 && (
272
+ <Flex gap={2} align='center' style={{ color: 'green' }}>
273
+ <CheckmarkCircleIcon />
274
+ <Text size={1}>All translations imported</Text>
275
+ </Flex>
276
+ )}
277
+ {importedTranslations.size > 0 &&
278
+ importedTranslations.size <
279
+ documents.length *
280
+ locales.filter((l) => l.enabled !== false).length && (
281
+ <Text size={1} style={{ color: '#666' }}>
282
+ {importedTranslations.size}/
283
+ {documents.length *
284
+ locales.filter((l) => l.enabled !== false)
285
+ .length}{' '}
286
+ imported
287
+ </Text>
288
+ )}
289
+ </Flex>
290
+ <Box />
291
+ </Flex>
292
+
293
+ <BatchProgress
294
+ isActive={importProgress.isImporting}
295
+ current={importProgress.current}
296
+ total={importProgress.total}
297
+ operationName={getProgressOperationName()}
298
+ />
299
+ </Stack>
300
+ </Stack>
301
+
302
+ <Text size={2}>
303
+ For more information, see the{' '}
304
+ <Link href='https://dash.generaltranslation.com'>
305
+ General Translation Dashboard
306
+ </Link>
307
+ .
308
+ </Text>
309
+ </Stack>
310
+ </Box>
311
+
312
+ <TranslateAllDialog
313
+ isOpen={isTranslateAllDialogOpen}
314
+ onClose={() => setIsTranslateAllDialogOpen(false)}
315
+ />
316
+ <ImportAllDialog
317
+ isOpen={isImportAllDialogOpen}
318
+ onClose={() => setIsImportAllDialogOpen(false)}
319
+ />
320
+ <ImportMissingDialog
321
+ isOpen={isImportMissingDialogOpen}
322
+ onClose={() => setIsImportMissingDialogOpen(false)}
323
+ />
324
+ </Container>
325
+ );
326
+ };
327
+
328
+ const TranslationsTool: React.FC = () => {
329
+ return (
330
+ <BaseTranslationWrapper showContainer={false}>
331
+ <TranslationsProvider>
332
+ <TranslationsToolContent />
333
+ </TranslationsProvider>
334
+ </BaseTranslationWrapper>
335
+ );
336
+ };
337
+
338
+ export default TranslationsTool;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import {
3
+ ThemeProvider,
4
+ ToastProvider,
5
+ Box,
6
+ Card,
7
+ Flex,
8
+ Spinner,
9
+ Text,
10
+ } from '@sanity/ui';
11
+ import { buildTheme } from '@sanity/ui/theme';
12
+ import { useSecrets } from '../../hooks/useSecrets';
13
+ import { Secrets } from '../../types';
14
+ import { pluginConfig } from '../../adapter/core';
15
+
16
+ const theme = buildTheme();
17
+
18
+ interface BaseTranslationWrapperProps {
19
+ children: React.ReactNode;
20
+ secretsNamespace?: string;
21
+ padding?: number;
22
+ showContainer?: boolean;
23
+ }
24
+
25
+ export const BaseTranslationWrapper: React.FC<BaseTranslationWrapperProps> = ({
26
+ children,
27
+ secretsNamespace = pluginConfig.getSecretsNamespace(),
28
+ padding = 4,
29
+ showContainer = true,
30
+ }) => {
31
+ const { loading: loadingSecrets, secrets } =
32
+ useSecrets<Secrets>(secretsNamespace);
33
+
34
+ const content = (
35
+ <>
36
+ {loadingSecrets && (
37
+ <Flex padding={5} align='center' justify='center'>
38
+ <Spinner />
39
+ </Flex>
40
+ )}
41
+
42
+ {!loadingSecrets && !secrets && (
43
+ <Box padding={padding}>
44
+ <Card tone='caution' padding={[2, 3, 4, 4]} shadow={1} radius={2}>
45
+ <Text>
46
+ Can't find secrets for your translation service. Did you load them
47
+ into this dataset?
48
+ </Text>
49
+ </Card>
50
+ </Box>
51
+ )}
52
+
53
+ {!loadingSecrets && secrets && children}
54
+ </>
55
+ );
56
+
57
+ return (
58
+ <ThemeProvider theme={theme}>
59
+ <ToastProvider paddingY={7}>
60
+ {showContainer ? <Box padding={padding}>{content}</Box> : content}
61
+ </ToastProvider>
62
+ </ThemeProvider>
63
+ );
64
+ };
65
+
66
+ export interface UseTranslationSecretsResult {
67
+ loadingSecrets: boolean;
68
+ secrets: Secrets | null;
69
+ }
70
+
71
+ export const useTranslationSecrets = (
72
+ secretsNamespace?: string
73
+ ): UseTranslationSecretsResult => {
74
+ const { loading: loadingSecrets, secrets } = useSecrets<Secrets>(
75
+ secretsNamespace || 'translationService.secrets'
76
+ );
77
+
78
+ return {
79
+ loadingSecrets,
80
+ secrets,
81
+ };
82
+ };
@@ -1,3 +1,5 @@
1
+ // adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
2
+
1
3
  import { useCallback, useState } from 'react';
2
4
  import { Flex, Card, Text, Grid, Box, Button } from '@sanity/ui';
3
5
  import { DownloadIcon, CheckmarkCircleIcon } from '@sanity/icons';