gt-sanity 0.0.6 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/index.d.mts +95 -73
  3. package/dist/index.d.ts +95 -73
  4. package/dist/index.js +6233 -1162
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +6292 -1193
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +9 -4
  9. package/src/adapter/core.ts +41 -4
  10. package/src/adapter/getLocales.ts +2 -2
  11. package/src/components/TranslationsProvider.tsx +942 -0
  12. package/src/components/page/BatchProgress.tsx +27 -0
  13. package/src/components/page/ImportAllDialog.tsx +51 -0
  14. package/src/components/page/ImportMissingDialog.tsx +55 -0
  15. package/src/components/page/TranslateAllDialog.tsx +55 -0
  16. package/src/components/page/TranslationsTable.tsx +81 -0
  17. package/src/components/page/TranslationsTool.tsx +299 -837
  18. package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
  19. package/src/components/shared/LocaleCheckbox.tsx +47 -0
  20. package/src/components/shared/SingleDocumentView.tsx +108 -0
  21. package/src/components/tab/TranslationView.tsx +379 -0
  22. package/src/components/tab/TranslationsTab.tsx +25 -0
  23. package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +6 -9
  24. package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +5 -24
  25. package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +3 -23
  26. package/src/configuration/baseDocumentLevelConfig/index.ts +16 -68
  27. package/src/configuration/baseFieldLevelConfig.ts +15 -50
  28. package/src/index.ts +29 -43
  29. package/src/sanity-api/findDocuments.ts +44 -0
  30. package/src/sanity-api/publishDocuments.ts +49 -0
  31. package/src/sanity-api/resolveRefs.ts +146 -0
  32. package/src/serialization/BaseDocumentMerger.ts +138 -0
  33. package/src/serialization/BaseSerializationConfig.ts +220 -0
  34. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +189 -0
  35. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +107 -0
  36. package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +397 -0
  37. package/src/serialization/__tests__/BaseDocumentDeserializer/documentLevelDeserialization.test.ts +107 -0
  38. package/src/serialization/__tests__/BaseDocumentDeserializer/fieldLevelDeserialization.test.ts +107 -0
  39. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +193 -0
  40. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +97 -0
  41. package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +36 -0
  42. package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +96 -0
  43. package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +142 -0
  44. package/src/serialization/__tests__/BaseDocumentMerger/utils.ts +52 -0
  45. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +39 -0
  46. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +8 -0
  47. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +8 -0
  48. package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +345 -0
  49. package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +53 -0
  50. package/src/serialization/__tests__/BaseDocumentSerializer/documentLevelSerialization.test.ts +120 -0
  51. package/src/serialization/__tests__/BaseDocumentSerializer/fieldLevelSerialization.test.ts +153 -0
  52. package/src/serialization/__tests__/BaseDocumentSerializer/utils.ts +27 -0
  53. package/src/serialization/__tests__/README +2 -0
  54. package/src/serialization/__tests__/__fixtures__/annotationAndInlineBlocks.json +140 -0
  55. package/src/serialization/__tests__/__fixtures__/customStyles.json +62 -0
  56. package/src/serialization/__tests__/__fixtures__/documentInlineMarks.json +70 -0
  57. package/src/serialization/__tests__/__fixtures__/documentLevelArticle.json +185 -0
  58. package/src/serialization/__tests__/__fixtures__/fieldLevelArticle.json +107 -0
  59. package/src/serialization/__tests__/__fixtures__/inlineDocumentLevelArticle.json +134 -0
  60. package/src/serialization/__tests__/__fixtures__/inlineSchema.ts +270 -0
  61. package/src/serialization/__tests__/__fixtures__/messy-html.html +26 -0
  62. package/src/serialization/__tests__/__fixtures__/nestedLanguageFields.json +54 -0
  63. package/src/serialization/__tests__/__fixtures__/schema.ts +310 -0
  64. package/src/serialization/__tests__/global.setup.ts +40 -0
  65. package/src/serialization/__tests__/helpers.ts +132 -0
  66. package/src/serialization/data.ts +82 -0
  67. package/src/serialization/deserialize/BaseDocumentDeserializer.ts +171 -0
  68. package/src/serialization/deserialize/helpers.ts +42 -0
  69. package/src/serialization/helpers.ts +18 -0
  70. package/src/serialization/index.ts +11 -0
  71. package/src/serialization/serialize/fieldFilters.ts +124 -0
  72. package/src/serialization/serialize/index.ts +284 -0
  73. package/src/serialization/types.ts +41 -0
  74. package/src/translation/importDocument.ts +4 -5
  75. package/src/translation/uploadFiles.ts +1 -1
  76. package/src/types.ts +3 -19
  77. package/src/utils/batchProcessor.ts +111 -0
  78. package/src/utils/importUtils.ts +95 -0
  79. package/src/utils/serialize.ts +25 -5
  80. package/src/utils/shared.ts +1 -1
  81. package/src/adapter/index.ts +0 -13
  82. package/src/components/NewTask.tsx +0 -251
  83. package/src/components/TaskView.tsx +0 -257
  84. package/src/components/TranslationContext.tsx +0 -24
  85. package/src/components/TranslationView.tsx +0 -114
  86. package/src/components/TranslationsTab.tsx +0 -181
  87. /package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +0 -0
  88. /package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +0 -0
@@ -1,4 +1,4 @@
1
- import { gtConfig } from '../adapter/core';
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
- publish: boolean = false
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
- gtConfig.getLanguageField(),
21
- false,
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 'sanity-naive-html-serializer';
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 'sanity-naive-html-serializer';
4
+ import type { SerializedDocument } from './serialization';
5
5
  import { PortableTextTypeComponent } from '@portabletext/to-html';
6
- import { DeserializerRule } from '@sanity/block-tools';
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
+ }
@@ -5,13 +5,24 @@ import {
5
5
  defaultStopTypes,
6
6
  customSerializers,
7
7
  customBlockDeserializers,
8
- } from 'sanity-naive-html-serializer';
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
- { types: {} },
14
- customBlockDeserializers
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
- defaultStopTypes,
29
- customSerializers
48
+ stopTypes,
49
+ serializers
30
50
  );
31
51
  return serialized;
32
52
  }
@@ -1 +1 @@
1
- export const SECRETS_NAMESPACE = 'generaltranslation';
1
+ export const SECRETS_NAMESPACE = 'generaltranslation.secrets';
@@ -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
- };