gt-sanity 0.0.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -8
- package/README.md +5 -5
- package/dist/index.d.mts +122 -95
- package/dist/index.d.ts +122 -95
- package/dist/index.js +9089 -1119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9099 -1100
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -4
- package/src/adapter/core.ts +111 -9
- package/src/adapter/createTask.ts +1 -1
- package/src/adapter/getLocales.ts +2 -2
- package/src/adapter/types.ts +9 -0
- package/src/components/TranslationsProvider.tsx +942 -0
- package/src/components/page/BatchProgress.tsx +27 -0
- package/src/components/page/ImportAllDialog.tsx +51 -0
- package/src/components/page/ImportMissingDialog.tsx +55 -0
- package/src/components/page/TranslateAllDialog.tsx +55 -0
- package/src/components/page/TranslationsTable.tsx +81 -0
- package/src/components/page/TranslationsTool.tsx +338 -0
- package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
- package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +2 -0
- package/src/components/shared/LocaleCheckbox.tsx +47 -0
- package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +2 -0
- package/src/components/shared/SingleDocumentView.tsx +108 -0
- package/src/components/tab/TranslationView.tsx +379 -0
- package/src/components/tab/TranslationsTab.tsx +25 -0
- package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +21 -11
- package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +57 -23
- package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +2 -0
- package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +31 -8
- package/src/configuration/baseDocumentLevelConfig/index.ts +18 -101
- package/src/configuration/baseFieldLevelConfig.ts +19 -51
- package/src/configuration/utils/checkSerializationVersion.ts +2 -0
- package/src/configuration/utils/findDocumentAtRevision.ts +2 -0
- package/src/configuration/utils/findLatestDraft.ts +2 -0
- package/src/hooks/useClient.ts +3 -1
- package/src/hooks/useSecrets.ts +2 -0
- package/src/index.ts +91 -67
- package/src/sanity-api/findDocuments.ts +44 -0
- package/src/sanity-api/publishDocuments.ts +49 -0
- package/src/sanity-api/resolveRefs.ts +146 -0
- package/src/serialization/BaseDocumentMerger.ts +138 -0
- package/src/serialization/BaseSerializationConfig.ts +220 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +189 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +107 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +397 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/documentLevelDeserialization.test.ts +107 -0
- package/src/serialization/__tests__/BaseDocumentDeserializer/fieldLevelDeserialization.test.ts +107 -0
- package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +193 -0
- package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +97 -0
- package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +36 -0
- package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +96 -0
- package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +142 -0
- package/src/serialization/__tests__/BaseDocumentMerger/utils.ts +52 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +39 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +8 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +8 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +345 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +53 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/documentLevelSerialization.test.ts +120 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/fieldLevelSerialization.test.ts +153 -0
- package/src/serialization/__tests__/BaseDocumentSerializer/utils.ts +27 -0
- package/src/serialization/__tests__/README +2 -0
- package/src/serialization/__tests__/__fixtures__/annotationAndInlineBlocks.json +140 -0
- package/src/serialization/__tests__/__fixtures__/customStyles.json +62 -0
- package/src/serialization/__tests__/__fixtures__/documentInlineMarks.json +70 -0
- package/src/serialization/__tests__/__fixtures__/documentLevelArticle.json +185 -0
- package/src/serialization/__tests__/__fixtures__/fieldLevelArticle.json +107 -0
- package/src/serialization/__tests__/__fixtures__/inlineDocumentLevelArticle.json +134 -0
- package/src/serialization/__tests__/__fixtures__/inlineSchema.ts +270 -0
- package/src/serialization/__tests__/__fixtures__/messy-html.html +26 -0
- package/src/serialization/__tests__/__fixtures__/nestedLanguageFields.json +54 -0
- package/src/serialization/__tests__/__fixtures__/schema.ts +310 -0
- package/src/serialization/__tests__/global.setup.ts +40 -0
- package/src/serialization/__tests__/helpers.ts +132 -0
- package/src/serialization/data.ts +82 -0
- package/src/serialization/deserialize/BaseDocumentDeserializer.ts +171 -0
- package/src/serialization/deserialize/helpers.ts +42 -0
- package/src/serialization/helpers.ts +18 -0
- package/src/serialization/index.ts +11 -0
- package/src/serialization/serialize/fieldFilters.ts +124 -0
- package/src/serialization/serialize/index.ts +284 -0
- package/src/serialization/types.ts +41 -0
- package/src/translation/checkTranslationStatus.ts +42 -0
- package/src/translation/createJobs.ts +16 -0
- package/src/translation/downloadTranslations.ts +68 -0
- package/src/translation/importDocument.ts +23 -0
- package/src/translation/initProject.ts +61 -0
- package/src/translation/uploadFiles.ts +32 -0
- package/src/types.ts +7 -20
- package/src/utils/applyDocuments.ts +72 -0
- package/src/utils/batchProcessor.ts +111 -0
- package/src/utils/importUtils.ts +95 -0
- package/src/utils/serialize.ts +52 -0
- package/src/utils/shared.ts +1 -0
- package/src/adapter/index.ts +0 -13
- package/src/components/NewTask.tsx +0 -249
- package/src/components/TaskView.tsx +0 -255
- package/src/components/TranslationContext.tsx +0 -19
- package/src/components/TranslationView.tsx +0 -82
- package/src/components/TranslationsTab.tsx +0 -177
- package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +0 -5
- package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +0 -69
- package/src/configuration/index.ts +0 -18
- package/src/configuration/utils/index.ts +0 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { Button, Flex, Switch, Box, Text } from '@sanity/ui';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { TranslationLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
const WrapText = styled(Box)`
|
|
7
|
+
white-space: normal;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
type LocaleCheckboxProps = {
|
|
11
|
+
locale: TranslationLocale;
|
|
12
|
+
toggle: (locale: string, shouldEnable: boolean) => void;
|
|
13
|
+
checked: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const LocaleCheckbox = ({
|
|
17
|
+
locale,
|
|
18
|
+
toggle,
|
|
19
|
+
checked,
|
|
20
|
+
}: LocaleCheckboxProps) => {
|
|
21
|
+
const onClick = useCallback(
|
|
22
|
+
() => toggle(locale.localeId, !checked),
|
|
23
|
+
[locale.localeId, toggle, checked]
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Button
|
|
28
|
+
mode='ghost'
|
|
29
|
+
onClick={onClick}
|
|
30
|
+
style={{ cursor: 'pointer' }}
|
|
31
|
+
radius={2}
|
|
32
|
+
>
|
|
33
|
+
<Flex align='center' gap={3}>
|
|
34
|
+
<Switch
|
|
35
|
+
style={{ pointerEvents: 'none' }}
|
|
36
|
+
onChange={onClick}
|
|
37
|
+
checked={checked}
|
|
38
|
+
/>
|
|
39
|
+
<WrapText>
|
|
40
|
+
<Text size={1} weight='semibold'>
|
|
41
|
+
{locale.description}
|
|
42
|
+
</Text>
|
|
43
|
+
</WrapText>
|
|
44
|
+
</Flex>
|
|
45
|
+
</Button>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Stack, Box, Card, Text, Flex, Spinner } from '@sanity/ui';
|
|
3
|
+
import { LanguageStatus } from './LanguageStatus';
|
|
4
|
+
import { useTranslations } from '../TranslationsProvider';
|
|
5
|
+
import { pluginConfig } from '../../adapter/core';
|
|
6
|
+
|
|
7
|
+
export const SingleDocumentView: React.FC = () => {
|
|
8
|
+
const {
|
|
9
|
+
documents,
|
|
10
|
+
locales,
|
|
11
|
+
loadingDocuments,
|
|
12
|
+
translationStatuses,
|
|
13
|
+
downloadStatus,
|
|
14
|
+
importedTranslations,
|
|
15
|
+
handleImportDocument,
|
|
16
|
+
} = useTranslations();
|
|
17
|
+
|
|
18
|
+
// Get the first (and only) document in single document mode
|
|
19
|
+
const document = documents[0];
|
|
20
|
+
|
|
21
|
+
if (loadingDocuments) {
|
|
22
|
+
return (
|
|
23
|
+
<Flex align='center' justify='center' padding={4}>
|
|
24
|
+
<Spinner />
|
|
25
|
+
</Flex>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!document) {
|
|
30
|
+
return (
|
|
31
|
+
<Card padding={4} tone='caution'>
|
|
32
|
+
<Text>No document found</Text>
|
|
33
|
+
</Card>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if this is a source language document
|
|
38
|
+
const currentDocumentLanguage =
|
|
39
|
+
document[pluginConfig.getLanguageField()] || pluginConfig.getSourceLocale();
|
|
40
|
+
const shouldShowTranslationComponents =
|
|
41
|
+
currentDocumentLanguage === pluginConfig.getSourceLocale();
|
|
42
|
+
|
|
43
|
+
if (!shouldShowTranslationComponents) {
|
|
44
|
+
return (
|
|
45
|
+
<Card padding={4} tone='neutral' border>
|
|
46
|
+
<Text size={1} muted>
|
|
47
|
+
Translation tools are only available for{' '}
|
|
48
|
+
<code>{pluginConfig.getSourceLocale()}</code> documents.
|
|
49
|
+
</Text>
|
|
50
|
+
</Card>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Box>
|
|
56
|
+
<Stack space={4}>
|
|
57
|
+
<Card shadow={1} padding={3}>
|
|
58
|
+
<Stack space={3}>
|
|
59
|
+
<Flex justify='space-between' align='flex-start'>
|
|
60
|
+
<Box flex={1}>
|
|
61
|
+
<Text weight='semibold' size={1}>
|
|
62
|
+
{document._id?.replace('drafts.', '') || document._id}
|
|
63
|
+
</Text>
|
|
64
|
+
<Text size={0} muted style={{ marginTop: '2px' }}>
|
|
65
|
+
{document._type}
|
|
66
|
+
</Text>
|
|
67
|
+
</Box>
|
|
68
|
+
</Flex>
|
|
69
|
+
|
|
70
|
+
<Stack space={2}>
|
|
71
|
+
{locales.length > 0 ? (
|
|
72
|
+
locales
|
|
73
|
+
.filter((locale) => locale.enabled !== false)
|
|
74
|
+
.map((locale) => {
|
|
75
|
+
const documentId =
|
|
76
|
+
document._id?.replace('drafts.', '') || document._id;
|
|
77
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
78
|
+
const status = translationStatuses.get(key);
|
|
79
|
+
const isDownloaded = downloadStatus.downloaded.has(key);
|
|
80
|
+
const isImported = importedTranslations.has(key);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<LanguageStatus
|
|
84
|
+
key={`${document._id}-${locale.localeId}`}
|
|
85
|
+
title={locale.description || locale.localeId}
|
|
86
|
+
progress={status?.progress || 0}
|
|
87
|
+
isImported={isImported || isDownloaded}
|
|
88
|
+
importFile={async () => {
|
|
89
|
+
await handleImportDocument(
|
|
90
|
+
documentId,
|
|
91
|
+
locale.localeId
|
|
92
|
+
);
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
})
|
|
97
|
+
) : (
|
|
98
|
+
<Text size={1} muted>
|
|
99
|
+
No locales configured
|
|
100
|
+
</Text>
|
|
101
|
+
)}
|
|
102
|
+
</Stack>
|
|
103
|
+
</Stack>
|
|
104
|
+
</Card>
|
|
105
|
+
</Stack>
|
|
106
|
+
</Box>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Add cleanup function to cancel async tasks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
8
|
+
import {
|
|
9
|
+
Stack,
|
|
10
|
+
Text,
|
|
11
|
+
Card,
|
|
12
|
+
Button,
|
|
13
|
+
Grid,
|
|
14
|
+
Box,
|
|
15
|
+
Flex,
|
|
16
|
+
Switch,
|
|
17
|
+
Tooltip,
|
|
18
|
+
} from '@sanity/ui';
|
|
19
|
+
import { pluginConfig } from '../../adapter/core';
|
|
20
|
+
import { useTranslations } from '../TranslationsProvider';
|
|
21
|
+
import { LanguageStatus } from '../shared/LanguageStatus';
|
|
22
|
+
import { LocaleCheckbox } from '../shared/LocaleCheckbox';
|
|
23
|
+
import { DownloadIcon, LinkIcon } from '@sanity/icons';
|
|
24
|
+
|
|
25
|
+
export const TranslationView = () => {
|
|
26
|
+
const {
|
|
27
|
+
documents,
|
|
28
|
+
locales,
|
|
29
|
+
translationStatuses,
|
|
30
|
+
isBusy,
|
|
31
|
+
handleTranslateAll,
|
|
32
|
+
handleImportDocument,
|
|
33
|
+
handleRefreshAll,
|
|
34
|
+
isRefreshing,
|
|
35
|
+
importedTranslations,
|
|
36
|
+
setLocales,
|
|
37
|
+
handlePatchDocumentReferences,
|
|
38
|
+
} = useTranslations();
|
|
39
|
+
|
|
40
|
+
const [autoImport, setAutoImport] = useState(false);
|
|
41
|
+
const [isImporting, setIsImporting] = useState(false);
|
|
42
|
+
const [autoRefresh, setAutoRefresh] = useState(true);
|
|
43
|
+
|
|
44
|
+
// Get the single document (first document in single document mode)
|
|
45
|
+
const document = documents[0];
|
|
46
|
+
|
|
47
|
+
// Extract the current document's language from the language field
|
|
48
|
+
const currentDocumentLanguage = useMemo(() => {
|
|
49
|
+
if (!document) return null;
|
|
50
|
+
|
|
51
|
+
// Get the language from the document's language field
|
|
52
|
+
const languageField = pluginConfig.getLanguageField();
|
|
53
|
+
const documentLanguage = document[languageField];
|
|
54
|
+
|
|
55
|
+
// If no language field is set, assume it's the source language
|
|
56
|
+
return documentLanguage || pluginConfig.getSourceLocale();
|
|
57
|
+
}, [document]);
|
|
58
|
+
|
|
59
|
+
// Only show translation components if we're on a source language document
|
|
60
|
+
const shouldShowTranslationComponents = useMemo(() => {
|
|
61
|
+
if (!currentDocumentLanguage) return false;
|
|
62
|
+
return currentDocumentLanguage === pluginConfig.getSourceLocale();
|
|
63
|
+
}, [currentDocumentLanguage]);
|
|
64
|
+
|
|
65
|
+
// Get available locales (excluding source locale)
|
|
66
|
+
const availableLocales = useMemo(() => {
|
|
67
|
+
const sourceLocale = pluginConfig.getSourceLocale();
|
|
68
|
+
return locales.filter(
|
|
69
|
+
(locale) => locale.enabled !== false && locale.localeId !== sourceLocale
|
|
70
|
+
);
|
|
71
|
+
}, [locales]);
|
|
72
|
+
|
|
73
|
+
// Get document ID for status tracking
|
|
74
|
+
const documentId = useMemo(() => {
|
|
75
|
+
if (!document) return null;
|
|
76
|
+
return document._id?.replace('drafts.', '') || document._id;
|
|
77
|
+
}, [document]);
|
|
78
|
+
|
|
79
|
+
// Auto import functionality
|
|
80
|
+
const checkAndImportCompletedTranslations = useCallback(async () => {
|
|
81
|
+
if (!autoImport || isImporting || !documentId) return;
|
|
82
|
+
|
|
83
|
+
const completedTranslations = availableLocales.filter((locale) => {
|
|
84
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
85
|
+
const status = translationStatuses.get(key);
|
|
86
|
+
return (
|
|
87
|
+
(status?.progress || 0) >= 100 &&
|
|
88
|
+
status?.isReady &&
|
|
89
|
+
!importedTranslations.has(key)
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (completedTranslations.length === 0) return;
|
|
94
|
+
|
|
95
|
+
setIsImporting(true);
|
|
96
|
+
try {
|
|
97
|
+
for (const locale of completedTranslations) {
|
|
98
|
+
await handleImportDocument(documentId, locale.localeId);
|
|
99
|
+
}
|
|
100
|
+
} finally {
|
|
101
|
+
setIsImporting(false);
|
|
102
|
+
}
|
|
103
|
+
}, [
|
|
104
|
+
autoImport,
|
|
105
|
+
isImporting,
|
|
106
|
+
documentId,
|
|
107
|
+
availableLocales,
|
|
108
|
+
translationStatuses,
|
|
109
|
+
importedTranslations,
|
|
110
|
+
handleImportDocument,
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const handleImportAll = useCallback(async () => {
|
|
114
|
+
if (isImporting || !documentId) return;
|
|
115
|
+
|
|
116
|
+
setIsImporting(true);
|
|
117
|
+
try {
|
|
118
|
+
const readyTranslations = availableLocales.filter((locale) => {
|
|
119
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
120
|
+
const status = translationStatuses.get(key);
|
|
121
|
+
return status?.isReady && !importedTranslations.has(key);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
for (const locale of readyTranslations) {
|
|
125
|
+
await handleImportDocument(documentId, locale.localeId);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
setIsImporting(false);
|
|
129
|
+
}
|
|
130
|
+
}, [
|
|
131
|
+
isImporting,
|
|
132
|
+
documentId,
|
|
133
|
+
availableLocales,
|
|
134
|
+
translationStatuses,
|
|
135
|
+
importedTranslations,
|
|
136
|
+
handleImportDocument,
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
// Check for completed translations on status updates
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
checkAndImportCompletedTranslations();
|
|
142
|
+
}, [checkAndImportCompletedTranslations]);
|
|
143
|
+
|
|
144
|
+
// Auto refresh functionality
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!autoRefresh || !documentId || availableLocales.length === 0) return;
|
|
147
|
+
|
|
148
|
+
const interval = setInterval(async () => {
|
|
149
|
+
await handleRefreshAll();
|
|
150
|
+
await checkAndImportCompletedTranslations();
|
|
151
|
+
}, 10000);
|
|
152
|
+
|
|
153
|
+
return () => clearInterval(interval);
|
|
154
|
+
}, [
|
|
155
|
+
autoRefresh,
|
|
156
|
+
documentId,
|
|
157
|
+
availableLocales.length,
|
|
158
|
+
handleRefreshAll,
|
|
159
|
+
checkAndImportCompletedTranslations,
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const initialRefresh = async () => {
|
|
164
|
+
await handleRefreshAll();
|
|
165
|
+
await checkAndImportCompletedTranslations();
|
|
166
|
+
};
|
|
167
|
+
initialRefresh();
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
// Locale toggle functionality
|
|
171
|
+
const toggleLocale = useCallback(
|
|
172
|
+
(localeId: string, shouldEnable: boolean) => {
|
|
173
|
+
const updatedLocales = locales.map((locale) =>
|
|
174
|
+
locale.localeId === localeId
|
|
175
|
+
? { ...locale, enabled: shouldEnable }
|
|
176
|
+
: locale
|
|
177
|
+
);
|
|
178
|
+
setLocales(updatedLocales);
|
|
179
|
+
},
|
|
180
|
+
[locales, setLocales]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const toggleAllLocales = useCallback(() => {
|
|
184
|
+
const sourceLocale = pluginConfig.getSourceLocale();
|
|
185
|
+
const nonSourceLocales = locales.filter(
|
|
186
|
+
(locale) => locale.localeId !== sourceLocale
|
|
187
|
+
);
|
|
188
|
+
const allEnabled = nonSourceLocales.every(
|
|
189
|
+
(locale) => locale.enabled === true || locale.enabled === undefined
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const updatedLocales = locales.map((locale) =>
|
|
193
|
+
locale.localeId === sourceLocale
|
|
194
|
+
? locale // Don't change source locale
|
|
195
|
+
: { ...locale, enabled: !allEnabled }
|
|
196
|
+
);
|
|
197
|
+
setLocales(updatedLocales);
|
|
198
|
+
}, [locales, setLocales]);
|
|
199
|
+
|
|
200
|
+
// Show message if we're not on a source language document
|
|
201
|
+
if (!shouldShowTranslationComponents) {
|
|
202
|
+
return (
|
|
203
|
+
<Card padding={4} tone='neutral' border>
|
|
204
|
+
<Text size={1} muted>
|
|
205
|
+
Translation tools are only available for{' '}
|
|
206
|
+
<code>{pluginConfig.getSourceLocale()}</code> documents.
|
|
207
|
+
</Text>
|
|
208
|
+
</Card>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<Stack space={6} padding={4}>
|
|
214
|
+
{/* Generate Translations Section */}
|
|
215
|
+
<Stack space={4}>
|
|
216
|
+
<Text as='h2' weight='semibold' size={2}>
|
|
217
|
+
Generate Translations
|
|
218
|
+
</Text>
|
|
219
|
+
|
|
220
|
+
{/* Locale Selection */}
|
|
221
|
+
<Stack space={3}>
|
|
222
|
+
<Flex align='center' justify='space-between'>
|
|
223
|
+
<Text weight='semibold' size={1}>
|
|
224
|
+
{availableLocales.length === 1
|
|
225
|
+
? 'Select locale'
|
|
226
|
+
: 'Select locales'}
|
|
227
|
+
</Text>
|
|
228
|
+
<Button
|
|
229
|
+
fontSize={1}
|
|
230
|
+
padding={2}
|
|
231
|
+
text='Toggle All'
|
|
232
|
+
onClick={toggleAllLocales}
|
|
233
|
+
/>
|
|
234
|
+
</Flex>
|
|
235
|
+
|
|
236
|
+
<Grid columns={[1, 1, 2, 3]} gap={1}>
|
|
237
|
+
{locales
|
|
238
|
+
.filter(
|
|
239
|
+
(locale) => locale.localeId !== pluginConfig.getSourceLocale()
|
|
240
|
+
)
|
|
241
|
+
.map((locale) => (
|
|
242
|
+
<LocaleCheckbox
|
|
243
|
+
key={locale.localeId}
|
|
244
|
+
locale={locale}
|
|
245
|
+
toggle={toggleLocale}
|
|
246
|
+
checked={
|
|
247
|
+
locale.enabled === true || locale.enabled === undefined
|
|
248
|
+
}
|
|
249
|
+
/>
|
|
250
|
+
))}
|
|
251
|
+
</Grid>
|
|
252
|
+
</Stack>
|
|
253
|
+
|
|
254
|
+
<Button
|
|
255
|
+
onClick={() => {
|
|
256
|
+
setAutoImport(true);
|
|
257
|
+
handleTranslateAll();
|
|
258
|
+
}}
|
|
259
|
+
disabled={isBusy || !availableLocales.length}
|
|
260
|
+
tone='positive'
|
|
261
|
+
text={isBusy ? 'Creating translations...' : 'Generate Translations'}
|
|
262
|
+
/>
|
|
263
|
+
</Stack>
|
|
264
|
+
|
|
265
|
+
{/* Translation Status Section */}
|
|
266
|
+
{documentId && availableLocales.length > 0 && (
|
|
267
|
+
<Stack space={4}>
|
|
268
|
+
<Flex align='center' justify='space-between'>
|
|
269
|
+
<Text as='h2' weight='semibold' size={2}>
|
|
270
|
+
Translation Status
|
|
271
|
+
</Text>
|
|
272
|
+
<Flex gap={3} align='center'>
|
|
273
|
+
<Flex gap={2} align='center'>
|
|
274
|
+
<Text size={1}>Auto-refresh</Text>
|
|
275
|
+
<Switch
|
|
276
|
+
checked={autoRefresh}
|
|
277
|
+
onChange={() => setAutoRefresh(!autoRefresh)}
|
|
278
|
+
/>
|
|
279
|
+
</Flex>
|
|
280
|
+
<Button
|
|
281
|
+
fontSize={1}
|
|
282
|
+
padding={2}
|
|
283
|
+
text='Refresh Status'
|
|
284
|
+
onClick={handleRefreshAll}
|
|
285
|
+
disabled={isRefreshing}
|
|
286
|
+
/>
|
|
287
|
+
</Flex>
|
|
288
|
+
</Flex>
|
|
289
|
+
|
|
290
|
+
<Box>
|
|
291
|
+
{availableLocales.map((locale) => {
|
|
292
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
293
|
+
const status = translationStatuses.get(key);
|
|
294
|
+
const progress = status?.progress || 0;
|
|
295
|
+
const isImported = importedTranslations.has(key);
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<LanguageStatus
|
|
299
|
+
key={key}
|
|
300
|
+
title={locale.description}
|
|
301
|
+
progress={progress}
|
|
302
|
+
isImported={isImported}
|
|
303
|
+
importFile={async () => {
|
|
304
|
+
if (!isImported && status?.isReady) {
|
|
305
|
+
await handleImportDocument(documentId, locale.localeId);
|
|
306
|
+
}
|
|
307
|
+
}}
|
|
308
|
+
/>
|
|
309
|
+
);
|
|
310
|
+
})}
|
|
311
|
+
</Box>
|
|
312
|
+
|
|
313
|
+
{/* Import Controls */}
|
|
314
|
+
<Stack space={3}>
|
|
315
|
+
<Flex gap={3} align='center' justify='space-between'>
|
|
316
|
+
<Flex gap={2} align='center'>
|
|
317
|
+
<Button
|
|
318
|
+
mode='ghost'
|
|
319
|
+
onClick={handleImportAll}
|
|
320
|
+
text={isImporting ? 'Importing...' : 'Import All'}
|
|
321
|
+
icon={DownloadIcon}
|
|
322
|
+
disabled={
|
|
323
|
+
isImporting ||
|
|
324
|
+
availableLocales.every((locale) => {
|
|
325
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
326
|
+
const status = translationStatuses.get(key);
|
|
327
|
+
return !status?.isReady || importedTranslations.has(key);
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
/>
|
|
331
|
+
<Text size={1} muted>
|
|
332
|
+
Imported{' '}
|
|
333
|
+
{
|
|
334
|
+
availableLocales.filter((locale) => {
|
|
335
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
336
|
+
return importedTranslations.has(key);
|
|
337
|
+
}).length
|
|
338
|
+
}
|
|
339
|
+
/
|
|
340
|
+
{
|
|
341
|
+
availableLocales.filter((locale) => {
|
|
342
|
+
const key = `${documentId}:${locale.localeId}`;
|
|
343
|
+
const status = translationStatuses.get(key);
|
|
344
|
+
return status?.isReady;
|
|
345
|
+
}).length
|
|
346
|
+
}
|
|
347
|
+
</Text>
|
|
348
|
+
</Flex>
|
|
349
|
+
<Flex gap={2} align='center' style={{ whiteSpace: 'nowrap' }}>
|
|
350
|
+
<Text size={1}>Auto-import when complete</Text>
|
|
351
|
+
<Switch
|
|
352
|
+
checked={autoImport}
|
|
353
|
+
onChange={() => setAutoImport(!autoImport)}
|
|
354
|
+
disabled={isImporting}
|
|
355
|
+
/>
|
|
356
|
+
</Flex>
|
|
357
|
+
</Flex>
|
|
358
|
+
|
|
359
|
+
<Flex justify='flex-start'>
|
|
360
|
+
<Tooltip
|
|
361
|
+
placement='top'
|
|
362
|
+
content={`Replaces references to ${pluginConfig.getSourceLocale()} documents in this document with the corresponding translated document reference`}
|
|
363
|
+
>
|
|
364
|
+
<Button
|
|
365
|
+
mode='ghost'
|
|
366
|
+
tone='caution'
|
|
367
|
+
onClick={handlePatchDocumentReferences}
|
|
368
|
+
text={isBusy ? 'Patching...' : 'Patch Document References'}
|
|
369
|
+
icon={isBusy ? null : LinkIcon}
|
|
370
|
+
disabled={isBusy}
|
|
371
|
+
/>
|
|
372
|
+
</Tooltip>
|
|
373
|
+
</Flex>
|
|
374
|
+
</Stack>
|
|
375
|
+
</Stack>
|
|
376
|
+
)}
|
|
377
|
+
</Stack>
|
|
378
|
+
);
|
|
379
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SanityDocument } from 'sanity';
|
|
3
|
+
import { BaseTranslationWrapper } from '../shared/BaseTranslationWrapper';
|
|
4
|
+
import { TranslationsProvider } from '../TranslationsProvider';
|
|
5
|
+
import { TranslationView } from './TranslationView';
|
|
6
|
+
|
|
7
|
+
type TranslationTabProps = {
|
|
8
|
+
document: {
|
|
9
|
+
displayed: SanityDocument;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const TranslationTab = (props: TranslationTabProps) => {
|
|
14
|
+
const { displayed } = props.document;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<BaseTranslationWrapper showContainer={false}>
|
|
18
|
+
<TranslationsProvider singleDocument={displayed}>
|
|
19
|
+
<TranslationView />
|
|
20
|
+
</TranslationsProvider>
|
|
21
|
+
</BaseTranslationWrapper>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default TranslationTab;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
// adapted from https://github.com/sanity-io/sanity-translations-tab. See LICENSE.md for more details.
|
|
2
|
+
|
|
1
3
|
import { SanityClient, SanityDocument, SanityDocumentLike } from 'sanity';
|
|
2
|
-
import { BaseDocumentMerger } from '
|
|
4
|
+
import { BaseDocumentMerger } from '../../serialization';
|
|
3
5
|
|
|
4
|
-
import { findLatestDraft
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from './helpers';
|
|
6
|
+
import { findLatestDraft } from '../utils/findLatestDraft';
|
|
7
|
+
import { findDocumentAtRevision } from '../utils/findDocumentAtRevision';
|
|
8
|
+
import { createI18nDocAndPatchMetadata } from './helpers/createI18nDocAndPatchMetadata';
|
|
9
|
+
import { getOrCreateTranslationMetadata } from './helpers/getOrCreateTranslationMetadata';
|
|
10
|
+
import { patchI18nDoc } from './helpers/patchI18nDoc';
|
|
10
11
|
import type { GTFile } from '../../types';
|
|
11
|
-
import {
|
|
12
|
+
import { pluginConfig } from '../../adapter/core';
|
|
12
13
|
|
|
13
14
|
export const documentLevelPatch = async (
|
|
14
15
|
docInfo: GTFile,
|
|
@@ -18,7 +19,7 @@ export const documentLevelPatch = async (
|
|
|
18
19
|
languageField: string = 'language',
|
|
19
20
|
mergeWithTargetLocale: boolean = false
|
|
20
21
|
): Promise<void> => {
|
|
21
|
-
const baseLanguage =
|
|
22
|
+
const baseLanguage = pluginConfig.getSourceLocale();
|
|
22
23
|
//this is the document we use to merge with the translated fields
|
|
23
24
|
let baseDoc: SanityDocument | null = null;
|
|
24
25
|
|
|
@@ -92,16 +93,25 @@ export const documentLevelPatch = async (
|
|
|
92
93
|
) as SanityDocumentLike;
|
|
93
94
|
|
|
94
95
|
if (i18nDoc) {
|
|
95
|
-
patchI18nDoc(
|
|
96
|
+
await patchI18nDoc(
|
|
97
|
+
docInfo.documentId,
|
|
98
|
+
i18nDoc._id,
|
|
99
|
+
baseDoc,
|
|
100
|
+
merged,
|
|
101
|
+
translatedFields,
|
|
102
|
+
client
|
|
103
|
+
);
|
|
96
104
|
}
|
|
97
105
|
//otherwise, create a new document
|
|
98
106
|
//and add the document reference to the metadata document
|
|
99
107
|
else {
|
|
100
|
-
createI18nDocAndPatchMetadata(
|
|
108
|
+
await createI18nDocAndPatchMetadata(
|
|
109
|
+
baseDoc,
|
|
101
110
|
merged,
|
|
102
111
|
localeId,
|
|
103
112
|
client,
|
|
104
113
|
translationMetadata,
|
|
114
|
+
docInfo.documentId,
|
|
105
115
|
languageField
|
|
106
116
|
);
|
|
107
117
|
}
|