@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 +96 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
402
|
-
const
|
|
403
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
},
|
|
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:
|
|
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 =
|
|
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])
|
|
3532
|
+
}, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]);
|
|
3492
3533
|
useEffect(() => {
|
|
3493
|
-
|
|
3494
|
-
}, [
|
|
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]),
|
|
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__ */
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
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 }) => {
|