@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.
Files changed (46) hide show
  1. package/dist/_chunks-cjs/index.cjs +18 -5
  2. package/dist/_chunks-cjs/index.cjs.map +1 -1
  3. package/dist/_chunks-cjs/index2.cjs +8 -6
  4. package/dist/_chunks-cjs/index2.cjs.map +1 -1
  5. package/dist/_chunks-es/index.js +18 -5
  6. package/dist/_chunks-es/index.js.map +1 -1
  7. package/dist/_chunks-es/index2.js +8 -6
  8. package/dist/_chunks-es/index2.js.map +1 -1
  9. package/dist/export.cjs +21 -4
  10. package/dist/export.cjs.map +1 -1
  11. package/dist/export.d.cts +2 -0
  12. package/dist/export.d.ts +2 -0
  13. package/dist/export.js +22 -5
  14. package/dist/export.js.map +1 -1
  15. package/dist/index.cjs +477 -124
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +482 -129
  18. package/dist/index.js.map +1 -1
  19. package/dist/locjson.d.cts +15 -0
  20. package/dist/locjson.d.ts +15 -0
  21. package/dist/progress.d.cts +0 -19
  22. package/dist/progress.d.ts +0 -19
  23. package/dist/smartcat.cjs +14 -2
  24. package/dist/smartcat.cjs.map +1 -1
  25. package/dist/smartcat.d.cts +10 -1
  26. package/dist/smartcat.d.ts +10 -1
  27. package/dist/smartcat.js +14 -2
  28. package/dist/smartcat.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/actions/AddToProjectAction.tsx +23 -2
  31. package/src/export/export.test.ts +42 -5
  32. package/src/export/index.ts +63 -8
  33. package/src/lib/locjson/filename.ts +7 -2
  34. package/src/lib/locjson/locjson.test.ts +64 -0
  35. package/src/lib/locjson/serialize.ts +38 -2
  36. package/src/progress/core.ts +44 -3
  37. package/src/progress/progress.test.ts +40 -0
  38. package/src/schema/translationProject.ts +19 -0
  39. package/src/smartcat/client.ts +15 -1
  40. package/src/tool/AddItemsSearch.tsx +462 -0
  41. package/src/tool/DocTitle.tsx +23 -0
  42. package/src/tool/ProjectView.tsx +29 -187
  43. package/src/tool/data.ts +7 -0
  44. package/src/tool/itemActions.tsx +216 -0
  45. package/src/tool/processing.test.ts +11 -1
  46. 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), fieldsByType = /* @__PURE__ */ new Map(), fieldsFor = (type) => {
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
- for (const rawDoc of docs) {
871
- const isDraft = rawDoc._id.startsWith("drafts."), doc = isDraft ? { ...rawDoc, _id: publishedId(rawDoc._id) } : rawDoc, name = resolveDocumentTitle(schema, doc);
872
- log({ level: "info", message: `${name || doc._id} \xB7 ${doc._type}${isDraft ? " (draft)" : ""}` });
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), skippedBlockTypes = /* @__PURE__ */ new Map(), locjson$1 = locjson.serializeToLocjson(doc, fields, {
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(ui.Card, { padding: 4, radius: 2, tone: "transparent", border: !0, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, align: "center", size: 1, children: "Add content from a document\u2019s \u201C\u22EF \u2192 Add to translation project\u201D menu." }) })
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,