@braze/sanity-plugin-ai-translation 0.2.79 → 0.2.81

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getPublishedId, pathToString, useClient, useValidationStatus, defineType, defineField, definePlugin } from "sanity";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
- import { WarningOutlineIcon, ErrorOutlineIcon, CheckmarkIcon, CloseIcon, PanelRightIcon, SyncIcon, SearchIcon, CircleIcon, ArrowRightIcon, ResetIcon, TranslateIcon } from "@sanity/icons";
3
+ import { WarningOutlineIcon, ErrorOutlineIcon, CheckmarkIcon, CloseIcon, SyncIcon, PanelRightIcon, SearchIcon, CircleIcon, ArrowRightIcon, ResetIcon, TranslateIcon } from "@sanity/icons";
4
4
  import { createContext, useReducer, useRef, useEffect, useContext, useMemo, useState, useCallback } from "react";
5
5
  import defaults from "lodash.defaults";
6
6
  import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Badge, Inline, Radio, Checkbox, Grid, TextInput, Select, Label, Code, Tooltip } from "@sanity/ui";
@@ -385,32 +385,63 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
385
385
  return devLog.debug("Source references with unpublished changes", {
386
386
  count: results.length
387
387
  }), results;
388
- }, getExistingTranslationId = (translationMetadata, targetLanguage) => translationMetadata?.translations?.length && translationMetadata.translations.find(
388
+ }, TRANSLATION_METADATA_TYPE = "translation.metadata", getExistingTranslationId = (translationMetadata, targetLanguage) => translationMetadata?.translations?.length && translationMetadata.translations.find(
389
389
  (translation) => translation._key === targetLanguage
390
390
  )?.value._ref || null, checkTargetDrafts = async ({
391
391
  client,
392
392
  translationMetadata,
393
393
  selectedTargetLanguages,
394
- sourceDocumentId
394
+ sourceDocumentId,
395
+ references
395
396
  }) => {
396
- if (!translationMetadata?.translations?.length)
397
- return devLog.debug("No translation metadata \u2014 skipping draft check", {
398
- sourceDocumentId
399
- }), [];
400
397
  const targetEntries = [];
401
- for (const lang of selectedTargetLanguages) {
402
- const targetId = getExistingTranslationId(translationMetadata, lang);
403
- targetId && targetEntries.push({ targetId, targetLanguage: lang });
398
+ if (translationMetadata?.translations?.length)
399
+ for (const lang of selectedTargetLanguages) {
400
+ const targetId = getExistingTranslationId(translationMetadata, lang);
401
+ targetId && targetEntries.push({ targetId, targetLanguage: lang, sourceDocumentId, isReference: !1 });
402
+ }
403
+ else
404
+ devLog.debug("No translation metadata \u2014 skipping main document draft check", {
405
+ sourceDocumentId
406
+ });
407
+ if (references && references.length > 0) {
408
+ const refIds = references.map((r) => r._id), refMetadataDocs = await client.fetch(
409
+ "*[_type == $metadataType && array::intersects(translations[].value._ref, $refIds)] { translations }",
410
+ { metadataType: TRANSLATION_METADATA_TYPE, refIds }
411
+ );
412
+ devLog.debug("Fetched reference metadata for draft check", {
413
+ refCount: refIds.length,
414
+ metadataDocsFound: refMetadataDocs.length
415
+ });
416
+ const refIdSet = new Set(refIds);
417
+ for (const metadataDoc of refMetadataDocs) {
418
+ if (!metadataDoc.translations?.length) continue;
419
+ const matchingRefId = metadataDoc.translations.find(
420
+ (t) => t.value?._ref && refIdSet.has(t.value._ref)
421
+ )?.value._ref;
422
+ if (matchingRefId)
423
+ for (const lang of selectedTargetLanguages) {
424
+ const targetId = getExistingTranslationId(metadataDoc, lang);
425
+ targetId && targetEntries.push({
426
+ targetId,
427
+ targetLanguage: lang,
428
+ sourceDocumentId: matchingRefId,
429
+ isReference: !0
430
+ });
431
+ }
432
+ }
404
433
  }
405
- if (targetEntries.length === 0)
434
+ const seen = /* @__PURE__ */ new Set(), deduped = targetEntries.filter((e) => seen.has(e.targetId) ? !1 : (seen.add(e.targetId), !0));
435
+ if (deduped.length === 0)
406
436
  return devLog.debug("No existing translations found for selected languages", {
407
437
  sourceDocumentId,
408
438
  selectedTargetLanguages
409
439
  }), [];
410
- const draftIds = targetEntries.map((e) => `drafts.${e.targetId}`);
440
+ const draftIds = deduped.map((e) => `drafts.${e.targetId}`);
411
441
  devLog.debug("Checking for stale drafts on target documents", {
412
442
  sourceDocumentId,
413
- draftIdsToCheck: draftIds.length
443
+ draftIdsToCheck: draftIds.length,
444
+ referenceTargets: deduped.filter((e) => e.isReference).length
414
445
  });
415
446
  const drafts = await client.fetch(
416
447
  "*[_id in $draftIds]{ _id, _type, title, name }",
@@ -419,23 +450,25 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
419
450
  if (drafts.length === 0)
420
451
  return devLog.debug("No stale drafts found", { sourceDocumentId }), [];
421
452
  const draftMap = new Map(drafts.map((d) => [d._id, d])), results = [];
422
- for (const entry of targetEntries) {
453
+ for (const entry of deduped) {
423
454
  const draftId = `drafts.${entry.targetId}`, draft = draftMap.get(draftId);
424
455
  draft && results.push({
425
456
  targetId: entry.targetId,
426
457
  draftId,
427
458
  _type: draft._type,
428
459
  title: draft.title || draft.name,
429
- sourceDocumentId,
430
- targetLanguage: entry.targetLanguage
460
+ sourceDocumentId: entry.sourceDocumentId,
461
+ targetLanguage: entry.targetLanguage,
462
+ isReference: entry.isReference || void 0
431
463
  });
432
464
  }
433
465
  return devLog.info("Stale drafts detected on target documents", {
434
466
  sourceDocumentId,
435
467
  staleDraftCount: results.length,
468
+ referenceDraftCount: results.filter((r) => r.isReference).length,
436
469
  affectedLanguages: results.map((r) => r.targetLanguage)
437
470
  }), results;
438
- }, TRANSLATION_METADATA_TYPE = "translation.metadata", getTranslationMetadata = async ({
471
+ }, getTranslationMetadata = async ({
439
472
  client,
440
473
  sourceDocument,
441
474
  sourceLanguage
@@ -3379,15 +3412,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3379
3412
  }
3380
3413
  );
3381
3414
  }, VALIDATION_TIMEOUT_MS = 2e3, DocumentValidator = ({ documentId, documentType, onResult }) => {
3382
- const publishedId = getPublishedId(documentId), { isValidating, validation } = useValidationStatus(publishedId, documentType), hasReceivedRealResult = useRef(!1), [timedOut, setTimedOut] = useState(!1);
3415
+ const publishedId = getPublishedId(documentId), { isValidating, validation } = useValidationStatus(publishedId, documentType), hasReceivedRealResult = useRef(!1), hasSettled = useRef(!1), [timedOut, setTimedOut] = useState(!1);
3383
3416
  return useEffect(() => {
3384
3417
  const timer = setTimeout(() => setTimedOut(!0), VALIDATION_TIMEOUT_MS);
3385
3418
  return () => clearTimeout(timer);
3386
3419
  }, []), useEffect(() => {
3420
+ if (hasSettled.current) return;
3387
3421
  (isValidating || validation.length > 0) && (hasReceivedRealResult.current = !0);
3388
- const stillWaitingForFirstResult = !hasReceivedRealResult.current && !timedOut, errors = validation.filter((v) => v.level === "error");
3389
- onResult(documentId, {
3390
- isValidating: isValidating || stillWaitingForFirstResult,
3422
+ const stillWaitingForFirstResult = !hasReceivedRealResult.current && !timedOut, errors = validation.filter((v) => v.level === "error"), computedIsValidating = isValidating || stillWaitingForFirstResult;
3423
+ !computedIsValidating && hasReceivedRealResult.current && (hasSettled.current = !0), onResult(documentId, {
3424
+ isValidating: computedIsValidating,
3391
3425
  errors
3392
3426
  });
3393
3427
  }, [isValidating, validation, documentId, onResult, timedOut]), null;
@@ -3404,7 +3438,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3404
3438
  translationMode,
3405
3439
  releaseConfig,
3406
3440
  documentSource
3407
- } = useTranslationContext(), client = useClient({ apiVersion: "vX" }), [validationResults, setValidationResults] = useState(/* @__PURE__ */ new Map()), [sourceRefsWithDrafts, setSourceRefsWithDrafts] = useState([]), [isSourceDraftCheckLoading, setIsSourceDraftCheckLoading] = useState(!0), [staleDrafts, setStaleDrafts] = useState([]), [isDraftCheckLoading, setIsDraftCheckLoading] = useState(!0), [draftAction, setDraftAction] = useState("none"), [isDraftActionInProgress, setIsDraftActionInProgress] = useState(!1), [conflictContext, setConflictContext] = useState(null), [isCheckingConflicts, setIsCheckingConflicts] = useState(!0), filteredReferences = references.filter((ref) => selectedReferenceIds.has(ref._id)), documentsToValidate = useMemo(() => {
3441
+ } = useTranslationContext(), client = useClient({ apiVersion: "vX" }), [scanKey, setScanKey] = useState(0), [validationResults, setValidationResults] = useState(/* @__PURE__ */ new Map()), [sourceRefsWithDrafts, setSourceRefsWithDrafts] = useState([]), [isSourceDraftCheckLoading, setIsSourceDraftCheckLoading] = useState(!0), [staleDrafts, setStaleDrafts] = useState([]), [isDraftCheckLoading, setIsDraftCheckLoading] = useState(!0), [draftAction, setDraftAction] = useState("none"), [isDraftActionInProgress, setIsDraftActionInProgress] = useState(!1), [conflictContext, setConflictContext] = useState(null), [isCheckingConflicts, setIsCheckingConflicts] = useState(!0), filteredReferences = useMemo(
3442
+ () => references.filter((ref) => selectedReferenceIds.has(ref._id)),
3443
+ [references, selectedReferenceIds]
3444
+ ), documentsToValidate = useMemo(() => {
3408
3445
  const docs = [
3409
3446
  {
3410
3447
  _id: document._id,
@@ -3418,6 +3455,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3418
3455
  return docs;
3419
3456
  }, [document, references, selectedReferenceIds, shouldTranslateReferences]), handleValidationResult = useCallback((id, result) => {
3420
3457
  setValidationResults((prev) => {
3458
+ const existing = prev.get(id);
3459
+ if (existing && existing.isValidating === result.isValidating && existing.errors.length === result.errors.length)
3460
+ return prev;
3421
3461
  const next = new Map(prev);
3422
3462
  return next.set(id, result), next;
3423
3463
  });
@@ -3460,7 +3500,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3460
3500
  client,
3461
3501
  translationMetadata,
3462
3502
  selectedTargetLanguages,
3463
- sourceDocumentId: document._id
3503
+ sourceDocumentId: document._id,
3504
+ references: shouldTranslateReferences ? filteredReferences : void 0
3464
3505
  });
3465
3506
  setStaleDrafts(drafts), drafts.length === 0 && setDraftAction("none");
3466
3507
  } catch (error) {
@@ -3471,7 +3512,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3471
3512
  } finally {
3472
3513
  setIsDraftCheckLoading(!1);
3473
3514
  }
3474
- }, [client, translationMetadata, selectedTargetLanguages, document._id]), runConflictCheck = useCallback(async () => {
3515
+ }, [client, translationMetadata, selectedTargetLanguages, document._id, shouldTranslateReferences, filteredReferences]), runConflictCheck = useCallback(async () => {
3475
3516
  if (releaseConfig.mode === "new") {
3476
3517
  setIsCheckingConflicts(!1), setConflictContext(null);
3477
3518
  return;
@@ -3488,10 +3529,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3488
3529
  });
3489
3530
  foundConflicts.length > 0 ? setConflictContext({ releaseId: checkedReleaseId, conflicts: foundConflicts }) : setConflictContext(null), setIsCheckingConflicts(!1);
3490
3531
  }
3491
- }, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]), hasRunChecks = useRef(!1);
3532
+ }, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]);
3492
3533
  useEffect(() => {
3493
- hasRunChecks.current || (hasRunChecks.current = !0, runSourceDraftCheck(), runDraftCheck(), runConflictCheck());
3494
- }, [runSourceDraftCheck, runDraftCheck, runConflictCheck]);
3534
+ runSourceDraftCheck(), runDraftCheck(), runConflictCheck();
3535
+ }, [scanKey]);
3495
3536
  const handleDiscardDrafts = useCallback(async () => {
3496
3537
  setIsDraftActionInProgress(!0);
3497
3538
  try {
@@ -3584,7 +3625,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3584
3625
  conflictContext || startTranslation(!1);
3585
3626
  }, [conflictContext, startTranslation]), handleBack = useCallback(() => {
3586
3627
  dispatch({ type: "SET_OVERALL_STATUS", payload: "idle" });
3587
- }, [dispatch]), sourceRefDraftIds = useMemo(
3628
+ }, [dispatch]), handleRescan = useCallback(() => {
3629
+ setValidationResults(/* @__PURE__ */ new Map()), setSourceRefsWithDrafts([]), setIsSourceDraftCheckLoading(!0), setStaleDrafts([]), setIsDraftCheckLoading(!0), setDraftAction("none"), setConflictContext(null), setIsCheckingConflicts(!0), setScanKey((k) => k + 1);
3630
+ }, []), sourceRefDraftIds = useMemo(
3588
3631
  () => new Set(sourceRefsWithDrafts.map((d) => d.publishedId)),
3589
3632
  [sourceRefsWithDrafts]
3590
3633
  ), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleCancelConflict = useCallback(() => {
@@ -3611,7 +3654,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3611
3654
  documentType: doc._type,
3612
3655
  onResult: handleValidationResult
3613
3656
  },
3614
- doc._id
3657
+ `${doc._id}-${scanKey}`
3615
3658
  )),
