gt-sanity 0.0.6 → 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 (88) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/index.d.mts +95 -73
  3. package/dist/index.d.ts +95 -73
  4. package/dist/index.js +6233 -1162
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +6292 -1193
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +9 -4
  9. package/src/adapter/core.ts +41 -4
  10. package/src/adapter/getLocales.ts +2 -2
  11. package/src/components/TranslationsProvider.tsx +942 -0
  12. package/src/components/page/BatchProgress.tsx +27 -0
  13. package/src/components/page/ImportAllDialog.tsx +51 -0
  14. package/src/components/page/ImportMissingDialog.tsx +55 -0
  15. package/src/components/page/TranslateAllDialog.tsx +55 -0
  16. package/src/components/page/TranslationsTable.tsx +81 -0
  17. package/src/components/page/TranslationsTool.tsx +299 -837
  18. package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
  19. package/src/components/shared/LocaleCheckbox.tsx +47 -0
  20. package/src/components/shared/SingleDocumentView.tsx +108 -0
  21. package/src/components/tab/TranslationView.tsx +379 -0
  22. package/src/components/tab/TranslationsTab.tsx +25 -0
  23. package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +6 -9
  24. package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +5 -24
  25. package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +3 -23
  26. package/src/configuration/baseDocumentLevelConfig/index.ts +16 -68
  27. package/src/configuration/baseFieldLevelConfig.ts +15 -50
  28. package/src/index.ts +29 -43
  29. package/src/sanity-api/findDocuments.ts +44 -0
  30. package/src/sanity-api/publishDocuments.ts +49 -0
  31. package/src/sanity-api/resolveRefs.ts +146 -0
  32. package/src/serialization/BaseDocumentMerger.ts +138 -0
  33. package/src/serialization/BaseSerializationConfig.ts +220 -0
  34. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +189 -0
  35. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +107 -0
  36. package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +397 -0
  37. package/src/serialization/__tests__/BaseDocumentDeserializer/documentLevelDeserialization.test.ts +107 -0
  38. package/src/serialization/__tests__/BaseDocumentDeserializer/fieldLevelDeserialization.test.ts +107 -0
  39. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +193 -0
  40. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +97 -0
  41. package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +36 -0
  42. package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +96 -0
  43. package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +142 -0
  44. package/src/serialization/__tests__/BaseDocumentMerger/utils.ts +52 -0
  45. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +39 -0
  46. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +8 -0
  47. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +8 -0
  48. package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +345 -0
  49. package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +53 -0
  50. package/src/serialization/__tests__/BaseDocumentSerializer/documentLevelSerialization.test.ts +120 -0
  51. package/src/serialization/__tests__/BaseDocumentSerializer/fieldLevelSerialization.test.ts +153 -0
  52. package/src/serialization/__tests__/BaseDocumentSerializer/utils.ts +27 -0
  53. package/src/serialization/__tests__/README +2 -0
  54. package/src/serialization/__tests__/__fixtures__/annotationAndInlineBlocks.json +140 -0
  55. package/src/serialization/__tests__/__fixtures__/customStyles.json +62 -0
  56. package/src/serialization/__tests__/__fixtures__/documentInlineMarks.json +70 -0
  57. package/src/serialization/__tests__/__fixtures__/documentLevelArticle.json +185 -0
  58. package/src/serialization/__tests__/__fixtures__/fieldLevelArticle.json +107 -0
  59. package/src/serialization/__tests__/__fixtures__/inlineDocumentLevelArticle.json +134 -0
  60. package/src/serialization/__tests__/__fixtures__/inlineSchema.ts +270 -0
  61. package/src/serialization/__tests__/__fixtures__/messy-html.html +26 -0
  62. package/src/serialization/__tests__/__fixtures__/nestedLanguageFields.json +54 -0
  63. package/src/serialization/__tests__/__fixtures__/schema.ts +310 -0
  64. package/src/serialization/__tests__/global.setup.ts +40 -0
  65. package/src/serialization/__tests__/helpers.ts +132 -0
  66. package/src/serialization/data.ts +82 -0
  67. package/src/serialization/deserialize/BaseDocumentDeserializer.ts +171 -0
  68. package/src/serialization/deserialize/helpers.ts +42 -0
  69. package/src/serialization/helpers.ts +18 -0
  70. package/src/serialization/index.ts +11 -0
  71. package/src/serialization/serialize/fieldFilters.ts +124 -0
  72. package/src/serialization/serialize/index.ts +284 -0
  73. package/src/serialization/types.ts +41 -0
  74. package/src/translation/importDocument.ts +4 -5
  75. package/src/translation/uploadFiles.ts +1 -1
  76. package/src/types.ts +3 -19
  77. package/src/utils/batchProcessor.ts +111 -0
  78. package/src/utils/importUtils.ts +95 -0
  79. package/src/utils/serialize.ts +25 -5
  80. package/src/utils/shared.ts +1 -1
  81. package/src/adapter/index.ts +0 -13
  82. package/src/components/NewTask.tsx +0 -251
  83. package/src/components/TaskView.tsx +0 -257
  84. package/src/components/TranslationContext.tsx +0 -24
  85. package/src/components/TranslationView.tsx +0 -114
  86. package/src/components/TranslationsTab.tsx +0 -181
  87. /package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +0 -0
  88. /package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +0 -0
