@smartcat/sanity-plugin 1.2.0 → 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/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineType, defineField, defineArrayMember, useClient, prepareForPreview, useSchema, usePerspective, useRelativeTime, useValuePreview, useWorkspace, useFormValue, definePlugin } from "sanity";
|
|
2
|
-
import { TranslateIcon, CogIcon, AddIcon, LaunchIcon, CheckmarkIcon, CopyIcon, SyncIcon, ArrowLeftIcon, TrashIcon, PublishIcon, DownloadIcon, CloseIcon, RemoveCircleIcon, JsonIcon } from "@sanity/icons";
|
|
2
|
+
import { TranslateIcon, CogIcon, AddIcon, LaunchIcon, CheckmarkIcon, CopyIcon, SyncIcon, ArrowLeftIcon, TrashIcon, PublishIcon, DownloadIcon, CloseIcon, RemoveCircleIcon, ChevronDownIcon, JsonIcon } from "@sanity/icons";
|
|
3
3
|
import { TRANSLATION_PROJECT_TYPE, SETTINGS_TYPE, API_VERSION, SETTINGS_DOC_ID, requireSourceLanguage } from "./_chunks-es/constants.js";
|
|
4
4
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
5
5
|
import { useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect, Fragment as Fragment$1 } from "react";
|
|
6
|
-
import { Select, useToast, Flex, Spinner, Stack, Label, TextInput, Card, Text, Badge, Switch, Button, Heading, Dialog, Box, Inline, useTheme_v2, useTheme, Checkbox, Container } from "@sanity/ui";
|
|
6
|
+
import { Select, useToast, Flex, Spinner, Stack, Label, TextInput, Card, Text, Badge, Switch, Button, Heading, Dialog, Box, Inline, useTheme_v2, useTheme, Checkbox, MenuButton, Menu, MenuItem, Container } from "@sanity/ui";
|
|
7
7
|
import { uuid } from "@sanity/uuid";
|
|
8
8
|
import { TEMPLATE_PREFIX, STANDARD_WORKFLOW_OPTIONS, isWorkflowValid } from "./_chunks-es/workflow.js";
|
|
9
|
-
import { ITEM_ID, ITEM_ID_FROM_SUBQUERY, itemDocDeref, summarizeCompletion, buildLanguageMappings, findDuplicateSmartcatLanguages, itemDocDerefRespectingDraft, targetPercent } from "./_chunks-es/index.js";
|
|
10
|
-
import { getTranslatableFields, deserializeFromLocjson, serializeToLocjson, getAtPath, buildLocjsonFilename,
|
|
9
|
+
import { ITEM_ID, ITEM_ID_FROM_SUBQUERY, itemDocDeref, summarizeCompletion, buildLanguageMappings, findDuplicateSmartcatLanguages, itemDocDerefRespectingDraft, toSmartcatLanguage, targetPercent } from "./_chunks-es/index.js";
|
|
10
|
+
import { getTranslatableFields, deserializeFromLocjson, localizationMode, serializeToLocjson, getAtPath, buildLocjsonFilename, enumerateLeaves, setAtPath, isInternationalizedArrayType, innerValueType } from "./_chunks-es/index2.js";
|
|
11
11
|
import { useIntentLink, useRouter, useRouterState, route } from "sanity/router";
|
|
12
12
|
import styled, { keyframes, css } from "styled-components";
|
|
13
13
|
const STATUS_OPTIONS = [
|
|
@@ -93,7 +93,19 @@ function createTranslationProjectType() {
|
|
|
93
93
|
// Set when the item was added from the published perspective, so the
|
|
94
94
|
// export translates the published document. Absent/false (incl. all
|
|
95
95
|
// legacy items) prefers the draft — see itemDocDerefRespectingDraft.
|
|
96
|
-
defineField({ name: "sourceIsPublished", title: "Source is published", type: "boolean" })
|
|
96
|
+
defineField({ name: "sourceIsPublished", title: "Source is published", type: "boolean" }),
|
|
97
|
+
// When true, the export sends the item's *existing* translations to
|
|
98
|
+
// Smartcat: one bilingual LocJSON per target language (populated from
|
|
99
|
+
// field/linked-doc translations), grouped via `{name}_{lang}.locjson`.
|
|
100
|
+
// Absent/false (incl. all legacy items) ⇒ today's behaviour: a single
|
|
101
|
+
// source-only file targeting all languages. Note: imported bilingual
|
|
102
|
+
// targets land confirmed at every workflow stage (Smartcat ignores
|
|
103
|
+
// per-segment status on LocJSON import), i.e. they skip human review.
|
|
104
|
+
defineField({
|
|
105
|
+
name: "sendExistingTranslations",
|
|
106
|
+
title: "Send existing translations",
|
|
107
|
+
type: "boolean"
|
|
108
|
+
})
|
|
97
109
|
],
|
|
98
110
|
preview: { select: { title: "docId", subtitle: "docType" } }
|
|
99
111
|
})
|
|
@@ -203,8 +215,15 @@ function createTranslationProjectType() {
|
|
|
203
215
|
type: "object",
|
|
204
216
|
name: "smartcatOutboxItem",
|
|
205
217
|
fields: [
|
|
218
|
+
defineField({ name: "sourceDocId", type: "string" }),
|
|
219
|
+
defineField({ name: "title", type: "string" }),
|
|
206
220
|
defineField({ name: "filename", type: "string" }),
|
|
207
|
-
defineField({ name: "locjson", type: "text" })
|
|
221
|
+
defineField({ name: "locjson", type: "text" }),
|
|
222
|
+
// Present only for per-language bilingual files (send-existing flow):
|
|
223
|
+
// the single target language this file carries, so the export Function
|
|
224
|
+
// uploads it with `documentModel targetLanguages:[lang]`. Absent ⇒
|
|
225
|
+
// upload targeting all project languages (the source-only default).
|
|
226
|
+
defineField({ name: "targetLanguage", type: "string" })
|
|
208
227
|
]
|
|
209
228
|
})
|
|
210
229
|
]
|
|
@@ -439,7 +458,7 @@ const PROJECTS_QUERY$1 = `*[_type == $type] | order(name asc){
|
|
|
439
458
|
function createAddToProjectAction(config) {
|
|
440
459
|
const sourceLanguage = config.sourceLanguage;
|
|
441
460
|
return (props) => {
|
|
442
|
-
const { id, draft, published, onComplete } = props, client = useClient({ apiVersion: API_VERSION }), schema = useSchema(), toast = useToast(), { selectedPerspectiveName } = usePerspective(), sourceIsPublished = selectedPerspectiveName === "published" || !draft, [open, setOpen] = useState(!1), [projects, setProjects] = useState(null), [selection, setSelection] = useState(CREATE_NEW), [newName, setNewName] = useState(""), [busy, setBusy] = useState(!1), [includeLinked, setIncludeLinked] = useState(!0), { templates, loaded, requestRefresh } = useTemplates(), [workflow, setWorkflow] = useWorkflowSelection(open, templates, loaded), publishedId2 = id.replace(/^drafts\./, ""), doc = draft ?? published, docTitle = doc && resolveDocumentTitle(schema, doc) || UNTITLED;
|
|
461
|
+
const { id, draft, published, onComplete } = props, client = useClient({ apiVersion: API_VERSION }), schema = useSchema(), toast = useToast(), { selectedPerspectiveName } = usePerspective(), sourceIsPublished = selectedPerspectiveName === "published" || !draft, [open, setOpen] = useState(!1), [projects, setProjects] = useState(null), [selection, setSelection] = useState(CREATE_NEW), [newName, setNewName] = useState(""), [busy, setBusy] = useState(!1), [includeLinked, setIncludeLinked] = useState(!0), [sendExisting, setSendExisting] = useState(!1), { templates, loaded, requestRefresh } = useTemplates(), [workflow, setWorkflow] = useWorkflowSelection(open, templates, loaded), publishedId2 = id.replace(/^drafts\./, ""), doc = draft ?? published, docTitle = doc && resolveDocumentTitle(schema, doc) || UNTITLED;
|
|
443
462
|
useEffect(() => {
|
|
444
463
|
if (!open) return;
|
|
445
464
|
requestRefresh();
|
|
@@ -462,7 +481,8 @@ function createAddToProjectAction(config) {
|
|
|
462
481
|
_key: uuid(),
|
|
463
482
|
docId,
|
|
464
483
|
docType,
|
|
465
|
-
sourceIsPublished
|
|
484
|
+
sourceIsPublished,
|
|
485
|
+
sendExistingTranslations: sendExisting
|
|
466
486
|
}), collected = /* @__PURE__ */ new Map([[publishedId2, doc?._type]]);
|
|
467
487
|
if (includeLinked) {
|
|
468
488
|
const linked = await gatherLinkedDocuments({ client, rootId: publishedId2, isRoot: (type) => config.rootTypes.includes(type), isTranslatable: (type) => getTranslatableFields(schema.get(type), { exclude: [config.documentI18nLanguageField] }).length > 0 });
|
|
@@ -504,7 +524,7 @@ function createAddToProjectAction(config) {
|
|
|
504
524
|
} finally {
|
|
505
525
|
setBusy(!1);
|
|
506
526
|
}
|
|
507
|
-
}, [selection, newName, workflow, publishedId2, doc, includeLinked, sourceIsPublished, schema, client, projects, close]), content = useMemo(() => projects === null ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsx(Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
527
|
+
}, [selection, newName, workflow, publishedId2, doc, includeLinked, sendExisting, sourceIsPublished, schema, client, projects, close]), content = useMemo(() => projects === null ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsx(Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
508
528
|
/* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
509
529
|
/* @__PURE__ */ jsx(Label, { size: 1, children: "Project" }),
|
|
510
530
|
/* @__PURE__ */ jsxs(
|
|
@@ -585,6 +605,20 @@ function createAddToProjectAction(config) {
|
|
|
585
605
|
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Include linked content" }),
|
|
586
606
|
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Also add documents this one references, recursively \u2014 skipping any already in the project." })
|
|
587
607
|
] })
|
|
608
|
+
] }),
|
|
609
|
+
/* @__PURE__ */ jsxs(Flex, { align: "flex-start", gap: 3, children: [
|
|
610
|
+
/* @__PURE__ */ jsx(
|
|
611
|
+
Switch,
|
|
612
|
+
{
|
|
613
|
+
checked: sendExisting,
|
|
614
|
+
onChange: (e) => setSendExisting(e.currentTarget.checked),
|
|
615
|
+
disabled: busy
|
|
616
|
+
}
|
|
617
|
+
),
|
|
618
|
+
/* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
619
|
+
/* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Send existing translations" }),
|
|
620
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Upload current translations alongside the source, so Smartcat starts pre-filled. Imported translations are marked confirmed at every stage \u2014 they skip human review." })
|
|
621
|
+
] })
|
|
588
622
|
] })
|
|
589
623
|
] }) }),
|
|
590
624
|
/* @__PURE__ */ jsxs(Flex, { justify: "flex-end", gap: 2, children: [
|
|
@@ -601,7 +635,7 @@ function createAddToProjectAction(config) {
|
|
|
601
635
|
}
|
|
602
636
|
)
|
|
603
637
|
] })
|
|
604
|
-
] }), [projects, selection, newName, workflow, templates, busy, includeLinked, docTitle, sourceIsPublished, close, handleConfirm]);
|
|
638
|
+
] }), [projects, selection, newName, workflow, templates, busy, includeLinked, sendExisting, docTitle, sourceIsPublished, close, handleConfirm]);
|
|
605
639
|
return {
|
|
606
640
|
label: "Add to translation project",
|
|
607
641
|
icon: TranslateIcon,
|
|
@@ -638,6 +672,7 @@ const PROJECTS_QUERY = `*[_type == "${TRANSLATION_PROJECT_TYPE}"] | order(_creat
|
|
|
638
672
|
_key,
|
|
639
673
|
"docId": ${ITEM_ID},
|
|
640
674
|
sourceIsPublished,
|
|
675
|
+
sendExistingTranslations,
|
|
641
676
|
"doc": ${itemDocDeref("{_id, _type}")},
|
|
642
677
|
"translations": *[_type == "translation.metadata" && references(${ITEM_ID_FROM_SUBQUERY})][0]
|
|
643
678
|
.translations[]{language, "id": value._ref}
|
|
@@ -835,8 +870,11 @@ const ITEMS_QUERY = `*[_id == $id][0]{
|
|
|
835
870
|
items[]{
|
|
836
871
|
"docId": ${ITEM_ID},
|
|
837
872
|
"docType": docType,
|
|
873
|
+
sendExistingTranslations,
|
|
838
874
|
"doc": ${itemDocDerefRespectingDraft()}
|
|
839
875
|
}
|
|
876
|
+
}`, SIBLINGS_QUERY = `*[_type == "translation.metadata" && references($ids)]{
|
|
877
|
+
"variants": translations[]{ "language": _key, "doc": value-> }
|
|
840
878
|
}`;
|
|
841
879
|
async function prepareExport({
|
|
842
880
|
client,
|
|
@@ -861,7 +899,22 @@ async function prepareExport({
|
|
|
861
899
|
}
|
|
862
900
|
const deletedDocIds = new Set(deletions.map((d) => d.sourceDocId)), items = (project?.items ?? []).filter(
|
|
863
901
|
(item) => !(item.docId && deletedDocIds.has(item.docId))
|
|
864
|
-
), unresolved = items.filter((item) => !item.doc), docs = items.map((item) => item.doc).filter((d) => !!d),
|
|
902
|
+
), unresolved = items.filter((item) => !item.doc), docs = items.map((item) => item.doc).filter((d) => !!d), siblingsByDocId = /* @__PURE__ */ new Map(), flaggedDocLevelIds = items.filter(
|
|
903
|
+
(item) => item.doc && item.sendExistingTranslations === !0 && localizationMode(schema.get(item.doc._type)) === "document"
|
|
904
|
+
).map((item) => publishedId(item.doc._id));
|
|
905
|
+
if (flaggedDocLevelIds.length > 0) {
|
|
906
|
+
const groups = await client.fetch(
|
|
907
|
+
SIBLINGS_QUERY,
|
|
908
|
+
{ ids: flaggedDocLevelIds }
|
|
909
|
+
);
|
|
910
|
+
for (const group of groups ?? []) {
|
|
911
|
+
const byLang = /* @__PURE__ */ new Map(), groupSourceIds = [];
|
|
912
|
+
for (const variant of group.variants ?? [])
|
|
913
|
+
variant.doc && (byLang.set(variant.language, variant.doc), groupSourceIds.push(publishedId(variant.doc._id)));
|
|
914
|
+
for (const sid of groupSourceIds) siblingsByDocId.set(sid, byLang);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const fieldsByType = /* @__PURE__ */ new Map(), fieldsFor = (type) => {
|
|
865
918
|
let fields = fieldsByType.get(type);
|
|
866
919
|
return fields || (fields = getTranslatableFields(schema.get(type), { exclude }), fieldsByType.set(type, fields)), fields;
|
|
867
920
|
}, configuredLanguages = languages.map((l) => l.id).join(", ") || "(none)", configuredTypes = translatableTypes === void 0 ? "all types" : translatableTypes.join(", ") || "(none)";
|
|
@@ -871,12 +924,46 @@ async function prepareExport({
|
|
|
871
924
|
level: "error",
|
|
872
925
|
message: `${item.docId ?? "unknown document"} (${item.docType ?? "unknown type"}): no document found \u2014 it may be unpublished or deleted; skipped`
|
|
873
926
|
});
|
|
874
|
-
const outbox = []
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
927
|
+
const outbox = [], queueBilingual = (doc, name, fields, isDraft) => {
|
|
928
|
+
const mode = localizationMode(schema.get(doc._type)), byLang = mode === "document" ? siblingsByDocId.get(doc._id) : void 0;
|
|
929
|
+
let queued = 0;
|
|
930
|
+
for (const targetSanityId of targetLanguages) {
|
|
931
|
+
const smartcatCode = toSmartcatLanguage(smartcatLanguages, targetSanityId), target = mode === "field" ? { language: targetSanityId } : { language: targetSanityId, doc: byLang?.get(targetSanityId) }, locjson = serializeToLocjson(doc, fields, { sourceLanguage: source, fieldLanguageKey, target });
|
|
932
|
+
if (locjson.units.length === 0) {
|
|
933
|
+
log({ level: "skip", indent: !0, message: `${targetSanityId}: no translatable content \u2014 not sent` });
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const prefilled = locjson.units.filter((u) => u.target.length > 0).length;
|
|
937
|
+
outbox.push({
|
|
938
|
+
_key: uuid(),
|
|
939
|
+
sourceDocId: doc._id,
|
|
940
|
+
title: name,
|
|
941
|
+
filename: buildLocjsonFilename(doc._type, doc._id, name, { draft: isDraft, language: smartcatCode }),
|
|
942
|
+
locjson: JSON.stringify(locjson),
|
|
943
|
+
targetLanguage: smartcatCode
|
|
944
|
+
}), queued++, log({
|
|
945
|
+
level: "success",
|
|
946
|
+
indent: !0,
|
|
947
|
+
message: `${targetSanityId} \u2192 ${smartcatCode}: queued ${locjson.units.length} unit(s), ${prefilled} with existing translation`
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
queued === 0 && log({ level: "skip", indent: !0, message: "no translatable content \u2014 not sent" });
|
|
951
|
+
};
|
|
952
|
+
for (const item of items) {
|
|
953
|
+
const rawDoc = item.doc;
|
|
954
|
+
if (!rawDoc) continue;
|
|
955
|
+
const isDraft = rawDoc._id.startsWith("drafts."), doc = isDraft ? { ...rawDoc, _id: publishedId(rawDoc._id) } : rawDoc, name = resolveDocumentTitle(schema, doc), sendExisting = item.sendExistingTranslations === !0;
|
|
956
|
+
log({
|
|
957
|
+
level: "info",
|
|
958
|
+
message: `${name || doc._id} \xB7 ${doc._type}${isDraft ? " (draft)" : ""}${sendExisting ? " \xB7 sending existing translations" : ""}`
|
|
959
|
+
});
|
|
878
960
|
try {
|
|
879
|
-
const fields = fieldsFor(doc._type)
|
|
961
|
+
const fields = fieldsFor(doc._type);
|
|
962
|
+
if (sendExisting) {
|
|
963
|
+
queueBilingual(doc, name, fields, isDraft);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
const skippedBlockTypes = /* @__PURE__ */ new Map(), locjson = serializeToLocjson(doc, fields, {
|
|
880
967
|
sourceLanguage: source,
|
|
881
968
|
fieldLanguageKey,
|
|
882
969
|
onSkippedField: ({ fieldPath, types }) => skippedBlockTypes.set(fieldPath, types)
|
|
@@ -1467,39 +1554,55 @@ const CodeArea = styled.textarea`
|
|
|
1467
1554
|
line-height: 1.5;
|
|
1468
1555
|
white-space: pre;
|
|
1469
1556
|
tab-size: 2;
|
|
1470
|
-
|
|
1557
|
+
`, VIEW_SIBLINGS_QUERY = '*[_type == "translation.metadata" && references($id)][0].translations[]{"language": _key, "doc": value->}';
|
|
1471
1558
|
function ViewLocjsonButton(props) {
|
|
1472
|
-
const { docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField } = props, [open, setOpen] = useState(!1), [content, setContent] = useState(null), [filename, setFilename] = useState(null), [error, setError] = useState(null), build = useCallback(
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1559
|
+
const { docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, targetLanguages, languages } = props, [open, setOpen] = useState(!1), [content, setContent] = useState(null), [filename, setFilename] = useState(null), [title, setTitle] = useState("Source only"), [error, setError] = useState(null), build = useCallback(
|
|
1560
|
+
async (targetLanguage) => {
|
|
1561
|
+
setOpen(!0), setContent(null), setFilename(null), setError(null), setTitle(targetLanguage ? `Source + ${languageTitle(languages, targetLanguage)}` : "Source only");
|
|
1562
|
+
try {
|
|
1563
|
+
const raw = await client.fetch(
|
|
1564
|
+
sourceIsPublished ? "*[_id == $id][0]" : 'coalesce(*[_id == "drafts." + $id][0], *[_id == $id][0])',
|
|
1565
|
+
{ id: docId }
|
|
1566
|
+
);
|
|
1567
|
+
if (!raw) throw new Error(`Document not found: ${docId}`);
|
|
1568
|
+
const isDraft = typeof raw._id == "string" && raw._id.startsWith("drafts."), doc = isDraft ? { ...raw, _id: docId } : raw, fields = getTranslatableFields(schema.get(type), { exclude: [documentI18nLanguageField] });
|
|
1569
|
+
let target;
|
|
1570
|
+
if (targetLanguage)
|
|
1571
|
+
if (localizationMode(schema.get(type)) === "document") {
|
|
1572
|
+
const sibling = (await client.fetch(VIEW_SIBLINGS_QUERY, { id: docId }) ?? []).find((v) => v.language === targetLanguage)?.doc ?? void 0;
|
|
1573
|
+
target = { language: targetLanguage, doc: sibling };
|
|
1574
|
+
} else
|
|
1575
|
+
target = { language: targetLanguage };
|
|
1576
|
+
const locjson = serializeToLocjson(doc, fields, {
|
|
1577
|
+
sourceLanguage,
|
|
1578
|
+
fieldLanguageKey: fieldI18nLanguageField,
|
|
1579
|
+
target
|
|
1580
|
+
}), smartcatCode = targetLanguage ? languages.find((l) => l.id === targetLanguage)?.smartcatLanguage || targetLanguage : void 0, smartcatName = buildLocjsonFilename(type, docId, resolveDocumentTitle(schema, doc), {
|
|
1581
|
+
draft: isDraft,
|
|
1582
|
+
language: smartcatCode
|
|
1583
|
+
});
|
|
1584
|
+
setFilename(smartcatName.split("/").pop() || smartcatName), setContent(JSON.stringify(locjson, null, 2));
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
[client, schema, docId, type, sourceIsPublished, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, languages]
|
|
1590
|
+
), download = useCallback(() => {
|
|
1489
1591
|
if (content == null || !filename) return;
|
|
1490
1592
|
const url = URL.createObjectURL(new Blob([content], { type: "application/json" })), anchor = document.createElement("a");
|
|
1491
1593
|
anchor.href = url, anchor.download = filename, anchor.click(), URL.revokeObjectURL(url);
|
|
1492
1594
|
}, [content, filename]);
|
|
1493
1595
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1494
1596
|
/* @__PURE__ */ jsx(
|
|
1495
|
-
|
|
1597
|
+
MenuButton,
|
|
1496
1598
|
{
|
|
1497
|
-
|
|
1498
|
-
icon: JsonIcon,
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1599
|
+
id: `locjson-menu-${docId}`,
|
|
1600
|
+
button: /* @__PURE__ */ jsx(Button, { mode: "bleed", icon: JsonIcon, iconRight: ChevronDownIcon, title: "View LocJSON", fontSize: 0, padding: 2 }),
|
|
1601
|
+
menu: /* @__PURE__ */ jsxs(Menu, { children: [
|
|
1602
|
+
/* @__PURE__ */ jsx(MenuItem, { text: "Source only", onClick: () => build() }),
|
|
1603
|
+
targetLanguages.map((lang) => /* @__PURE__ */ jsx(MenuItem, { text: `Source + ${languageTitle(languages, lang)}`, onClick: () => build(lang) }, lang))
|
|
1604
|
+
] }),
|
|
1605
|
+
popover: { portal: !0 }
|
|
1503
1606
|
}
|
|
1504
1607
|
),
|
|
1505
1608
|
open && /* @__PURE__ */ jsx(
|
|
@@ -1509,7 +1612,9 @@ function ViewLocjsonButton(props) {
|
|
|
1509
1612
|
header: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
1510
1613
|
/* @__PURE__ */ jsxs(Text, { weight: "semibold", children: [
|
|
1511
1614
|
"LocJSON \xB7 ",
|
|
1512
|
-
type
|
|
1615
|
+
type,
|
|
1616
|
+
" \xB7 ",
|
|
1617
|
+
title
|
|
1513
1618
|
] }),
|
|
1514
1619
|
/* @__PURE__ */ jsx(
|
|
1515
1620
|
Button,
|
|
@@ -1923,6 +2028,16 @@ Smartcat language code: '${l.smartcatLanguage || l.id}'`,
|
|
|
1923
2028
|
children: modeByType.get(item.doc._type) === "field" ? "fields" : "document"
|
|
1924
2029
|
}
|
|
1925
2030
|
),
|
|
2031
|
+
/* @__PURE__ */ jsx(
|
|
2032
|
+
Badge,
|
|
2033
|
+
{
|
|
2034
|
+
tone: item.sendExistingTranslations ? "primary" : "default",
|
|
2035
|
+
fontSize: 0,
|
|
2036
|
+
paddingX: 2,
|
|
2037
|
+
title: item.sendExistingTranslations ? "Existing translations are uploaded to Smartcat (one bilingual file per language) and marked confirmed" : "Only source text is sent; Smartcat translates from scratch",
|
|
2038
|
+
children: item.sendExistingTranslations ? "source + translations" : "source only"
|
|
2039
|
+
}
|
|
2040
|
+
),
|
|
1926
2041
|
item.doc && /* @__PURE__ */ jsx(OpenInStudioButton, { id: item.doc._id, type: item.doc._type }),
|
|
1927
2042
|
item.doc && /* @__PURE__ */ jsx(
|
|
1928
2043
|
ViewLocjsonButton,
|
|
@@ -1934,7 +2049,9 @@ Smartcat language code: '${l.smartcatLanguage || l.id}'`,
|
|
|
1934
2049
|
schema,
|
|
1935
2050
|
sourceLanguage,
|
|
1936
2051
|
documentI18nLanguageField,
|
|
1937
|
-
fieldI18nLanguageField
|
|
2052
|
+
fieldI18nLanguageField,
|
|
2053
|
+
targetLanguages: targets,
|
|
2054
|
+
languages
|
|
1938
2055
|
}
|
|
1939
2056
|
)
|
|
1940
2057
|
] }),
|