3616
3659
  /* @__PURE__ */ jsx(Text, { weight: "semibold", size: 2, children: "Pre-flight Checks" }),
3617
3660
  /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
@@ -3687,7 +3730,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3687
3730
  /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 3, children: staleDrafts.map((draft) => /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3688
3731
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3689
3732
  /* @__PURE__ */ jsx(Text, { size: 1, children: draft.title || draft.targetId }),
3690
- /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage })
3733
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage }),
3734
+ draft.isReference && /* @__PURE__ */ jsx(Badge, { fontSize: 0, children: "reference" })
3691
3735
  ] }),
3692
3736
  /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: draft.targetId, documentType: draft._type })
3693
3737
  ] }, draft.draftId)) }) }) }),
@@ -3731,16 +3775,28 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3731
3775
  ] }) }),
3732
3776
  /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3733
3777
  /* @__PURE__ */ jsx(Button, { mode: "ghost", text: "Back", onClick: handleBack }),
3734
- /* @__PURE__ */ jsx(
3735
- Button,
3736
- {
3737
- mode: "ghost",
3738
- tone: "positive",
3739
- text: isLoading ? "Checking\u2026" : "Start Translation",
3740
- onClick: handleStartTranslation,
3741
- disabled: !canStartTranslation
3742
- }
3743
- )
3778
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3779
+ /* @__PURE__ */ jsx(
3780
+ Button,
3781
+ {
3782
+ mode: "ghost",
3783
+ text: "Rescan",
3784
+ icon: SyncIcon,
3785
+ onClick: handleRescan,
3786
+ disabled: isLoading
3787
+ }
3788
+ ),
3789
+ /* @__PURE__ */ jsx(
3790
+ Button,
3791
+ {
3792
+ mode: "ghost",
3793
+ tone: "positive",
3794
+ text: isLoading ? "Checking\u2026" : "Start Translation",
3795
+ onClick: handleStartTranslation,
3796
+ disabled: !canStartTranslation
3797
+ }
3798
+ )
3799
+ ] })
3744
3800
  ] })
3745
3801
  ] });
3746
3802
  }, ActionButtonBar = ({ setIsDialogOpen }) => {