@smartcat/sanity-plugin 1.2.1 → 1.4.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 +477 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +482 -129
- 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/AddItemsSearch.tsx +462 -0
- package/src/tool/DocTitle.tsx +23 -0
- package/src/tool/ProjectView.tsx +29 -187
- package/src/tool/data.ts +7 -0
- package/src/tool/itemActions.tsx +216 -0
- package/src/tool/processing.test.ts +11 -1
- package/src/tool/processing.ts +116 -4
package/dist/index.cjs
CHANGED
|
@@ -88,7 +88,19 @@ function createTranslationProjectType() {
|
|
|
88
88
|
// Set when the item was added from the published perspective, so the
|
|
89
89
|
// export translates the published document. Absent/false (incl. all
|
|
90
90
|
// legacy items) prefers the draft — see itemDocDerefRespectingDraft.
|
|
91
|
-
sanity.defineField({ name: "sourceIsPublished", title: "Source is published", type: "boolean" })
|
|
91
|
+
sanity.defineField({ name: "sourceIsPublished", title: "Source is published", type: "boolean" }),
|
|
92
|
+
// When true, the export sends the item's *existing* translations to
|
|
93
|
+
// Smartcat: one bilingual LocJSON per target language (populated from
|
|
94
|
+
// field/linked-doc translations), grouped via `{name}_{lang}.locjson`.
|
|
95
|
+
// Absent/false (incl. all legacy items) ⇒ today's behaviour: a single
|
|
96
|
+
// source-only file targeting all languages. Note: imported bilingual
|
|
97
|
+
// targets land confirmed at every workflow stage (Smartcat ignores
|
|
98
|
+
// per-segment status on LocJSON import), i.e. they skip human review.
|
|
99
|
+
sanity.defineField({
|
|
100
|
+
name: "sendExistingTranslations",
|
|
101
|
+
title: "Send existing translations",
|
|
102
|
+
type: "boolean"
|
|
103
|
+
})
|
|
92
104
|
],
|
|
93
105
|
preview: { select: { title: "docId", subtitle: "docType" } }
|
|
94
106
|
})
|
|
@@ -198,8 +210,15 @@ function createTranslationProjectType() {
|
|
|
198
210
|
type: "object",
|
|
199
211
|
name: "smartcatOutboxItem",
|
|
200
212
|
fields: [
|
|
213
|
+
sanity.defineField({ name: "sourceDocId", type: "string" }),
|
|
214
|
+
sanity.defineField({ name: "title", type: "string" }),
|
|
201
215
|
sanity.defineField({ name: "filename", type: "string" }),
|
|
202
|
-
sanity.defineField({ name: "locjson", type: "text" })
|
|
216
|
+
sanity.defineField({ name: "locjson", type: "text" }),
|
|
217
|
+
// Present only for per-language bilingual files (send-existing flow):
|
|
218
|
+
// the single target language this file carries, so the export Function
|
|
219
|
+
// uploads it with `documentModel targetLanguages:[lang]`. Absent ⇒
|
|
220
|
+
// upload targeting all project languages (the source-only default).
|
|
221
|
+
sanity.defineField({ name: "targetLanguage", type: "string" })
|
|
203
222
|
]
|
|
204
223
|
})
|
|
205
224
|
]
|
|
@@ -434,7 +453,7 @@ const PROJECTS_QUERY$1 = `*[_type == $type] | order(name asc){
|
|
|
434
453
|
function createAddToProjectAction(config) {
|
|
435
454
|
const sourceLanguage = config.sourceLanguage;
|
|
436
455
|
return (props) => {
|
|
437
|
-
const { id, draft, published, onComplete } = props, client = sanity.useClient({ apiVersion: constants.API_VERSION }), schema = sanity.useSchema(), toast = ui.useToast(), { selectedPerspectiveName } = sanity.usePerspective(), sourceIsPublished = selectedPerspectiveName === "published" || !draft, [open, setOpen] = react.useState(!1), [projects, setProjects] = react.useState(null), [selection, setSelection] = react.useState(CREATE_NEW), [newName, setNewName] = react.useState(""), [busy, setBusy] = react.useState(!1), [includeLinked, setIncludeLinked] = react.useState(!0), { templates, loaded, requestRefresh } = useTemplates(), [workflow2, setWorkflow] = useWorkflowSelection(open, templates, loaded), publishedId2 = id.replace(/^drafts\./, ""), doc = draft ?? published, docTitle = doc && resolveDocumentTitle(schema, doc) || UNTITLED;
|
|
456
|
+
const { id, draft, published, onComplete } = props, client = sanity.useClient({ apiVersion: constants.API_VERSION }), schema = sanity.useSchema(), toast = ui.useToast(), { selectedPerspectiveName } = sanity.usePerspective(), sourceIsPublished = selectedPerspectiveName === "published" || !draft, [open, setOpen] = react.useState(!1), [projects, setProjects] = react.useState(null), [selection, setSelection] = react.useState(CREATE_NEW), [newName, setNewName] = react.useState(""), [busy, setBusy] = react.useState(!1), [includeLinked, setIncludeLinked] = react.useState(!0), [sendExisting, setSendExisting] = react.useState(!1), { templates, loaded, requestRefresh } = useTemplates(), [workflow2, setWorkflow] = useWorkflowSelection(open, templates, loaded), publishedId2 = id.replace(/^drafts\./, ""), doc = draft ?? published, docTitle = doc && resolveDocumentTitle(schema, doc) || UNTITLED;
|
|
438
457
|
react.useEffect(() => {
|
|
439
458
|
if (!open) return;
|
|
440
459
|
requestRefresh();
|
|
@@ -457,7 +476,8 @@ function createAddToProjectAction(config) {
|
|
|
457
476
|
_key: uuid.uuid(),
|
|
458
477
|
docId,
|
|
459
478
|
docType,
|
|
460
|
-
sourceIsPublished
|
|
479
|
+
sourceIsPublished,
|
|
480
|
+
sendExistingTranslations: sendExisting
|
|
461
481
|
}), collected = /* @__PURE__ */ new Map([[publishedId2, doc?._type]]);
|
|
462
482
|
if (includeLinked) {
|
|
463
483
|
const linked = await gatherLinkedDocuments({ client, rootId: publishedId2, isRoot: (type) => config.rootTypes.includes(type), isTranslatable: (type) => locjson.getTranslatableFields(schema.get(type), { exclude: [config.documentI18nLanguageField] }).length > 0 });
|
|
@@ -499,7 +519,7 @@ function createAddToProjectAction(config) {
|
|
|
499
519
|
} finally {
|
|
500
520
|
setBusy(!1);
|
|
501
521
|
}
|
|
502
|
-
}, [selection, newName, workflow2, publishedId2, doc, includeLinked, sourceIsPublished, schema, client, projects, close]), content = react.useMemo(() => projects === null ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, children: [
|
|
522
|
+
}, [selection, newName, workflow2, publishedId2, doc, includeLinked, sendExisting, sourceIsPublished, schema, client, projects, close]), content = react.useMemo(() => projects === null ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, children: [
|
|
503
523
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
504
524
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: 1, children: "Project" }),
|
|
505
525
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -580,6 +600,20 @@ function createAddToProjectAction(config) {
|
|
|
580
600
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: "Include linked content" }),
|
|
581
601
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "Also add documents this one references, recursively \u2014 skipping any already in the project." })
|
|
582
602
|
] })
|
|
603
|
+
] }),
|
|
604
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "flex-start", gap: 3, children: [
|
|
605
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
606
|
+
ui.Switch,
|
|
607
|
+
{
|
|
608
|
+
checked: sendExisting,
|
|
609
|
+
onChange: (e) => setSendExisting(e.currentTarget.checked),
|
|
610
|
+
disabled: busy
|
|
611
|
+
}
|
|
612
|
+
),
|
|
613
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
614
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "medium", children: "Send existing translations" }),
|
|
615
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.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." })
|
|
616
|
+
] })
|
|
583
617
|
] })
|
|
584
618
|
] }) }),
|
|
585
619
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { justify: "flex-end", gap: 2, children: [
|
|
@@ -596,7 +630,7 @@ function createAddToProjectAction(config) {
|
|
|
596
630
|
}
|
|
597
631
|
)
|
|
598
632
|
] })
|
|
599
|
-
] }), [projects, selection, newName, workflow2, templates, busy, includeLinked, docTitle, sourceIsPublished, close, handleConfirm]);
|
|
633
|
+
] }), [projects, selection, newName, workflow2, templates, busy, includeLinked, sendExisting, docTitle, sourceIsPublished, close, handleConfirm]);
|
|
600
634
|
return {
|
|
601
635
|
label: "Add to translation project",
|
|
602
636
|
icon: icons.TranslateIcon,
|
|
@@ -633,6 +667,7 @@ const PROJECTS_QUERY = `*[_type == "${constants.TRANSLATION_PROJECT_TYPE}"] | or
|
|
|
633
667
|
_key,
|
|
634
668
|
"docId": ${progress.ITEM_ID},
|
|
635
669
|
sourceIsPublished,
|
|
670
|
+
sendExistingTranslations,
|
|
636
671
|
"doc": ${progress.itemDocDeref("{_id, _type}")},
|
|
637
672
|
"translations": *[_type == "translation.metadata" && references(${progress.ITEM_ID_FROM_SUBQUERY})][0]
|
|
638
673
|
.translations[]{language, "id": value._ref}
|
|
@@ -830,9 +865,15 @@ const ITEMS_QUERY = `*[_id == $id][0]{
|
|
|
830
865
|
items[]{
|
|
831
866
|
"docId": ${progress.ITEM_ID},
|
|
832
867
|
"docType": docType,
|
|
868
|
+
sendExistingTranslations,
|
|
833
869
|
"doc": ${progress.itemDocDerefRespectingDraft()}
|
|
834
870
|
}
|
|
835
871
|
}`;
|
|
872
|
+
function siblingsQuery$1(fieldLanguageKey) {
|
|
873
|
+
return `*[_type == "translation.metadata" && references($ids)]{
|
|
874
|
+
"variants": translations[]{ "language": coalesce(${fieldLanguageKey}, _key), "doc": value-> }
|
|
875
|
+
}`;
|
|
876
|
+
}
|
|
836
877
|
async function prepareExport({
|
|
837
878
|
client,
|
|
838
879
|
schema,
|
|
@@ -856,7 +897,22 @@ async function prepareExport({
|
|
|
856
897
|
}
|
|
857
898
|
const deletedDocIds = new Set(deletions.map((d) => d.sourceDocId)), items = (project?.items ?? []).filter(
|
|
858
899
|
(item) => !(item.docId && deletedDocIds.has(item.docId))
|
|
859
|
-
), unresolved = items.filter((item) => !item.doc), docs = items.map((item) => item.doc).filter((d) => !!d),
|
|
900
|
+
), unresolved = items.filter((item) => !item.doc), docs = items.map((item) => item.doc).filter((d) => !!d), siblingsByDocId = /* @__PURE__ */ new Map(), flaggedDocLevelIds = items.filter(
|
|
901
|
+
(item) => item.doc && item.sendExistingTranslations === !0 && locjson.localizationMode(schema.get(item.doc._type)) === "document"
|
|
902
|
+
).map((item) => publishedId(item.doc._id));
|
|
903
|
+
if (flaggedDocLevelIds.length > 0) {
|
|
904
|
+
const groups = await client.fetch(
|
|
905
|
+
siblingsQuery$1(fieldLanguageKey),
|
|
906
|
+
{ ids: flaggedDocLevelIds }
|
|
907
|
+
);
|
|
908
|
+
for (const group of groups ?? []) {
|
|
909
|
+
const byLang = /* @__PURE__ */ new Map(), groupSourceIds = [];
|
|
910
|
+
for (const variant of group.variants ?? [])
|
|
911
|
+
variant.doc && (byLang.set(variant.language, variant.doc), groupSourceIds.push(publishedId(variant.doc._id)));
|
|
912
|
+
for (const sid of groupSourceIds) siblingsByDocId.set(sid, byLang);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const fieldsByType = /* @__PURE__ */ new Map(), fieldsFor = (type) => {
|
|
860
916
|
let fields = fieldsByType.get(type);
|
|
861
917
|
return fields || (fields = locjson.getTranslatableFields(schema.get(type), { exclude }), fieldsByType.set(type, fields)), fields;
|
|
862
918
|
}, configuredLanguages = languages.map((l) => l.id).join(", ") || "(none)", configuredTypes = translatableTypes === void 0 ? "all types" : translatableTypes.join(", ") || "(none)";
|
|
@@ -866,12 +922,46 @@ async function prepareExport({
|
|
|
866
922
|
level: "error",
|
|
867
923
|
message: `${item.docId ?? "unknown document"} (${item.docType ?? "unknown type"}): no document found \u2014 it may be unpublished or deleted; skipped`
|
|
868
924
|
});
|
|
869
|
-
const outbox = []
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
925
|
+
const outbox = [], queueBilingual = (doc, name, fields, isDraft) => {
|
|
926
|
+
const mode = locjson.localizationMode(schema.get(doc._type)), byLang = mode === "document" ? siblingsByDocId.get(doc._id) : void 0;
|
|
927
|
+
let queued = 0;
|
|
928
|
+
for (const targetSanityId of targetLanguages) {
|
|
929
|
+
const smartcatCode = progress.toSmartcatLanguage(smartcatLanguages, targetSanityId), target = mode === "field" ? { language: targetSanityId } : { language: targetSanityId, doc: byLang?.get(targetSanityId) }, locjson$1 = locjson.serializeToLocjson(doc, fields, { sourceLanguage: source, fieldLanguageKey, target });
|
|
930
|
+
if (locjson$1.units.length === 0) {
|
|
931
|
+
log({ level: "skip", indent: !0, message: `${targetSanityId}: no translatable content \u2014 not sent` });
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
const prefilled = locjson$1.units.filter((u) => u.target.length > 0).length;
|
|
935
|
+
outbox.push({
|
|
936
|
+
_key: uuid.uuid(),
|
|
937
|
+
sourceDocId: doc._id,
|
|
938
|
+
title: name,
|
|
939
|
+
filename: locjson.buildLocjsonFilename(doc._type, doc._id, name, { draft: isDraft, language: smartcatCode }),
|
|
940
|
+
locjson: JSON.stringify(locjson$1),
|
|
941
|
+
targetLanguage: smartcatCode
|
|
942
|
+
}), queued++, log({
|
|
943
|
+
level: "success",
|
|
944
|
+
indent: !0,
|
|
945
|
+
message: `${targetSanityId} \u2192 ${smartcatCode}: queued ${locjson$1.units.length} unit(s), ${prefilled} with existing translation`
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
queued === 0 && log({ level: "skip", indent: !0, message: "no translatable content \u2014 not sent" });
|
|
949
|
+
};
|
|
950
|
+
for (const item of items) {
|
|
951
|
+
const rawDoc = item.doc;
|
|
952
|
+
if (!rawDoc) continue;
|
|
953
|
+
const isDraft = rawDoc._id.startsWith("drafts."), doc = isDraft ? { ...rawDoc, _id: publishedId(rawDoc._id) } : rawDoc, name = resolveDocumentTitle(schema, doc), sendExisting = item.sendExistingTranslations === !0;
|
|
954
|
+
log({
|
|
955
|
+
level: "info",
|
|
956
|
+
message: `${name || doc._id} \xB7 ${doc._type}${isDraft ? " (draft)" : ""}${sendExisting ? " \xB7 sending existing translations" : ""}`
|
|
957
|
+
});
|
|
873
958
|
try {
|
|
874
|
-
const fields = fieldsFor(doc._type)
|
|
959
|
+
const fields = fieldsFor(doc._type);
|
|
960
|
+
if (sendExisting) {
|
|
961
|
+
queueBilingual(doc, name, fields, isDraft);
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
const skippedBlockTypes = /* @__PURE__ */ new Map(), locjson$1 = locjson.serializeToLocjson(doc, fields, {
|
|
875
965
|
sourceLanguage: source,
|
|
876
966
|
fieldLanguageKey,
|
|
877
967
|
onSkippedField: ({ fieldPath, types }) => skippedBlockTypes.set(fieldPath, types)
|
|
@@ -1167,6 +1257,14 @@ function sameValue(a, b) {
|
|
|
1167
1257
|
function stableString(value) {
|
|
1168
1258
|
return Array.isArray(value) ? `[${value.map(stableString).join(",")}]` : value && typeof value == "object" ? `{${Object.entries(value).filter(([k]) => k !== "_key").sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableString(v)}`).join(",")}}` : JSON.stringify(value) ?? "undefined";
|
|
1169
1259
|
}
|
|
1260
|
+
function DocTitle({ doc }) {
|
|
1261
|
+
const schema = sanity.useSchema(), schemaType = doc ? schema.get(doc._type) : void 0, preview = sanity.useValuePreview({
|
|
1262
|
+
enabled: !!(doc && schemaType),
|
|
1263
|
+
schemaType,
|
|
1264
|
+
value: doc ? { _id: doc._id, _type: doc._type } : void 0
|
|
1265
|
+
}), title = typeof preview.value?.title == "string" ? preview.value.title : void 0;
|
|
1266
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: title ?? (preview.isLoading ? "\u2026" : UNTITLED) });
|
|
1267
|
+
}
|
|
1170
1268
|
function languageTitle(languages, id) {
|
|
1171
1269
|
return languages.find((l) => l.id === id)?.title || id;
|
|
1172
1270
|
}
|
|
@@ -1268,6 +1366,347 @@ function ItemProgress({
|
|
|
1268
1366
|
target._key
|
|
1269
1367
|
)) });
|
|
1270
1368
|
}
|
|
1369
|
+
function OpenInStudioButton({ id, type }) {
|
|
1370
|
+
const { onClick, href } = router.useIntentLink({ intent: "edit", params: { id, type } });
|
|
1371
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1372
|
+
ui.Button,
|
|
1373
|
+
{
|
|
1374
|
+
as: "a",
|
|
1375
|
+
href,
|
|
1376
|
+
onClick,
|
|
1377
|
+
mode: "bleed",
|
|
1378
|
+
icon: icons.LaunchIcon,
|
|
1379
|
+
text: "Open in Studio",
|
|
1380
|
+
fontSize: 0,
|
|
1381
|
+
padding: 2
|
|
1382
|
+
}
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const CodeArea = styled__default.default.textarea`
|
|
1386
|
+
width: 100%;
|
|
1387
|
+
height: 70vh;
|
|
1388
|
+
resize: none;
|
|
1389
|
+
border: none;
|
|
1390
|
+
outline: none;
|
|
1391
|
+
padding: 0;
|
|
1392
|
+
background: transparent;
|
|
1393
|
+
color: inherit;
|
|
1394
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1395
|
+
font-size: 12px;
|
|
1396
|
+
line-height: 1.5;
|
|
1397
|
+
white-space: pre;
|
|
1398
|
+
tab-size: 2;
|
|
1399
|
+
`;
|
|
1400
|
+
function siblingsQuery(fieldLanguageKey) {
|
|
1401
|
+
return `*[_type == "translation.metadata" && references($id)][0].translations[]{"language": coalesce(${fieldLanguageKey}, _key), "doc": value->}`;
|
|
1402
|
+
}
|
|
1403
|
+
function ViewLocjsonButton(props) {
|
|
1404
|
+
const { docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, targetLanguages, languages } = props, [open, setOpen] = react.useState(!1), [content, setContent] = react.useState(null), [filename, setFilename] = react.useState(null), [title, setTitle] = react.useState("Source only"), [error, setError] = react.useState(null), build = react.useCallback(
|
|
1405
|
+
async (targetLanguage) => {
|
|
1406
|
+
setOpen(!0), setContent(null), setFilename(null), setError(null), setTitle(targetLanguage ? `Source + ${languageTitle(languages, targetLanguage)}` : "Source only");
|
|
1407
|
+
try {
|
|
1408
|
+
const raw = await client.fetch(
|
|
1409
|
+
sourceIsPublished ? "*[_id == $id][0]" : 'coalesce(*[_id == "drafts." + $id][0], *[_id == $id][0])',
|
|
1410
|
+
{ id: docId }
|
|
1411
|
+
);
|
|
1412
|
+
if (!raw) throw new Error(`Document not found: ${docId}`);
|
|
1413
|
+
const isDraft = typeof raw._id == "string" && raw._id.startsWith("drafts."), doc = isDraft ? { ...raw, _id: docId } : raw, fields = locjson.getTranslatableFields(schema.get(type), { exclude: [documentI18nLanguageField] });
|
|
1414
|
+
let target;
|
|
1415
|
+
if (targetLanguage)
|
|
1416
|
+
if (locjson.localizationMode(schema.get(type)) === "document") {
|
|
1417
|
+
const sibling = (await client.fetch(siblingsQuery(fieldI18nLanguageField), { id: docId }) ?? []).find((v) => v.language === targetLanguage)?.doc ?? void 0;
|
|
1418
|
+
target = { language: targetLanguage, doc: sibling };
|
|
1419
|
+
} else
|
|
1420
|
+
target = { language: targetLanguage };
|
|
1421
|
+
const locjson$1 = locjson.serializeToLocjson(doc, fields, {
|
|
1422
|
+
sourceLanguage,
|
|
1423
|
+
fieldLanguageKey: fieldI18nLanguageField,
|
|
1424
|
+
target
|
|
1425
|
+
}), smartcatCode = targetLanguage ? languages.find((l) => l.id === targetLanguage)?.smartcatLanguage || targetLanguage : void 0, smartcatName = locjson.buildLocjsonFilename(type, docId, resolveDocumentTitle(schema, doc), {
|
|
1426
|
+
draft: isDraft,
|
|
1427
|
+
language: smartcatCode
|
|
1428
|
+
});
|
|
1429
|
+
setFilename(smartcatName.split("/").pop() || smartcatName), setContent(JSON.stringify(locjson$1, null, 2));
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
[client, schema, docId, type, sourceIsPublished, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, languages]
|
|
1435
|
+
), download = react.useCallback(() => {
|
|
1436
|
+
if (content == null || !filename) return;
|
|
1437
|
+
const url = URL.createObjectURL(new Blob([content], { type: "application/json" })), anchor = document.createElement("a");
|
|
1438
|
+
anchor.href = url, anchor.download = filename, anchor.click(), URL.revokeObjectURL(url);
|
|
1439
|
+
}, [content, filename]);
|
|
1440
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1441
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1442
|
+
ui.MenuButton,
|
|
1443
|
+
{
|
|
1444
|
+
id: `locjson-menu-${docId}`,
|
|
1445
|
+
button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { mode: "bleed", icon: icons.JsonIcon, iconRight: icons.ChevronDownIcon, title: "View LocJSON", fontSize: 0, padding: 2 }),
|
|
1446
|
+
menu: /* @__PURE__ */ jsxRuntime.jsxs(ui.Menu, { children: [
|
|
1447
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, { text: "Source only", onClick: () => build() }),
|
|
1448
|
+
targetLanguages.map((lang) => /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, { text: `Source + ${languageTitle(languages, lang)}`, onClick: () => build(lang) }, lang))
|
|
1449
|
+
] }),
|
|
1450
|
+
popover: { portal: !0 }
|
|
1451
|
+
}
|
|
1452
|
+
),
|
|
1453
|
+
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1454
|
+
ui.Dialog,
|
|
1455
|
+
{
|
|
1456
|
+
id: `locjson-${docId}`,
|
|
1457
|
+
header: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 3, children: [
|
|
1458
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { weight: "semibold", children: [
|
|
1459
|
+
"LocJSON \xB7 ",
|
|
1460
|
+
type,
|
|
1461
|
+
" \xB7 ",
|
|
1462
|
+
title
|
|
1463
|
+
] }),
|
|
1464
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1465
|
+
ui.Button,
|
|
1466
|
+
{
|
|
1467
|
+
icon: icons.DownloadIcon,
|
|
1468
|
+
text: "Download",
|
|
1469
|
+
mode: "ghost",
|
|
1470
|
+
fontSize: 1,
|
|
1471
|
+
padding: 2,
|
|
1472
|
+
disabled: content == null,
|
|
1473
|
+
onClick: download
|
|
1474
|
+
}
|
|
1475
|
+
)
|
|
1476
|
+
] }),
|
|
1477
|
+
onClose: () => setOpen(!1),
|
|
1478
|
+
width: 3,
|
|
1479
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 4, children: error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: error }) : content === null ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", padding: 5, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1480
|
+
CodeArea,
|
|
1481
|
+
{
|
|
1482
|
+
readOnly: !0,
|
|
1483
|
+
spellCheck: !1,
|
|
1484
|
+
value: content,
|
|
1485
|
+
onFocus: (e) => e.currentTarget.select()
|
|
1486
|
+
}
|
|
1487
|
+
) })
|
|
1488
|
+
}
|
|
1489
|
+
)
|
|
1490
|
+
] });
|
|
1491
|
+
}
|
|
1492
|
+
const QUERY_LIMIT = 50, DISPLAY_LIMIT = 25, MAX_TOKENS = 6, MIN_QUERY_LENGTH = 2, TITLE_FIELDS = "[title, name, heading, label, question, slug.current, _type, _id]", SCORE_FIELDS = "[title, name, heading, label, question, slug.current]", EXCLUDED_TYPES = ["smartcat.translationProject", "smartcat.settings", "translation.metadata"];
|
|
1493
|
+
function buildSearch(rawQuery, searchContent, translatableTypes) {
|
|
1494
|
+
if (rawQuery.trim().length < MIN_QUERY_LENGTH) return null;
|
|
1495
|
+
const tokens = rawQuery.trim().split(/\s+/).map((t) => t.replace(/["*]/g, "").replace(/^-+/, "").trim()).filter(Boolean).slice(0, MAX_TOKENS);
|
|
1496
|
+
if (tokens.length === 0) return null;
|
|
1497
|
+
const target = searchContent ? "@" : TITLE_FIELDS, params = {}, tokenClauses = [], scoreClauses = [];
|
|
1498
|
+
tokens.forEach((tok, i) => {
|
|
1499
|
+
params[`t${i}`] = `${tok}*`, tokenClauses.push(`${target} match text::query($t${i})`), scoreClauses.push(`boost(${SCORE_FIELDS} match text::query($t${i}), 2)`);
|
|
1500
|
+
});
|
|
1501
|
+
let typeFilter;
|
|
1502
|
+
return translatableTypes ? (params.types = translatableTypes, typeFilter = "_type in $types") : (params.excluded = EXCLUDED_TYPES, typeFilter = '!(_type in $excluded) && !(_type match "sanity.*")'), { query: `*[${[typeFilter, ...tokenClauses].join(" && ")}] | score(${scoreClauses.join(", ")}) | order(_score desc)[0...${QUERY_LIMIT}]{_id, _type, "hasDraft": _id in path("drafts.**") || count(*[_id == "drafts." + ^._id]) > 0}`, params };
|
|
1503
|
+
}
|
|
1504
|
+
function dedupeHits(rows) {
|
|
1505
|
+
const byDoc = /* @__PURE__ */ new Map();
|
|
1506
|
+
for (const row of rows) {
|
|
1507
|
+
const docId = row._id.replace(/^drafts\./, ""), existing = byDoc.get(docId);
|
|
1508
|
+
existing ? existing.hasDraft = existing.hasDraft || row.hasDraft : byDoc.set(docId, { docId, _type: row._type, hasDraft: row.hasDraft });
|
|
1509
|
+
}
|
|
1510
|
+
return [...byDoc.values()].slice(0, DISPLAY_LIMIT);
|
|
1511
|
+
}
|
|
1512
|
+
const MEMBERSHIP_QUERY = `*[_type == "${constants.TRANSLATION_PROJECT_TYPE}" && count(items[${progress.ITEM_ID} in $ids]) > 0] | order(name asc){
|
|
1513
|
+
_id,
|
|
1514
|
+
name,
|
|
1515
|
+
"docIds": items[${progress.ITEM_ID} in $ids]{"id": ${progress.ITEM_ID}}
|
|
1516
|
+
}`;
|
|
1517
|
+
function ProjectChip({ project, current }) {
|
|
1518
|
+
const router$1 = router.useRouter(), onClick = react.useCallback(
|
|
1519
|
+
(e) => {
|
|
1520
|
+
e.preventDefault(), router$1.navigate({ projectId: project._id });
|
|
1521
|
+
},
|
|
1522
|
+
[router$1, project._id]
|
|
1523
|
+
);
|
|
1524
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("a", { href: "#", onClick, children: [
|
|
1525
|
+
project.name || "Untitled project",
|
|
1526
|
+
current ? " (this project)" : ""
|
|
1527
|
+
] });
|
|
1528
|
+
}
|
|
1529
|
+
function AddItemsSearch({
|
|
1530
|
+
projectId,
|
|
1531
|
+
translatableTypes,
|
|
1532
|
+
existingDocIds,
|
|
1533
|
+
sourceLanguage,
|
|
1534
|
+
languages,
|
|
1535
|
+
targetLanguages,
|
|
1536
|
+
documentI18nLanguageField,
|
|
1537
|
+
fieldI18nLanguageField
|
|
1538
|
+
}) {
|
|
1539
|
+
const client = sanity.useClient({ apiVersion: constants.API_VERSION }), schema = sanity.useSchema(), toast = ui.useToast(), [query, setQuery] = react.useState(""), [searchContent, setSearchContent] = react.useState(!1), [results, setResults] = react.useState(null), [loading, setLoading] = react.useState(!1), [addingId, setAddingId] = react.useState(null), [addedIds, setAddedIds] = react.useState(/* @__PURE__ */ new Set()), [projectsByDoc, setProjectsByDoc] = react.useState(/* @__PURE__ */ new Map());
|
|
1540
|
+
react.useEffect(() => {
|
|
1541
|
+
const built = buildSearch(query, searchContent, translatableTypes);
|
|
1542
|
+
if (!built) {
|
|
1543
|
+
setResults(null), setLoading(!1);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
setLoading(!0);
|
|
1547
|
+
let cancelled = !1;
|
|
1548
|
+
const handle = setTimeout(() => {
|
|
1549
|
+
client.fetch(built.query, built.params).then((res) => {
|
|
1550
|
+
cancelled || setResults(dedupeHits(res));
|
|
1551
|
+
}).catch((err) => {
|
|
1552
|
+
cancelled || (setResults([]), toast.push({ status: "error", title: "Search failed", description: String(err) }));
|
|
1553
|
+
}).finally(() => {
|
|
1554
|
+
cancelled || setLoading(!1);
|
|
1555
|
+
});
|
|
1556
|
+
}, 250);
|
|
1557
|
+
return () => {
|
|
1558
|
+
cancelled = !0, clearTimeout(handle);
|
|
1559
|
+
};
|
|
1560
|
+
}, [query, searchContent, translatableTypes, client, toast]);
|
|
1561
|
+
const docIdsKey = (results ?? []).map((r) => r.docId).join(",");
|
|
1562
|
+
react.useEffect(() => {
|
|
1563
|
+
const ids = docIdsKey ? docIdsKey.split(",") : [];
|
|
1564
|
+
if (ids.length === 0) {
|
|
1565
|
+
setProjectsByDoc(/* @__PURE__ */ new Map());
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
let cancelled = !1;
|
|
1569
|
+
const load = () => client.fetch(MEMBERSHIP_QUERY, { ids }).then((rows) => {
|
|
1570
|
+
if (cancelled) return;
|
|
1571
|
+
const map = /* @__PURE__ */ new Map();
|
|
1572
|
+
for (const row of rows)
|
|
1573
|
+
for (const { id } of row.docIds ?? []) {
|
|
1574
|
+
const list = map.get(id) ?? [];
|
|
1575
|
+
list.push({ _id: row._id, name: row.name }), map.set(id, list);
|
|
1576
|
+
}
|
|
1577
|
+
setProjectsByDoc(map);
|
|
1578
|
+
}).catch(() => !cancelled && setProjectsByDoc(/* @__PURE__ */ new Map()));
|
|
1579
|
+
load();
|
|
1580
|
+
const sub = client.listen(MEMBERSHIP_QUERY, { ids }, { visibility: "query" }).subscribe({ next: load, error: () => {
|
|
1581
|
+
} });
|
|
1582
|
+
return () => {
|
|
1583
|
+
cancelled = !0, sub.unsubscribe();
|
|
1584
|
+
};
|
|
1585
|
+
}, [docIdsKey, client]);
|
|
1586
|
+
const existingKey = [...existingDocIds].sort().join(",");
|
|
1587
|
+
react.useEffect(() => {
|
|
1588
|
+
setAddedIds((prev) => {
|
|
1589
|
+
if (prev.size === 0) return prev;
|
|
1590
|
+
const next = new Set([...prev].filter((id) => !existingDocIds.has(id)));
|
|
1591
|
+
return next.size === prev.size ? prev : next;
|
|
1592
|
+
});
|
|
1593
|
+
}, [existingKey]);
|
|
1594
|
+
const addItem = async (hit, sendExistingTranslations) => {
|
|
1595
|
+
setAddingId(hit.docId);
|
|
1596
|
+
try {
|
|
1597
|
+
const item = {
|
|
1598
|
+
_type: "smartcat.projectItem",
|
|
1599
|
+
_key: uuid.uuid(),
|
|
1600
|
+
docId: hit.docId,
|
|
1601
|
+
docType: hit._type,
|
|
1602
|
+
// Prefer the draft as source when one exists (the plugin translates the
|
|
1603
|
+
// draft version); the caller chose whether to also upload existing
|
|
1604
|
+
// translations — matching a fresh "Add to translation project".
|
|
1605
|
+
sourceIsPublished: !hit.hasDraft,
|
|
1606
|
+
sendExistingTranslations
|
|
1607
|
+
};
|
|
1608
|
+
await client.patch(projectId).setIfMissing({ items: [] }).insert("after", "items[-1]", [item]).commit(), setAddedIds((prev) => new Set(prev).add(hit.docId)), toast.push({ status: "success", title: "Added to project" });
|
|
1609
|
+
} catch (err) {
|
|
1610
|
+
toast.push({ status: "error", title: "Failed to add item", description: String(err) });
|
|
1611
|
+
} finally {
|
|
1612
|
+
setAddingId(null);
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
1616
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 4, radius: 2, border: !0, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, children: [
|
|
1617
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
|
|
1618
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1619
|
+
ui.TextInput,
|
|
1620
|
+
{
|
|
1621
|
+
icon: icons.SearchIcon,
|
|
1622
|
+
placeholder: "Search documents to add \u2014 by title, or schema type\u2026",
|
|
1623
|
+
value: query,
|
|
1624
|
+
onChange: (e) => setQuery(e.currentTarget.value),
|
|
1625
|
+
clearButton: query.length > 0,
|
|
1626
|
+
onClear: () => setQuery("")
|
|
1627
|
+
}
|
|
1628
|
+
),
|
|
1629
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { as: "label", align: "center", gap: 2, style: { cursor: "pointer" }, children: [
|
|
1630
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: searchContent, onChange: (e) => setSearchContent(e.currentTarget.checked) }),
|
|
1631
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "Also search in content (slower; matches body text, not just titles)" })
|
|
1632
|
+
] })
|
|
1633
|
+
] }),
|
|
1634
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, { muted: !0 }) }) : results === null ? null : results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { muted: !0, align: "center", size: 1, children: [
|
|
1635
|
+
"No documents match \u201C",
|
|
1636
|
+
query.trim(),
|
|
1637
|
+
"\u201D."
|
|
1638
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 2, children: results.map((hit) => {
|
|
1639
|
+
const inThisProject = addedIds.has(hit.docId) || existingDocIds.has(hit.docId), memberships = projectsByDoc.get(hit.docId) ?? [], previewId = hit.hasDraft ? `drafts.${hit.docId}` : hit.docId;
|
|
1640
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 2, radius: 2, shadow: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 3, children: [
|
|
1641
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { flex: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
|
|
1642
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 2, children: [
|
|
1643
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1644
|
+
ui.Badge,
|
|
1645
|
+
{
|
|
1646
|
+
tone: hit.hasDraft ? "caution" : "positive",
|
|
1647
|
+
paddingX: 2,
|
|
1648
|
+
title: hit.hasDraft ? "The draft version will be the translation source" : "The published version will be the translation source",
|
|
1649
|
+
children: hit.hasDraft ? "Draft" : "Published"
|
|
1650
|
+
}
|
|
1651
|
+
),
|
|
1652
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "medium", textOverflow: "ellipsis", children: /* @__PURE__ */ jsxRuntime.jsx(DocTitle, { doc: { _id: previewId, _type: hit._type } }) }),
|
|
1653
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "primary", paddingX: 2, children: hit._type })
|
|
1654
|
+
] }),
|
|
1655
|
+
memberships.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: 0, muted: !0, children: [
|
|
1656
|
+
"In:",
|
|
1657
|
+
" ",
|
|
1658
|
+
memberships.map((p, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
1659
|
+
i > 0 && ", ",
|
|
1660
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProjectChip, { project: p, current: p._id === projectId })
|
|
1661
|
+
] }, p._id))
|
|
1662
|
+
] })
|
|
1663
|
+
] }) }),
|
|
1664
|
+
/* @__PURE__ */ jsxRuntime.jsx(OpenInStudioButton, { id: hit.docId, type: hit._type }),
|
|
1665
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1666
|
+
ViewLocjsonButton,
|
|
1667
|
+
{
|
|
1668
|
+
docId: hit.docId,
|
|
1669
|
+
type: hit._type,
|
|
1670
|
+
sourceIsPublished: !hit.hasDraft,
|
|
1671
|
+
client,
|
|
1672
|
+
schema,
|
|
1673
|
+
sourceLanguage,
|
|
1674
|
+
documentI18nLanguageField,
|
|
1675
|
+
fieldI18nLanguageField,
|
|
1676
|
+
targetLanguages,
|
|
1677
|
+
languages
|
|
1678
|
+
}
|
|
1679
|
+
),
|
|
1680
|
+
inThisProject ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { mode: "bleed", icon: icons.CheckmarkIcon, text: "Added", fontSize: 1, padding: 2, disabled: !0 }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1681
|
+
ui.MenuButton,
|
|
1682
|
+
{
|
|
1683
|
+
id: `add-${hit.docId}`,
|
|
1684
|
+
button: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1685
|
+
ui.Button,
|
|
1686
|
+
{
|
|
1687
|
+
mode: "ghost",
|
|
1688
|
+
icon: icons.AddIcon,
|
|
1689
|
+
iconRight: icons.ChevronDownIcon,
|
|
1690
|
+
text: "Add",
|
|
1691
|
+
fontSize: 1,
|
|
1692
|
+
padding: 2,
|
|
1693
|
+
loading: addingId === hit.docId,
|
|
1694
|
+
disabled: addingId !== null
|
|
1695
|
+
}
|
|
1696
|
+
),
|
|
1697
|
+
menu: /* @__PURE__ */ jsxRuntime.jsxs(ui.Menu, { children: [
|
|
1698
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, { text: "Add source", onClick: () => addItem(hit, !1) }),
|
|
1699
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, { text: "Add source + translations", onClick: () => addItem(hit, !0) })
|
|
1700
|
+
] }),
|
|
1701
|
+
popover: { portal: !0 }
|
|
1702
|
+
}
|
|
1703
|
+
)
|
|
1704
|
+
] }) }, hit.docId);
|
|
1705
|
+
}) })
|
|
1706
|
+
] }) }),
|
|
1707
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "You can also add content from a document\u2019s \u201C\u22EF \u2192 Add to translation project\u201D menu." })
|
|
1708
|
+
] });
|
|
1709
|
+
}
|
|
1271
1710
|
function logsToText(logs) {
|
|
1272
1711
|
return logs.map((l) => {
|
|
1273
1712
|
const prefix = l.indent ? " " : "", detail = [
|
|
@@ -1410,14 +1849,6 @@ function languageMappingLabel(language) {
|
|
|
1410
1849
|
const code = language.smartcatLanguage || language.id;
|
|
1411
1850
|
return code === language.id ? language.id : `${language.id} \u2192 ${code}`;
|
|
1412
1851
|
}
|
|
1413
|
-
function DocTitle({ doc }) {
|
|
1414
|
-
const schema = sanity.useSchema(), schemaType = doc ? schema.get(doc._type) : void 0, preview = sanity.useValuePreview({
|
|
1415
|
-
enabled: !!(doc && schemaType),
|
|
1416
|
-
schemaType,
|
|
1417
|
-
value: doc ? { _id: doc._id, _type: doc._type } : void 0
|
|
1418
|
-
}), title = typeof preview.value?.title == "string" ? preview.value.title : void 0;
|
|
1419
|
-
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: title ?? (preview.isLoading ? "\u2026" : UNTITLED) });
|
|
1420
|
-
}
|
|
1421
1852
|
const IN_FLIGHT = ["queued", "importing", "downloaded"], CAN_IMPORT = ["sent", "translating", "completed"], rotate = styled.keyframes`
|
|
1422
1853
|
to { transform: rotate(360deg); }
|
|
1423
1854
|
`, SpinningSyncIcon = styled__default.default(icons.SyncIcon)`
|
|
@@ -1432,108 +1863,6 @@ function LastUpdate({ date }) {
|
|
|
1432
1863
|
relative
|
|
1433
1864
|
] });
|
|
1434
1865
|
}
|
|
1435
|
-
function OpenInStudioButton({ id, type }) {
|
|
1436
|
-
const { onClick, href } = router.useIntentLink({ intent: "edit", params: { id, type } });
|
|
1437
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1438
|
-
ui.Button,
|
|
1439
|
-
{
|
|
1440
|
-
as: "a",
|
|
1441
|
-
href,
|
|
1442
|
-
onClick,
|
|
1443
|
-
mode: "bleed",
|
|
1444
|
-
icon: icons.LaunchIcon,
|
|
1445
|
-
text: "Open in Studio",
|
|
1446
|
-
fontSize: 0,
|
|
1447
|
-
padding: 2
|
|
1448
|
-
}
|
|
1449
|
-
);
|
|
1450
|
-
}
|
|
1451
|
-
const CodeArea = styled__default.default.textarea`
|
|
1452
|
-
width: 100%;
|
|
1453
|
-
height: 70vh;
|
|
1454
|
-
resize: none;
|
|
1455
|
-
border: none;
|
|
1456
|
-
outline: none;
|
|
1457
|
-
padding: 0;
|
|
1458
|
-
background: transparent;
|
|
1459
|
-
color: inherit;
|
|
1460
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1461
|
-
font-size: 12px;
|
|
1462
|
-
line-height: 1.5;
|
|
1463
|
-
white-space: pre;
|
|
1464
|
-
tab-size: 2;
|
|
1465
|
-
`;
|
|
1466
|
-
function ViewLocjsonButton(props) {
|
|
1467
|
-
const { docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField } = props, [open, setOpen] = react.useState(!1), [content, setContent] = react.useState(null), [filename, setFilename] = react.useState(null), [error, setError] = react.useState(null), build = react.useCallback(async () => {
|
|
1468
|
-
setOpen(!0), setContent(null), setFilename(null), setError(null);
|
|
1469
|
-
try {
|
|
1470
|
-
const raw = await client.fetch(
|
|
1471
|
-
sourceIsPublished ? "*[_id == $id][0]" : 'coalesce(*[_id == "drafts." + $id][0], *[_id == $id][0])',
|
|
1472
|
-
{ id: docId }
|
|
1473
|
-
);
|
|
1474
|
-
if (!raw) throw new Error(`Document not found: ${docId}`);
|
|
1475
|
-
const isDraft = typeof raw._id == "string" && raw._id.startsWith("drafts."), doc = isDraft ? { ...raw, _id: docId } : raw, fields = locjson.getTranslatableFields(schema.get(type), { exclude: [documentI18nLanguageField] }), locjson$1 = locjson.serializeToLocjson(doc, fields, {
|
|
1476
|
-
sourceLanguage,
|
|
1477
|
-
fieldLanguageKey: fieldI18nLanguageField
|
|
1478
|
-
}), smartcatName = locjson.buildLocjsonFilename(type, docId, resolveDocumentTitle(schema, doc), { draft: isDraft });
|
|
1479
|
-
setFilename(smartcatName.split("/").pop() || smartcatName), setContent(JSON.stringify(locjson$1, null, 2));
|
|
1480
|
-
} catch (err) {
|
|
1481
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1482
|
-
}
|
|
1483
|
-
}, [client, schema, docId, type, sourceIsPublished, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField]), download = react.useCallback(() => {
|
|
1484
|
-
if (content == null || !filename) return;
|
|
1485
|
-
const url = URL.createObjectURL(new Blob([content], { type: "application/json" })), anchor = document.createElement("a");
|
|
1486
|
-
anchor.href = url, anchor.download = filename, anchor.click(), URL.revokeObjectURL(url);
|
|
1487
|
-
}, [content, filename]);
|
|
1488
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1489
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1490
|
-
ui.Button,
|
|
1491
|
-
{
|
|
1492
|
-
mode: "bleed",
|
|
1493
|
-
icon: icons.JsonIcon,
|
|
1494
|
-
title: "View LocJSON",
|
|
1495
|
-
fontSize: 0,
|
|
1496
|
-
padding: 2,
|
|
1497
|
-
onClick: build
|
|
1498
|
-
}
|
|
1499
|
-
),
|
|
1500
|
-
open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1501
|
-
ui.Dialog,
|
|
1502
|
-
{
|
|
1503
|
-
id: `locjson-${docId}`,
|
|
1504
|
-
header: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 3, children: [
|
|
1505
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { weight: "semibold", children: [
|
|
1506
|
-
"LocJSON \xB7 ",
|
|
1507
|
-
type
|
|
1508
|
-
] }),
|
|
1509
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1510
|
-
ui.Button,
|
|
1511
|
-
{
|
|
1512
|
-
icon: icons.DownloadIcon,
|
|
1513
|
-
text: "Download",
|
|
1514
|
-
mode: "ghost",
|
|
1515
|
-
fontSize: 1,
|
|
1516
|
-
padding: 2,
|
|
1517
|
-
disabled: content == null,
|
|
1518
|
-
onClick: download
|
|
1519
|
-
}
|
|
1520
|
-
)
|
|
1521
|
-
] }),
|
|
1522
|
-
onClose: () => setOpen(!1),
|
|
1523
|
-
width: 3,
|
|
1524
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 4, children: error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: error }) : content === null ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { align: "center", justify: "center", padding: 5, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1525
|
-
CodeArea,
|
|
1526
|
-
{
|
|
1527
|
-
readOnly: !0,
|
|
1528
|
-
spellCheck: !1,
|
|
1529
|
-
value: content,
|
|
1530
|
-
onFocus: (e) => e.currentTarget.select()
|
|
1531
|
-
}
|
|
1532
|
-
) })
|
|
1533
|
-
}
|
|
1534
|
-
)
|
|
1535
|
-
] });
|
|
1536
|
-
}
|
|
1537
1866
|
function ProjectView({
|
|
1538
1867
|
projectId,
|
|
1539
1868
|
onBack,
|
|
@@ -1918,6 +2247,16 @@ Smartcat language code: '${l.smartcatLanguage || l.id}'`,
|
|
|
1918
2247
|
children: modeByType.get(item.doc._type) === "field" ? "fields" : "document"
|
|
1919
2248
|
}
|
|
1920
2249
|
),
|
|
2250
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2251
|
+
ui.Badge,
|
|
2252
|
+
{
|
|
2253
|
+
tone: item.sendExistingTranslations ? "primary" : "default",
|
|
2254
|
+
fontSize: 0,
|
|
2255
|
+
paddingX: 2,
|
|
2256
|
+
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",
|
|
2257
|
+
children: item.sendExistingTranslations ? "source + translations" : "source only"
|
|
2258
|
+
}
|
|
2259
|
+
),
|
|
1921
2260
|
item.doc && /* @__PURE__ */ jsxRuntime.jsx(OpenInStudioButton, { id: item.doc._id, type: item.doc._type }),
|
|
1922
2261
|
item.doc && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1923
2262
|
ViewLocjsonButton,
|
|
@@ -1929,7 +2268,9 @@ Smartcat language code: '${l.smartcatLanguage || l.id}'`,
|
|
|
1929
2268
|
schema,
|
|
1930
2269
|
sourceLanguage,
|
|
1931
2270
|
documentI18nLanguageField,
|
|
1932
|
-
fieldI18nLanguageField
|
|
2271
|
+
fieldI18nLanguageField,
|
|
2272
|
+
targetLanguages: targets,
|
|
2273
|
+
languages
|
|
1933
2274
|
}
|
|
1934
2275
|
)
|
|
1935
2276
|
] }),
|
|
@@ -1974,7 +2315,19 @@ Smartcat language code: '${l.smartcatLanguage || l.id}'`,
|
|
|
1974
2315
|
item._key
|
|
1975
2316
|
);
|
|
1976
2317
|
}) }),
|
|
1977
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2318
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2319
|
+
AddItemsSearch,
|
|
2320
|
+
{
|
|
2321
|
+
projectId,
|
|
2322
|
+
translatableTypes,
|
|
2323
|
+
existingDocIds: new Set(items.map((i) => i.docId)),
|
|
2324
|
+
sourceLanguage,
|
|
2325
|
+
languages,
|
|
2326
|
+
targetLanguages: targets,
|
|
2327
|
+
documentI18nLanguageField,
|
|
2328
|
+
fieldI18nLanguageField
|
|
2329
|
+
}
|
|
2330
|
+
)
|
|
1978
2331
|
] }),
|
|
1979
2332
|
confirmDelete && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1980
2333
|
ui.Dialog,
|