gt-sanity 0.0.5
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 +138 -0
- package/README.md +100 -0
- package/dist/index.d.mts +241 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +2119 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2099 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +92 -0
- package/sanity.json +8 -0
- package/src/adapter/core.ts +44 -0
- package/src/adapter/createTask.ts +41 -0
- package/src/adapter/getLocales.ts +13 -0
- package/src/adapter/getTranslation.ts +20 -0
- package/src/adapter/getTranslationTask.ts +31 -0
- package/src/adapter/index.ts +13 -0
- package/src/components/LanguageStatus.tsx +65 -0
- package/src/components/NewTask.tsx +249 -0
- package/src/components/ProgressBar.tsx +38 -0
- package/src/components/TaskView.tsx +255 -0
- package/src/components/TranslationContext.tsx +19 -0
- package/src/components/TranslationView.tsx +82 -0
- package/src/components/TranslationsTab.tsx +177 -0
- package/src/configuration/README.md +8 -0
- package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +108 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +47 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +43 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +77 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +15 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +5 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +25 -0
- package/src/configuration/baseDocumentLevelConfig/index.ts +129 -0
- package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +69 -0
- package/src/configuration/baseFieldLevelConfig.ts +118 -0
- package/src/configuration/index.ts +18 -0
- package/src/configuration/utils/checkSerializationVersion.ts +13 -0
- package/src/configuration/utils/findDocumentAtRevision.ts +22 -0
- package/src/configuration/utils/findLatestDraft.ts +16 -0
- package/src/configuration/utils/index.ts +3 -0
- package/src/hooks/useClient.ts +5 -0
- package/src/hooks/useSecrets.ts +33 -0
- package/src/index.ts +120 -0
- package/src/types.ts +124 -0
- package/v2-incompatible.js +11 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { GT } from 'generaltranslation';
|
|
2
|
+
import { libraryDefaultLocale } from 'generaltranslation/internal';
|
|
3
|
+
import type { Secrets } from '../types';
|
|
4
|
+
|
|
5
|
+
export const gt = new GT();
|
|
6
|
+
|
|
7
|
+
export function overrideConfig(secrets: Secrets | null) {
|
|
8
|
+
gt.setConfig({
|
|
9
|
+
...(secrets?.project && { projectId: secrets?.project }),
|
|
10
|
+
...(secrets?.secret && { apiKey: secrets?.secret }),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class GTConfig {
|
|
15
|
+
sourceLocale: string;
|
|
16
|
+
locales: string[];
|
|
17
|
+
private static instance: GTConfig;
|
|
18
|
+
constructor(sourceLocale: string, locales: string[]) {
|
|
19
|
+
this.sourceLocale = sourceLocale;
|
|
20
|
+
this.locales = locales;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static getInstance() {
|
|
24
|
+
if (!this.instance) {
|
|
25
|
+
this.instance = new GTConfig(gt.sourceLocale || libraryDefaultLocale, []);
|
|
26
|
+
}
|
|
27
|
+
return this.instance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setSourceLocale(sourceLocale: string) {
|
|
31
|
+
this.sourceLocale = sourceLocale;
|
|
32
|
+
}
|
|
33
|
+
getSourceLocale() {
|
|
34
|
+
return this.sourceLocale;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setLocales(locales: string[]) {
|
|
38
|
+
this.locales = locales;
|
|
39
|
+
}
|
|
40
|
+
getLocales() {
|
|
41
|
+
return this.locales;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export const gtConfig = GTConfig.getInstance();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Adapter, GTFile, GTSerializedDocument, Secrets } from '../types';
|
|
2
|
+
import { getTranslationTask } from './getTranslationTask';
|
|
3
|
+
import { gt, overrideConfig } from './core';
|
|
4
|
+
import { libraryDefaultLocale } from 'generaltranslation/internal';
|
|
5
|
+
|
|
6
|
+
// note: this function is used to create a new translation task
|
|
7
|
+
// uploads files & calls the getTranslationTask function
|
|
8
|
+
export const createTask: Adapter['createTask'] = async (
|
|
9
|
+
documentInfo: GTFile,
|
|
10
|
+
serializedDocument: GTSerializedDocument,
|
|
11
|
+
localeIds: string[],
|
|
12
|
+
secrets: Secrets | null,
|
|
13
|
+
workflowUid?: string,
|
|
14
|
+
callbackUrl?: string
|
|
15
|
+
) => {
|
|
16
|
+
const fileName = `sanity-${documentInfo.documentId}`;
|
|
17
|
+
overrideConfig(secrets);
|
|
18
|
+
const uploadResult = await gt.uploadSourceFiles(
|
|
19
|
+
[
|
|
20
|
+
{
|
|
21
|
+
source: {
|
|
22
|
+
content: serializedDocument.content,
|
|
23
|
+
fileName,
|
|
24
|
+
fileId: documentInfo.documentId,
|
|
25
|
+
fileFormat: 'HTML',
|
|
26
|
+
locale: gt.sourceLocale || libraryDefaultLocale,
|
|
27
|
+
versionId: documentInfo.versionId || undefined,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
{
|
|
32
|
+
sourceLocale: gt.sourceLocale || libraryDefaultLocale,
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
const enqueueResult = await gt.enqueueFiles(uploadResult.uploadedFiles, {
|
|
36
|
+
sourceLocale: gt.sourceLocale || libraryDefaultLocale,
|
|
37
|
+
targetLocales: localeIds,
|
|
38
|
+
});
|
|
39
|
+
const task = await getTranslationTask(documentInfo, secrets);
|
|
40
|
+
return task;
|
|
41
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Adapter, Secrets } from '../types';
|
|
2
|
+
import { gt, gtConfig } from './core';
|
|
3
|
+
|
|
4
|
+
// note: this function is used to get the available locales for a project
|
|
5
|
+
export const getLocales: Adapter['getLocales'] = async (
|
|
6
|
+
secrets: Secrets | null
|
|
7
|
+
) => {
|
|
8
|
+
return gtConfig.getLocales().map((locale: string) => ({
|
|
9
|
+
localeId: locale,
|
|
10
|
+
description: gt.getLocaleProperties(locale).name,
|
|
11
|
+
enabled: true,
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Adapter, GTFile, Secrets } from '../types';
|
|
2
|
+
import { gt, overrideConfig } from './core';
|
|
3
|
+
|
|
4
|
+
// note: downloads the translation for a given task and locale
|
|
5
|
+
export const getTranslation: Adapter['getTranslation'] = async (
|
|
6
|
+
documentInfo: GTFile,
|
|
7
|
+
localeId: string,
|
|
8
|
+
secrets: Secrets | null
|
|
9
|
+
) => {
|
|
10
|
+
if (!secrets) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
overrideConfig(secrets);
|
|
14
|
+
const text = await gt.downloadTranslatedFile({
|
|
15
|
+
fileId: documentInfo.documentId,
|
|
16
|
+
versionId: documentInfo.versionId || undefined,
|
|
17
|
+
locale: localeId,
|
|
18
|
+
});
|
|
19
|
+
return text;
|
|
20
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Adapter, GTFile, Secrets } from '../types';
|
|
2
|
+
import { gt, overrideConfig } from './core';
|
|
3
|
+
|
|
4
|
+
// note: this function is used to get the status of a current translation task
|
|
5
|
+
export const getTranslationTask: Adapter['getTranslationTask'] = async (
|
|
6
|
+
documentInfo: GTFile,
|
|
7
|
+
secrets: Secrets | null
|
|
8
|
+
) => {
|
|
9
|
+
if (!documentInfo.documentId || !secrets) {
|
|
10
|
+
return {
|
|
11
|
+
document: documentInfo,
|
|
12
|
+
locales: [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
overrideConfig(secrets);
|
|
16
|
+
const task = await gt.querySourceFile({
|
|
17
|
+
fileId: documentInfo.documentId,
|
|
18
|
+
versionId: documentInfo.versionId || undefined,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
document: {
|
|
23
|
+
documentId: task.sourceFile.fileId,
|
|
24
|
+
versionId: task.sourceFile.versionId,
|
|
25
|
+
},
|
|
26
|
+
locales: task.translations.map((translation) => ({
|
|
27
|
+
localeId: translation.locale,
|
|
28
|
+
progress: translation.completedAt ? 100 : 0,
|
|
29
|
+
})),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { Flex, Card, Text, Grid, Box, Button } from '@sanity/ui';
|
|
3
|
+
import { DownloadIcon, CheckmarkCircleIcon } from '@sanity/icons';
|
|
4
|
+
import ProgressBar from './ProgressBar';
|
|
5
|
+
|
|
6
|
+
type LanguageStatusProps = {
|
|
7
|
+
title: string;
|
|
8
|
+
progress: number;
|
|
9
|
+
importFile: () => Promise<void>;
|
|
10
|
+
isImported?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const LanguageStatus = ({
|
|
14
|
+
title,
|
|
15
|
+
progress,
|
|
16
|
+
importFile,
|
|
17
|
+
isImported = false,
|
|
18
|
+
}: LanguageStatusProps) => {
|
|
19
|
+
const [isBusy, setIsBusy] = useState(false);
|
|
20
|
+
|
|
21
|
+
const handleImport = useCallback(async () => {
|
|
22
|
+
setIsBusy(true);
|
|
23
|
+
try {
|
|
24
|
+
await importFile();
|
|
25
|
+
} finally {
|
|
26
|
+
setIsBusy(false);
|
|
27
|
+
}
|
|
28
|
+
}, [importFile, setIsBusy]);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Card shadow={1}>
|
|
32
|
+
<Grid columns={5} gap={3} padding={3}>
|
|
33
|
+
<Flex columnStart={1} columnEnd={3} align='center'>
|
|
34
|
+
<Text weight='bold' size={1}>
|
|
35
|
+
{title}
|
|
36
|
+
</Text>
|
|
37
|
+
</Flex>
|
|
38
|
+
{typeof progress === 'number' ? (
|
|
39
|
+
<Flex columnStart={3} columnEnd={5} align='center'>
|
|
40
|
+
<ProgressBar progress={progress} />
|
|
41
|
+
</Flex>
|
|
42
|
+
) : null}
|
|
43
|
+
<Box columnStart={5} columnEnd={6}>
|
|
44
|
+
{isImported ? (
|
|
45
|
+
<Flex align='center' justify='center' style={{ color: 'green' }}>
|
|
46
|
+
<CheckmarkCircleIcon />
|
|
47
|
+
<Text size={1} style={{ marginLeft: '4px' }}>
|
|
48
|
+
Imported
|
|
49
|
+
</Text>
|
|
50
|
+
</Flex>
|
|
51
|
+
) : (
|
|
52
|
+
<Button
|
|
53
|
+
style={{ width: `100%` }}
|
|
54
|
+
mode='ghost'
|
|
55
|
+
onClick={handleImport}
|
|
56
|
+
text={isBusy ? 'Importing...' : 'Import'}
|
|
57
|
+
icon={isBusy ? null : DownloadIcon}
|
|
58
|
+
disabled={isBusy || !progress || progress < 100}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
</Box>
|
|
62
|
+
</Grid>
|
|
63
|
+
</Card>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useContext,
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import styled from 'styled-components';
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
Box,
|
|
12
|
+
Flex,
|
|
13
|
+
Grid,
|
|
14
|
+
Select,
|
|
15
|
+
Stack,
|
|
16
|
+
Switch,
|
|
17
|
+
Text,
|
|
18
|
+
useToast,
|
|
19
|
+
} from '@sanity/ui';
|
|
20
|
+
|
|
21
|
+
import { TranslationContext } from './TranslationContext';
|
|
22
|
+
import { TranslationLocale } from '../types';
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
locales: TranslationLocale[];
|
|
26
|
+
refreshTask: () => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type LocaleCheckboxProps = {
|
|
30
|
+
locale: TranslationLocale;
|
|
31
|
+
toggle: (locale: string, checked: boolean) => void;
|
|
32
|
+
checked: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const WrapText = styled(Box)`
|
|
36
|
+
white-space: normal;
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const LocaleCheckbox = ({ locale, toggle, checked }: LocaleCheckboxProps) => {
|
|
40
|
+
const onClick = useCallback(
|
|
41
|
+
() => toggle(locale.localeId, !checked),
|
|
42
|
+
[locale, toggle, checked]
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Button
|
|
47
|
+
mode='ghost'
|
|
48
|
+
onClick={onClick}
|
|
49
|
+
disabled={locale.enabled === false}
|
|
50
|
+
style={{ cursor: `pointer` }}
|
|
51
|
+
radius={2}
|
|
52
|
+
>
|
|
53
|
+
<Flex align='center' gap={3}>
|
|
54
|
+
<Switch
|
|
55
|
+
style={{ pointerEvents: `none` }}
|
|
56
|
+
disabled={locale.enabled === false}
|
|
57
|
+
onChange={onClick}
|
|
58
|
+
checked={checked}
|
|
59
|
+
/>
|
|
60
|
+
<WrapText>
|
|
61
|
+
<Text size={1} weight='semibold'>
|
|
62
|
+
{locale.description}
|
|
63
|
+
</Text>
|
|
64
|
+
</WrapText>
|
|
65
|
+
</Flex>
|
|
66
|
+
</Button>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const NewTask = ({ locales, refreshTask }: Props) => {
|
|
71
|
+
const possibleLocales = locales.filter((locale) => locale.enabled !== false);
|
|
72
|
+
// Lets just stick to the canonical document id for keeping track of
|
|
73
|
+
// translations
|
|
74
|
+
const [selectedLocales, setSelectedLocales] = useState<React.ReactNode[]>(
|
|
75
|
+
locales
|
|
76
|
+
.filter((locale) => locale.enabled !== false)
|
|
77
|
+
.map((locale) => locale.localeId)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
setSelectedLocales(
|
|
82
|
+
locales
|
|
83
|
+
.filter((locale) => locale.enabled !== false)
|
|
84
|
+
.map((locale) => locale.localeId)
|
|
85
|
+
);
|
|
86
|
+
}, [locales]);
|
|
87
|
+
|
|
88
|
+
const [selectedWorkflowUid, setSelectedWorkflowUid] = useState<string>();
|
|
89
|
+
const [isBusy, setIsBusy] = useState(false);
|
|
90
|
+
|
|
91
|
+
const context = useContext(TranslationContext);
|
|
92
|
+
const toast = useToast();
|
|
93
|
+
|
|
94
|
+
const toggleLocale = useCallback(
|
|
95
|
+
(locale: string, selected: boolean) => {
|
|
96
|
+
if (!selected) {
|
|
97
|
+
setSelectedLocales(selectedLocales.filter((l) => l !== locale));
|
|
98
|
+
} else if (!selectedLocales.includes(locale)) {
|
|
99
|
+
setSelectedLocales([...selectedLocales, locale]);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[selectedLocales, setSelectedLocales]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const createTask = useCallback(() => {
|
|
106
|
+
if (!context) {
|
|
107
|
+
toast.push({
|
|
108
|
+
title: 'Unable to create task: missing context',
|
|
109
|
+
status: 'error',
|
|
110
|
+
closable: true,
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setIsBusy(true);
|
|
116
|
+
|
|
117
|
+
context
|
|
118
|
+
.exportForTranslation(context.documentInfo)
|
|
119
|
+
.then((serialized) =>
|
|
120
|
+
context.adapter.createTask(
|
|
121
|
+
context.documentInfo,
|
|
122
|
+
serialized,
|
|
123
|
+
selectedLocales as string[],
|
|
124
|
+
context.secrets,
|
|
125
|
+
selectedWorkflowUid,
|
|
126
|
+
context.callbackUrl
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
.then(() => {
|
|
130
|
+
toast.push({
|
|
131
|
+
title: 'Job successfully created',
|
|
132
|
+
status: 'success',
|
|
133
|
+
closable: true,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/** Reset form fields */
|
|
137
|
+
setSelectedLocales([]);
|
|
138
|
+
setSelectedWorkflowUid('');
|
|
139
|
+
|
|
140
|
+
/** Update task data in TranslationView */
|
|
141
|
+
refreshTask();
|
|
142
|
+
})
|
|
143
|
+
.catch((err) => {
|
|
144
|
+
let errorMsg;
|
|
145
|
+
if (err instanceof Error) {
|
|
146
|
+
errorMsg = err.message;
|
|
147
|
+
} else {
|
|
148
|
+
errorMsg = err ? String(err) : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
toast.push({
|
|
152
|
+
title: `Error creating translation job`,
|
|
153
|
+
description: errorMsg,
|
|
154
|
+
status: 'error',
|
|
155
|
+
closable: true,
|
|
156
|
+
});
|
|
157
|
+
})
|
|
158
|
+
.finally(() => {
|
|
159
|
+
setIsBusy(false);
|
|
160
|
+
});
|
|
161
|
+
}, [context, selectedLocales, selectedWorkflowUid, toast, refreshTask]);
|
|
162
|
+
|
|
163
|
+
const onClick = useCallback(() => {
|
|
164
|
+
setSelectedLocales(
|
|
165
|
+
possibleLocales.length === selectedLocales.length
|
|
166
|
+
? // Disable all
|
|
167
|
+
[]
|
|
168
|
+
: // Enable all
|
|
169
|
+
locales
|
|
170
|
+
.filter((locale) => locale.enabled !== false)
|
|
171
|
+
.map((locale) => locale.localeId)
|
|
172
|
+
);
|
|
173
|
+
}, [possibleLocales, selectedLocales, setSelectedLocales, locales]);
|
|
174
|
+
|
|
175
|
+
const onToggle = useCallback(
|
|
176
|
+
(locale: string, checked: boolean) => {
|
|
177
|
+
toggleLocale(locale, checked);
|
|
178
|
+
},
|
|
179
|
+
[toggleLocale]
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const onWorkflowChange = useCallback(
|
|
183
|
+
(e: ChangeEvent<HTMLSelectElement>) => {
|
|
184
|
+
setSelectedWorkflowUid(e.target.value);
|
|
185
|
+
},
|
|
186
|
+
[setSelectedWorkflowUid]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<Stack paddingTop={4} space={4}>
|
|
191
|
+
<Text as='h2' weight='semibold' size={2}>
|
|
192
|
+
Generate New Translations
|
|
193
|
+
</Text>
|
|
194
|
+
<Stack space={3}>
|
|
195
|
+
<Flex align='center' justify='space-between'>
|
|
196
|
+
<Text weight='semibold' size={1}>
|
|
197
|
+
{possibleLocales.length === 1 ? `Select locale` : `Select locales`}
|
|
198
|
+
</Text>
|
|
199
|
+
|
|
200
|
+
<Button
|
|
201
|
+
fontSize={1}
|
|
202
|
+
padding={2}
|
|
203
|
+
text='Toggle All'
|
|
204
|
+
onClick={onClick}
|
|
205
|
+
/>
|
|
206
|
+
</Flex>
|
|
207
|
+
|
|
208
|
+
<Grid columns={[1, 1, 2, 3]} gap={1}>
|
|
209
|
+
{(locales || []).map((l) => (
|
|
210
|
+
<LocaleCheckbox
|
|
211
|
+
key={l.localeId}
|
|
212
|
+
locale={l}
|
|
213
|
+
toggle={onToggle}
|
|
214
|
+
checked={selectedLocales.includes(l.localeId)}
|
|
215
|
+
/>
|
|
216
|
+
))}
|
|
217
|
+
</Grid>
|
|
218
|
+
</Stack>
|
|
219
|
+
|
|
220
|
+
{context?.workflowOptions && context.workflowOptions.length > 0 && (
|
|
221
|
+
<Stack space={3}>
|
|
222
|
+
<Text weight='semibold' size={1} as='label' htmlFor='workflow-select'>
|
|
223
|
+
Select translation workflow
|
|
224
|
+
</Text>
|
|
225
|
+
<Grid columns={[1, 1, 2]}>
|
|
226
|
+
<Select id='workflowSelect' onChange={onWorkflowChange}>
|
|
227
|
+
<option>Default locale workflows</option>
|
|
228
|
+
{context.workflowOptions.map((w) => (
|
|
229
|
+
<option
|
|
230
|
+
key={`workflow-opt-${w.workflowUid}`}
|
|
231
|
+
value={w.workflowUid}
|
|
232
|
+
>
|
|
233
|
+
{w.workflowName}
|
|
234
|
+
</option>
|
|
235
|
+
))}
|
|
236
|
+
</Select>
|
|
237
|
+
</Grid>
|
|
238
|
+
</Stack>
|
|
239
|
+
)}
|
|
240
|
+
|
|
241
|
+
<Button
|
|
242
|
+
onClick={createTask}
|
|
243
|
+
disabled={isBusy || !selectedLocales.length}
|
|
244
|
+
tone='positive'
|
|
245
|
+
text={isBusy ? 'Queueing translations...' : 'Generate Translations'}
|
|
246
|
+
/>
|
|
247
|
+
</Stack>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Card, Flex, Label } from '@sanity/ui';
|
|
2
|
+
|
|
3
|
+
export default function ProgressBar({ progress }: { progress: number }) {
|
|
4
|
+
if (typeof progress === 'undefined') {
|
|
5
|
+
console.warn('No progress prop passed to ProgressBar');
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Card border radius={2} style={{ width: `100%`, position: `relative` }}>
|
|
11
|
+
<Flex
|
|
12
|
+
style={{
|
|
13
|
+
position: `absolute`,
|
|
14
|
+
left: 0,
|
|
15
|
+
right: 0,
|
|
16
|
+
top: 0,
|
|
17
|
+
bottom: 0,
|
|
18
|
+
zIndex: 1,
|
|
19
|
+
}}
|
|
20
|
+
align='center'
|
|
21
|
+
justify='center'
|
|
22
|
+
>
|
|
23
|
+
<Label size={1}>{progress}%</Label>
|
|
24
|
+
</Flex>
|
|
25
|
+
<Card
|
|
26
|
+
style={{
|
|
27
|
+
width: '100%',
|
|
28
|
+
transform: `scaleX(${progress / 100})`,
|
|
29
|
+
transformOrigin: 'left',
|
|
30
|
+
transition: 'transform .2s ease',
|
|
31
|
+
boxSizing: 'border-box',
|
|
32
|
+
}}
|
|
33
|
+
padding={2}
|
|
34
|
+
tone='positive'
|
|
35
|
+
/>
|
|
36
|
+
</Card>
|
|
37
|
+
);
|
|
38
|
+
}
|