gt-sanity 0.0.6 → 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.
- package/LICENSE.md +1 -1
- package/dist/index.d.mts +95 -73
- package/dist/index.d.ts +95 -73
- package/dist/index.js +9066 -1207
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9083 -1197
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -3
- package/src/adapter/core.ts +41 -4
- package/src/adapter/getLocales.ts +2 -2
- package/src/components/TranslationsProvider.tsx +942 -0
- package/src/components/page/BatchProgress.tsx +27 -0
- package/src/components/page/ImportAllDialog.tsx +51 -0
- package/src/components/page/ImportMissingDialog.tsx +55 -0
- package/src/components/page/TranslateAllDialog.tsx +55 -0
- package/src/components/page/TranslationsTable.tsx +81 -0
- package/src/components/page/TranslationsTool.tsx +299 -837
- package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
- package/src/components/shared/LocaleCheckbox.tsx +47 -0
- package/src/components/shared/SingleDocumentView.tsx +108 -0
- package/src/components/tab/TranslationView.tsx +379 -0
- package/src/components/tab/TranslationsTab.tsx +25 -0
- package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +6 -9
- package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +5 -24
- package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +3 -23
- package/src/configuration/baseDocumentLevelConfig/index.ts +16 -68
- package/src/configuration/baseFieldLevelConfig.ts +15 -50
- package/src/index.ts +29 -43
- package/src/sanity-api/findDocuments.ts +44 -0
- package/src/sanity-api/publishDocuments.ts +49 -0
- package/src/sanity-api/resolveRefs.ts +146 -0
- package/src/serialization/BaseDocumentMerger.ts +138 -0
- package/src/serialization/BaseSerializationConfig.ts +220 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +189 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +107 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +397 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/documentLevelDeserialization.test.ts +107 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/fieldLevelDeserialization.test.ts +107 -0
- package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +193 -0
- package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +97 -0
- package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +36 -0
- package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +96 -0
- package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +142 -0
- package/src/serialization/__tests__/BaseDocumentMerger/utils.ts +52 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +39 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +8 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +8 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +345 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +53 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/documentLevelSerialization.test.ts +120 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/fieldLevelSerialization.test.ts +153 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/utils.ts +27 -0
- package/src/serialization/__tests__/README +2 -0
- package/src/serialization/__tests__/__fixtures__/annotationAndInlineBlocks.json +140 -0
- package/src/serialization/__tests__/__fixtures__/customStyles.json +62 -0
- package/src/serialization/__tests__/__fixtures__/documentInlineMarks.json +70 -0
- package/src/serialization/__tests__/__fixtures__/documentLevelArticle.json +185 -0
- package/src/serialization/__tests__/__fixtures__/fieldLevelArticle.json +107 -0
- package/src/serialization/__tests__/__fixtures__/inlineDocumentLevelArticle.json +134 -0
- package/src/serialization/__tests__/__fixtures__/inlineSchema.ts +270 -0
- package/src/serialization/__tests__/__fixtures__/messy-html.html +26 -0
- package/src/serialization/__tests__/__fixtures__/nestedLanguageFields.json +54 -0
- package/src/serialization/__tests__/__fixtures__/schema.ts +310 -0
- package/src/serialization/__tests__/global.setup.ts +40 -0
- package/src/serialization/__tests__/helpers.ts +132 -0
- package/src/serialization/data.ts +82 -0
- package/src/serialization/deserialize/BaseDocumentDeserializer.ts +171 -0
- package/src/serialization/deserialize/helpers.ts +42 -0
- package/src/serialization/helpers.ts +18 -0
- package/src/serialization/index.ts +11 -0
- package/src/serialization/serialize/fieldFilters.ts +124 -0
- package/src/serialization/serialize/index.ts +284 -0
- package/src/serialization/types.ts +41 -0
- package/src/translation/importDocument.ts +4 -5
- package/src/translation/uploadFiles.ts +1 -1
- package/src/types.ts +3 -19
- package/src/utils/batchProcessor.ts +111 -0
- package/src/utils/importUtils.ts +95 -0
- package/src/utils/serialize.ts +25 -5
- package/src/utils/shared.ts +1 -1
- package/src/adapter/index.ts +0 -13
- package/src/components/NewTask.tsx +0 -251
- package/src/components/TaskView.tsx +0 -257
- package/src/components/TranslationContext.tsx +0 -24
- package/src/components/TranslationView.tsx +0 -114
- package/src/components/TranslationsTab.tsx +0 -181
- /package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +0 -0
- /package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +0 -0
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
-
|
|
3
|
-
import { useCallback, useContext, useState, useEffect } from 'react';
|
|
4
|
-
import { Box, Button, Flex, Text, Stack, useToast, Switch } from '@sanity/ui';
|
|
5
|
-
import {
|
|
6
|
-
ArrowTopRightIcon,
|
|
7
|
-
DownloadIcon,
|
|
8
|
-
CheckmarkCircleIcon,
|
|
9
|
-
} from '@sanity/icons';
|
|
10
|
-
|
|
11
|
-
import { TranslationContext } from './TranslationContext';
|
|
12
|
-
import { TranslationLocale, TranslationTask } from '../types';
|
|
13
|
-
import { LanguageStatus } from './LanguageStatus';
|
|
14
|
-
|
|
15
|
-
type JobProps = {
|
|
16
|
-
task: TranslationTask;
|
|
17
|
-
locales: TranslationLocale[];
|
|
18
|
-
refreshTask: () => Promise<void>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const getLocale = (
|
|
22
|
-
localeId: string,
|
|
23
|
-
locales: TranslationLocale[]
|
|
24
|
-
): TranslationLocale | undefined =>
|
|
25
|
-
locales.find((l) => l.localeId === localeId);
|
|
26
|
-
|
|
27
|
-
export const TaskView = ({ task, locales, refreshTask }: JobProps) => {
|
|
28
|
-
const context = useContext(TranslationContext);
|
|
29
|
-
const toast = useToast();
|
|
30
|
-
|
|
31
|
-
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
32
|
-
const [autoRefresh, setAutoRefresh] = useState(true);
|
|
33
|
-
const [isBusy, setIsBusy] = useState(false);
|
|
34
|
-
const [autoImport, setAutoImport] = useState(true);
|
|
35
|
-
const [importedFiles, setImportedFiles] = useState<Set<string>>(new Set());
|
|
36
|
-
|
|
37
|
-
const importFile = useCallback(
|
|
38
|
-
async (localeId: string) => {
|
|
39
|
-
if (!context) {
|
|
40
|
-
toast.push({
|
|
41
|
-
title:
|
|
42
|
-
'Missing context, unable to import translation. Try refreshing or clicking away from this tab and back.',
|
|
43
|
-
status: 'error',
|
|
44
|
-
closable: true,
|
|
45
|
-
});
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const locale = getLocale(localeId, locales);
|
|
50
|
-
const localeTitle = locale?.description || localeId;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const translation = await context.adapter.getTranslation(
|
|
54
|
-
task.document,
|
|
55
|
-
localeId,
|
|
56
|
-
context.secrets
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
const sanityId = context.localeIdAdapter
|
|
60
|
-
? await context.localeIdAdapter(localeId)
|
|
61
|
-
: localeId;
|
|
62
|
-
|
|
63
|
-
await context.importTranslation(sanityId, translation);
|
|
64
|
-
|
|
65
|
-
setImportedFiles((prev) => new Set([...prev, localeId]));
|
|
66
|
-
|
|
67
|
-
toast.push({
|
|
68
|
-
title: `Imported ${localeTitle} translation`,
|
|
69
|
-
status: 'success',
|
|
70
|
-
closable: true,
|
|
71
|
-
});
|
|
72
|
-
} catch (err) {
|
|
73
|
-
let errorMsg;
|
|
74
|
-
if (err instanceof Error) {
|
|
75
|
-
errorMsg = err.message;
|
|
76
|
-
} else {
|
|
77
|
-
errorMsg = err ? String(err) : null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
toast.push({
|
|
81
|
-
title: `Error getting ${localeTitle} translation`,
|
|
82
|
-
description: errorMsg,
|
|
83
|
-
status: 'error',
|
|
84
|
-
closable: true,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
[locales, context, task.document, toast]
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const checkAndImportCompletedFiles = useCallback(async () => {
|
|
92
|
-
if (!autoImport || isBusy) return;
|
|
93
|
-
|
|
94
|
-
const completedFiles = task.locales.filter(
|
|
95
|
-
(locale) =>
|
|
96
|
-
(locale.progress || 0) >= 100 && !importedFiles.has(locale.localeId)
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
if (completedFiles.length === 0) return;
|
|
100
|
-
|
|
101
|
-
setIsBusy(true);
|
|
102
|
-
try {
|
|
103
|
-
for (const locale of completedFiles) {
|
|
104
|
-
await importFile(locale.localeId);
|
|
105
|
-
}
|
|
106
|
-
} finally {
|
|
107
|
-
setIsBusy(false);
|
|
108
|
-
}
|
|
109
|
-
}, [autoImport, isBusy, task.locales, importedFiles, importFile]);
|
|
110
|
-
|
|
111
|
-
const handleRefreshClick = useCallback(async () => {
|
|
112
|
-
if (isRefreshing) return;
|
|
113
|
-
setIsRefreshing(true);
|
|
114
|
-
await refreshTask();
|
|
115
|
-
await checkAndImportCompletedFiles();
|
|
116
|
-
setIsRefreshing(false);
|
|
117
|
-
}, [refreshTask, setIsRefreshing, checkAndImportCompletedFiles]);
|
|
118
|
-
|
|
119
|
-
const handleImportAll = useCallback(async () => {
|
|
120
|
-
if (isBusy) return;
|
|
121
|
-
setIsBusy(true);
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const filesToImport = task.locales.filter(
|
|
125
|
-
(locale) => !importedFiles.has(locale.localeId)
|
|
126
|
-
);
|
|
127
|
-
for (const locale of filesToImport) {
|
|
128
|
-
await importFile(locale.localeId);
|
|
129
|
-
}
|
|
130
|
-
} finally {
|
|
131
|
-
setIsBusy(false);
|
|
132
|
-
}
|
|
133
|
-
}, [task.locales, importFile, isBusy, importedFiles]);
|
|
134
|
-
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
if (!autoRefresh || importedFiles.size === task.locales.length) return;
|
|
137
|
-
|
|
138
|
-
const interval = setInterval(async () => {
|
|
139
|
-
await handleRefreshClick();
|
|
140
|
-
}, 5000);
|
|
141
|
-
|
|
142
|
-
return () => clearInterval(interval);
|
|
143
|
-
}, [
|
|
144
|
-
handleRefreshClick,
|
|
145
|
-
autoRefresh,
|
|
146
|
-
importedFiles.size,
|
|
147
|
-
task.locales.length,
|
|
148
|
-
]);
|
|
149
|
-
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
checkAndImportCompletedFiles();
|
|
152
|
-
}, [checkAndImportCompletedFiles, task.locales]);
|
|
153
|
-
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
setImportedFiles((prev) => {
|
|
156
|
-
const newSet = new Set<string>();
|
|
157
|
-
for (const localeId of prev) {
|
|
158
|
-
if (task.locales.some((locale) => locale.localeId === localeId)) {
|
|
159
|
-
newSet.add(localeId);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return newSet;
|
|
163
|
-
});
|
|
164
|
-
}, [task.locales]);
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<Stack space={4}>
|
|
168
|
-
<Flex align='center' justify='space-between'>
|
|
169
|
-
<Text as='h2' weight='semibold' size={2}>
|
|
170
|
-
Translation Progress
|
|
171
|
-
</Text>
|
|
172
|
-
|
|
173
|
-
<Flex gap={3} align='center'>
|
|
174
|
-
<Flex gap={2} align='center'>
|
|
175
|
-
<Text size={1}>Auto-refresh</Text>
|
|
176
|
-
<Switch
|
|
177
|
-
checked={autoRefresh}
|
|
178
|
-
onChange={() => setAutoRefresh(!autoRefresh)}
|
|
179
|
-
/>
|
|
180
|
-
</Flex>
|
|
181
|
-
{task.linkToVendorTask && (
|
|
182
|
-
<Button
|
|
183
|
-
as='a'
|
|
184
|
-
text='View Job'
|
|
185
|
-
iconRight={ArrowTopRightIcon}
|
|
186
|
-
href={task.linkToVendorTask}
|
|
187
|
-
target='_blank'
|
|
188
|
-
rel='noreferrer noopener'
|
|
189
|
-
fontSize={1}
|
|
190
|
-
padding={2}
|
|
191
|
-
mode='bleed'
|
|
192
|
-
/>
|
|
193
|
-
)}
|
|
194
|
-
<Button
|
|
195
|
-
fontSize={1}
|
|
196
|
-
padding={2}
|
|
197
|
-
text='Refresh Status'
|
|
198
|
-
onClick={handleRefreshClick}
|
|
199
|
-
disabled={isRefreshing}
|
|
200
|
-
/>
|
|
201
|
-
</Flex>
|
|
202
|
-
</Flex>
|
|
203
|
-
|
|
204
|
-
<Box>
|
|
205
|
-
{task.locales.map((localeTask) => {
|
|
206
|
-
const reportPercent = localeTask.progress || 0;
|
|
207
|
-
const locale = getLocale(localeTask.localeId, locales);
|
|
208
|
-
return (
|
|
209
|
-
<LanguageStatus
|
|
210
|
-
key={[task.document.documentId, localeTask.localeId].join('.')}
|
|
211
|
-
importFile={async () => {
|
|
212
|
-
await importFile(localeTask.localeId);
|
|
213
|
-
}}
|
|
214
|
-
title={locale?.description || localeTask.localeId}
|
|
215
|
-
progress={reportPercent}
|
|
216
|
-
isImported={importedFiles.has(localeTask.localeId)}
|
|
217
|
-
/>
|
|
218
|
-
);
|
|
219
|
-
})}
|
|
220
|
-
</Box>
|
|
221
|
-
<Stack space={3}>
|
|
222
|
-
<Flex gap={3} align='center' justify='space-between'>
|
|
223
|
-
<Flex gap={2} align='center'>
|
|
224
|
-
<Button
|
|
225
|
-
mode='ghost'
|
|
226
|
-
onClick={handleImportAll}
|
|
227
|
-
text={isBusy ? 'Importing...' : 'Import All'}
|
|
228
|
-
icon={isBusy ? null : DownloadIcon}
|
|
229
|
-
disabled={isBusy || importedFiles.size === task.locales.length}
|
|
230
|
-
/>
|
|
231
|
-
{importedFiles.size === task.locales.length &&
|
|
232
|
-
task.locales.length > 0 && (
|
|
233
|
-
<Flex gap={2} align='center' style={{ color: 'green' }}>
|
|
234
|
-
<CheckmarkCircleIcon />
|
|
235
|
-
<Text size={1}>All translations imported</Text>
|
|
236
|
-
</Flex>
|
|
237
|
-
)}
|
|
238
|
-
{importedFiles.size > 0 &&
|
|
239
|
-
importedFiles.size < task.locales.length && (
|
|
240
|
-
<Text size={1} style={{ color: '#666' }}>
|
|
241
|
-
{importedFiles.size}/{task.locales.length} imported
|
|
242
|
-
</Text>
|
|
243
|
-
)}
|
|
244
|
-
</Flex>
|
|
245
|
-
<Flex gap={2} align='center' style={{ whiteSpace: 'nowrap' }}>
|
|
246
|
-
<Text size={1}>Auto-import when complete</Text>
|
|
247
|
-
<Switch
|
|
248
|
-
checked={autoImport}
|
|
249
|
-
onChange={() => setAutoImport(!autoImport)}
|
|
250
|
-
disabled={isBusy}
|
|
251
|
-
/>
|
|
252
|
-
</Flex>
|
|
253
|
-
</Flex>
|
|
254
|
-
</Stack>
|
|
255
|
-
</Stack>
|
|
256
|
-
);
|
|
257
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { SanityDocument } from 'sanity';
|
|
5
|
-
import { GTSerializedDocument } from '../types';
|
|
6
|
-
import { Adapter, GTFile, Secrets, WorkflowIdentifiers } from '../types';
|
|
7
|
-
|
|
8
|
-
export type ContextProps = {
|
|
9
|
-
documentInfo: GTFile;
|
|
10
|
-
document: SanityDocument;
|
|
11
|
-
languageField: string;
|
|
12
|
-
adapter: Adapter;
|
|
13
|
-
importTranslation: (languageId: string, document: string) => Promise<void>;
|
|
14
|
-
exportForTranslation: (documentInfo: GTFile) => Promise<GTSerializedDocument>;
|
|
15
|
-
secrets: Secrets;
|
|
16
|
-
workflowOptions?: WorkflowIdentifiers[];
|
|
17
|
-
localeIdAdapter?: (id: string) => string | Promise<string>;
|
|
18
|
-
callbackUrl?: string;
|
|
19
|
-
mergeWithTargetLocale?: boolean;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const TranslationContext = React.createContext<ContextProps | null>(
|
|
23
|
-
null
|
|
24
|
-
);
|
|
@@ -1,114 +0,0 @@
|
|
|
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 { useCallback, useContext, useEffect, useState, useMemo } from 'react';
|
|
8
|
-
import { Stack, useToast, Text, Card } from '@sanity/ui';
|
|
9
|
-
import { TranslationContext } from './TranslationContext';
|
|
10
|
-
import { gtConfig } from '../adapter/core';
|
|
11
|
-
|
|
12
|
-
import { NewTask } from './NewTask';
|
|
13
|
-
import { TaskView } from './TaskView';
|
|
14
|
-
import { TranslationTask, TranslationLocale } from '../types';
|
|
15
|
-
|
|
16
|
-
export const TranslationView = () => {
|
|
17
|
-
const [locales, setLocales] = useState<TranslationLocale[]>([]);
|
|
18
|
-
const [task, setTask] = useState<TranslationTask | null>(null);
|
|
19
|
-
|
|
20
|
-
const context = useContext(TranslationContext);
|
|
21
|
-
const toast = useToast();
|
|
22
|
-
|
|
23
|
-
// Extract the current document's language from the language field
|
|
24
|
-
const currentDocumentLanguage = useMemo(() => {
|
|
25
|
-
if (!context?.document || !context?.languageField) return null;
|
|
26
|
-
|
|
27
|
-
// Get the language from the document's language field
|
|
28
|
-
const documentLanguage = context.document[context.languageField];
|
|
29
|
-
|
|
30
|
-
// If no language field is set, assume it's the source language
|
|
31
|
-
return documentLanguage || gtConfig.getSourceLocale();
|
|
32
|
-
}, [context?.document, context?.languageField]);
|
|
33
|
-
|
|
34
|
-
// Only show translation components if we're on a source language document
|
|
35
|
-
const shouldShowTranslationComponents = useMemo(() => {
|
|
36
|
-
if (!currentDocumentLanguage) return false;
|
|
37
|
-
return currentDocumentLanguage === gtConfig.getSourceLocale();
|
|
38
|
-
}, [currentDocumentLanguage]);
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
async function fetchData() {
|
|
42
|
-
if (!context) {
|
|
43
|
-
toast.push({
|
|
44
|
-
title: 'Unable to load translation data: missing context',
|
|
45
|
-
status: 'error',
|
|
46
|
-
closable: true,
|
|
47
|
-
});
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const locales = await context.adapter.getLocales(context.secrets);
|
|
52
|
-
setLocales(locales);
|
|
53
|
-
try {
|
|
54
|
-
const task = await context?.adapter.getTranslationTask(
|
|
55
|
-
context.documentInfo,
|
|
56
|
-
context.secrets
|
|
57
|
-
);
|
|
58
|
-
setTask(task);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
let errorMsg;
|
|
61
|
-
if (err instanceof Error) {
|
|
62
|
-
errorMsg = err.message;
|
|
63
|
-
} else {
|
|
64
|
-
errorMsg = err ? String(err) : null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Hacky bypass for when a document is not yet translated and has never been uploaded
|
|
68
|
-
if (errorMsg?.toLowerCase().includes('no source file found')) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
toast.push({
|
|
73
|
-
title: `Error creating translation job`,
|
|
74
|
-
description: errorMsg,
|
|
75
|
-
status: 'error',
|
|
76
|
-
closable: true,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
fetchData();
|
|
82
|
-
}, [context, toast]);
|
|
83
|
-
|
|
84
|
-
const refreshTask = useCallback(async () => {
|
|
85
|
-
const task = await context?.adapter.getTranslationTask(
|
|
86
|
-
context.documentInfo,
|
|
87
|
-
context.secrets
|
|
88
|
-
);
|
|
89
|
-
if (task) {
|
|
90
|
-
setTask(task);
|
|
91
|
-
}
|
|
92
|
-
}, [context, setTask]);
|
|
93
|
-
|
|
94
|
-
// Show message if we're not on a source language document
|
|
95
|
-
if (!shouldShowTranslationComponents) {
|
|
96
|
-
return (
|
|
97
|
-
<Card padding={4} tone='neutral' border>
|
|
98
|
-
<Text size={1} muted>
|
|
99
|
-
Translation tools are only available for{' '}
|
|
100
|
-
<code>{gtConfig.getSourceLocale()}</code> documents.
|
|
101
|
-
</Text>
|
|
102
|
-
</Card>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<Stack space={6}>
|
|
108
|
-
<NewTask locales={locales} refreshTask={refreshTask} />
|
|
109
|
-
{task && (
|
|
110
|
-
<TaskView task={task} locales={locales} refreshTask={refreshTask} />
|
|
111
|
-
)}
|
|
112
|
-
</Stack>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
-
|
|
3
|
-
import { useMemo } from 'react';
|
|
4
|
-
import { SanityDocument, useSchema } from 'sanity';
|
|
5
|
-
import { randomKey } from '@sanity/util/content';
|
|
6
|
-
import {
|
|
7
|
-
ThemeProvider,
|
|
8
|
-
ToastProvider,
|
|
9
|
-
Stack,
|
|
10
|
-
Text,
|
|
11
|
-
Layer,
|
|
12
|
-
Box,
|
|
13
|
-
Card,
|
|
14
|
-
Flex,
|
|
15
|
-
Spinner,
|
|
16
|
-
} from '@sanity/ui';
|
|
17
|
-
|
|
18
|
-
import { TranslationContext } from './TranslationContext';
|
|
19
|
-
import { TranslationView } from './TranslationView';
|
|
20
|
-
import { useClient } from '../hooks/useClient';
|
|
21
|
-
import { useSecrets } from '../hooks/useSecrets';
|
|
22
|
-
import { GTFile, Secrets, TranslationsTabConfigOptions } from '../types';
|
|
23
|
-
|
|
24
|
-
type TranslationTabProps = {
|
|
25
|
-
document: {
|
|
26
|
-
displayed: SanityDocument;
|
|
27
|
-
};
|
|
28
|
-
options: TranslationsTabConfigOptions;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const TranslationTab = (props: TranslationTabProps) => {
|
|
32
|
-
const { displayed } = props.document;
|
|
33
|
-
const client = useClient();
|
|
34
|
-
const schema = useSchema();
|
|
35
|
-
|
|
36
|
-
const documentId =
|
|
37
|
-
displayed && displayed._id
|
|
38
|
-
? (displayed._id.split('drafts.').pop() as string)
|
|
39
|
-
: '';
|
|
40
|
-
|
|
41
|
-
const revisionId = displayed && displayed._rev ? displayed._rev : undefined;
|
|
42
|
-
|
|
43
|
-
const { errors, importTranslation, exportForTranslation } = useMemo(() => {
|
|
44
|
-
const { serializationOptions, languageField, mergeWithTargetLocale } =
|
|
45
|
-
props.options;
|
|
46
|
-
const ctx = {
|
|
47
|
-
client,
|
|
48
|
-
schema,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const allErrors = [];
|
|
52
|
-
|
|
53
|
-
const importTranslationFunc = props.options.importTranslation;
|
|
54
|
-
if (!importTranslationFunc) {
|
|
55
|
-
allErrors.push({
|
|
56
|
-
key: randomKey(12),
|
|
57
|
-
text: (
|
|
58
|
-
<>
|
|
59
|
-
You need to provide an <code>importTranslation</code> function. See
|
|
60
|
-
documentation.
|
|
61
|
-
</>
|
|
62
|
-
),
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const contextImportTranslation = (localeId: string, doc: string) => {
|
|
67
|
-
return importTranslationFunc(
|
|
68
|
-
{ documentId, versionId: revisionId },
|
|
69
|
-
localeId,
|
|
70
|
-
doc,
|
|
71
|
-
ctx,
|
|
72
|
-
serializationOptions,
|
|
73
|
-
languageField,
|
|
74
|
-
mergeWithTargetLocale
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const exportTranslationFunc = props.options.exportForTranslation;
|
|
79
|
-
if (!exportTranslationFunc) {
|
|
80
|
-
allErrors.push({
|
|
81
|
-
key: randomKey(12),
|
|
82
|
-
text: (
|
|
83
|
-
<>
|
|
84
|
-
You need to provide an <code>exportForTranslation</code> function.
|
|
85
|
-
See documentation.
|
|
86
|
-
</>
|
|
87
|
-
),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const contextExportForTranslation = (docInfo: GTFile) => {
|
|
92
|
-
return exportTranslationFunc(
|
|
93
|
-
docInfo,
|
|
94
|
-
ctx,
|
|
95
|
-
serializationOptions,
|
|
96
|
-
languageField
|
|
97
|
-
);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
errors: allErrors,
|
|
102
|
-
importTranslation: contextImportTranslation,
|
|
103
|
-
exportForTranslation: contextExportForTranslation,
|
|
104
|
-
};
|
|
105
|
-
}, [props.options, documentId, revisionId, client, schema]);
|
|
106
|
-
|
|
107
|
-
const { loading, secrets } = useSecrets<Secrets>(
|
|
108
|
-
`${props.options.secretsNamespace || 'translationService'}.secrets`
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const hasErrors = errors.length > 0;
|
|
112
|
-
|
|
113
|
-
if (loading || !secrets) {
|
|
114
|
-
return (
|
|
115
|
-
<ThemeProvider>
|
|
116
|
-
<Flex padding={5} align='center' justify='center'>
|
|
117
|
-
<Spinner />
|
|
118
|
-
</Flex>
|
|
119
|
-
</ThemeProvider>
|
|
120
|
-
);
|
|
121
|
-
} else if (!secrets) {
|
|
122
|
-
return (
|
|
123
|
-
<ThemeProvider>
|
|
124
|
-
<Box padding={4}>
|
|
125
|
-
<Card tone='caution' padding={[2, 3, 4, 4]} shadow={1} radius={2}>
|
|
126
|
-
<Text>
|
|
127
|
-
Can't find secrets for your translation service. Did you load them
|
|
128
|
-
into this dataset?
|
|
129
|
-
</Text>
|
|
130
|
-
</Card>
|
|
131
|
-
</Box>
|
|
132
|
-
</ThemeProvider>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
return (
|
|
136
|
-
<ThemeProvider>
|
|
137
|
-
<Box padding={4}>
|
|
138
|
-
<Layer>
|
|
139
|
-
<ToastProvider paddingY={7}>
|
|
140
|
-
{hasErrors && (
|
|
141
|
-
<Stack space={3}>
|
|
142
|
-
{errors.map((error) => (
|
|
143
|
-
<Card
|
|
144
|
-
key={error.key}
|
|
145
|
-
tone='caution'
|
|
146
|
-
padding={[2, 3, 4, 4]}
|
|
147
|
-
shadow={1}
|
|
148
|
-
radius={2}
|
|
149
|
-
>
|
|
150
|
-
<Text>{error.text}</Text>
|
|
151
|
-
</Card>
|
|
152
|
-
))}
|
|
153
|
-
</Stack>
|
|
154
|
-
)}
|
|
155
|
-
{!hasErrors && (
|
|
156
|
-
<TranslationContext.Provider
|
|
157
|
-
value={{
|
|
158
|
-
documentInfo: { documentId, versionId: revisionId },
|
|
159
|
-
document: displayed,
|
|
160
|
-
languageField: props.options.languageField || 'language',
|
|
161
|
-
secrets,
|
|
162
|
-
importTranslation,
|
|
163
|
-
exportForTranslation,
|
|
164
|
-
adapter: props.options.adapter,
|
|
165
|
-
workflowOptions: props.options.workflowOptions,
|
|
166
|
-
localeIdAdapter: props.options.localeIdAdapter,
|
|
167
|
-
callbackUrl: props.options.callbackUrl,
|
|
168
|
-
mergeWithTargetLocale: props.options.mergeWithTargetLocale,
|
|
169
|
-
}}
|
|
170
|
-
>
|
|
171
|
-
<TranslationView />
|
|
172
|
-
</TranslationContext.Provider>
|
|
173
|
-
)}
|
|
174
|
-
</ToastProvider>
|
|
175
|
-
</Layer>
|
|
176
|
-
</Box>
|
|
177
|
-
</ThemeProvider>
|
|
178
|
-
);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
export default TranslationTab;
|
|
File without changes
|
|
File without changes
|