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.
- package/LICENSE.md +1 -8
- package/README.md +5 -5
- package/dist/index.d.mts +122 -95
- package/dist/index.d.ts +122 -95
- package/dist/index.js +9089 -1119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9099 -1100
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -4
- package/src/adapter/core.ts +111 -9
- package/src/adapter/createTask.ts +1 -1
- package/src/adapter/getLocales.ts +2 -2
- package/src/adapter/types.ts +9 -0
- 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 +338 -0
- package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
- package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +2 -0
- package/src/components/shared/LocaleCheckbox.tsx +47 -0
- package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +2 -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 +21 -11
- package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +57 -23
- package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +31 -8
- package/src/configuration/baseDocumentLevelConfig/index.ts +18 -101
- package/src/configuration/baseFieldLevelConfig.ts +19 -51
- package/src/configuration/utils/checkSerializationVersion.ts +2 -0
- package/src/configuration/utils/findDocumentAtRevision.ts +2 -0
- package/src/configuration/utils/findLatestDraft.ts +2 -0
- package/src/hooks/useClient.ts +3 -1
- package/src/hooks/useSecrets.ts +2 -0
- package/src/index.ts +91 -67
- 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/checkTranslationStatus.ts +42 -0
- package/src/translation/createJobs.ts +16 -0
- package/src/translation/downloadTranslations.ts +68 -0
- package/src/translation/importDocument.ts +23 -0
- package/src/translation/initProject.ts +61 -0
- package/src/translation/uploadFiles.ts +32 -0
- package/src/types.ts +7 -20
- package/src/utils/applyDocuments.ts +72 -0
- package/src/utils/batchProcessor.ts +111 -0
- package/src/utils/importUtils.ts +95 -0
- package/src/utils/serialize.ts +52 -0
- package/src/utils/shared.ts +1 -0
- package/src/adapter/index.ts +0 -13
- package/src/components/NewTask.tsx +0 -249
- package/src/components/TaskView.tsx +0 -255
- package/src/components/TranslationContext.tsx +0 -19
- package/src/components/TranslationView.tsx +0 -82
- package/src/components/TranslationsTab.tsx +0 -177
- package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +0 -5
- package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +0 -69
- package/src/configuration/index.ts +0 -18
- package/src/configuration/utils/index.ts +0 -3
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useState,
|
|
3
|
-
useContext,
|
|
4
|
-
ChangeEvent,
|
|
5
|
-
useCallback,
|
|
6
|
-
useEffect,
|
|
7
|
-
} from 'react';
|
|
8
|
-
import styled from 'styled-components';
|
|
9
|
-
import {
|
|
10
|
-
Button,
|
|
11
|
-
Box,
|
|
12
|
-
Flex,
|
|
13
|
-
Grid,
|
|
14
|
-
Select,
|
|
15
|
-
Stack,
|
|
16
|
-
Switch,
|
|
17
|
-
Text,
|
|
18
|
-
useToast,
|
|
19
|
-
} from '@sanity/ui';
|
|
20
|
-
|
|
21
|
-
import { TranslationContext } from './TranslationContext';
|
|
22
|
-
import { TranslationLocale } from '../types';
|
|
23
|
-
|
|
24
|
-
type Props = {
|
|
25
|
-
locales: TranslationLocale[];
|
|
26
|
-
refreshTask: () => Promise<void>;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type LocaleCheckboxProps = {
|
|
30
|
-
locale: TranslationLocale;
|
|
31
|
-
toggle: (locale: string, checked: boolean) => void;
|
|
32
|
-
checked: boolean;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const WrapText = styled(Box)`
|
|
36
|
-
white-space: normal;
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
const LocaleCheckbox = ({ locale, toggle, checked }: LocaleCheckboxProps) => {
|
|
40
|
-
const onClick = useCallback(
|
|
41
|
-
() => toggle(locale.localeId, !checked),
|
|
42
|
-
[locale, toggle, checked]
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Button
|
|
47
|
-
mode='ghost'
|
|
48
|
-
onClick={onClick}
|
|
49
|
-
disabled={locale.enabled === false}
|
|
50
|
-
style={{ cursor: `pointer` }}
|
|
51
|
-
radius={2}
|
|
52
|
-
>
|
|
53
|
-
<Flex align='center' gap={3}>
|
|
54
|
-
<Switch
|
|
55
|
-
style={{ pointerEvents: `none` }}
|
|
56
|
-
disabled={locale.enabled === false}
|
|
57
|
-
onChange={onClick}
|
|
58
|
-
checked={checked}
|
|
59
|
-
/>
|
|
60
|
-
<WrapText>
|
|
61
|
-
<Text size={1} weight='semibold'>
|
|
62
|
-
{locale.description}
|
|
63
|
-
</Text>
|
|
64
|
-
</WrapText>
|
|
65
|
-
</Flex>
|
|
66
|
-
</Button>
|
|
67
|
-
);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const NewTask = ({ locales, refreshTask }: Props) => {
|
|
71
|
-
const possibleLocales = locales.filter((locale) => locale.enabled !== false);
|
|
72
|
-
// Lets just stick to the canonical document id for keeping track of
|
|
73
|
-
// translations
|
|
74
|
-
const [selectedLocales, setSelectedLocales] = useState<React.ReactNode[]>(
|
|
75
|
-
locales
|
|
76
|
-
.filter((locale) => locale.enabled !== false)
|
|
77
|
-
.map((locale) => locale.localeId)
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
setSelectedLocales(
|
|
82
|
-
locales
|
|
83
|
-
.filter((locale) => locale.enabled !== false)
|
|
84
|
-
.map((locale) => locale.localeId)
|
|
85
|
-
);
|
|
86
|
-
}, [locales]);
|
|
87
|
-
|
|
88
|
-
const [selectedWorkflowUid, setSelectedWorkflowUid] = useState<string>();
|
|
89
|
-
const [isBusy, setIsBusy] = useState(false);
|
|
90
|
-
|
|
91
|
-
const context = useContext(TranslationContext);
|
|
92
|
-
const toast = useToast();
|
|
93
|
-
|
|
94
|
-
const toggleLocale = useCallback(
|
|
95
|
-
(locale: string, selected: boolean) => {
|
|
96
|
-
if (!selected) {
|
|
97
|
-
setSelectedLocales(selectedLocales.filter((l) => l !== locale));
|
|
98
|
-
} else if (!selectedLocales.includes(locale)) {
|
|
99
|
-
setSelectedLocales([...selectedLocales, locale]);
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
[selectedLocales, setSelectedLocales]
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const createTask = useCallback(() => {
|
|
106
|
-
if (!context) {
|
|
107
|
-
toast.push({
|
|
108
|
-
title: 'Unable to create task: missing context',
|
|
109
|
-
status: 'error',
|
|
110
|
-
closable: true,
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
setIsBusy(true);
|
|
116
|
-
|
|
117
|
-
context
|
|
118
|
-
.exportForTranslation(context.documentInfo)
|
|
119
|
-
.then((serialized) =>
|
|
120
|
-
context.adapter.createTask(
|
|
121
|
-
context.documentInfo,
|
|
122
|
-
serialized,
|
|
123
|
-
selectedLocales as string[],
|
|
124
|
-
context.secrets,
|
|
125
|
-
selectedWorkflowUid,
|
|
126
|
-
context.callbackUrl
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
.then(() => {
|
|
130
|
-
toast.push({
|
|
131
|
-
title: 'Job successfully created',
|
|
132
|
-
status: 'success',
|
|
133
|
-
closable: true,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
/** Reset form fields */
|
|
137
|
-
setSelectedLocales([]);
|
|
138
|
-
setSelectedWorkflowUid('');
|
|
139
|
-
|
|
140
|
-
/** Update task data in TranslationView */
|
|
141
|
-
refreshTask();
|
|
142
|
-
})
|
|
143
|
-
.catch((err) => {
|
|
144
|
-
let errorMsg;
|
|
145
|
-
if (err instanceof Error) {
|
|
146
|
-
errorMsg = err.message;
|
|
147
|
-
} else {
|
|
148
|
-
errorMsg = err ? String(err) : null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
toast.push({
|
|
152
|
-
title: `Error creating translation job`,
|
|
153
|
-
description: errorMsg,
|
|
154
|
-
status: 'error',
|
|
155
|
-
closable: true,
|
|
156
|
-
});
|
|
157
|
-
})
|
|
158
|
-
.finally(() => {
|
|
159
|
-
setIsBusy(false);
|
|
160
|
-
});
|
|
161
|
-
}, [context, selectedLocales, selectedWorkflowUid, toast, refreshTask]);
|
|
162
|
-
|
|
163
|
-
const onClick = useCallback(() => {
|
|
164
|
-
setSelectedLocales(
|
|
165
|
-
possibleLocales.length === selectedLocales.length
|
|
166
|
-
? // Disable all
|
|
167
|
-
[]
|
|
168
|
-
: // Enable all
|
|
169
|
-
locales
|
|
170
|
-
.filter((locale) => locale.enabled !== false)
|
|
171
|
-
.map((locale) => locale.localeId)
|
|
172
|
-
);
|
|
173
|
-
}, [possibleLocales, selectedLocales, setSelectedLocales, locales]);
|
|
174
|
-
|
|
175
|
-
const onToggle = useCallback(
|
|
176
|
-
(locale: string, checked: boolean) => {
|
|
177
|
-
toggleLocale(locale, checked);
|
|
178
|
-
},
|
|
179
|
-
[toggleLocale]
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
const onWorkflowChange = useCallback(
|
|
183
|
-
(e: ChangeEvent<HTMLSelectElement>) => {
|
|
184
|
-
setSelectedWorkflowUid(e.target.value);
|
|
185
|
-
},
|
|
186
|
-
[setSelectedWorkflowUid]
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<Stack paddingTop={4} space={4}>
|
|
191
|
-
<Text as='h2' weight='semibold' size={2}>
|
|
192
|
-
Generate New Translations
|
|
193
|
-
</Text>
|
|
194
|
-
<Stack space={3}>
|
|
195
|
-
<Flex align='center' justify='space-between'>
|
|
196
|
-
<Text weight='semibold' size={1}>
|
|
197
|
-
{possibleLocales.length === 1 ? `Select locale` : `Select locales`}
|
|
198
|
-
</Text>
|
|
199
|
-
|
|
200
|
-
<Button
|
|
201
|
-
fontSize={1}
|
|
202
|
-
padding={2}
|
|
203
|
-
text='Toggle All'
|
|
204
|
-
onClick={onClick}
|
|
205
|
-
/>
|
|
206
|
-
</Flex>
|
|
207
|
-
|
|
208
|
-
<Grid columns={[1, 1, 2, 3]} gap={1}>
|
|
209
|
-
{(locales || []).map((l) => (
|
|
210
|
-
<LocaleCheckbox
|
|
211
|
-
key={l.localeId}
|
|
212
|
-
locale={l}
|
|
213
|
-
toggle={onToggle}
|
|
214
|
-
checked={selectedLocales.includes(l.localeId)}
|
|
215
|
-
/>
|
|
216
|
-
))}
|
|
217
|
-
</Grid>
|
|
218
|
-
</Stack>
|
|
219
|
-
|
|
220
|
-
{context?.workflowOptions && context.workflowOptions.length > 0 && (
|
|
221
|
-
<Stack space={3}>
|
|
222
|
-
<Text weight='semibold' size={1} as='label' htmlFor='workflow-select'>
|
|
223
|
-
Select translation workflow
|
|
224
|
-
</Text>
|
|
225
|
-
<Grid columns={[1, 1, 2]}>
|
|
226
|
-
<Select id='workflowSelect' onChange={onWorkflowChange}>
|
|
227
|
-
<option>Default locale workflows</option>
|
|
228
|
-
{context.workflowOptions.map((w) => (
|
|
229
|
-
<option
|
|
230
|
-
key={`workflow-opt-${w.workflowUid}`}
|
|
231
|
-
value={w.workflowUid}
|
|
232
|
-
>
|
|
233
|
-
{w.workflowName}
|
|
234
|
-
</option>
|
|
235
|
-
))}
|
|
236
|
-
</Select>
|
|
237
|
-
</Grid>
|
|
238
|
-
</Stack>
|
|
239
|
-
)}
|
|
240
|
-
|
|
241
|
-
<Button
|
|
242
|
-
onClick={createTask}
|
|
243
|
-
disabled={isBusy || !selectedLocales.length}
|
|
244
|
-
tone='positive'
|
|
245
|
-
text={isBusy ? 'Queueing translations...' : 'Generate Translations'}
|
|
246
|
-
/>
|
|
247
|
-
</Stack>
|
|
248
|
-
);
|
|
249
|
-
};
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { useCallback, useContext, useState, useEffect } from 'react';
|
|
2
|
-
import { Box, Button, Flex, Text, Stack, useToast, Switch } from '@sanity/ui';
|
|
3
|
-
import {
|
|
4
|
-
ArrowTopRightIcon,
|
|
5
|
-
DownloadIcon,
|
|
6
|
-
CheckmarkCircleIcon,
|
|
7
|
-
} from '@sanity/icons';
|
|
8
|
-
|
|
9
|
-
import { TranslationContext } from './TranslationContext';
|
|
10
|
-
import { TranslationLocale, TranslationTask } from '../types';
|
|
11
|
-
import { LanguageStatus } from './LanguageStatus';
|
|
12
|
-
|
|
13
|
-
type JobProps = {
|
|
14
|
-
task: TranslationTask;
|
|
15
|
-
locales: TranslationLocale[];
|
|
16
|
-
refreshTask: () => Promise<void>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const getLocale = (
|
|
20
|
-
localeId: string,
|
|
21
|
-
locales: TranslationLocale[]
|
|
22
|
-
): TranslationLocale | undefined =>
|
|
23
|
-
locales.find((l) => l.localeId === localeId);
|
|
24
|
-
|
|
25
|
-
export const TaskView = ({ task, locales, refreshTask }: JobProps) => {
|
|
26
|
-
const context = useContext(TranslationContext);
|
|
27
|
-
const toast = useToast();
|
|
28
|
-
|
|
29
|
-
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
30
|
-
const [autoRefresh, setAutoRefresh] = useState(true);
|
|
31
|
-
const [isBusy, setIsBusy] = useState(false);
|
|
32
|
-
const [autoImport, setAutoImport] = useState(true);
|
|
33
|
-
const [importedFiles, setImportedFiles] = useState<Set<string>>(new Set());
|
|
34
|
-
|
|
35
|
-
const importFile = useCallback(
|
|
36
|
-
async (localeId: string) => {
|
|
37
|
-
if (!context) {
|
|
38
|
-
toast.push({
|
|
39
|
-
title:
|
|
40
|
-
'Missing context, unable to import translation. Try refreshing or clicking away from this tab and back.',
|
|
41
|
-
status: 'error',
|
|
42
|
-
closable: true,
|
|
43
|
-
});
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const locale = getLocale(localeId, locales);
|
|
48
|
-
const localeTitle = locale?.description || localeId;
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const translation = await context.adapter.getTranslation(
|
|
52
|
-
task.document,
|
|
53
|
-
localeId,
|
|
54
|
-
context.secrets
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const sanityId = context.localeIdAdapter
|
|
58
|
-
? await context.localeIdAdapter(localeId)
|
|
59
|
-
: localeId;
|
|
60
|
-
|
|
61
|
-
await context.importTranslation(sanityId, translation);
|
|
62
|
-
|
|
63
|
-
setImportedFiles((prev) => new Set([...prev, localeId]));
|
|
64
|
-
|
|
65
|
-
toast.push({
|
|
66
|
-
title: `Imported ${localeTitle} translation`,
|
|
67
|
-
status: 'success',
|
|
68
|
-
closable: true,
|
|
69
|
-
});
|
|
70
|
-
} catch (err) {
|
|
71
|
-
let errorMsg;
|
|
72
|
-
if (err instanceof Error) {
|
|
73
|
-
errorMsg = err.message;
|
|
74
|
-
} else {
|
|
75
|
-
errorMsg = err ? String(err) : null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
toast.push({
|
|
79
|
-
title: `Error getting ${localeTitle} translation`,
|
|
80
|
-
description: errorMsg,
|
|
81
|
-
status: 'error',
|
|
82
|
-
closable: true,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
[locales, context, task.document, toast]
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const checkAndImportCompletedFiles = useCallback(async () => {
|
|
90
|
-
if (!autoImport || isBusy) return;
|
|
91
|
-
|
|
92
|
-
const completedFiles = task.locales.filter(
|
|
93
|
-
(locale) =>
|
|
94
|
-
(locale.progress || 0) >= 100 && !importedFiles.has(locale.localeId)
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
if (completedFiles.length === 0) return;
|
|
98
|
-
|
|
99
|
-
setIsBusy(true);
|
|
100
|
-
try {
|
|
101
|
-
for (const locale of completedFiles) {
|
|
102
|
-
await importFile(locale.localeId);
|
|
103
|
-
}
|
|
104
|
-
} finally {
|
|
105
|
-
setIsBusy(false);
|
|
106
|
-
}
|
|
107
|
-
}, [autoImport, isBusy, task.locales, importedFiles, importFile]);
|
|
108
|
-
|
|
109
|
-
const handleRefreshClick = useCallback(async () => {
|
|
110
|
-
if (isRefreshing) return;
|
|
111
|
-
setIsRefreshing(true);
|
|
112
|
-
await refreshTask();
|
|
113
|
-
await checkAndImportCompletedFiles();
|
|
114
|
-
setIsRefreshing(false);
|
|
115
|
-
}, [refreshTask, setIsRefreshing, checkAndImportCompletedFiles]);
|
|
116
|
-
|
|
117
|
-
const handleImportAll = useCallback(async () => {
|
|
118
|
-
if (isBusy) return;
|
|
119
|
-
setIsBusy(true);
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const filesToImport = task.locales.filter(
|
|
123
|
-
(locale) => !importedFiles.has(locale.localeId)
|
|
124
|
-
);
|
|
125
|
-
for (const locale of filesToImport) {
|
|
126
|
-
await importFile(locale.localeId);
|
|
127
|
-
}
|
|
128
|
-
} finally {
|
|
129
|
-
setIsBusy(false);
|
|
130
|
-
}
|
|
131
|
-
}, [task.locales, importFile, isBusy, importedFiles]);
|
|
132
|
-
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
if (!autoRefresh || importedFiles.size === task.locales.length) return;
|
|
135
|
-
|
|
136
|
-
const interval = setInterval(async () => {
|
|
137
|
-
await handleRefreshClick();
|
|
138
|
-
}, 5000);
|
|
139
|
-
|
|
140
|
-
return () => clearInterval(interval);
|
|
141
|
-
}, [
|
|
142
|
-
handleRefreshClick,
|
|
143
|
-
autoRefresh,
|
|
144
|
-
importedFiles.size,
|
|
145
|
-
task.locales.length,
|
|
146
|
-
]);
|
|
147
|
-
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
checkAndImportCompletedFiles();
|
|
150
|
-
}, [checkAndImportCompletedFiles, task.locales]);
|
|
151
|
-
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
setImportedFiles((prev) => {
|
|
154
|
-
const newSet = new Set<string>();
|
|
155
|
-
for (const localeId of prev) {
|
|
156
|
-
if (task.locales.some((locale) => locale.localeId === localeId)) {
|
|
157
|
-
newSet.add(localeId);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return newSet;
|
|
161
|
-
});
|
|
162
|
-
}, [task.locales]);
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<Stack space={4}>
|
|
166
|
-
<Flex align='center' justify='space-between'>
|
|
167
|
-
<Text as='h2' weight='semibold' size={2}>
|
|
168
|
-
Translation Progress
|
|
169
|
-
</Text>
|
|
170
|
-
|
|
171
|
-
<Flex gap={3} align='center'>
|
|
172
|
-
<Flex gap={2} align='center'>
|
|
173
|
-
<Text size={1}>Auto-refresh</Text>
|
|
174
|
-
<Switch
|
|
175
|
-
checked={autoRefresh}
|
|
176
|
-
onChange={() => setAutoRefresh(!autoRefresh)}
|
|
177
|
-
/>
|
|
178
|
-
</Flex>
|
|
179
|
-
{task.linkToVendorTask && (
|
|
180
|
-
<Button
|
|
181
|
-
as='a'
|
|
182
|
-
text='View Job'
|
|
183
|
-
iconRight={ArrowTopRightIcon}
|
|
184
|
-
href={task.linkToVendorTask}
|
|
185
|
-
target='_blank'
|
|
186
|
-
rel='noreferrer noopener'
|
|
187
|
-
fontSize={1}
|
|
188
|
-
padding={2}
|
|
189
|
-
mode='bleed'
|
|
190
|
-
/>
|
|
191
|
-
)}
|
|
192
|
-
<Button
|
|
193
|
-
fontSize={1}
|
|
194
|
-
padding={2}
|
|
195
|
-
text='Refresh Status'
|
|
196
|
-
onClick={handleRefreshClick}
|
|
197
|
-
disabled={isRefreshing}
|
|
198
|
-
/>
|
|
199
|
-
</Flex>
|
|
200
|
-
</Flex>
|
|
201
|
-
|
|
202
|
-
<Box>
|
|
203
|
-
{task.locales.map((localeTask) => {
|
|
204
|
-
const reportPercent = localeTask.progress || 0;
|
|
205
|
-
const locale = getLocale(localeTask.localeId, locales);
|
|
206
|
-
return (
|
|
207
|
-
<LanguageStatus
|
|
208
|
-
key={[task.document.documentId, localeTask.localeId].join('.')}
|
|
209
|
-
importFile={async () => {
|
|
210
|
-
await importFile(localeTask.localeId);
|
|
211
|
-
}}
|
|
212
|
-
title={locale?.description || localeTask.localeId}
|
|
213
|
-
progress={reportPercent}
|
|
214
|
-
isImported={importedFiles.has(localeTask.localeId)}
|
|
215
|
-
/>
|
|
216
|
-
);
|
|
217
|
-
})}
|
|
218
|
-
</Box>
|
|
219
|
-
<Stack space={3}>
|
|
220
|
-
<Flex gap={3} align='center' justify='space-between'>
|
|
221
|
-
<Flex gap={2} align='center'>
|
|
222
|
-
<Button
|
|
223
|
-
mode='ghost'
|
|
224
|
-
onClick={handleImportAll}
|
|
225
|
-
text={isBusy ? 'Importing...' : 'Import All'}
|
|
226
|
-
icon={isBusy ? null : DownloadIcon}
|
|
227
|
-
disabled={isBusy || importedFiles.size === task.locales.length}
|
|
228
|
-
/>
|
|
229
|
-
{importedFiles.size === task.locales.length &&
|
|
230
|
-
task.locales.length > 0 && (
|
|
231
|
-
<Flex gap={2} align='center' style={{ color: 'green' }}>
|
|
232
|
-
<CheckmarkCircleIcon />
|
|
233
|
-
<Text size={1}>All translations imported</Text>
|
|
234
|
-
</Flex>
|
|
235
|
-
)}
|
|
236
|
-
{importedFiles.size > 0 &&
|
|
237
|
-
importedFiles.size < task.locales.length && (
|
|
238
|
-
<Text size={1} style={{ color: '#666' }}>
|
|
239
|
-
{importedFiles.size}/{task.locales.length} imported
|
|
240
|
-
</Text>
|
|
241
|
-
)}
|
|
242
|
-
</Flex>
|
|
243
|
-
<Flex gap={2} align='center' style={{ whiteSpace: 'nowrap' }}>
|
|
244
|
-
<Text size={1}>Auto-import when complete</Text>
|
|
245
|
-
<Switch
|
|
246
|
-
checked={autoImport}
|
|
247
|
-
onChange={() => setAutoImport(!autoImport)}
|
|
248
|
-
disabled={isBusy}
|
|
249
|
-
/>
|
|
250
|
-
</Flex>
|
|
251
|
-
</Flex>
|
|
252
|
-
</Stack>
|
|
253
|
-
</Stack>
|
|
254
|
-
);
|
|
255
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { GTSerializedDocument } from '../types';
|
|
3
|
-
import { Adapter, GTFile, Secrets, WorkflowIdentifiers } from '../types';
|
|
4
|
-
|
|
5
|
-
export type ContextProps = {
|
|
6
|
-
documentInfo: GTFile;
|
|
7
|
-
adapter: Adapter;
|
|
8
|
-
importTranslation: (languageId: string, document: string) => Promise<void>;
|
|
9
|
-
exportForTranslation: (documentInfo: GTFile) => Promise<GTSerializedDocument>;
|
|
10
|
-
secrets: Secrets;
|
|
11
|
-
workflowOptions?: WorkflowIdentifiers[];
|
|
12
|
-
localeIdAdapter?: (id: string) => string | Promise<string>;
|
|
13
|
-
callbackUrl?: string;
|
|
14
|
-
mergeWithTargetLocale?: boolean;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const TranslationContext = React.createContext<ContextProps | null>(
|
|
18
|
-
null
|
|
19
|
-
);
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Add cleanup function to cancel async tasks
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
6
|
-
import { Stack, useToast } from '@sanity/ui';
|
|
7
|
-
import { TranslationContext } from './TranslationContext';
|
|
8
|
-
|
|
9
|
-
import { NewTask } from './NewTask';
|
|
10
|
-
import { TaskView } from './TaskView';
|
|
11
|
-
import { TranslationTask, TranslationLocale } from '../types';
|
|
12
|
-
|
|
13
|
-
export const TranslationView = () => {
|
|
14
|
-
const [locales, setLocales] = useState<TranslationLocale[]>([]);
|
|
15
|
-
const [task, setTask] = useState<TranslationTask | null>(null);
|
|
16
|
-
|
|
17
|
-
const context = useContext(TranslationContext);
|
|
18
|
-
const toast = useToast();
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
async function fetchData() {
|
|
22
|
-
if (!context) {
|
|
23
|
-
toast.push({
|
|
24
|
-
title: 'Unable to load translation data: missing context',
|
|
25
|
-
status: 'error',
|
|
26
|
-
closable: true,
|
|
27
|
-
});
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const locales = await context.adapter.getLocales(context.secrets);
|
|
32
|
-
setLocales(locales);
|
|
33
|
-
try {
|
|
34
|
-
const task = await context?.adapter.getTranslationTask(
|
|
35
|
-
context.documentInfo,
|
|
36
|
-
context.secrets
|
|
37
|
-
);
|
|
38
|
-
setTask(task);
|
|
39
|
-
} catch (err) {
|
|
40
|
-
let errorMsg;
|
|
41
|
-
if (err instanceof Error) {
|
|
42
|
-
errorMsg = err.message;
|
|
43
|
-
} else {
|
|
44
|
-
errorMsg = err ? String(err) : null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Hacky bypass for when a document is not yet translated and has never been uploaded
|
|
48
|
-
if (errorMsg?.toLowerCase().includes('no source file found')) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
toast.push({
|
|
53
|
-
title: `Error creating translation job`,
|
|
54
|
-
description: errorMsg,
|
|
55
|
-
status: 'error',
|
|
56
|
-
closable: true,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
fetchData();
|
|
62
|
-
}, [context, toast]);
|
|
63
|
-
|
|
64
|
-
const refreshTask = useCallback(async () => {
|
|
65
|
-
const task = await context?.adapter.getTranslationTask(
|
|
66
|
-
context.documentInfo,
|
|
67
|
-
context.secrets
|
|
68
|
-
);
|
|
69
|
-
if (task) {
|
|
70
|
-
setTask(task);
|
|
71
|
-
}
|
|
72
|
-
}, [context, setTask]);
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<Stack space={6}>
|
|
76
|
-
<NewTask locales={locales} refreshTask={refreshTask} />
|
|
77
|
-
{task && (
|
|
78
|
-
<TaskView task={task} locales={locales} refreshTask={refreshTask} />
|
|
79
|
-
)}
|
|
80
|
-
</Stack>
|
|
81
|
-
);
|
|
82
|
-
};
|