@@ -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
+ };
@@ -0,0 +1,47 @@
1
+ import { useCallback } from 'react';
2
+ import { Button, Flex, Switch, Box, Text } from '@sanity/ui';
3
+ import styled from 'styled-components';
4
+ import { TranslationLocale } from '../../types';
5
+
6
+ const WrapText = styled(Box)`
7
+ white-space: normal;
8
+ `;
9
+
10
+ type LocaleCheckboxProps = {
11
+ locale: TranslationLocale;
12
+ toggle: (locale: string, shouldEnable: boolean) => void;
13
+ checked: boolean;
14
+ };
15
+
16
+ export const LocaleCheckbox = ({
17
+ locale,
18
+ toggle,
19
+ checked,
20
+ }: LocaleCheckboxProps) => {
21
+ const onClick = useCallback(
22
+ () => toggle(locale.localeId, !checked),
23
+ [locale.localeId, toggle, checked]
24
+ );
25
+
26
+ return (
27
+ <Button
28
+ mode='ghost'
29
+ onClick={onClick}
30
+ style={{ cursor: 'pointer' }}
31
+ radius={2}
32
+ >
33
+ <Flex align='center' gap={3}>
34
+ <Switch
35
+ style={{ pointerEvents: 'none' }}
36
+ onChange={onClick}
37
+ checked={checked}
38
+ />
39
+ <WrapText>
40
+ <Text size={1} weight='semibold'>
41
+ {locale.description}
42
+ </Text>
43
+ </WrapText>
44
+ </Flex>
45
+ </Button>
46
+ );
47
+ };
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import { Stack, Box, Card, Text, Flex, Spinner } from '@sanity/ui';
3
+ import { LanguageStatus } from './LanguageStatus';
4
+ import { useTranslations } from '../TranslationsProvider';
5
+ import { pluginConfig } from '../../adapter/core';
6
+
7
+ export const SingleDocumentView: React.FC = () => {
8
+ const {
9
+ documents,
10
+ locales,
11
+ loadingDocuments,
12
+ translationStatuses,
13
+ downloadStatus,
14
+ importedTranslations,
15
+ handleImportDocument,
16
+ } = useTranslations();
17
+
18
+ // Get the first (and only) document in single document mode
19
+ const document = documents[0];
20
+
21
+ if (loadingDocuments) {
22
+ return (
23
+ <Flex align='center' justify='center' padding={4}>
24
+ <Spinner />
25
+ </Flex>
26
+ );
27
+ }
28
+
29
+ if (!document) {
30
+ return (
31
+ <Card padding={4} tone='caution'>
32
+ <Text>No document found</Text>
33
+ </Card>
34
+ );
35
+ }
36
+
37
+ // Check if this is a source language document
38
+ const currentDocumentLanguage =
39
+ document[pluginConfig.getLanguageField()] || pluginConfig.getSourceLocale();
40
+ const shouldShowTranslationComponents =
41
+ currentDocumentLanguage === pluginConfig.getSourceLocale();
42
+
43
+ if (!shouldShowTranslationComponents) {
44
+ return (
45
+ <Card padding={4} tone='neutral' border>
46
+ <Text size={1} muted>
47
+ Translation tools are only available for{' '}
48
+ <code>{pluginConfig.getSourceLocale()}</code> documents.
49
+ </Text>
50
+ </Card>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <Box>
56
+ <Stack space={4}>
57
+ <Card shadow={1} padding={3}>
58
+ <Stack space={3}>
59
+ <Flex justify='space-between' align='flex-start'>
60
+ <Box flex={1}>
61
+ <Text weight='semibold' size={1}>
62
+ {document._id?.replace('drafts.', '') || document._id}
63
+ </Text>
64
+ <Text size={0} muted style={{ marginTop: '2px' }}>
65
+ {document._type}
66
+ </Text>
67
+ </Box>
68
+ </Flex>
69
+
70
+ <Stack space={2}>
71
+ {locales.length > 0 ? (
72
+ locales
73
+ .filter((locale) => locale.enabled !== false)
74
+ .map((locale) => {
75
+ const documentId =
76
+ document._id?.replace('drafts.', '') || document._id;
77
+ const key = `${documentId}:${locale.localeId}`;
78
+ const status = translationStatuses.get(key);
79
+ const isDownloaded = downloadStatus.downloaded.has(key);
80
+ const isImported = importedTranslations.has(key);
81
+
82
+ return (
83
+ <LanguageStatus
84
+ key={`${document._id}-${locale.localeId}`}
85
+ title={locale.description || locale.localeId}
86
+ progress={status?.progress || 0}
87
+ isImported={isImported || isDownloaded}
88
+ importFile={async () => {
89
+ await handleImportDocument(
90
+ documentId,
91
+ locale.localeId
92
+ );
93
+ }}
94
+ />
95
+ );
96
+ })
97
+ ) : (
98
+ <Text size={1} muted>
99
+ No locales configured
100
+ </Text>
101
+ )}
102
+ </Stack>
103
+ </Stack>
104
+ </Card>
105
+ </Stack>
106
+ </Box>
107
+ );
108
+ };
@@ -0,0 +1,379 @@
1
+ // adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
2
+
3
+ /**
4
+ * Add cleanup function to cancel async tasks
5
+ */
6
+
7
+ import { useMemo, useState, useCallback, useEffect } from 'react';
8
+ import {
9
+ Stack,
10
+ Text,
11
+ Card,
12
+ Button,
13
+ Grid,
14
+ Box,
15
+ Flex,
16
+ Switch,
17
+ Tooltip,
18
+ } from '@sanity/ui';
19
+ import { pluginConfig } from '../../adapter/core';
20
+ import { useTranslations } from '../TranslationsProvider';
21
+ import { LanguageStatus } from '../shared/LanguageStatus';
22
+ import { LocaleCheckbox } from '../shared/LocaleCheckbox';
23
+ import { DownloadIcon, LinkIcon } from '@sanity/icons';
24
+
25
+ export const TranslationView = () => {
26
+ const {
27
+ documents,
28
+ locales,
29
+ translationStatuses,
30
+ isBusy,
31
+ handleTranslateAll,
32
+ handleImportDocument,
33
+ handleRefreshAll,
34
+ isRefreshing,
35
+ importedTranslations,
36
+ setLocales,
37
+ handlePatchDocumentReferences,
38
+ } = useTranslations();
39
+
40
+ const [autoImport, setAutoImport] = useState(false);
41
+ const [isImporting, setIsImporting] = useState(false);
42
+ const [autoRefresh, setAutoRefresh] = useState(true);
43
+
44
+ // Get the single document (first document in single document mode)
45
+ const document = documents[0];
46
+
47
+ // Extract the current document's language from the language field
48
+ const currentDocumentLanguage = useMemo(() => {
49
+ if (!document) return null;
50
+
51
+ // Get the language from the document's language field
52
+ const languageField = pluginConfig.getLanguageField();
53
+ const documentLanguage = document[languageField];
54
+
55
+ // If no language field is set, assume it's the source language
56
+ return documentLanguage || pluginConfig.getSourceLocale();
57
+ }, [document]);
58
+
59
+ // Only show translation components if we're on a source language document
60
+ const shouldShowTranslationComponents = useMemo(() => {
61
+ if (!currentDocumentLanguage) return false;
62
+ return currentDocumentLanguage === pluginConfig.getSourceLocale();
63
+ }, [currentDocumentLanguage]);
64
+
65
+ // Get available locales (excluding source locale)
66
+ const availableLocales = useMemo(() => {
67
+ const sourceLocale = pluginConfig.getSourceLocale();
68
+ return locales.filter(
69
+ (locale) => locale.enabled !== false && locale.localeId !== sourceLocale
70
+ );
71
+ }, [locales]);
72
+
73
+ // Get document ID for status tracking
74
+ const documentId = useMemo(() => {
75
+ if (!document) return null;
76
+ return document._id?.replace('drafts.', '') || document._id;
77
+ }, [document]);
78
+
79
+ // Auto import functionality
80
+ const checkAndImportCompletedTranslations = useCallback(async () => {
81
+ if (!autoImport || isImporting || !documentId) return;
82
+
83
+ const completedTranslations = availableLocales.filter((locale) => {
84
+ const key = `${documentId}:${locale.localeId}`;
85
+ const status = translationStatuses.get(key);
86
+ return (
87
+ (status?.progress || 0) >= 100 &&
88
+ status?.isReady &&
89
+ !importedTranslations.has(key)
90
+ );
91
+ });
92
+
93
+ if (completedTranslations.length === 0) return;
94
+
95
+ setIsImporting(true);
96
+ try {
97
+ for (const locale of completedTranslations) {
98
+ await handleImportDocument(documentId, locale.localeId);
99
+ }
100
+ } finally {
101
+ setIsImporting(false);
102
+ }
103
+ }, [
104
+ autoImport,
105
+ isImporting,
106
+ documentId,
107
+ availableLocales,
108
+ translationStatuses,
109
+ importedTranslations,
110
+ handleImportDocument,
111
+ ]);
112
+
113
+ const handleImportAll = useCallback(async () => {
114
+ if (isImporting || !documentId) return;
115
+
116
+ setIsImporting(true);
117
+ try {
118
+ const readyTranslations = availableLocales.filter((locale) => {
119
+ const key = `${documentId}:${locale.localeId}`;
120
+ const status = translationStatuses.get(key);
121
+ return status?.isReady && !importedTranslations.has(key);
122
+ });
123
+
124
+ for (const locale of readyTranslations) {
125
+ await handleImportDocument(documentId, locale.localeId);
126
+ }
127
+ } finally {
128
+ setIsImporting(false);
129
+ }
130
+ }, [
131
+ isImporting,
132
+ documentId,
133
+ availableLocales,
134
+ translationStatuses,
135
+ importedTranslations,
136
+ handleImportDocument,
137
+ ]);
138
+
139
+ // Check for completed translations on status updates
140
+ useEffect(() => {
141
+ checkAndImportCompletedTranslations();
142
+ }, [checkAndImportCompletedTranslations]);
143
+
144
+ // Auto refresh functionality
145
+ useEffect(() => {
146
+ if (!autoRefresh || !documentId || availableLocales.length === 0) return;
147
+
148
+ const interval = setInterval(async () => {
149
+ await handleRefreshAll();
150
+ await checkAndImportCompletedTranslations();
151
+ }, 10000);
152
+
153
+ return () => clearInterval(interval);
154
+ }, [
155
+ autoRefresh,
156
+ documentId,
157
+ availableLocales.length,
158
+ handleRefreshAll,
159
+ checkAndImportCompletedTranslations,
160
+ ]);
161
+
162
+ useEffect(() => {
163
+ const initialRefresh = async () => {
164
+ await handleRefreshAll();
165
+ await checkAndImportCompletedTranslations();
166
+ };
167
+ initialRefresh();
168
+ }, []);
169
+
170
+ // Locale toggle functionality
171
+ const toggleLocale = useCallback(
172
+ (localeId: string, shouldEnable: boolean) => {
173
+ const updatedLocales = locales.map((locale) =>
174
+ locale.localeId === localeId
175
+ ? { ...locale, enabled: shouldEnable }
176
+ : locale
177
+ );
178
+ setLocales(updatedLocales);
179
+ },
180
+ [locales, setLocales]
181
+ );
182
+
183
+ const toggleAllLocales = useCallback(() => {
184
+ const sourceLocale = pluginConfig.getSourceLocale();
185
+ const nonSourceLocales = locales.filter(
186
+ (locale) => locale.localeId !== sourceLocale
187
+ );
188
+ const allEnabled = nonSourceLocales.every(
189
+ (locale) => locale.enabled === true || locale.enabled === undefined
190
+ );
191
+
192
+ const updatedLocales = locales.map((locale) =>
193
+ locale.localeId === sourceLocale
194
+ ? locale // Don't change source locale
195
+ : { ...locale, enabled: !allEnabled }
196
+ );
197
+ setLocales(updatedLocales);
198
+ }, [locales, setLocales]);
199
+
200
+ // Show message if we're not on a source language document
201
+ if (!shouldShowTranslationComponents) {
202
+ return (
203
+ <Card padding={4} tone='neutral' border>
204
+ <Text size={1} muted>
205
+ Translation tools are only available for{' '}
206
+ <code>{pluginConfig.getSourceLocale()}</code> documents.
207
+ </Text>
208
+ </Card>
209
+ );
210
+ }
211
+
212
+ return (
213
+ <Stack space={6} padding={4}>
214
+ {/* Generate Translations Section */}
215
+ <Stack space={4}>
216
+ <Text as='h2' weight='semibold' size={2}>
217
+ Generate Translations
218
+ </Text>
219
+
220
+ {/* Locale Selection */}
221
+ <Stack space={3}>
222
+ <Flex align='center' justify='space-between'>
223
+ <Text weight='semibold' size={1}>
224
+ {availableLocales.length === 1
225
+ ? 'Select locale'
226
+ : 'Select locales'}
227
+ </Text>
228
+ <Button
229
+ fontSize={1}
230
+ padding={2}
231
+ text='Toggle All'
232
+ onClick={toggleAllLocales}
233
+ />
234
+ </Flex>
235
+
236
+ <Grid columns={[1, 1, 2, 3]} gap={1}>
237
+ {locales
238
+ .filter(
239
+ (locale) => locale.localeId !== pluginConfig.getSourceLocale()
240
+ )
241
+ .map((locale) => (
242
+ <LocaleCheckbox
243
+ key={locale.localeId}
244
+ locale={locale}
245
+ toggle={toggleLocale}
246
+ checked={
247
+ locale.enabled === true || locale.enabled === undefined
248
+ }
249
+ />
250
+ ))}
251
+ </Grid>
252
+ </Stack>
253
+
254
+ <Button
255
+ onClick={() => {
256
+ setAutoImport(true);
257
+ handleTranslateAll();
258
+ }}
259
+ disabled={isBusy || !availableLocales.length}
260
+ tone='positive'
261
+ text={isBusy ? 'Creating translations...' : 'Generate Translations'}
262
+ />
263
+ </Stack>
264
+
265
+ {/* Translation Status Section */}
266
+ {documentId && availableLocales.length > 0 && (
267
+ <Stack space={4}>
268
+ <Flex align='center' justify='space-between'>
269
+ <Text as='h2' weight='semibold' size={2}>
270
+ Translation Status
271
+ </Text>
272
+ <Flex gap={3} align='center'>
273
+ <Flex gap={2} align='center'>
274
+ <Text size={1}>Auto-refresh</Text>
275
+ <Switch
276
+ checked={autoRefresh}
277
+ onChange={() => setAutoRefresh(!autoRefresh)}
278
+ />
279
+ </Flex>
280
+ <Button
281
+ fontSize={1}
282
+ padding={2}
283
+ text='Refresh Status'
284
+ onClick={handleRefreshAll}
285
+ disabled={isRefreshing}
286
+ />
287
+ </Flex>
288
+ </Flex>
289
+
290
+ <Box>
291
+ {availableLocales.map((locale) => {
292
+ const key = `${documentId}:${locale.localeId}`;
293
+ const status = translationStatuses.get(key);
294
+ const progress = status?.progress || 0;
295
+ const isImported = importedTranslations.has(key);
296
+
297
+ return (
298
+ <LanguageStatus
299
+ key={key}
300
+ title={locale.description}
301
+ progress={progress}
302
+ isImported={isImported}
303
+ importFile={async () => {
304
+ if (!isImported && status?.isReady) {
305
+ await handleImportDocument(documentId, locale.localeId);
306
+ }
307
+ }}
308
+ />
309
+ );
310
+ })}
311
+ </Box>
312
+
313
+ {/* Import Controls */}
314
+ <Stack space={3}>
315
+ <Flex gap={3} align='center' justify='space-between'>
316
+ <Flex gap={2} align='center'>
317
+ <Button
318
+ mode='ghost'
319
+ onClick={handleImportAll}
320
+ text={isImporting ? 'Importing...' : 'Import All'}
321
+ icon={DownloadIcon}
322
+ disabled={
323
+ isImporting ||
324
+ availableLocales.every((locale) => {
325
+ const key = `${documentId}:${locale.localeId}`;
326
+ const status = translationStatuses.get(key);
327
+ return !status?.isReady || importedTranslations.has(key);
328
+ })
329
+ }
330
+ />
331
+ <Text size={1} muted>
332
+ Imported{' '}
333
+ {
334
+ availableLocales.filter((locale) => {
335
+ const key = `${documentId}:${locale.localeId}`;
336
+ return importedTranslations.has(key);
337
+ }).length
338
+ }
339
+ /
340
+ {
341
+ availableLocales.filter((locale) => {
342
+ const key = `${documentId}:${locale.localeId}`;
343
+ const status = translationStatuses.get(key);
344
+ return status?.isReady;
345
+ }).length
346
+ }
347
+ </Text>
348
+ </Flex>
349
+ <Flex gap={2} align='center' style={{ whiteSpace: 'nowrap' }}>
350
+ <Text size={1}>Auto-import when complete</Text>
351
+ <Switch
352
+ checked={autoImport}
353
+ onChange={() => setAutoImport(!autoImport)}
354
+ disabled={isImporting}
355
+ />
356
+ </Flex>
357
+ </Flex>
358
+
359
+ <Flex justify='flex-start'>
360
+ <Tooltip
361
+ placement='top'
362
+ content={`Replaces references to ${pluginConfig.getSourceLocale()} documents in this document with the corresponding translated document reference`}
363
+ >
364
+ <Button
365
+ mode='ghost'
366
+ tone='caution'
367
+ onClick={handlePatchDocumentReferences}
368
+ text={isBusy ? 'Patching...' : 'Patch Document References'}
369
+ icon={isBusy ? null : LinkIcon}
370
+ disabled={isBusy}
371
+ />
372
+ </Tooltip>
373
+ </Flex>
374
+ </Stack>
375
+ </Stack>
376
+ )}
377
+ </Stack>
378
+ );
379
+ };
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { SanityDocument } from 'sanity';
3
+ import { BaseTranslationWrapper } from '../shared/BaseTranslationWrapper';
4
+ import { TranslationsProvider } from '../TranslationsProvider';
5
+ import { TranslationView } from './TranslationView';
6
+
7
+ type TranslationTabProps = {
8
+ document: {
9
+ displayed: SanityDocument;
10
+ };
11
+ };
12
+
13
+ const TranslationTab = (props: TranslationTabProps) => {
14
+ const { displayed } = props.document;
15
+
16
+ return (
17
+ <BaseTranslationWrapper showContainer={false}>
18
+ <TranslationsProvider singleDocument={displayed}>
19
+ <TranslationView />
20
+ </TranslationsProvider>
21
+ </BaseTranslationWrapper>
22
+ );
23
+ };
24
+
25
+ export default TranslationTab;
@@ -1,7 +1,7 @@
1
1
  // adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
2
2
 
3
3
  import { SanityClient, SanityDocument, SanityDocumentLike } from 'sanity';
4
- import { BaseDocumentMerger } from 'sanity-naive-html-serializer';
4
+ import { BaseDocumentMerger } from '../../serialization';
5
5
 
6
6
  import { findLatestDraft } from '../utils/findLatestDraft';
7
7
  import { findDocumentAtRevision } from '../utils/findDocumentAtRevision';
@@ -9,7 +9,7 @@ import { createI18nDocAndPatchMetadata } from './helpers/createI18nDocAndPatchMe
9
9
  import { getOrCreateTranslationMetadata } from './helpers/getOrCreateTranslationMetadata';
10
10
  import { patchI18nDoc } from './helpers/patchI18nDoc';
11
11
  import type { GTFile } from '../../types';
12
- import { gtConfig } from '../../adapter/core';
12
+ import { pluginConfig } from '../../adapter/core';
13
13
 
14
14
  export const documentLevelPatch = async (
15
15
  docInfo: GTFile,
@@ -17,10 +17,9 @@ export const documentLevelPatch = async (
17
17
  localeId: string,
18
18
  client: SanityClient,
19
19
  languageField: string = 'language',
20
- mergeWithTargetLocale: boolean = false,
21
- publish: boolean = false
20
+ mergeWithTargetLocale: boolean = false
22
21
  ): Promise<void> => {
23
- const baseLanguage = gtConfig.getSourceLocale();
22
+ const baseLanguage = pluginConfig.getSourceLocale();
24
23
  //this is the document we use to merge with the translated fields
25
24
  let baseDoc: SanityDocument | null = null;
26
25
 
@@ -100,8 +99,7 @@ export const documentLevelPatch = async (
100
99
  baseDoc,
101
100
  merged,
102
101
  translatedFields,
103
- client,
104
- publish
102
+ client
105
103
  );
106
104
  }
107
105
  //otherwise, create a new document
@@ -114,8 +112,7 @@ export const documentLevelPatch = async (
114
112
  client,
115
113
  translationMetadata,
116
114
  docInfo.documentId,
117
- languageField,
118
- publish
115
+ languageField
119
116
  );
120
117
  }
121
118
  };