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

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,7 @@ ${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 = references.filter((ref) => selectedReferenceIds.has(ref._id)), documentsToValidate = useMemo(() => {
3408
3442
  const docs = [
3409
3443
  {
3410
3444
  _id: document._id,
@@ -3460,7 +3494,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3460
3494
  client,
3461
3495
  translationMetadata,
3462
3496
  selectedTargetLanguages,
3463
- sourceDocumentId: document._id
3497
+ sourceDocumentId: document._id,
3498
+ references: shouldTranslateReferences ? filteredReferences : void 0
3464
3499
  });
3465
3500
  setStaleDrafts(drafts), drafts.length === 0 && setDraftAction("none");
3466
3501
  } catch (error) {
@@ -3471,7 +3506,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3471
3506
  } finally {
3472
3507
  setIsDraftCheckLoading(!1);
3473
3508
  }
3474
- }, [client, translationMetadata, selectedTargetLanguages, document._id]), runConflictCheck = useCallback(async () => {
3509
+ }, [client, translationMetadata, selectedTargetLanguages, document._id, shouldTranslateReferences, filteredReferences]), runConflictCheck = useCallback(async () => {
3475
3510
  if (releaseConfig.mode === "new") {
3476
3511
  setIsCheckingConflicts(!1), setConflictContext(null);
3477
3512
  return;
@@ -3488,10 +3523,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3488
3523
  });
3489
3524
  foundConflicts.length > 0 ? setConflictContext({ releaseId: checkedReleaseId, conflicts: foundConflicts }) : setConflictContext(null), setIsCheckingConflicts(!1);
3490
3525
  }
3491
- }, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]), hasRunChecks = useRef(!1);
3526
+ }, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]);
3492
3527
  useEffect(() => {
3493
- hasRunChecks.current || (hasRunChecks.current = !0, runSourceDraftCheck(), runDraftCheck(), runConflictCheck());
3494
- }, [runSourceDraftCheck, runDraftCheck, runConflictCheck]);
3528
+ runSourceDraftCheck(), runDraftCheck(), runConflictCheck();
3529
+ }, [scanKey]);
3495
3530
  const handleDiscardDrafts = useCallback(async () => {
3496
3531
  setIsDraftActionInProgress(!0);
3497
3532
  try {
@@ -3584,7 +3619,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3584
3619
  conflictContext || startTranslation(!1);
3585
3620
  }, [conflictContext, startTranslation]), handleBack = useCallback(() => {
3586
3621
  dispatch({ type: "SET_OVERALL_STATUS", payload: "idle" });
3587
- }, [dispatch]), sourceRefDraftIds = useMemo(
3622
+ }, [dispatch]), handleRescan = useCallback(() => {
3623
+ setValidationResults(/* @__PURE__ */ new Map()), setSourceRefsWithDrafts([]), setIsSourceDraftCheckLoading(!0), setStaleDrafts([]), setIsDraftCheckLoading(!0), setDraftAction("none"), setConflictContext(null), setIsCheckingConflicts(!0), setScanKey((k) => k + 1);
3624
+ }, []), sourceRefDraftIds = useMemo(
3588
3625
  () => new Set(sourceRefsWithDrafts.map((d) => d.publishedId)),
3589
3626
  [sourceRefsWithDrafts]
3590
3627
  ), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleCancelConflict = useCallback(() => {
@@ -3611,7 +3648,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3611
3648
  documentType: doc._type,
3612
3649
  onResult: handleValidationResult
3613
3650
  },
3614
- doc._id
3651
+ `${doc._id}-${scanKey}`
3615
3652
  )),
3616
3653
  /* @__PURE__ */ jsx(Text, { weight: "semibold", size: 2, children: "Pre-flight Checks" }),
3617
3654
  /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
@@ -3687,7 +3724,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3687
3724
  /* @__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
3725
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3689
3726
  /* @__PURE__ */ jsx(Text, { size: 1, children: draft.title || draft.targetId }),
3690
- /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage })
3727
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage }),
3728
+ draft.isReference && /* @__PURE__ */ jsx(Badge, { fontSize: 0, children: "reference" })
3691
3729
  ] }),
3692
3730
  /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: draft.targetId, documentType: draft._type })
3693
3731
  ] }, draft.draftId)) }) }) }),
@@ -3731,16 +3769,28 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3731
3769
  ] }) }),
3732
3770
  /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3733
3771
  /* @__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
- )
3772
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
3773
+ /* @__PURE__ */ jsx(
3774
+ Button,
3775
+ {
3776
+ mode: "ghost",
3777
+ text: "Rescan",
3778
+ icon: SyncIcon,
3779
+ onClick: handleRescan,
3780
+ disabled: isLoading
3781
+ }
3782
+ ),
3783
+ /* @__PURE__ */ jsx(
3784
+ Button,
3785
+ {
3786
+ mode: "ghost",
3787
+ tone: "positive",
3788
+ text: isLoading ? "Checking\u2026" : "Start Translation",
3789
+ onClick: handleStartTranslation,
3790
+ disabled: !canStartTranslation
3791
+ }
3792
+ )
3793
+ ] })
3744
3794
  ] })
3745
3795
  ] });
3746
3796
  }, ActionButtonBar = ({ setIsDialogOpen }) => {