@smartcat/sanity-plugin 1.0.0 → 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.
- package/README.md +294 -0
- package/dist/_chunks-cjs/constants.cjs +13 -0
- package/dist/_chunks-cjs/constants.cjs.map +1 -0
- package/dist/_chunks-cjs/index.cjs +168 -0
- package/dist/_chunks-cjs/index.cjs.map +1 -0
- package/dist/_chunks-cjs/index2.cjs +311 -0
- package/dist/_chunks-cjs/index2.cjs.map +1 -0
- package/dist/_chunks-cjs/workflow.cjs +16 -0
- package/dist/_chunks-cjs/workflow.cjs.map +1 -0
- package/dist/_chunks-es/constants.js +14 -0
- package/dist/_chunks-es/constants.js.map +1 -0
- package/dist/_chunks-es/index.js +169 -0
- package/dist/_chunks-es/index.js.map +1 -0
- package/dist/_chunks-es/index2.js +313 -0
- package/dist/_chunks-es/index2.js.map +1 -0
- package/dist/_chunks-es/workflow.js +17 -0
- package/dist/_chunks-es/workflow.js.map +1 -0
- package/dist/export.cjs +160 -0
- package/dist/export.cjs.map +1 -0
- package/dist/export.d.cts +162 -0
- package/dist/export.d.ts +162 -0
- package/dist/export.js +162 -0
- package/dist/export.js.map +1 -0
- package/dist/import.cjs +169 -0
- package/dist/import.cjs.map +1 -0
- package/dist/import.d.cts +155 -0
- package/dist/import.d.ts +155 -0
- package/dist/import.js +169 -0
- package/dist/import.js.map +1 -0
- package/dist/index.cjs +2103 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +122 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +2110 -0
- package/dist/index.js.map +1 -0
- package/dist/locjson.cjs +12 -0
- package/dist/locjson.cjs.map +1 -0
- package/dist/locjson.d.cts +287 -0
- package/dist/locjson.d.ts +287 -0
- package/dist/locjson.js +12 -0
- package/dist/locjson.js.map +1 -0
- package/dist/progress.cjs +16 -0
- package/dist/progress.cjs.map +1 -0
- package/dist/progress.d.cts +262 -0
- package/dist/progress.d.ts +262 -0
- package/dist/progress.js +16 -0
- package/dist/progress.js.map +1 -0
- package/dist/smartcat.cjs +287 -0
- package/dist/smartcat.cjs.map +1 -0
- package/dist/smartcat.d.cts +234 -0
- package/dist/smartcat.d.ts +234 -0
- package/dist/smartcat.js +287 -0
- package/dist/smartcat.js.map +1 -0
- package/dist/templates.cjs +12 -0
- package/dist/templates.cjs.map +1 -0
- package/dist/templates.d.cts +52 -0
- package/dist/templates.d.ts +52 -0
- package/dist/templates.js +12 -0
- package/dist/templates.js.map +1 -0
- package/package.json +101 -15
- package/src/actions/AddToProjectAction.tsx +258 -0
- package/src/export/export.test.ts +537 -0
- package/src/export/index.ts +393 -0
- package/src/form/TranslationStatusInput.tsx +212 -0
- package/src/import/import.test.ts +346 -0
- package/src/import/index.ts +418 -0
- package/src/index.ts +63 -0
- package/src/lib/constants.ts +23 -0
- package/src/lib/documentTitle.test.ts +43 -0
- package/src/lib/documentTitle.ts +30 -0
- package/src/lib/languageMap.test.ts +56 -0
- package/src/lib/languageMap.ts +71 -0
- package/src/lib/linkedDocuments.test.ts +56 -0
- package/src/lib/linkedDocuments.ts +100 -0
- package/src/lib/locjson/deserialize.ts +60 -0
- package/src/lib/locjson/fields.ts +355 -0
- package/src/lib/locjson/filename.ts +27 -0
- package/src/lib/locjson/index.ts +10 -0
- package/src/lib/locjson/locjson.test.ts +545 -0
- package/src/lib/locjson/paths.test.ts +41 -0
- package/src/lib/locjson/paths.ts +73 -0
- package/src/lib/locjson/portableText.ts +98 -0
- package/src/lib/locjson/serialize.ts +124 -0
- package/src/lib/locjson/types.ts +106 -0
- package/src/lib/log.ts +33 -0
- package/src/lib/projectItems.ts +17 -0
- package/src/lib/resolveConfig.test.ts +62 -0
- package/src/lib/resolveConfig.ts +47 -0
- package/src/lib/workflow.test.ts +37 -0
- package/src/lib/workflow.ts +48 -0
- package/src/lib/zip.fixtures.ts +75 -0
- package/src/lib/zip.test.ts +110 -0
- package/src/lib/zip.ts +164 -0
- package/src/progress/core.ts +387 -0
- package/src/progress/index.ts +7 -0
- package/src/progress/progress.test.ts +268 -0
- package/src/schema/settings.ts +55 -0
- package/src/schema/translatableOptions.ts +25 -0
- package/src/schema/translationProject.ts +264 -0
- package/src/smartcat/client.test.ts +222 -0
- package/src/smartcat/client.ts +351 -0
- package/src/smartcat/index.ts +10 -0
- package/src/smartcat/types.ts +99 -0
- package/src/templates/core.ts +55 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/templates.test.ts +50 -0
- package/src/tool/Dashboard.tsx +44 -0
- package/src/tool/ItemProgress.tsx +176 -0
- package/src/tool/LogPanel.tsx +207 -0
- package/src/tool/ProjectView.tsx +975 -0
- package/src/tool/ProjectsView.tsx +208 -0
- package/src/tool/StatusBadge.tsx +33 -0
- package/src/tool/StudioInit.tsx +42 -0
- package/src/tool/WorkflowSelect.tsx +41 -0
- package/src/tool/data.ts +79 -0
- package/src/tool/index.ts +19 -0
- package/src/tool/processing.test.ts +623 -0
- package/src/tool/processing.ts +766 -0
- package/src/tool/useTemplates.ts +84 -0
- package/src/tool/useWorkflowSelection.ts +65 -0
- package/src/types.ts +77 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import {useCallback, useEffect, useState} from 'react'
|
|
2
|
+
import {useClient, useRelativeTime} from 'sanity'
|
|
3
|
+
import {
|
|
4
|
+
Box,
|
|
5
|
+
Button,
|
|
6
|
+
Card,
|
|
7
|
+
Dialog,
|
|
8
|
+
Flex,
|
|
9
|
+
Heading,
|
|
10
|
+
Label,
|
|
11
|
+
Spinner,
|
|
12
|
+
Stack,
|
|
13
|
+
Text,
|
|
14
|
+
TextInput,
|
|
15
|
+
useToast,
|
|
16
|
+
} from '@sanity/ui'
|
|
17
|
+
import {AddIcon, TranslateIcon} from '@sanity/icons'
|
|
18
|
+
import {API_VERSION, TRANSLATION_PROJECT_TYPE} from '../lib/constants'
|
|
19
|
+
import {PROJECTS_QUERY, type ProjectListItem} from './data'
|
|
20
|
+
import {StatusBadge} from './StatusBadge'
|
|
21
|
+
import {WorkflowSelect} from './WorkflowSelect'
|
|
22
|
+
import {useTemplates} from './useTemplates'
|
|
23
|
+
import {useWorkflowSelection} from './useWorkflowSelection'
|
|
24
|
+
|
|
25
|
+
interface ProjectsViewProps {
|
|
26
|
+
sourceLanguage: string
|
|
27
|
+
onOpenProject: (id: string) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A single project row. Extracted so `useRelativeTime` can run per row. */
|
|
31
|
+
function ProjectRow({project: p, onOpen}: {project: ProjectListItem; onOpen: (id: string) => void}) {
|
|
32
|
+
const createdAgo = useRelativeTime(p._createdAt, {useTemporalPhrase: true})
|
|
33
|
+
return (
|
|
34
|
+
<Card
|
|
35
|
+
padding={3}
|
|
36
|
+
radius={2}
|
|
37
|
+
shadow={1}
|
|
38
|
+
as="button"
|
|
39
|
+
onClick={() => onOpen(p._id)}
|
|
40
|
+
style={{cursor: 'pointer', textAlign: 'left', width: '100%'}}
|
|
41
|
+
>
|
|
42
|
+
<Flex align="center" gap={3}>
|
|
43
|
+
<Box style={{width: 110}}>
|
|
44
|
+
<Text size={1} muted title={new Date(p._createdAt).toLocaleString()}>
|
|
45
|
+
{createdAgo}
|
|
46
|
+
</Text>
|
|
47
|
+
</Box>
|
|
48
|
+
<Box style={{width: 100}}>
|
|
49
|
+
<StatusBadge status={p.status} />
|
|
50
|
+
</Box>
|
|
51
|
+
<Box>
|
|
52
|
+
<TranslateIcon />
|
|
53
|
+
</Box>
|
|
54
|
+
<Box flex={1}>
|
|
55
|
+
<Text weight="semibold">{p.name}</Text>
|
|
56
|
+
</Box>
|
|
57
|
+
<Text size={1} muted>
|
|
58
|
+
{p.count} item{p.count === 1 ? '' : 's'}
|
|
59
|
+
</Text>
|
|
60
|
+
</Flex>
|
|
61
|
+
</Card>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function ProjectsView({sourceLanguage, onOpenProject}: ProjectsViewProps) {
|
|
66
|
+
const client = useClient({apiVersion: API_VERSION})
|
|
67
|
+
const toast = useToast()
|
|
68
|
+
const [projects, setProjects] = useState<ProjectListItem[] | null>(null)
|
|
69
|
+
const [creating, setCreating] = useState(false)
|
|
70
|
+
const [newName, setNewName] = useState('')
|
|
71
|
+
const [busy, setBusy] = useState(false)
|
|
72
|
+
const {templates, loaded, requestRefresh} = useTemplates()
|
|
73
|
+
const [workflow, setWorkflow] = useWorkflowSelection(creating, templates, loaded)
|
|
74
|
+
|
|
75
|
+
// Refresh the template list each time the create dialog opens.
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (creating) requestRefresh()
|
|
78
|
+
}, [creating, requestRefresh])
|
|
79
|
+
|
|
80
|
+
const load = useCallback(() => {
|
|
81
|
+
return client
|
|
82
|
+
.fetch<ProjectListItem[]>(PROJECTS_QUERY)
|
|
83
|
+
.then(setProjects)
|
|
84
|
+
.catch((err) => toast.push({status: 'error', title: 'Failed to load projects', description: String(err)}))
|
|
85
|
+
}, [client, toast])
|
|
86
|
+
|
|
87
|
+
// Initial load + live updates.
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
load()
|
|
90
|
+
const sub = client
|
|
91
|
+
.listen(PROJECTS_QUERY, {}, {visibility: 'query'})
|
|
92
|
+
.subscribe({next: () => load(), error: () => {}})
|
|
93
|
+
return () => sub.unsubscribe()
|
|
94
|
+
}, [client, load])
|
|
95
|
+
|
|
96
|
+
const handleCreate = useCallback(async () => {
|
|
97
|
+
const name = newName.trim()
|
|
98
|
+
if (!name) return
|
|
99
|
+
setBusy(true)
|
|
100
|
+
try {
|
|
101
|
+
const created = await client.create({
|
|
102
|
+
_type: TRANSLATION_PROJECT_TYPE,
|
|
103
|
+
name,
|
|
104
|
+
status: 'draft',
|
|
105
|
+
sourceLanguage,
|
|
106
|
+
workflow,
|
|
107
|
+
items: [],
|
|
108
|
+
})
|
|
109
|
+
toast.push({status: 'success', title: `Created “${name}”`})
|
|
110
|
+
setCreating(false)
|
|
111
|
+
setNewName('')
|
|
112
|
+
onOpenProject(created._id)
|
|
113
|
+
} catch (err) {
|
|
114
|
+
toast.push({status: 'error', title: 'Failed to create project', description: String(err)})
|
|
115
|
+
} finally {
|
|
116
|
+
setBusy(false)
|
|
117
|
+
}
|
|
118
|
+
}, [newName, workflow, client, sourceLanguage, toast, onOpenProject])
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Stack space={4}>
|
|
122
|
+
<Flex align="center" justify="space-between">
|
|
123
|
+
<Heading size={2}>Translation projects</Heading>
|
|
124
|
+
<Button icon={AddIcon} text="New project" tone="primary" onClick={() => setCreating(true)} />
|
|
125
|
+
</Flex>
|
|
126
|
+
|
|
127
|
+
{projects === null ? (
|
|
128
|
+
<Flex align="center" justify="center" padding={5}>
|
|
129
|
+
<Spinner muted />
|
|
130
|
+
</Flex>
|
|
131
|
+
) : projects.length === 0 ? (
|
|
132
|
+
<Card padding={5} radius={2} tone="transparent" border>
|
|
133
|
+
<Stack space={3}>
|
|
134
|
+
<Text muted align="center">
|
|
135
|
+
No translation projects yet.
|
|
136
|
+
</Text>
|
|
137
|
+
<Flex justify="center">
|
|
138
|
+
<Button icon={AddIcon} text="Create your first project" onClick={() => setCreating(true)} />
|
|
139
|
+
</Flex>
|
|
140
|
+
</Stack>
|
|
141
|
+
</Card>
|
|
142
|
+
) : (
|
|
143
|
+
<Stack space={2}>
|
|
144
|
+
{projects.map((p) => (
|
|
145
|
+
<ProjectRow key={p._id} project={p} onOpen={onOpenProject} />
|
|
146
|
+
))}
|
|
147
|
+
</Stack>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{creating && (
|
|
151
|
+
<Dialog
|
|
152
|
+
id="create-project"
|
|
153
|
+
header="New translation project"
|
|
154
|
+
onClose={() => {
|
|
155
|
+
setCreating(false)
|
|
156
|
+
setNewName('')
|
|
157
|
+
}}
|
|
158
|
+
width={1}
|
|
159
|
+
>
|
|
160
|
+
<Box padding={4}>
|
|
161
|
+
<Stack space={4}>
|
|
162
|
+
<Stack space={3}>
|
|
163
|
+
<Label size={1}>Project name</Label>
|
|
164
|
+
<TextInput
|
|
165
|
+
value={newName}
|
|
166
|
+
placeholder="e.g. Spring campaign"
|
|
167
|
+
autoFocus
|
|
168
|
+
onChange={(e) => setNewName(e.currentTarget.value)}
|
|
169
|
+
onKeyDown={(e) => {
|
|
170
|
+
if (e.key === 'Enter') handleCreate()
|
|
171
|
+
}}
|
|
172
|
+
disabled={busy}
|
|
173
|
+
/>
|
|
174
|
+
</Stack>
|
|
175
|
+
<Stack space={3}>
|
|
176
|
+
<Label size={1}>Workflow</Label>
|
|
177
|
+
<WorkflowSelect
|
|
178
|
+
value={workflow}
|
|
179
|
+
onChange={setWorkflow}
|
|
180
|
+
templates={templates}
|
|
181
|
+
disabled={busy}
|
|
182
|
+
/>
|
|
183
|
+
</Stack>
|
|
184
|
+
<Flex justify="flex-end" gap={2}>
|
|
185
|
+
<Button
|
|
186
|
+
mode="bleed"
|
|
187
|
+
text="Cancel"
|
|
188
|
+
onClick={() => {
|
|
189
|
+
setCreating(false)
|
|
190
|
+
setNewName('')
|
|
191
|
+
}}
|
|
192
|
+
disabled={busy}
|
|
193
|
+
/>
|
|
194
|
+
<Button
|
|
195
|
+
tone="primary"
|
|
196
|
+
text="Create"
|
|
197
|
+
onClick={handleCreate}
|
|
198
|
+
loading={busy}
|
|
199
|
+
disabled={!newName.trim() || !workflow}
|
|
200
|
+
/>
|
|
201
|
+
</Flex>
|
|
202
|
+
</Stack>
|
|
203
|
+
</Box>
|
|
204
|
+
</Dialog>
|
|
205
|
+
)}
|
|
206
|
+
</Stack>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {Badge, type BadgeTone} from '@sanity/ui'
|
|
2
|
+
import type {TranslationProjectStatus} from '../types'
|
|
3
|
+
import {STATUS_OPTIONS} from '../schema/translationProject'
|
|
4
|
+
|
|
5
|
+
// Badge color is a UI-only concern (no schema equivalent), so it lives here.
|
|
6
|
+
const TONES: Record<TranslationProjectStatus, BadgeTone> = {
|
|
7
|
+
draft: 'default',
|
|
8
|
+
queued: 'caution',
|
|
9
|
+
sent: 'primary',
|
|
10
|
+
translating: 'primary',
|
|
11
|
+
importing: 'caution',
|
|
12
|
+
downloaded: 'primary',
|
|
13
|
+
completed: 'positive',
|
|
14
|
+
error: 'critical',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Display titles come from the schema's STATUS_OPTIONS — the single source.
|
|
18
|
+
const LABELS: Record<string, string> = Object.fromEntries(
|
|
19
|
+
STATUS_OPTIONS.map((o) => [o.value, o.title]),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
/** Display label for a project status (from the schema's status titles). */
|
|
23
|
+
export function statusLabel(status: TranslationProjectStatus): string {
|
|
24
|
+
return LABELS[status] ?? status
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function StatusBadge({status}: {status: TranslationProjectStatus}) {
|
|
28
|
+
return (
|
|
29
|
+
<Badge tone={TONES[status] ?? 'default'} fontSize={0} padding={2} radius={2}>
|
|
30
|
+
{statusLabel(status)}
|
|
31
|
+
</Badge>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {useEffect, useRef} from 'react'
|
|
2
|
+
import {useClient, type LayoutProps} from 'sanity'
|
|
3
|
+
import {API_VERSION, SETTINGS_DOC_ID, SETTINGS_TYPE} from '../lib/constants'
|
|
4
|
+
import {isRefreshInFlight} from './useTemplates'
|
|
5
|
+
|
|
6
|
+
interface SettingsDoc {
|
|
7
|
+
templatesRequestedAt?: string
|
|
8
|
+
templatesSyncedAt?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const INIT_QUERY = `*[_id == $id][0]{templatesRequestedAt, templatesSyncedAt}`
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Studio layout wrapper. On first load it bootstraps the template cache *only if
|
|
15
|
+
* it has never been initialized* — i.e. no `templatesSyncedAt` and no request
|
|
16
|
+
* already in flight. Routine freshness is handled by the create dialogs; this
|
|
17
|
+
* just seeds an empty cache the very first time the Studio runs.
|
|
18
|
+
*/
|
|
19
|
+
export function StudioInit(props: LayoutProps) {
|
|
20
|
+
const client = useClient({apiVersion: API_VERSION})
|
|
21
|
+
const didRun = useRef(false)
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (didRun.current) return
|
|
25
|
+
didRun.current = true
|
|
26
|
+
client
|
|
27
|
+
.fetch<SettingsDoc | null>(INIT_QUERY, {id: SETTINGS_DOC_ID})
|
|
28
|
+
.then((s) => {
|
|
29
|
+
// Skip if already initialized, or a fresh request is still in flight.
|
|
30
|
+
// A stale request (older than the window) falls through and re-bumps.
|
|
31
|
+
if (s?.templatesSyncedAt || isRefreshInFlight(s)) return
|
|
32
|
+
return client
|
|
33
|
+
.transaction()
|
|
34
|
+
.createIfNotExists({_id: SETTINGS_DOC_ID, _type: SETTINGS_TYPE})
|
|
35
|
+
.patch(SETTINGS_DOC_ID, (p) => p.set({templatesRequestedAt: new Date().toISOString()}))
|
|
36
|
+
.commit({visibility: 'async'})
|
|
37
|
+
})
|
|
38
|
+
.catch(() => {})
|
|
39
|
+
}, [client])
|
|
40
|
+
|
|
41
|
+
return props.renderDefault(props)
|
|
42
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {Select} from '@sanity/ui'
|
|
2
|
+
import type {SmartcatTemplate} from '../smartcat/types'
|
|
3
|
+
import {STANDARD_WORKFLOW_OPTIONS, TEMPLATE_PREFIX} from '../lib/workflow'
|
|
4
|
+
|
|
5
|
+
interface WorkflowSelectProps {
|
|
6
|
+
value: string
|
|
7
|
+
onChange: (value: string) => void
|
|
8
|
+
templates: SmartcatTemplate[]
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Workflow picker for new projects: cached Smartcat templates grouped under
|
|
14
|
+
* "Templates", followed by the standard presets under "Standard workflows".
|
|
15
|
+
* Templates are expected pre-sorted by name (see `useTemplates`).
|
|
16
|
+
*/
|
|
17
|
+
export function WorkflowSelect({value, onChange, templates, disabled}: WorkflowSelectProps) {
|
|
18
|
+
return (
|
|
19
|
+
<Select value={value} onChange={(e) => onChange(e.currentTarget.value)} disabled={disabled}>
|
|
20
|
+
<option value="" disabled>
|
|
21
|
+
Select a workflow…
|
|
22
|
+
</option>
|
|
23
|
+
{templates.length > 0 && (
|
|
24
|
+
<optgroup label="Templates">
|
|
25
|
+
{templates.map((t) => (
|
|
26
|
+
<option key={t.id} value={`${TEMPLATE_PREFIX}${t.id}`}>
|
|
27
|
+
{t.name}
|
|
28
|
+
</option>
|
|
29
|
+
))}
|
|
30
|
+
</optgroup>
|
|
31
|
+
)}
|
|
32
|
+
<optgroup label="Standard workflows">
|
|
33
|
+
{STANDARD_WORKFLOW_OPTIONS.map((o) => (
|
|
34
|
+
<option key={o.value} value={o.value}>
|
|
35
|
+
{o.label}
|
|
36
|
+
</option>
|
|
37
|
+
))}
|
|
38
|
+
</optgroup>
|
|
39
|
+
</Select>
|
|
40
|
+
)
|
|
41
|
+
}
|
package/src/tool/data.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {TRANSLATION_PROJECT_TYPE} from '../lib/constants'
|
|
2
|
+
import {ITEM_ID, ITEM_ID_FROM_SUBQUERY, itemDocDeref} from '../lib/projectItems'
|
|
3
|
+
import type {TranslationProjectStatus} from '../types'
|
|
4
|
+
import type {DocProgress} from '../progress/core'
|
|
5
|
+
|
|
6
|
+
/** A row in the projects list. */
|
|
7
|
+
export interface ProjectListItem {
|
|
8
|
+
_id: string
|
|
9
|
+
name: string
|
|
10
|
+
status: TranslationProjectStatus
|
|
11
|
+
count: number
|
|
12
|
+
_createdAt: string
|
|
13
|
+
_updatedAt: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A resolved item inside a project. */
|
|
17
|
+
export interface ProjectItemRef {
|
|
18
|
+
_key: string
|
|
19
|
+
/** Published id of the added document, normalized across old (`_ref`) and new (`docId`) items. */
|
|
20
|
+
docId: string
|
|
21
|
+
doc: {_id: string; _type: string} | null
|
|
22
|
+
/** Locale variants linked via translation.metadata: language -> variant doc id. */
|
|
23
|
+
translations: {language: string; id: string}[] | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A single project with its resolved items. */
|
|
27
|
+
export interface ProjectDetail {
|
|
28
|
+
_id: string
|
|
29
|
+
name: string
|
|
30
|
+
status: TranslationProjectStatus
|
|
31
|
+
sourceLanguage: string | null
|
|
32
|
+
targetLanguages: string[] | null
|
|
33
|
+
smartcatProjectId: string | null
|
|
34
|
+
smartcatProjectUrl: string | null
|
|
35
|
+
lastError: string | null
|
|
36
|
+
/** JSON-encoded LogLine[] from the most recent Function run. */
|
|
37
|
+
functionLog: string | null
|
|
38
|
+
/** Targets the last download run deferred (size/time caps) — drives auto-continue. */
|
|
39
|
+
importRemaining: number | null
|
|
40
|
+
items: ProjectItemRef[] | null
|
|
41
|
+
progress: DocProgress[] | null
|
|
42
|
+
progressSyncedAt: string | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const PROJECTS_QUERY = `*[_type == "${TRANSLATION_PROJECT_TYPE}"] | order(_createdAt desc){
|
|
46
|
+
_id,
|
|
47
|
+
name,
|
|
48
|
+
status,
|
|
49
|
+
_createdAt,
|
|
50
|
+
_updatedAt,
|
|
51
|
+
"count": count(items)
|
|
52
|
+
}`
|
|
53
|
+
|
|
54
|
+
export const PROJECT_DETAIL_QUERY = `*[_id == $id][0]{
|
|
55
|
+
_id,
|
|
56
|
+
name,
|
|
57
|
+
status,
|
|
58
|
+
sourceLanguage,
|
|
59
|
+
targetLanguages,
|
|
60
|
+
smartcatProjectId,
|
|
61
|
+
smartcatProjectUrl,
|
|
62
|
+
lastError,
|
|
63
|
+
functionLog,
|
|
64
|
+
importRemaining,
|
|
65
|
+
progressSyncedAt,
|
|
66
|
+
items[]{
|
|
67
|
+
_key,
|
|
68
|
+
"docId": ${ITEM_ID},
|
|
69
|
+
"doc": ${itemDocDeref('{_id, _type}')},
|
|
70
|
+
"translations": *[_type == "translation.metadata" && references(${ITEM_ID_FROM_SUBQUERY})][0]
|
|
71
|
+
.translations[]{language, "id": value._ref}
|
|
72
|
+
},
|
|
73
|
+
progress[]{
|
|
74
|
+
_key,
|
|
75
|
+
sourceDocId,
|
|
76
|
+
title,
|
|
77
|
+
targets[]{_key, language, smartcatDocumentId, stages, complete, imported, syncedAt}
|
|
78
|
+
}
|
|
79
|
+
}`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {route} from 'sanity/router'
|
|
2
|
+
import {TranslateIcon} from '@sanity/icons'
|
|
3
|
+
import type {Tool} from 'sanity'
|
|
4
|
+
import {createDashboardComponent} from './Dashboard'
|
|
5
|
+
import type {ResolvedSmartcatConfig} from '../lib/resolveConfig'
|
|
6
|
+
|
|
7
|
+
/** Tool name; also the URL segment used to deep-link into the tool. */
|
|
8
|
+
export const TRANSLATION_TOOL_NAME = 'translation-projects'
|
|
9
|
+
|
|
10
|
+
/** Builds the "Translations" Studio tool (top-nav dashboard). */
|
|
11
|
+
export function createTranslationsTool(config: ResolvedSmartcatConfig): Tool {
|
|
12
|
+
return {
|
|
13
|
+
name: TRANSLATION_TOOL_NAME,
|
|
14
|
+
title: 'Translation Projects',
|
|
15
|
+
icon: TranslateIcon,
|
|
16
|
+
component: createDashboardComponent(config),
|
|
17
|
+
router: route.create('/', [route.create('/project/:projectId')]),
|
|
18
|
+
}
|
|
19
|
+
}
|