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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pluginConfig } from '../adapter/core';
|
|
2
2
|
import { documentLevelPatch } from '../configuration/baseDocumentLevelConfig/documentLevelPatch';
|
|
3
3
|
import type { GTFile, TranslationFunctionContext } from '../types';
|
|
4
4
|
import { deserializeDocument } from '../utils/serialize';
|
|
@@ -8,7 +8,7 @@ export async function importDocument(
|
|
|
8
8
|
localeId: string,
|
|
9
9
|
document: string,
|
|
10
10
|
context: TranslationFunctionContext,
|
|
11
|
-
|
|
11
|
+
mergeWithTargetLocale: boolean = false
|
|
12
12
|
) {
|
|
13
13
|
const { client } = context;
|
|
14
14
|
const deserialized = deserializeDocument(document);
|
|
@@ -17,8 +17,7 @@ export async function importDocument(
|
|
|
17
17
|
deserialized,
|
|
18
18
|
localeId,
|
|
19
19
|
client,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
publish
|
|
20
|
+
pluginConfig.getLanguageField(),
|
|
21
|
+
mergeWithTargetLocale
|
|
23
22
|
);
|
|
24
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GTFile, Secrets } from '../types';
|
|
2
2
|
import { gt, overrideConfig } from '../adapter/core';
|
|
3
3
|
import { libraryDefaultLocale } from 'generaltranslation/internal';
|
|
4
|
-
import type { SerializedDocument } from '
|
|
4
|
+
import type { SerializedDocument } from '../serialization';
|
|
5
5
|
|
|
6
6
|
// note: this function is used to create a new translation task
|
|
7
7
|
// uploads files & calls the getTranslationTask function
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
2
|
|
|
3
3
|
import { SanityClient, Schema, TypedObject } from 'sanity';
|
|
4
|
-
import { SerializedDocument } from '
|
|
4
|
+
import type { SerializedDocument } from './serialization';
|
|
5
5
|
import { PortableTextTypeComponent } from '@portabletext/to-html';
|
|
6
|
-
import { DeserializerRule } from '@
|
|
6
|
+
import type { DeserializerRule } from '@portabletext/block-tools';
|
|
7
7
|
|
|
8
8
|
export type TranslationTaskLocaleStatus = {
|
|
9
9
|
localeId: string;
|
|
@@ -74,15 +74,7 @@ export type GTSerializedDocument = Omit<SerializedDocument, 'name'> & GTFile;
|
|
|
74
74
|
|
|
75
75
|
export type ExportForTranslation = (
|
|
76
76
|
documentInfo: GTFile,
|
|
77
|
-
context: TranslationFunctionContext
|
|
78
|
-
serializationOptions?: {
|
|
79
|
-
additionalStopTypes?: string[];
|
|
80
|
-
additionalSerializers?: Record<
|
|
81
|
-
string,
|
|
82
|
-
PortableTextTypeComponent | undefined
|
|
83
|
-
>;
|
|
84
|
-
},
|
|
85
|
-
languageField?: string
|
|
77
|
+
context: TranslationFunctionContext
|
|
86
78
|
) => Promise<GTSerializedDocument>;
|
|
87
79
|
|
|
88
80
|
export type ImportTranslation = (
|
|
@@ -90,14 +82,6 @@ export type ImportTranslation = (
|
|
|
90
82
|
localeId: string,
|
|
91
83
|
document: string,
|
|
92
84
|
context: TranslationFunctionContext,
|
|
93
|
-
serializationOptions?: {
|
|
94
|
-
additionalDeserializers?: Record<
|
|
95
|
-
string,
|
|
96
|
-
(value: HTMLElement) => TypedObject
|
|
97
|
-
>;
|
|
98
|
-
additionalBlockDeserializers?: DeserializerRule[];
|
|
99
|
-
},
|
|
100
|
-
languageField?: string,
|
|
101
85
|
mergeWithTargetLocale?: boolean,
|
|
102
86
|
publish?: boolean
|
|
103
87
|
) => Promise<void>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { GTFile, TranslationFunctionContext } from '../types';
|
|
2
|
+
import { importDocument } from '../translation/importDocument';
|
|
3
|
+
|
|
4
|
+
export interface BatchProcessorOptions {
|
|
5
|
+
batchSize?: number;
|
|
6
|
+
onProgress?: (current: number, total: number) => void;
|
|
7
|
+
onItemSuccess?: (item: any, result: any) => void;
|
|
8
|
+
onItemFailure?: (item: any, error: any) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ImportBatchItem {
|
|
12
|
+
docInfo: GTFile;
|
|
13
|
+
locale: string;
|
|
14
|
+
data: any;
|
|
15
|
+
translationContext: TranslationFunctionContext;
|
|
16
|
+
key: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function processBatch<T>(
|
|
20
|
+
items: T[],
|
|
21
|
+
processor: (item: T) => Promise<any>,
|
|
22
|
+
options: BatchProcessorOptions = {}
|
|
23
|
+
): Promise<{
|
|
24
|
+
successCount: number;
|
|
25
|
+
failureCount: number;
|
|
26
|
+
successfulItems: any[];
|
|
27
|
+
failedItems: { item: T; error: any }[];
|
|
28
|
+
}> {
|
|
29
|
+
const { batchSize = 20, onProgress, onItemSuccess, onItemFailure } = options;
|
|
30
|
+
|
|
31
|
+
let successCount = 0;
|
|
32
|
+
let failureCount = 0;
|
|
33
|
+
const successfulItems: any[] = [];
|
|
34
|
+
const failedItems: { item: T; error: any }[] = [];
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
37
|
+
const batch = items.slice(i, i + batchSize);
|
|
38
|
+
|
|
39
|
+
const batchPromises = batch.map(async (item) => {
|
|
40
|
+
try {
|
|
41
|
+
const result = await processor(item);
|
|
42
|
+
onItemSuccess?.(item, result);
|
|
43
|
+
return { success: true, item, result };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
onItemFailure?.(item, error);
|
|
46
|
+
return { success: false, item, error };
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const batchResults = await Promise.all(batchPromises);
|
|
51
|
+
|
|
52
|
+
batchResults.forEach((result) => {
|
|
53
|
+
if (result.success) {
|
|
54
|
+
successCount++;
|
|
55
|
+
successfulItems.push(result.result);
|
|
56
|
+
} else {
|
|
57
|
+
failureCount++;
|
|
58
|
+
failedItems.push({ item: result.item, error: result.error });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
onProgress?.(i + batch.length, items.length);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { successCount, failureCount, successfulItems, failedItems };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function processImportBatch(
|
|
69
|
+
items: ImportBatchItem[],
|
|
70
|
+
options: BatchProcessorOptions = {}
|
|
71
|
+
): Promise<{
|
|
72
|
+
successCount: number;
|
|
73
|
+
failureCount: number;
|
|
74
|
+
successfulImports: string[];
|
|
75
|
+
failedItems: { item: ImportBatchItem; error: any }[];
|
|
76
|
+
}> {
|
|
77
|
+
const successfulImports: string[] = [];
|
|
78
|
+
|
|
79
|
+
const result = await processBatch(
|
|
80
|
+
items,
|
|
81
|
+
async (item: ImportBatchItem) => {
|
|
82
|
+
await importDocument(
|
|
83
|
+
item.docInfo,
|
|
84
|
+
item.locale,
|
|
85
|
+
item.data,
|
|
86
|
+
item.translationContext,
|
|
87
|
+
false
|
|
88
|
+
);
|
|
89
|
+
return item.key;
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
...options,
|
|
93
|
+
onItemSuccess: (item: ImportBatchItem, key: string) => {
|
|
94
|
+
successfulImports.push(key);
|
|
95
|
+
options.onItemSuccess?.(item, key);
|
|
96
|
+
},
|
|
97
|
+
onItemFailure: (item: ImportBatchItem, error: any) => {
|
|
98
|
+
console.error(
|
|
99
|
+
`Failed to import ${item.docInfo.documentId} (${item.locale}):`,
|
|
100
|
+
error
|
|
101
|
+
);
|
|
102
|
+
options.onItemFailure?.(item, error);
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
...result,
|
|
109
|
+
successfulImports,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SanityDocument } from 'sanity';
|
|
2
|
+
import { GTFile, Secrets, TranslationFunctionContext } from '../types';
|
|
3
|
+
import {
|
|
4
|
+
downloadTranslations,
|
|
5
|
+
BatchedFiles,
|
|
6
|
+
} from '../translation/downloadTranslations';
|
|
7
|
+
import { processImportBatch, ImportBatchItem } from './batchProcessor';
|
|
8
|
+
|
|
9
|
+
export interface TranslationStatus {
|
|
10
|
+
progress: number;
|
|
11
|
+
isReady: boolean;
|
|
12
|
+
translationId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ImportResult {
|
|
16
|
+
successCount: number;
|
|
17
|
+
failureCount: number;
|
|
18
|
+
successfulImports: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ImportOptions {
|
|
22
|
+
filterReadyFiles?: (key: string, status: TranslationStatus) => boolean;
|
|
23
|
+
onProgress?: (current: number, total: number) => void;
|
|
24
|
+
onImportSuccess?: (key: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function getReadyFilesForImport(
|
|
28
|
+
documents: SanityDocument[],
|
|
29
|
+
translationStatuses: Map<string, TranslationStatus>,
|
|
30
|
+
options: ImportOptions = {}
|
|
31
|
+
): Promise<BatchedFiles> {
|
|
32
|
+
const { filterReadyFiles = () => true } = options;
|
|
33
|
+
const readyFiles: BatchedFiles = [];
|
|
34
|
+
|
|
35
|
+
for (const [key, status] of translationStatuses.entries()) {
|
|
36
|
+
if (
|
|
37
|
+
status.isReady &&
|
|
38
|
+
status.translationId &&
|
|
39
|
+
filterReadyFiles(key, status)
|
|
40
|
+
) {
|
|
41
|
+
const [documentId, locale] = key.split(':');
|
|
42
|
+
const document = documents.find(
|
|
43
|
+
(doc) => (doc._id?.replace('drafts.', '') || doc._id) === documentId
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (document) {
|
|
47
|
+
readyFiles.push({
|
|
48
|
+
documentId,
|
|
49
|
+
versionId: document._rev,
|
|
50
|
+
translationId: status.translationId,
|
|
51
|
+
locale,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return readyFiles;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function importTranslations(
|
|
61
|
+
readyFiles: BatchedFiles,
|
|
62
|
+
secrets: Secrets,
|
|
63
|
+
translationContext: TranslationFunctionContext,
|
|
64
|
+
options: ImportOptions = {}
|
|
65
|
+
): Promise<ImportResult> {
|
|
66
|
+
if (readyFiles.length === 0) {
|
|
67
|
+
return { successCount: 0, failureCount: 0, successfulImports: [] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const downloadedFiles = await downloadTranslations(readyFiles, secrets);
|
|
71
|
+
|
|
72
|
+
const importItems: ImportBatchItem[] = downloadedFiles.map((file) => ({
|
|
73
|
+
docInfo: {
|
|
74
|
+
documentId: file.docData.documentId,
|
|
75
|
+
versionId: file.docData.versionId,
|
|
76
|
+
},
|
|
77
|
+
locale: file.docData.locale,
|
|
78
|
+
data: file.data,
|
|
79
|
+
translationContext,
|
|
80
|
+
key: `${file.docData.documentId}:${file.docData.locale}`,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const result = await processImportBatch(importItems, {
|
|
84
|
+
onProgress: options.onProgress,
|
|
85
|
+
onItemSuccess: (item, key) => {
|
|
86
|
+
options.onImportSuccess?.(key);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
successCount: result.successCount,
|
|
92
|
+
failureCount: result.failureCount,
|
|
93
|
+
successfulImports: result.successfulImports,
|
|
94
|
+
};
|
|
95
|
+
}
|
package/src/utils/serialize.ts
CHANGED
|
@@ -5,13 +5,24 @@ import {
|
|
|
5
5
|
defaultStopTypes,
|
|
6
6
|
customSerializers,
|
|
7
7
|
customBlockDeserializers,
|
|
8
|
-
} from '
|
|
8
|
+
} from '../serialization/';
|
|
9
|
+
import { PortableTextHtmlComponents } from '@portabletext/to-html';
|
|
10
|
+
import { pluginConfig } from '../adapter/core';
|
|
11
|
+
import { merge } from 'lodash';
|
|
9
12
|
|
|
10
13
|
export function deserializeDocument(document: string) {
|
|
14
|
+
const deserializers = merge(
|
|
15
|
+
{ types: {} },
|
|
16
|
+
pluginConfig.getAdditionalDeserializers()
|
|
17
|
+
) satisfies Partial<PortableTextHtmlComponents>;
|
|
18
|
+
const blockDeserializers = [
|
|
19
|
+
...customBlockDeserializers,
|
|
20
|
+
...pluginConfig.getAdditionalBlockDeserializers(),
|
|
21
|
+
];
|
|
11
22
|
const deserialized = BaseDocumentDeserializer.deserializeDocument(
|
|
12
23
|
document,
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
deserializers,
|
|
25
|
+
blockDeserializers
|
|
15
26
|
) as SanityDocument;
|
|
16
27
|
return deserialized;
|
|
17
28
|
}
|
|
@@ -21,12 +32,21 @@ export function serializeDocument(
|
|
|
21
32
|
schema: Schema,
|
|
22
33
|
baseLanguage: string
|
|
23
34
|
) {
|
|
35
|
+
const stopTypes = [
|
|
36
|
+
...defaultStopTypes,
|
|
37
|
+
...pluginConfig.getAdditionalStopTypes(),
|
|
38
|
+
];
|
|
39
|
+
const serializers = merge(
|
|
40
|
+
customSerializers,
|
|
41
|
+
pluginConfig.getAdditionalSerializers()
|
|
42
|
+
) satisfies Partial<PortableTextHtmlComponents>;
|
|
43
|
+
|
|
24
44
|
const serialized = BaseDocumentSerializer(schema).serializeDocument(
|
|
25
45
|
document,
|
|
26
46
|
'document',
|
|
27
47
|
baseLanguage,
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
stopTypes,
|
|
49
|
+
serializers
|
|
30
50
|
);
|
|
31
51
|
return serialized;
|
|
32
52
|
}
|
package/src/utils/shared.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SECRETS_NAMESPACE = 'generaltranslation';
|
|
1
|
+
export const SECRETS_NAMESPACE = 'generaltranslation.secrets';
|
package/src/adapter/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Adapter } from '../types';
|
|
2
|
-
|
|
3
|
-
import { getLocales } from './getLocales';
|
|
4
|
-
import { getTranslationTask } from './getTranslationTask';
|
|
5
|
-
import { getTranslation } from './getTranslation';
|
|
6
|
-
import { createTask } from './createTask';
|
|
7
|
-
|
|
8
|
-
export const GTAdapter: Adapter = {
|
|
9
|
-
getLocales,
|
|
10
|
-
getTranslationTask,
|
|
11
|
-
createTask,
|
|
12
|
-
getTranslation,
|
|
13
|
-
};
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
-
|
|
3
|
-
import React, {
|
|
4
|
-
useState,
|
|
5
|
-
useContext,
|
|
6
|
-
ChangeEvent,
|
|
7
|
-
useCallback,
|
|
8
|
-
useEffect,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import styled from 'styled-components';
|
|
11
|
-
import {
|
|
12
|
-
Button,
|
|
13
|
-
Box,
|
|
14
|
-
Flex,
|
|
15
|
-
Grid,
|
|
16
|
-
Select,
|
|
17
|
-
Stack,
|
|
18
|
-
Switch,
|
|
19
|
-
Text,
|
|
20
|
-
useToast,
|
|
21
|
-
} from '@sanity/ui';
|
|
22
|
-
|
|
23
|
-
import { TranslationContext } from './TranslationContext';
|
|
24
|
-
import { TranslationLocale } from '../types';
|
|
25
|
-
|
|
26
|
-
type Props = {
|
|
27
|
-
locales: TranslationLocale[];
|
|
28
|
-
refreshTask: () => Promise<void>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
type LocaleCheckboxProps = {
|
|
32
|
-
locale: TranslationLocale;
|
|
33
|
-
toggle: (locale: string, checked: boolean) => void;
|
|
34
|
-
checked: boolean;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const WrapText = styled(Box)`
|
|
38
|
-
white-space: normal;
|
|
39
|
-
`;
|
|
40
|
-
|
|
41
|
-
const LocaleCheckbox = ({ locale, toggle, checked }: LocaleCheckboxProps) => {
|
|
42
|
-
const onClick = useCallback(
|
|
43
|
-
() => toggle(locale.localeId, !checked),
|
|
44
|
-
[locale, toggle, checked]
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<Button
|
|
49
|
-
mode='ghost'
|
|
50
|
-
onClick={onClick}
|
|
51
|
-
disabled={locale.enabled === false}
|
|
52
|
-
style={{ cursor: `pointer` }}
|
|
53
|
-
radius={2}
|
|
54
|
-
>
|
|
55
|
-
<Flex align='center' gap={3}>
|
|
56
|
-
<Switch
|
|
57
|
-
style={{ pointerEvents: `none` }}
|
|
58
|
-
disabled={locale.enabled === false}
|
|
59
|
-
onChange={onClick}
|
|
60
|
-
checked={checked}
|
|
61
|
-
/>
|
|
62
|
-
<WrapText>
|
|
63
|
-
<Text size={1} weight='semibold'>
|
|
64
|
-
{locale.description}
|
|
65
|
-
</Text>
|
|
66
|
-
</WrapText>
|
|
67
|
-
</Flex>
|
|
68
|
-
</Button>
|
|
69
|
-
);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export const NewTask = ({ locales, refreshTask }: Props) => {
|
|
73
|
-
const possibleLocales = locales.filter((locale) => locale.enabled !== false);
|
|
74
|
-
// Lets just stick to the canonical document id for keeping track of
|
|
75
|
-
// translations
|
|
76
|
-
const [selectedLocales, setSelectedLocales] = useState<React.ReactNode[]>(
|
|
77
|
-
locales
|
|
78
|
-
.filter((locale) => locale.enabled !== false)
|
|
79
|
-
.map((locale) => locale.localeId)
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
setSelectedLocales(
|
|
84
|
-
locales
|
|
85
|
-
.filter((locale) => locale.enabled !== false)
|
|
86
|
-
.map((locale) => locale.localeId)
|
|
87
|
-
);
|
|
88
|
-
}, [locales]);
|
|
89
|
-
|
|
90
|
-
const [selectedWorkflowUid, setSelectedWorkflowUid] = useState<string>();
|
|
91
|
-
const [isBusy, setIsBusy] = useState(false);
|
|
92
|
-
|
|
93
|
-
const context = useContext(TranslationContext);
|
|
94
|
-
const toast = useToast();
|
|
95
|
-
|
|
96
|
-
const toggleLocale = useCallback(
|
|
97
|
-
(locale: string, selected: boolean) => {
|
|
98
|
-
if (!selected) {
|
|
99
|
-
setSelectedLocales(selectedLocales.filter((l) => l !== locale));
|
|
100
|
-
} else if (!selectedLocales.includes(locale)) {
|
|
101
|
-
setSelectedLocales([...selectedLocales, locale]);
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
[selectedLocales, setSelectedLocales]
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const createTask = useCallback(() => {
|
|
108
|
-
if (!context) {
|
|
109
|
-
toast.push({
|
|
110
|
-
title: 'Unable to create task: missing context',
|
|
111
|
-
status: 'error',
|
|
112
|
-
closable: true,
|
|
113
|
-
});
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
setIsBusy(true);
|
|
118
|
-
|
|
119
|
-
context
|
|
120
|
-
.exportForTranslation(context.documentInfo)
|
|
121
|
-
.then((serialized) =>
|
|
122
|
-
context.adapter.createTask(
|
|
123
|
-
context.documentInfo,
|
|
124
|
-
serialized,
|
|
125
|
-
selectedLocales as string[],
|
|
126
|
-
context.secrets,
|
|
127
|
-
selectedWorkflowUid,
|
|
128
|
-
context.callbackUrl
|
|
129
|
-
)
|
|
130
|
-
)
|
|
131
|
-
.then(() => {
|
|
132
|
-
toast.push({
|
|
133
|
-
title: 'Job successfully created',
|
|
134
|
-
status: 'success',
|
|
135
|
-
closable: true,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
/** Reset form fields */
|
|
139
|
-
setSelectedLocales([]);
|
|
140
|
-
setSelectedWorkflowUid('');
|
|
141
|
-
|
|
142
|
-
/** Update task data in TranslationView */
|
|
143
|
-
refreshTask();
|
|
144
|
-
})
|
|
145
|
-
.catch((err) => {
|
|
146
|
-
let errorMsg;
|
|
147
|
-
if (err instanceof Error) {
|
|
148
|
-
errorMsg = err.message;
|
|
149
|
-
} else {
|
|
150
|
-
errorMsg = err ? String(err) : null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
toast.push({
|
|
154
|
-
title: `Error creating translation job`,
|
|
155
|
-
description: errorMsg,
|
|
156
|
-
status: 'error',
|
|
157
|
-
closable: true,
|
|
158
|
-
});
|
|
159
|
-
})
|
|
160
|
-
.finally(() => {
|
|
161
|
-
setIsBusy(false);
|
|
162
|
-
});
|
|
163
|
-
}, [context, selectedLocales, selectedWorkflowUid, toast, refreshTask]);
|
|
164
|
-
|
|
165
|
-
const onClick = useCallback(() => {
|
|
166
|
-
setSelectedLocales(
|
|
167
|
-
possibleLocales.length === selectedLocales.length
|
|
168
|
-
? // Disable all
|
|
169
|
-
[]
|
|
170
|
-
: // Enable all
|
|
171
|
-
locales
|
|
172
|
-
.filter((locale) => locale.enabled !== false)
|
|
173
|
-
.map((locale) => locale.localeId)
|
|
174
|
-
);
|
|
175
|
-
}, [possibleLocales, selectedLocales, setSelectedLocales, locales]);
|
|
176
|
-
|
|
177
|
-
const onToggle = useCallback(
|
|
178
|
-
(locale: string, checked: boolean) => {
|
|
179
|
-
toggleLocale(locale, checked);
|
|
180
|
-
},
|
|
181
|
-
[toggleLocale]
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
const onWorkflowChange = useCallback(
|
|
185
|
-
(e: ChangeEvent<HTMLSelectElement>) => {
|
|
186
|
-
setSelectedWorkflowUid(e.target.value);
|
|
187
|
-
},
|
|
188
|
-
[setSelectedWorkflowUid]
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<Stack paddingTop={4} space={4}>
|
|
193
|
-
<Text as='h2' weight='semibold' size={2}>
|
|
194
|
-
Generate New Translations
|
|
195
|
-
</Text>
|
|
196
|
-
<Stack space={3}>
|
|
197
|
-
<Flex align='center' justify='space-between'>
|
|
198
|
-
<Text weight='semibold' size={1}>
|
|
199
|
-
{possibleLocales.length === 1 ? `Select locale` : `Select locales`}
|
|
200
|
-
</Text>
|
|
201
|
-
|
|
202
|
-
<Button
|
|
203
|
-
fontSize={1}
|
|
204
|
-
padding={2}
|
|
205
|
-
text='Toggle All'
|
|
206
|
-
onClick={onClick}
|
|
207
|
-
/>
|
|
208
|
-
</Flex>
|
|
209
|
-
|
|
210
|
-
<Grid columns={[1, 1, 2, 3]} gap={1}>
|
|
211
|
-
{(locales || []).map((l) => (
|
|
212
|
-
<LocaleCheckbox
|
|
213
|
-
key={l.localeId}
|
|
214
|
-
locale={l}
|
|
215
|
-
toggle={onToggle}
|
|
216
|
-
checked={selectedLocales.includes(l.localeId)}
|
|
217
|
-
/>
|
|
218
|
-
))}
|
|
219
|
-
</Grid>
|
|
220
|
-
</Stack>
|
|
221
|
-
|
|
222
|
-
{context?.workflowOptions && context.workflowOptions.length > 0 && (
|
|
223
|
-
<Stack space={3}>
|
|
224
|
-
<Text weight='semibold' size={1} as='label' htmlFor='workflow-select'>
|
|
225
|
-
Select translation workflow
|
|
226
|
-
</Text>
|
|
227
|
-
<Grid columns={[1, 1, 2]}>
|
|
228
|
-
<Select id='workflowSelect' onChange={onWorkflowChange}>
|
|
229
|
-
<option>Default locale workflows</option>
|
|
230
|
-
{context.workflowOptions.map((w) => (
|
|
231
|
-
<option
|
|
232
|
-
key={`workflow-opt-${w.workflowUid}`}
|
|
233
|
-
value={w.workflowUid}
|
|
234
|
-
>
|
|
235
|
-
{w.workflowName}
|
|
236
|
-
</option>
|
|
237
|
-
))}
|
|
238
|
-
</Select>
|
|
239
|
-
</Grid>
|
|
240
|
-
</Stack>
|
|
241
|
-
)}
|
|
242
|
-
|
|
243
|
-
<Button
|
|
244
|
-
onClick={createTask}
|
|
245
|
-
disabled={isBusy || !selectedLocales.length}
|
|
246
|
-
tone='positive'
|
|
247
|
-
text={isBusy ? 'Queueing translations...' : 'Generate Translations'}
|
|
248
|
-
/>
|
|
249
|
-
</Stack>
|
|
250
|
-
);
|
|
251
|
-
};
|