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,875 +1,337 @@
|
|
|
1
|
-
import React, { useState
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
4
|
Button,
|
|
5
|
-
Card,
|
|
6
5
|
Container,
|
|
7
|
-
Dialog,
|
|
8
6
|
Flex,
|
|
9
7
|
Heading,
|
|
10
|
-
Spinner,
|
|
11
8
|
Stack,
|
|
12
9
|
Switch,
|
|
13
10
|
Text,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
useToast,
|
|
11
|
+
Spinner,
|
|
12
|
+
Tooltip,
|
|
17
13
|
} from '@sanity/ui';
|
|
18
|
-
import { DownloadIcon, CheckmarkCircleIcon } from '@sanity/icons';
|
|
19
|
-
import { buildTheme } from '@sanity/ui/theme';
|
|
20
|
-
import { Link } from 'sanity/router';
|
|
21
|
-
import { SanityDocument, useSchema } from 'sanity';
|
|
22
|
-
import { useClient } from '../../hooks/useClient';
|
|
23
|
-
import { useSecrets } from '../../hooks/useSecrets';
|
|
24
|
-
import { GTAdapter } from '../../adapter';
|
|
25
|
-
import {
|
|
26
|
-
GTFile,
|
|
27
|
-
Secrets,
|
|
28
|
-
TranslationLocale,
|
|
29
|
-
TranslationFunctionContext,
|
|
30
|
-
} from '../../types';
|
|
31
|
-
import { gtConfig } from '../../adapter/core';
|
|
32
|
-
import { LanguageStatus } from '../LanguageStatus';
|
|
33
|
-
import { serializeDocument } from '../../utils/serialize';
|
|
34
|
-
import { uploadFiles } from '../../translation/uploadFiles';
|
|
35
|
-
import { initProject } from '../../translation/initProject';
|
|
36
|
-
import { createJobs } from '../../translation/createJobs';
|
|
37
14
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 = () => {
|
|
47
30
|
const [isTranslateAllDialogOpen, setIsTranslateAllDialogOpen] =
|
|
48
31
|
useState(false);
|
|
49
32
|
const [isImportAllDialogOpen, setIsImportAllDialogOpen] = useState(false);
|
|
50
|
-
const [
|
|
51
|
-
|
|
52
|
-
const [locales, setLocales] = useState<TranslationLocale[]>([]);
|
|
53
|
-
const [autoPublish, setAutoPublish] = useState(true);
|
|
54
|
-
const [autoRefresh, setAutoRefresh] = useState(false);
|
|
55
|
-
const [loadingDocuments, setLoadingDocuments] = useState(false);
|
|
56
|
-
const [importProgress, setImportProgress] = useState({
|
|
57
|
-
current: 0,
|
|
58
|
-
total: 0,
|
|
59
|
-
isImporting: false,
|
|
60
|
-
});
|
|
61
|
-
const [importedTranslations, setImportedTranslations] = useState<Set<string>>(
|
|
62
|
-
new Set()
|
|
63
|
-
);
|
|
64
|
-
const [downloadStatus, setDownloadStatus] = useState({
|
|
65
|
-
downloaded: new Set<string>(),
|
|
66
|
-
failed: new Set<string>(),
|
|
67
|
-
skipped: new Set<string>(),
|
|
68
|
-
});
|
|
69
|
-
const [translationStatuses, setTranslationStatuses] = useState<
|
|
70
|
-
Map<string, { progress: number; isReady: boolean; translationId?: string }>
|
|
71
|
-
>(new Map());
|
|
72
|
-
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
73
|
-
|
|
74
|
-
const client = useClient();
|
|
75
|
-
const schema = useSchema();
|
|
76
|
-
const translationContext: TranslationFunctionContext = { client, schema };
|
|
77
|
-
const toast = useToast();
|
|
78
|
-
const { loading: loadingSecrets, secrets } = useSecrets<Secrets>(
|
|
79
|
-
`${gtConfig.getSecretsNamespace()}.secrets`
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const fetchDocuments = useCallback(async () => {
|
|
83
|
-
setLoadingDocuments(true);
|
|
84
|
-
try {
|
|
85
|
-
const translateDocuments = gtConfig.getTranslateDocuments();
|
|
86
|
-
|
|
87
|
-
// Build filter conditions based on translateDocuments configuration
|
|
88
|
-
const filterConditions = translateDocuments
|
|
89
|
-
.map((filter) => {
|
|
90
|
-
if (filter.type && filter.documentId) {
|
|
91
|
-
// Both type and documentId must match
|
|
92
|
-
return `(_type == "${filter.type}" && _id == "${filter.documentId}")`;
|
|
93
|
-
} else if (filter.type) {
|
|
94
|
-
// Only type must match
|
|
95
|
-
return `_type == "${filter.type}"`;
|
|
96
|
-
} else if (filter.documentId) {
|
|
97
|
-
// Only documentId must match
|
|
98
|
-
return `_id == "${filter.documentId}"`;
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
})
|
|
102
|
-
.filter(Boolean);
|
|
103
|
-
|
|
104
|
-
// If no filters are configured, fall back to the original query
|
|
105
|
-
const languageField = gtConfig.getLanguageField();
|
|
106
|
-
const sourceLocale = gtConfig.getSourceLocale();
|
|
107
|
-
const languageFilter = `(!defined(${languageField}) || ${languageField} == "${sourceLocale}")`;
|
|
108
|
-
|
|
109
|
-
let query;
|
|
110
|
-
if (filterConditions.length === 0) {
|
|
111
|
-
query = `*[!(_type in ["system.group"]) && !(_id in path("_.**")) && ${languageFilter}]`;
|
|
112
|
-
} else {
|
|
113
|
-
const filterQuery = filterConditions.join(' || ');
|
|
114
|
-
query = `*[!(_type in ["system.group"]) && !(_id in path("_.**")) && (${filterQuery}) && ${languageFilter}]`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const docs = await client.fetch(query);
|
|
118
|
-
setDocuments(docs);
|
|
119
|
-
} catch {
|
|
120
|
-
toast.push({
|
|
121
|
-
title: 'Error fetching documents',
|
|
122
|
-
status: 'error',
|
|
123
|
-
closable: true,
|
|
124
|
-
});
|
|
125
|
-
} finally {
|
|
126
|
-
setLoadingDocuments(false);
|
|
127
|
-
}
|
|
128
|
-
}, [client, toast]);
|
|
129
|
-
|
|
130
|
-
const fetchLocales = useCallback(async () => {
|
|
131
|
-
if (!secrets) return;
|
|
132
|
-
try {
|
|
133
|
-
const availableLocales = await GTAdapter.getLocales(secrets);
|
|
134
|
-
setLocales(availableLocales);
|
|
135
|
-
} catch {
|
|
136
|
-
toast.push({
|
|
137
|
-
title: 'Error fetching locales',
|
|
138
|
-
status: 'error',
|
|
139
|
-
closable: true,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}, [secrets, toast]);
|
|
143
|
-
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
fetchDocuments();
|
|
146
|
-
}, [fetchDocuments]);
|
|
147
|
-
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (secrets) {
|
|
150
|
-
fetchLocales();
|
|
151
|
-
}
|
|
152
|
-
}, [fetchLocales, secrets]);
|
|
153
|
-
|
|
154
|
-
const handleTranslateAll = useCallback(async () => {
|
|
155
|
-
if (!secrets || documents.length === 0) return;
|
|
156
|
-
|
|
157
|
-
setIsBusy(true);
|
|
158
|
-
setIsTranslateAllDialogOpen(false);
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const availableLocaleIds = locales
|
|
162
|
-
.filter((locale) => locale.enabled !== false)
|
|
163
|
-
.map((locale) => locale.localeId);
|
|
164
|
-
|
|
165
|
-
console.log('documents', documents);
|
|
166
|
-
// Transform documents to the required format
|
|
167
|
-
const transformedDocuments = documents
|
|
168
|
-
.map((doc) => {
|
|
169
|
-
delete doc[gtConfig.getLanguageField()];
|
|
170
|
-
const baseLanguage = gtConfig.getSourceLocale();
|
|
171
|
-
try {
|
|
172
|
-
const serialized = serializeDocument(doc, schema, baseLanguage);
|
|
173
|
-
return {
|
|
174
|
-
info: {
|
|
175
|
-
documentId: doc._id?.replace('drafts.', '') || doc._id,
|
|
176
|
-
versionId: doc._rev,
|
|
177
|
-
},
|
|
178
|
-
serializedDocument: serialized,
|
|
179
|
-
};
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error('Error transforming document', doc._id, error);
|
|
182
|
-
}
|
|
183
|
-
return null;
|
|
184
|
-
})
|
|
185
|
-
.filter((doc) => doc !== null);
|
|
186
|
-
|
|
187
|
-
console.log('transformedDocuments', transformedDocuments);
|
|
188
|
-
console.log('transformedDocuments.length', transformedDocuments.length);
|
|
189
|
-
const uploadResult = await uploadFiles(transformedDocuments, secrets);
|
|
190
|
-
await initProject(uploadResult, { timeout: 600 }, secrets);
|
|
191
|
-
await createJobs(uploadResult, availableLocaleIds, secrets);
|
|
192
|
-
|
|
193
|
-
toast.push({
|
|
194
|
-
title: `Translation tasks created for ${documents.length} documents`,
|
|
195
|
-
status: 'success',
|
|
196
|
-
closable: true,
|
|
197
|
-
});
|
|
198
|
-
} catch {
|
|
199
|
-
toast.push({
|
|
200
|
-
title: 'Error creating translation tasks',
|
|
201
|
-
status: 'error',
|
|
202
|
-
closable: true,
|
|
203
|
-
});
|
|
204
|
-
} finally {
|
|
205
|
-
setIsBusy(false);
|
|
206
|
-
}
|
|
207
|
-
}, [secrets, documents, locales, toast]);
|
|
208
|
-
|
|
209
|
-
const handleImportAll = useCallback(async () => {
|
|
210
|
-
if (!secrets || documents.length === 0) return;
|
|
211
|
-
|
|
212
|
-
setIsBusy(true);
|
|
213
|
-
setIsImportAllDialogOpen(false);
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
// Collect all ready translations
|
|
217
|
-
const readyFiles: BatchedFiles = [];
|
|
218
|
-
|
|
219
|
-
for (const [key, status] of translationStatuses.entries()) {
|
|
220
|
-
if (status.isReady && status.translationId) {
|
|
221
|
-
const [documentId, locale] = key.split(':');
|
|
222
|
-
const document = documents.find(
|
|
223
|
-
(doc) => (doc._id?.replace('drafts.', '') || doc._id) === documentId
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
if (document) {
|
|
227
|
-
readyFiles.push({
|
|
228
|
-
documentId,
|
|
229
|
-
versionId: document._rev,
|
|
230
|
-
translationId: status.translationId,
|
|
231
|
-
locale,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (readyFiles.length === 0) {
|
|
238
|
-
toast.push({
|
|
239
|
-
title: 'No ready translations to import',
|
|
240
|
-
status: 'warning',
|
|
241
|
-
closable: true,
|
|
242
|
-
});
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Download all ready translations
|
|
247
|
-
const downloadedFiles = await downloadTranslations(readyFiles, secrets);
|
|
248
|
-
|
|
249
|
-
// Set up progress tracking
|
|
250
|
-
setImportProgress({
|
|
251
|
-
current: 0,
|
|
252
|
-
total: downloadedFiles.length,
|
|
253
|
-
isImporting: true,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Batch import in groups of 10
|
|
257
|
-
const batchSize = 10;
|
|
258
|
-
let successCount = 0;
|
|
259
|
-
let failureCount = 0;
|
|
260
|
-
const successfulImports: string[] = [];
|
|
261
|
-
|
|
262
|
-
for (let i = 0; i < downloadedFiles.length; i += batchSize) {
|
|
263
|
-
const batch = downloadedFiles.slice(i, i + batchSize);
|
|
264
|
-
|
|
265
|
-
// Process batch in parallel
|
|
266
|
-
const batchPromises = batch.map(async (file) => {
|
|
267
|
-
try {
|
|
268
|
-
const docInfo: GTFile = {
|
|
269
|
-
documentId: file.docData.documentId,
|
|
270
|
-
versionId: file.docData.versionId,
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
await importDocument(
|
|
274
|
-
docInfo,
|
|
275
|
-
file.docData.locale,
|
|
276
|
-
file.data,
|
|
277
|
-
translationContext,
|
|
278
|
-
autoPublish
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
const key = `${file.docData.documentId}:${file.docData.locale}`;
|
|
282
|
-
successfulImports.push(key);
|
|
283
|
-
setImportedTranslations((prev) => new Set([...prev, key]));
|
|
284
|
-
return { success: true, file };
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error(
|
|
287
|
-
`Failed to import ${file.docData.documentId} (${file.docData.locale}):`,
|
|
288
|
-
error
|
|
289
|
-
);
|
|
290
|
-
return { success: false, file, error };
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const batchResults = await Promise.all(batchPromises);
|
|
295
|
-
|
|
296
|
-
batchResults.forEach((result) => {
|
|
297
|
-
if (result.success) {
|
|
298
|
-
successCount++;
|
|
299
|
-
} else {
|
|
300
|
-
failureCount++;
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// Update progress
|
|
305
|
-
setImportProgress({
|
|
306
|
-
current: i + batch.length,
|
|
307
|
-
total: downloadedFiles.length,
|
|
308
|
-
isImporting: true,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Update download status for successful imports
|
|
313
|
-
if (successfulImports.length > 0) {
|
|
314
|
-
const newDownloadStatus = {
|
|
315
|
-
...downloadStatus,
|
|
316
|
-
downloaded: new Set([
|
|
317
|
-
...downloadStatus.downloaded,
|
|
318
|
-
...successfulImports,
|
|
319
|
-
]),
|
|
320
|
-
};
|
|
321
|
-
setDownloadStatus(newDownloadStatus);
|
|
322
|
-
}
|
|
33
|
+
const [isImportMissingDialogOpen, setIsImportMissingDialogOpen] =
|
|
34
|
+
useState(false);
|
|
323
35
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
status: successCount > 0 ? 'success' : 'error',
|
|
327
|
-
closable: true,
|
|
328
|
-
});
|
|
329
|
-
} catch (error) {
|
|
330
|
-
console.error('Error importing translations:', error);
|
|
331
|
-
toast.push({
|
|
332
|
-
title: 'Error importing translations',
|
|
333
|
-
status: 'error',
|
|
334
|
-
closable: true,
|
|
335
|
-
});
|
|
336
|
-
} finally {
|
|
337
|
-
setIsBusy(false);
|
|
338
|
-
setImportProgress({ current: 0, total: 0, isImporting: false });
|
|
339
|
-
}
|
|
340
|
-
}, [
|
|
341
|
-
secrets,
|
|
36
|
+
const {
|
|
37
|
+
isBusy,
|
|
342
38
|
documents,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
// Update translation statuses
|
|
380
|
-
const newStatuses = new Map(translationStatuses);
|
|
381
|
-
|
|
382
|
-
// Reset all to not ready first
|
|
383
|
-
for (const doc of documents) {
|
|
384
|
-
for (const localeId of availableLocaleIds) {
|
|
385
|
-
const documentId = doc._id?.replace('drafts.', '') || doc._id;
|
|
386
|
-
const key = `${documentId}:${localeId}`;
|
|
387
|
-
newStatuses.set(key, { progress: 0, isReady: false });
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Update with ready translations
|
|
392
|
-
if (Array.isArray(readyTranslations)) {
|
|
393
|
-
for (const translation of readyTranslations) {
|
|
394
|
-
const key = `${translation.fileId}:${translation.locale}`;
|
|
395
|
-
newStatuses.set(key, {
|
|
396
|
-
progress: 100,
|
|
397
|
-
isReady: true,
|
|
398
|
-
translationId: translation.id,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
setTranslationStatuses(newStatuses);
|
|
404
|
-
|
|
405
|
-
toast.push({
|
|
406
|
-
title: `Refreshed status for ${documents.length} documents`,
|
|
407
|
-
status: 'success',
|
|
408
|
-
closable: true,
|
|
409
|
-
});
|
|
410
|
-
} catch (error) {
|
|
411
|
-
console.error('Error refreshing translation status:', error);
|
|
412
|
-
toast.push({
|
|
413
|
-
title: 'Error refreshing translation status',
|
|
414
|
-
status: 'error',
|
|
415
|
-
closable: true,
|
|
416
|
-
});
|
|
417
|
-
} finally {
|
|
418
|
-
setIsRefreshing(false);
|
|
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...';
|
|
419
73
|
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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';
|
|
431
88
|
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Auto-refresh functionality
|
|
435
|
-
useEffect(() => {
|
|
436
|
-
if (!autoRefresh || documents.length === 0 || !secrets) return;
|
|
437
|
-
|
|
438
|
-
const interval = setInterval(async () => {
|
|
439
|
-
await handleRefreshAll();
|
|
440
|
-
}, 10000); // Refresh every 10 seconds
|
|
441
|
-
|
|
442
|
-
return () => clearInterval(interval);
|
|
443
|
-
}, [autoRefresh, documents.length, secrets, handleRefreshAll]);
|
|
444
|
-
|
|
445
|
-
// Initialize imported translations from download status
|
|
446
|
-
useEffect(() => {
|
|
447
|
-
setImportedTranslations(new Set(downloadStatus.downloaded));
|
|
448
|
-
}, [downloadStatus.downloaded]);
|
|
449
|
-
|
|
450
|
-
const handleImportDocument = useCallback(
|
|
451
|
-
async (documentId: string, localeId: string) => {
|
|
452
|
-
if (!secrets) return;
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
const key = `${documentId}:${localeId}`;
|
|
456
|
-
const status = translationStatuses.get(key);
|
|
457
|
-
|
|
458
|
-
if (!status?.isReady || !status.translationId) {
|
|
459
|
-
toast.push({
|
|
460
|
-
title: `Translation not ready for ${documentId} (${localeId})`,
|
|
461
|
-
status: 'warning',
|
|
462
|
-
closable: true,
|
|
463
|
-
});
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const document = documents.find(
|
|
468
|
-
(doc) => (doc._id?.replace('drafts.', '') || doc._id) === documentId
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
if (!document) {
|
|
472
|
-
toast.push({
|
|
473
|
-
title: `Document ${documentId} not found`,
|
|
474
|
-
status: 'error',
|
|
475
|
-
closable: true,
|
|
476
|
-
});
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Download single translation
|
|
481
|
-
const downloadedFiles = await downloadTranslations(
|
|
482
|
-
[
|
|
483
|
-
{
|
|
484
|
-
documentId,
|
|
485
|
-
versionId: document._rev,
|
|
486
|
-
translationId: status.translationId,
|
|
487
|
-
locale: localeId,
|
|
488
|
-
},
|
|
489
|
-
],
|
|
490
|
-
secrets
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
if (downloadedFiles.length > 0) {
|
|
494
|
-
try {
|
|
495
|
-
const docInfo: GTFile = {
|
|
496
|
-
documentId,
|
|
497
|
-
versionId: document._rev,
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
await importDocument(
|
|
501
|
-
docInfo,
|
|
502
|
-
localeId,
|
|
503
|
-
downloadedFiles[0].data,
|
|
504
|
-
translationContext,
|
|
505
|
-
autoPublish
|
|
506
|
-
);
|
|
89
|
+
};
|
|
507
90
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
setImportedTranslations((prev) => new Set([...prev, key]));
|
|
515
|
-
|
|
516
|
-
toast.push({
|
|
517
|
-
title: `Successfully imported translation for ${documentId} (${localeId})`,
|
|
518
|
-
status: 'success',
|
|
519
|
-
closable: true,
|
|
520
|
-
});
|
|
521
|
-
} catch (importError) {
|
|
522
|
-
console.error('Failed to import translation:', importError);
|
|
523
|
-
toast.push({
|
|
524
|
-
title: `Failed to import translation for ${documentId} (${localeId})`,
|
|
525
|
-
status: 'error',
|
|
526
|
-
closable: true,
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
toast.push({
|
|
531
|
-
title: `No translation content received for ${documentId}`,
|
|
532
|
-
status: 'warning',
|
|
533
|
-
closable: true,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
} catch (error) {
|
|
537
|
-
console.error('Error importing translation:', error);
|
|
538
|
-
toast.push({
|
|
539
|
-
title: `Error importing translation for ${documentId}`,
|
|
540
|
-
status: 'error',
|
|
541
|
-
closable: true,
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
},
|
|
545
|
-
[
|
|
546
|
-
secrets,
|
|
547
|
-
translationStatuses,
|
|
548
|
-
documents,
|
|
549
|
-
downloadStatus,
|
|
550
|
-
toast,
|
|
551
|
-
autoPublish,
|
|
552
|
-
translationContext,
|
|
553
|
-
]
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
if (loadingSecrets) {
|
|
557
|
-
return (
|
|
558
|
-
<ThemeProvider theme={theme}>
|
|
559
|
-
<Container width={2}>
|
|
560
|
-
<Flex padding={5} align='center' justify='center'>
|
|
561
|
-
<Spinner />
|
|
562
|
-
</Flex>
|
|
563
|
-
</Container>
|
|
564
|
-
</ThemeProvider>
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
if (!secrets) {
|
|
569
|
-
return (
|
|
570
|
-
<ThemeProvider theme={theme}>
|
|
571
|
-
<Container width={2}>
|
|
572
|
-
<Box padding={4} marginTop={5}>
|
|
573
|
-
<Card tone='caution' padding={[2, 3, 4, 4]} shadow={1} radius={2}>
|
|
574
|
-
<Text>
|
|
575
|
-
Can't find secrets for your translation service. Did you load
|
|
576
|
-
them into this dataset?
|
|
577
|
-
</Text>
|
|
578
|
-
</Card>
|
|
579
|
-
</Box>
|
|
580
|
-
</Container>
|
|
581
|
-
</ThemeProvider>
|
|
582
|
-
);
|
|
583
|
-
}
|
|
91
|
+
// Reset current operation when operation completes
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (!isBusy && !importProgress.isImporting) {
|
|
94
|
+
setCurrentOperation(null);
|
|
95
|
+
}
|
|
96
|
+
}, [isBusy, importProgress.isImporting]);
|
|
584
97
|
|
|
585
98
|
return (
|
|
586
|
-
<
|
|
587
|
-
<
|
|
588
|
-
<
|
|
589
|
-
<
|
|
590
|
-
<Stack space={
|
|
591
|
-
<
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
Translations
|
|
595
|
-
</Heading>
|
|
596
|
-
<Text size={2}>
|
|
597
|
-
Manage your document translations from this centralized
|
|
598
|
-
location.
|
|
599
|
-
</Text>
|
|
600
|
-
</Stack>
|
|
601
|
-
|
|
602
|
-
<Flex gap={3} align='center'>
|
|
603
|
-
<Flex gap={2} align='center'>
|
|
604
|
-
<Text size={1}>Auto-refresh</Text>
|
|
605
|
-
<Switch
|
|
606
|
-
checked={autoRefresh}
|
|
607
|
-
onChange={() => setAutoRefresh(!autoRefresh)}
|
|
608
|
-
/>
|
|
609
|
-
</Flex>
|
|
610
|
-
<Button
|
|
611
|
-
fontSize={1}
|
|
612
|
-
padding={2}
|
|
613
|
-
text={isRefreshing ? 'Refreshing...' : 'Refresh Status'}
|
|
614
|
-
onClick={handleRefreshAll}
|
|
615
|
-
disabled={
|
|
616
|
-
isRefreshing ||
|
|
617
|
-
isBusy ||
|
|
618
|
-
loadingDocuments ||
|
|
619
|
-
documents.length === 0
|
|
620
|
-
}
|
|
621
|
-
/>
|
|
622
|
-
</Flex>
|
|
623
|
-
</Flex>
|
|
624
|
-
|
|
625
|
-
<Stack space={4}>
|
|
626
|
-
<Box>
|
|
627
|
-
<Text size={1} muted>
|
|
628
|
-
{loadingDocuments
|
|
629
|
-
? 'Loading documents...'
|
|
630
|
-
: `Found ${documents.length} documents available for translation`}
|
|
631
|
-
</Text>
|
|
632
|
-
</Box>
|
|
633
|
-
|
|
634
|
-
<Flex justify='center'>
|
|
635
|
-
<Button
|
|
636
|
-
style={{ width: '200px' }}
|
|
637
|
-
tone='critical'
|
|
638
|
-
text={isBusy ? 'Processing...' : 'Translate All'}
|
|
639
|
-
onClick={() => setIsTranslateAllDialogOpen(true)}
|
|
640
|
-
disabled={
|
|
641
|
-
isBusy || loadingDocuments || documents.length === 0
|
|
642
|
-
}
|
|
643
|
-
/>
|
|
644
|
-
</Flex>
|
|
645
|
-
|
|
646
|
-
{loadingDocuments ? (
|
|
647
|
-
<Flex align='center' justify='center' padding={4}>
|
|
648
|
-
<Spinner />
|
|
649
|
-
</Flex>
|
|
650
|
-
) : (
|
|
651
|
-
<Box style={{ maxHeight: '60vh', overflowY: 'auto' }}>
|
|
652
|
-
<Stack space={2}>
|
|
653
|
-
{documents.map((document) => (
|
|
654
|
-
<Card key={document._id} shadow={1} padding={3}>
|
|
655
|
-
<Stack space={3}>
|
|
656
|
-
<Flex justify='space-between' align='flex-start'>
|
|
657
|
-
<Box flex={1}>
|
|
658
|
-
<Text weight='semibold' size={1}>
|
|
659
|
-
{document._id?.replace('drafts.', '') ||
|
|
660
|
-
document._id}
|
|
661
|
-
</Text>
|
|
662
|
-
<Text
|
|
663
|
-
size={0}
|
|
664
|
-
muted
|
|
665
|
-
style={{ marginTop: '2px' }}
|
|
666
|
-
>
|
|
667
|
-
{document._type}
|
|
668
|
-
</Text>
|
|
669
|
-
</Box>
|
|
670
|
-
</Flex>
|
|
671
|
-
|
|
672
|
-
<Stack space={2}>
|
|
673
|
-
{locales.length > 0 ? (
|
|
674
|
-
locales
|
|
675
|
-
.filter((locale) => locale.enabled !== false)
|
|
676
|
-
.map((locale) => {
|
|
677
|
-
const documentId =
|
|
678
|
-
document._id?.replace('drafts.', '') ||
|
|
679
|
-
document._id;
|
|
680
|
-
const key = `${documentId}:${locale.localeId}`;
|
|
681
|
-
const status = translationStatuses.get(key);
|
|
682
|
-
const isDownloaded =
|
|
683
|
-
downloadStatus.downloaded.has(key);
|
|
684
|
-
const isImported =
|
|
685
|
-
importedTranslations.has(key);
|
|
686
|
-
|
|
687
|
-
return (
|
|
688
|
-
<LanguageStatus
|
|
689
|
-
key={`${document._id}-${locale.localeId}`}
|
|
690
|
-
title={
|
|
691
|
-
locale.description || locale.localeId
|
|
692
|
-
}
|
|
693
|
-
progress={status?.progress || 0}
|
|
694
|
-
isImported={isImported || isDownloaded}
|
|
695
|
-
importFile={async () => {
|
|
696
|
-
await handleImportDocument(
|
|
697
|
-
documentId,
|
|
698
|
-
locale.localeId
|
|
699
|
-
);
|
|
700
|
-
}}
|
|
701
|
-
/>
|
|
702
|
-
);
|
|
703
|
-
})
|
|
704
|
-
) : (
|
|
705
|
-
<Text size={1} muted>
|
|
706
|
-
No locales configured
|
|
707
|
-
</Text>
|
|
708
|
-
)}
|
|
709
|
-
</Stack>
|
|
710
|
-
</Stack>
|
|
711
|
-
</Card>
|
|
712
|
-
))}
|
|
713
|
-
</Stack>
|
|
714
|
-
</Box>
|
|
715
|
-
)}
|
|
716
|
-
|
|
717
|
-
<Stack space={3}>
|
|
718
|
-
<Flex gap={3} align='center' justify='space-between'>
|
|
719
|
-
<Flex gap={2} align='center'>
|
|
720
|
-
<Button
|
|
721
|
-
mode='ghost'
|
|
722
|
-
onClick={() => setIsImportAllDialogOpen(true)}
|
|
723
|
-
text={isBusy ? 'Importing...' : 'Import All'}
|
|
724
|
-
icon={isBusy ? null : DownloadIcon}
|
|
725
|
-
disabled={
|
|
726
|
-
isBusy || loadingDocuments || documents.length === 0
|
|
727
|
-
}
|
|
728
|
-
/>
|
|
729
|
-
{importedTranslations.size ===
|
|
730
|
-
documents.length *
|
|
731
|
-
locales.filter((l) => l.enabled !== false).length &&
|
|
732
|
-
documents.length > 0 &&
|
|
733
|
-
locales.length > 0 && (
|
|
734
|
-
<Flex
|
|
735
|
-
gap={2}
|
|
736
|
-
align='center'
|
|
737
|
-
style={{ color: 'green' }}
|
|
738
|
-
>
|
|
739
|
-
<CheckmarkCircleIcon />
|
|
740
|
-
<Text size={1}>All translations imported</Text>
|
|
741
|
-
</Flex>
|
|
742
|
-
)}
|
|
743
|
-
{importedTranslations.size > 0 &&
|
|
744
|
-
importedTranslations.size <
|
|
745
|
-
documents.length *
|
|
746
|
-
locales.filter((l) => l.enabled !== false)
|
|
747
|
-
.length && (
|
|
748
|
-
<Text size={1} style={{ color: '#666' }}>
|
|
749
|
-
{importedTranslations.size}/
|
|
750
|
-
{documents.length *
|
|
751
|
-
locales.filter((l) => l.enabled !== false)
|
|
752
|
-
.length}{' '}
|
|
753
|
-
imported
|
|
754
|
-
</Text>
|
|
755
|
-
)}
|
|
756
|
-
</Flex>
|
|
757
|
-
<Flex
|
|
758
|
-
gap={2}
|
|
759
|
-
align='center'
|
|
760
|
-
style={{ whiteSpace: 'nowrap' }}
|
|
761
|
-
>
|
|
762
|
-
<Text size={1}>Auto-Publish</Text>
|
|
763
|
-
<Switch
|
|
764
|
-
checked={autoPublish}
|
|
765
|
-
onChange={() => setAutoPublish(!autoPublish)}
|
|
766
|
-
disabled={isBusy}
|
|
767
|
-
/>
|
|
768
|
-
</Flex>
|
|
769
|
-
</Flex>
|
|
770
|
-
|
|
771
|
-
{/* Import Progress UI */}
|
|
772
|
-
{importProgress.isImporting && (
|
|
773
|
-
<Flex justify='center' align='center' gap={3}>
|
|
774
|
-
<Spinner size={1} />
|
|
775
|
-
<Text size={1}>
|
|
776
|
-
Importing {importProgress.current} of{' '}
|
|
777
|
-
{importProgress.total} translations...
|
|
778
|
-
</Text>
|
|
779
|
-
</Flex>
|
|
780
|
-
)}
|
|
781
|
-
</Stack>
|
|
782
|
-
</Stack>
|
|
783
|
-
|
|
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>
|
|
784
107
|
<Text size={2}>
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
General Translation Dashboard
|
|
788
|
-
</Link>
|
|
789
|
-
.
|
|
108
|
+
Manage your document translations from this centralized
|
|
109
|
+
location.
|
|
790
110
|
</Text>
|
|
791
111
|
</Stack>
|
|
792
|
-
</Box>
|
|
793
112
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
+
>
|
|
803
170
|
<Button
|
|
804
|
-
text='Cancel'
|
|
805
171
|
mode='ghost'
|
|
806
|
-
onClick={() =>
|
|
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
|
+
}
|
|
807
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
|
+
>
|
|
808
194
|
<Button
|
|
809
|
-
|
|
810
|
-
tone='
|
|
811
|
-
onClick={
|
|
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
|
+
}
|
|
812
213
|
/>
|
|
813
|
-
</
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
<Stack space={3}>
|
|
819
|
-
<Text>
|
|
820
|
-
Are you sure you want to create translation tasks for all{' '}
|
|
821
|
-
{documents.length} documents?
|
|
822
|
-
</Text>
|
|
823
|
-
<Text size={1} muted>
|
|
824
|
-
This will submit all documents to General Translation for
|
|
825
|
-
processing.
|
|
826
|
-
</Text>
|
|
827
|
-
</Stack>
|
|
828
|
-
</Box>
|
|
829
|
-
</Dialog>
|
|
830
|
-
)}
|
|
831
|
-
|
|
832
|
-
{/* Import All Confirmation Dialog */}
|
|
833
|
-
{isImportAllDialogOpen && (
|
|
834
|
-
<Dialog
|
|
835
|
-
header='Confirm Import'
|
|
836
|
-
id='import-all-dialog'
|
|
837
|
-
onClose={() => setIsImportAllDialogOpen(false)}
|
|
838
|
-
footer={
|
|
839
|
-
<Box padding={3}>
|
|
840
|
-
<Flex gap={2}>
|
|
214
|
+
</Tooltip>
|
|
215
|
+
<Tooltip
|
|
216
|
+
placement='top'
|
|
217
|
+
content='Replaces references in documents with the corresponding translated document reference'
|
|
218
|
+
>
|
|
841
219
|
<Button
|
|
842
|
-
text='Cancel'
|
|
843
220
|
mode='ghost'
|
|
844
|
-
|
|
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
|
+
}
|
|
845
240
|
/>
|
|
241
|
+
</Tooltip>
|
|
242
|
+
<Tooltip
|
|
243
|
+
placement='top'
|
|
244
|
+
content='Publishes all translations whose source document is published'
|
|
245
|
+
>
|
|
846
246
|
<Button
|
|
847
|
-
|
|
848
|
-
tone='
|
|
849
|
-
onClick={
|
|
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
|
+
}
|
|
850
265
|
/>
|
|
851
|
-
</
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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>
|
|
873
335
|
);
|
|
874
336
|
};
|
|
875
337
|
|