@smartcat/sanity-plugin 1.2.1 → 1.3.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/dist/_chunks-cjs/index.cjs +18 -5
- package/dist/_chunks-cjs/index.cjs.map +1 -1
- package/dist/_chunks-cjs/index2.cjs +8 -6
- package/dist/_chunks-cjs/index2.cjs.map +1 -1
- package/dist/_chunks-es/index.js +18 -5
- package/dist/_chunks-es/index.js.map +1 -1
- package/dist/_chunks-es/index2.js +8 -6
- package/dist/_chunks-es/index2.js.map +1 -1
- package/dist/export.cjs +21 -4
- package/dist/export.cjs.map +1 -1
- package/dist/export.d.cts +2 -0
- package/dist/export.d.ts +2 -0
- package/dist/export.js +22 -5
- package/dist/export.js.map +1 -1
- package/dist/index.cjs +156 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +160 -43
- package/dist/index.js.map +1 -1
- package/dist/locjson.d.cts +15 -0
- package/dist/locjson.d.ts +15 -0
- package/dist/progress.d.cts +0 -19
- package/dist/progress.d.ts +0 -19
- package/dist/smartcat.cjs +14 -2
- package/dist/smartcat.cjs.map +1 -1
- package/dist/smartcat.d.cts +10 -1
- package/dist/smartcat.d.ts +10 -1
- package/dist/smartcat.js +14 -2
- package/dist/smartcat.js.map +1 -1
- package/package.json +1 -1
- package/src/actions/AddToProjectAction.tsx +23 -2
- package/src/export/export.test.ts +42 -5
- package/src/export/index.ts +63 -8
- package/src/lib/locjson/filename.ts +7 -2
- package/src/lib/locjson/locjson.test.ts +64 -0
- package/src/lib/locjson/serialize.ts +38 -2
- package/src/progress/core.ts +44 -3
- package/src/progress/progress.test.ts +40 -0
- package/src/schema/translationProject.ts +19 -0
- package/src/smartcat/client.ts +15 -1
- package/src/tool/ProjectView.tsx +108 -44
- package/src/tool/data.ts +7 -0
- package/src/tool/processing.ts +110 -4
package/src/tool/ProjectView.tsx
CHANGED
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
Flex,
|
|
18
18
|
Heading,
|
|
19
19
|
Inline,
|
|
20
|
+
Menu,
|
|
21
|
+
MenuButton,
|
|
22
|
+
MenuItem,
|
|
20
23
|
Spinner,
|
|
21
24
|
Stack,
|
|
22
25
|
Text,
|
|
@@ -33,6 +36,7 @@ import {
|
|
|
33
36
|
SyncIcon,
|
|
34
37
|
RemoveCircleIcon,
|
|
35
38
|
JsonIcon,
|
|
39
|
+
ChevronDownIcon,
|
|
36
40
|
} from '@sanity/icons'
|
|
37
41
|
import styled, {css, keyframes} from 'styled-components'
|
|
38
42
|
import {API_VERSION} from '../lib/constants'
|
|
@@ -158,10 +162,16 @@ const CodeArea = styled.textarea`
|
|
|
158
162
|
tab-size: 2;
|
|
159
163
|
`
|
|
160
164
|
|
|
165
|
+
/** Target-locale siblings of a document-level item, via translation.metadata. */
|
|
166
|
+
const VIEW_SIBLINGS_QUERY = `*[_type == "translation.metadata" && references($id)][0].translations[]{"language": _key, "doc": value->}`
|
|
167
|
+
|
|
161
168
|
/**
|
|
162
|
-
*
|
|
163
|
-
* and serialize options as {@link prepareExport} — and shows it
|
|
164
|
-
* in a read-only modal
|
|
169
|
+
* Dropdown that builds the LocJSON the export would produce for an item — same
|
|
170
|
+
* field selection and serialize options as {@link prepareExport} — and shows it
|
|
171
|
+
* (pretty-printed) in a read-only modal. Offers "Source only" (what a plain
|
|
172
|
+
* export sends) plus "Source + <language>" for each target language: the
|
|
173
|
+
* bilingual file the send-existing flow would upload for that language, so you
|
|
174
|
+
* can inspect exactly what goes to Smartcat per language.
|
|
165
175
|
*/
|
|
166
176
|
function ViewLocjsonButton(props: {
|
|
167
177
|
docId: string
|
|
@@ -173,45 +183,80 @@ function ViewLocjsonButton(props: {
|
|
|
173
183
|
sourceLanguage: string
|
|
174
184
|
documentI18nLanguageField: string
|
|
175
185
|
fieldI18nLanguageField: string
|
|
186
|
+
/** Project target languages (Sanity locale ids). */
|
|
187
|
+
targetLanguages: string[]
|
|
188
|
+
/** Configured languages (for titles + Smartcat codes). */
|
|
189
|
+
languages: SmartcatLanguage[]
|
|
176
190
|
}) {
|
|
177
|
-
const {docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField} = props
|
|
191
|
+
const {docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, targetLanguages, languages} = props
|
|
178
192
|
const [open, setOpen] = useState(false)
|
|
179
193
|
const [content, setContent] = useState<string | null>(null)
|
|
180
194
|
const [filename, setFilename] = useState<string | null>(null)
|
|
195
|
+
const [title, setTitle] = useState<string>('Source only')
|
|
181
196
|
const [error, setError] = useState<string | null>(null)
|
|
182
197
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
198
|
+
// Build the LocJSON for one variant: `targetLanguage` undefined ⇒ source-only
|
|
199
|
+
// (a plain export), otherwise the bilingual file for that Sanity locale.
|
|
200
|
+
const build = useCallback(
|
|
201
|
+
async (targetLanguage?: string) => {
|
|
202
|
+
setOpen(true)
|
|
203
|
+
setContent(null)
|
|
204
|
+
setFilename(null)
|
|
205
|
+
setError(null)
|
|
206
|
+
setTitle(targetLanguage ? `Source + ${languageTitle(languages, targetLanguage)}` : 'Source only')
|
|
207
|
+
try {
|
|
208
|
+
// Resolve the same version the export will send: the draft (fallback to
|
|
209
|
+
// published) unless the item was added from the published perspective.
|
|
210
|
+
const raw = (await client.fetch(
|
|
211
|
+
sourceIsPublished
|
|
212
|
+
? '*[_id == $id][0]'
|
|
213
|
+
: 'coalesce(*[_id == "drafts." + $id][0], *[_id == $id][0])',
|
|
214
|
+
{id: docId},
|
|
215
|
+
)) as (Record<string, unknown> & {_id: string}) | null
|
|
216
|
+
if (!raw) throw new Error(`Document not found: ${docId}`)
|
|
217
|
+
const isDraft = typeof raw._id === 'string' && raw._id.startsWith('drafts.')
|
|
218
|
+
// Serialize under the canonical (bare) id, matching the export.
|
|
219
|
+
const doc = isDraft ? {...raw, _id: docId} : raw
|
|
220
|
+
const fields = getTranslatableFields(schema.get(type), {exclude: [documentI18nLanguageField]})
|
|
221
|
+
|
|
222
|
+
// For a bilingual variant, resolve the existing translation the same way
|
|
223
|
+
// the export does: field-level from the inline member, document-level from
|
|
224
|
+
// the target-locale sibling document.
|
|
225
|
+
let target: {language: string; doc?: Record<string, unknown> & {_id: string; _type: string}} | undefined
|
|
226
|
+
if (targetLanguage) {
|
|
227
|
+
if (localizationMode(schema.get(type)) === 'document') {
|
|
228
|
+
const variants = (await client.fetch(VIEW_SIBLINGS_QUERY, {id: docId})) as
|
|
229
|
+
| {language: string; doc: (Record<string, unknown> & {_id: string; _type: string}) | null}[]
|
|
230
|
+
| null
|
|
231
|
+
const sibling = (variants ?? []).find((v) => v.language === targetLanguage)?.doc ?? undefined
|
|
232
|
+
target = {language: targetLanguage, doc: sibling}
|
|
233
|
+
} else {
|
|
234
|
+
target = {language: targetLanguage}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const locjson = serializeToLocjson(doc as never, fields, {
|
|
239
|
+
sourceLanguage,
|
|
240
|
+
fieldLanguageKey: fieldI18nLanguageField,
|
|
241
|
+
target,
|
|
242
|
+
})
|
|
243
|
+
// Same name the export sends to Smartcat; the last path segment is the
|
|
244
|
+
// file's basename (the Smartcat "folder/" prefix can't be a local filename).
|
|
245
|
+
const smartcatCode = targetLanguage
|
|
246
|
+
? languages.find((l) => l.id === targetLanguage)?.smartcatLanguage || targetLanguage
|
|
247
|
+
: undefined
|
|
248
|
+
const smartcatName = buildLocjsonFilename(type, docId, resolveDocumentTitle(schema, doc as never), {
|
|
249
|
+
draft: isDraft,
|
|
250
|
+
language: smartcatCode,
|
|
251
|
+
})
|
|
252
|
+
setFilename(smartcatName.split('/').pop() || smartcatName)
|
|
253
|
+
setContent(JSON.stringify(locjson, null, 2))
|
|
254
|
+
} catch (err) {
|
|
255
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
[client, schema, docId, type, sourceIsPublished, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, languages],
|
|
259
|
+
)
|
|
215
260
|
|
|
216
261
|
const download = useCallback(() => {
|
|
217
262
|
if (content == null || !filename) return
|
|
@@ -225,20 +270,25 @@ function ViewLocjsonButton(props: {
|
|
|
225
270
|
|
|
226
271
|
return (
|
|
227
272
|
<>
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
icon={JsonIcon}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
273
|
+
<MenuButton
|
|
274
|
+
id={`locjson-menu-${docId}`}
|
|
275
|
+
button={<Button mode="bleed" icon={JsonIcon} iconRight={ChevronDownIcon} title="View LocJSON" fontSize={0} padding={2} />}
|
|
276
|
+
menu={
|
|
277
|
+
<Menu>
|
|
278
|
+
<MenuItem text="Source only" onClick={() => build()} />
|
|
279
|
+
{targetLanguages.map((lang) => (
|
|
280
|
+
<MenuItem key={lang} text={`Source + ${languageTitle(languages, lang)}`} onClick={() => build(lang)} />
|
|
281
|
+
))}
|
|
282
|
+
</Menu>
|
|
283
|
+
}
|
|
284
|
+
popover={{portal: true}}
|
|
235
285
|
/>
|
|
236
286
|
{open && (
|
|
237
287
|
<Dialog
|
|
238
288
|
id={`locjson-${docId}`}
|
|
239
289
|
header={
|
|
240
290
|
<Flex align="center" gap={3}>
|
|
241
|
-
<Text weight="semibold">LocJSON · {type}</Text>
|
|
291
|
+
<Text weight="semibold">LocJSON · {type} · {title}</Text>
|
|
242
292
|
<Button
|
|
243
293
|
icon={DownloadIcon}
|
|
244
294
|
text="Download"
|
|
@@ -912,6 +962,18 @@ export function ProjectView({
|
|
|
912
962
|
{modeByType.get(item.doc._type) === 'field' ? 'fields' : 'document'}
|
|
913
963
|
</Badge>
|
|
914
964
|
)}
|
|
965
|
+
<Badge
|
|
966
|
+
tone={item.sendExistingTranslations ? 'primary' : 'default'}
|
|
967
|
+
fontSize={0}
|
|
968
|
+
paddingX={2}
|
|
969
|
+
title={
|
|
970
|
+
item.sendExistingTranslations
|
|
971
|
+
? 'Existing translations are uploaded to Smartcat (one bilingual file per language) and marked confirmed'
|
|
972
|
+
: 'Only source text is sent; Smartcat translates from scratch'
|
|
973
|
+
}
|
|
974
|
+
>
|
|
975
|
+
{item.sendExistingTranslations ? 'source + translations' : 'source only'}
|
|
976
|
+
</Badge>
|
|
915
977
|
{item.doc && <OpenInStudioButton id={item.doc._id} type={item.doc._type} />}
|
|
916
978
|
{item.doc && (
|
|
917
979
|
<ViewLocjsonButton
|
|
@@ -923,6 +985,8 @@ export function ProjectView({
|
|
|
923
985
|
sourceLanguage={sourceLanguage}
|
|
924
986
|
documentI18nLanguageField={documentI18nLanguageField}
|
|
925
987
|
fieldI18nLanguageField={fieldI18nLanguageField}
|
|
988
|
+
targetLanguages={targets}
|
|
989
|
+
languages={languages}
|
|
926
990
|
/>
|
|
927
991
|
)}
|
|
928
992
|
</Flex>
|
package/src/tool/data.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface ProjectItemRef {
|
|
|
24
24
|
* and which version the export serializes. Null on legacy items (pre-flag).
|
|
25
25
|
*/
|
|
26
26
|
sourceIsPublished: boolean | null
|
|
27
|
+
/**
|
|
28
|
+
* Whether the export sends this item's existing translations (one bilingual
|
|
29
|
+
* file per target language) or only its source text. Null/false on legacy
|
|
30
|
+
* items (pre-flag) ⇒ source-only, today's behaviour.
|
|
31
|
+
*/
|
|
32
|
+
sendExistingTranslations: boolean | null
|
|
27
33
|
doc: {_id: string; _type: string} | null
|
|
28
34
|
/** Locale variants linked via translation.metadata: language -> variant doc id. */
|
|
29
35
|
translations: {language: string; id: string}[] | null
|
|
@@ -73,6 +79,7 @@ export const PROJECT_DETAIL_QUERY = `*[_id == $id][0]{
|
|
|
73
79
|
_key,
|
|
74
80
|
"docId": ${ITEM_ID},
|
|
75
81
|
sourceIsPublished,
|
|
82
|
+
sendExistingTranslations,
|
|
76
83
|
"doc": ${itemDocDeref('{_id, _type}')},
|
|
77
84
|
"translations": *[_type == "translation.metadata" && references(${ITEM_ID_FROM_SUBQUERY})][0]
|
|
78
85
|
.translations[]{language, "id": value._ref}
|
package/src/tool/processing.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {serializeToLocjson} from '../lib/locjson/serialize'
|
|
|
14
14
|
import {buildLocjsonFilename} from '../lib/locjson/filename'
|
|
15
15
|
import {deserializeFromLocjson} from '../lib/locjson/deserialize'
|
|
16
16
|
import {requireSourceLanguage} from '../lib/constants'
|
|
17
|
-
import {buildLanguageMappings, findDuplicateSmartcatLanguages} from '../lib/languageMap'
|
|
17
|
+
import {buildLanguageMappings, findDuplicateSmartcatLanguages, toSmartcatLanguage} from '../lib/languageMap'
|
|
18
18
|
import type {LocalizationMode, LocJSON} from '../lib/locjson/types'
|
|
19
19
|
import {summarizeCompletion, type DocProgress} from '../progress/core'
|
|
20
20
|
import type {SmartcatLanguage} from '../types'
|
|
@@ -142,10 +142,22 @@ const ITEMS_QUERY = `*[_id == $id][0]{
|
|
|
142
142
|
items[]{
|
|
143
143
|
"docId": ${ITEM_ID},
|
|
144
144
|
"docType": docType,
|
|
145
|
+
sendExistingTranslations,
|
|
145
146
|
"doc": ${itemDocDerefRespectingDraft()}
|
|
146
147
|
}
|
|
147
148
|
}`
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Target-locale sibling documents for a set of source ids, via
|
|
152
|
+
* document-internationalization's `translation.metadata`. Returns, per metadata
|
|
153
|
+
* group, every locale variant's `{language, doc}` — the send-existing flow reads
|
|
154
|
+
* each target language's sibling as the translation to upload. Document-level
|
|
155
|
+
* only; field-level types keep all languages inline on the same document.
|
|
156
|
+
*/
|
|
157
|
+
const SIBLINGS_QUERY = `*[_type == "translation.metadata" && references($ids)]{
|
|
158
|
+
"variants": translations[]{ "language": _key, "doc": value-> }
|
|
159
|
+
}`
|
|
160
|
+
|
|
149
161
|
export async function prepareExport({
|
|
150
162
|
client,
|
|
151
163
|
schema,
|
|
@@ -165,6 +177,7 @@ export async function prepareExport({
|
|
|
165
177
|
items?: {
|
|
166
178
|
docId?: string
|
|
167
179
|
docType?: string
|
|
180
|
+
sendExistingTranslations?: boolean
|
|
168
181
|
doc: (Record<string, unknown> & {_id: string; _type: string}) | null
|
|
169
182
|
}[]
|
|
170
183
|
} | null>(ITEMS_QUERY, {id: projectId})
|
|
@@ -195,6 +208,37 @@ export async function prepareExport({
|
|
|
195
208
|
.map((item) => item.doc)
|
|
196
209
|
.filter((d): d is Record<string, unknown> & {_id: string; _type: string} => Boolean(d))
|
|
197
210
|
|
|
211
|
+
// For "send existing translations" document-level items, fetch each source's
|
|
212
|
+
// target-locale sibling documents up front (one query), keyed by
|
|
213
|
+
// bare-source-id → (Sanity locale → sibling doc). Field-level types need none —
|
|
214
|
+
// their translations live inline on the same document.
|
|
215
|
+
type TargetDoc = Record<string, unknown> & {_id: string; _type: string}
|
|
216
|
+
const siblingsByDocId = new Map<string, Map<string, TargetDoc>>()
|
|
217
|
+
const flaggedDocLevelIds = items
|
|
218
|
+
.filter(
|
|
219
|
+
(item) =>
|
|
220
|
+
item.doc && item.sendExistingTranslations === true && localizationMode(schema.get(item.doc._type)) === 'document',
|
|
221
|
+
)
|
|
222
|
+
.map((item) => publishedId((item.doc as {_id: string})._id))
|
|
223
|
+
if (flaggedDocLevelIds.length > 0) {
|
|
224
|
+
const groups = await client.fetch<{variants: {language: string; doc: TargetDoc | null}[]}[]>(
|
|
225
|
+
SIBLINGS_QUERY,
|
|
226
|
+
{ids: flaggedDocLevelIds},
|
|
227
|
+
)
|
|
228
|
+
for (const group of groups ?? []) {
|
|
229
|
+
const byLang = new Map<string, TargetDoc>()
|
|
230
|
+
const groupSourceIds: string[] = []
|
|
231
|
+
for (const variant of group.variants ?? []) {
|
|
232
|
+
if (!variant.doc) continue
|
|
233
|
+
byLang.set(variant.language, variant.doc)
|
|
234
|
+
groupSourceIds.push(publishedId(variant.doc._id))
|
|
235
|
+
}
|
|
236
|
+
// Every member of a translation group can reach the same sibling set, so key
|
|
237
|
+
// the map by each variant's id — the source doc looks up its own group.
|
|
238
|
+
for (const sid of groupSourceIds) siblingsByDocId.set(sid, byLang)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
198
242
|
// The translatable field list is per-type, so compute it once per type even
|
|
199
243
|
// when a project holds many documents of the same type.
|
|
200
244
|
const fieldsByType = new Map<string, ReturnType<typeof getTranslatableFields>>()
|
|
@@ -228,17 +272,79 @@ export async function prepareExport({
|
|
|
228
272
|
})
|
|
229
273
|
}
|
|
230
274
|
|
|
231
|
-
const outbox: {
|
|
232
|
-
|
|
275
|
+
const outbox: {
|
|
276
|
+
_key: string
|
|
277
|
+
sourceDocId: string
|
|
278
|
+
title?: string
|
|
279
|
+
filename: string
|
|
280
|
+
locjson: string
|
|
281
|
+
/** Present for per-language bilingual files (send-existing flow). */
|
|
282
|
+
targetLanguage?: string
|
|
283
|
+
}[] = []
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Queue one bilingual LocJSON per target language for a "send existing
|
|
287
|
+
* translations" item. Each file carries the source plus that language's
|
|
288
|
+
* existing translation (field-level: the inline member; document-level: the
|
|
289
|
+
* locale-sibling document), is named `…_{smartcatCode}.locjson` so Smartcat
|
|
290
|
+
* groups the languages as one document, and records its single target language.
|
|
291
|
+
*/
|
|
292
|
+
const queueBilingual = (
|
|
293
|
+
doc: Record<string, unknown> & {_id: string; _type: string},
|
|
294
|
+
name: string | undefined,
|
|
295
|
+
fields: TranslatableField[],
|
|
296
|
+
isDraft: boolean,
|
|
297
|
+
): void => {
|
|
298
|
+
const mode = localizationMode(schema.get(doc._type))
|
|
299
|
+
const byLang = mode === 'document' ? siblingsByDocId.get(doc._id) : undefined
|
|
300
|
+
let queued = 0
|
|
301
|
+
for (const targetSanityId of targetLanguages) {
|
|
302
|
+
const smartcatCode = toSmartcatLanguage(smartcatLanguages, targetSanityId)
|
|
303
|
+
const target =
|
|
304
|
+
mode === 'field' ? {language: targetSanityId} : {language: targetSanityId, doc: byLang?.get(targetSanityId)}
|
|
305
|
+
const locjson = serializeToLocjson(doc as never, fields, {sourceLanguage: source, fieldLanguageKey, target})
|
|
306
|
+
if (locjson.units.length === 0) {
|
|
307
|
+
log({level: 'skip', indent: true, message: `${targetSanityId}: no translatable content — not sent`})
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
const prefilled = locjson.units.filter((u) => u.target.length > 0).length
|
|
311
|
+
outbox.push({
|
|
312
|
+
_key: uuid(),
|
|
313
|
+
sourceDocId: doc._id,
|
|
314
|
+
title: name,
|
|
315
|
+
filename: buildLocjsonFilename(doc._type, doc._id, name, {draft: isDraft, language: smartcatCode}),
|
|
316
|
+
locjson: JSON.stringify(locjson),
|
|
317
|
+
targetLanguage: smartcatCode,
|
|
318
|
+
})
|
|
319
|
+
queued++
|
|
320
|
+
log({
|
|
321
|
+
level: 'success',
|
|
322
|
+
indent: true,
|
|
323
|
+
message: `${targetSanityId} → ${smartcatCode}: queued ${locjson.units.length} unit(s), ${prefilled} with existing translation`,
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
if (queued === 0) log({level: 'skip', indent: true, message: 'no translatable content — not sent'})
|
|
327
|
+
}
|
|
328
|
+
for (const item of items) {
|
|
329
|
+
const rawDoc = item.doc
|
|
330
|
+
if (!rawDoc) continue
|
|
233
331
|
// Serialize under the canonical (bare) id so identity, metadata and progress
|
|
234
332
|
// stay stable, even when the *content* came from the draft. Mark draft-sourced
|
|
235
333
|
// files so they're recognizable in Smartcat.
|
|
236
334
|
const isDraft = rawDoc._id.startsWith('drafts.')
|
|
237
335
|
const doc = isDraft ? {...rawDoc, _id: publishedId(rawDoc._id)} : rawDoc
|
|
238
336
|
const name = resolveDocumentTitle(schema, doc)
|
|
239
|
-
|
|
337
|
+
const sendExisting = item.sendExistingTranslations === true
|
|
338
|
+
log({
|
|
339
|
+
level: 'info',
|
|
340
|
+
message: `${name || doc._id} · ${doc._type}${isDraft ? ' (draft)' : ''}${sendExisting ? ' · sending existing translations' : ''}`,
|
|
341
|
+
})
|
|
240
342
|
try {
|
|
241
343
|
const fields = fieldsFor(doc._type)
|
|
344
|
+
if (sendExisting) {
|
|
345
|
+
queueBilingual(doc, name, fields, isDraft)
|
|
346
|
+
continue
|
|
347
|
+
}
|
|
242
348
|
// Fields skipped because they hold a block type that can't be serialized
|
|
243
349
|
// (e.g. a custom `table`): left untranslated on purpose, reported as errors
|
|
244
350
|
// below so the editor knows to act rather than lose content silently.
|