@braze/sanity-plugin-ai-translation 0.2.78 → 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,16 +1,16 @@
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, SyncIcon, SearchIcon, ErrorOutlineIcon, CheckmarkIcon, CircleIcon, ArrowRightIcon, ResetIcon, PanelRightIcon, 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
- import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Inline, Checkbox, Grid, Radio, TextInput, Badge, Select, Label, Code, Tooltip } from "@sanity/ui";
6
+ import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Badge, Inline, Radio, Checkbox, Grid, TextInput, Select, Label, Code, Tooltip } from "@sanity/ui";
7
+ import { IntentLink } from "sanity/router";
8
+ import { useOpenInNewPane, useListeningQuery } from "sanity-plugin-utils";
7
9
  import { toPlainText } from "@portabletext/react";
8
10
  import { uuid } from "@sanity/uuid";
9
11
  import cloneDeep from "lodash.clonedeep";
10
12
  import get from "lodash.get";
11
13
  import set from "lodash.set";
12
- import { useListeningQuery, useOpenInNewPane } from "sanity-plugin-utils";
13
- import { IntentLink } from "sanity/router";
14
14
  const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === "true" || process.env.SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === "true", devLog = {
15
15
  info: (message, context) => {
16
16
  isLoggingEnabled() && console.info("[AI Translation]", message, context ? JSON.stringify(context, null, 2) : "");
@@ -123,6 +123,9 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
123
123
  mode: "new",
124
124
  newReleaseTitle: ""
125
125
  },
126
+ hasDraft: !1,
127
+ hasPublished: !1,
128
+ documentSource: "draft",
126
129
  reconcilingStatus: "idle",
127
130
  reconciliationProgress: void 0,
128
131
  dispatch: () => {
@@ -229,10 +232,21 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
229
232
  ...context,
230
233
  reconciliationProgress: action.payload
231
234
  };
235
+ case "SET_DOCUMENT_SOURCE":
236
+ return {
237
+ ...context,
238
+ documentSource: action.payload
239
+ };
232
240
  default:
233
241
  return context;
234
242
  }
235
- }, generateDefaultReleaseTitle = (document) => `[AI Translation] ${document.title || document.name || document._id.split("-").slice(0, 2).join("-")}`, TranslationContextProvider = ({ document, config, children }) => {
243
+ }, generateDefaultReleaseTitle = (document) => `[AI Translation] ${document.title || document.name || document._id.split("-").slice(0, 2).join("-")}`, TranslationContextProvider = ({
244
+ document,
245
+ config,
246
+ hasDraft,
247
+ hasPublished,
248
+ children
249
+ }) => {
236
250
  devLog.debug("TranslationContextProvider initialized", {
237
251
  documentId: document._id,
238
252
  documentType: document._type,
@@ -258,6 +272,9 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
258
272
  mode: "new",
259
273
  newReleaseTitle: defaultReleaseTitle
260
274
  },
275
+ hasDraft,
276
+ hasPublished,
277
+ documentSource: "draft",
261
278
  // Reconciliation state for existing releases
262
279
  reconcilingStatus: "idle",
263
280
  reconciliationProgress: void 0
@@ -344,9 +361,114 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
344
361
  "useTranslationContext must be used within a TranslationContextProvider with document and config props"
345
362
  );
346
363
  return context;
347
- }, getExistingTranslationId = (translationMetadata, targetLanguage) => translationMetadata?.translations?.length && translationMetadata.translations.find(
364
+ }, checkSourceDrafts = async ({
365
+ client,
366
+ referenceIds
367
+ }) => {
368
+ if (referenceIds.length === 0)
369
+ return [];
370
+ const publishedIds = referenceIds.map((id) => getPublishedId(id)), draftIds = publishedIds.map((id) => `drafts.${id}`);
371
+ devLog.debug("Checking for unpublished changes on source references", {
372
+ referenceCount: publishedIds.length
373
+ });
374
+ const drafts = await client.fetch(
375
+ "*[_id in $draftIds]{ _id, _type, title, name }",
376
+ { draftIds }
377
+ );
378
+ if (drafts.length === 0)
379
+ return devLog.debug("No source references have unpublished changes"), [];
380
+ const results = drafts.map((d) => ({
381
+ publishedId: getPublishedId(d._id),
382
+ _type: d._type,
383
+ title: d.title || d.name
384
+ }));
385
+ return devLog.debug("Source references with unpublished changes", {
386
+ count: results.length
387
+ }), results;
388
+ }, TRANSLATION_METADATA_TYPE = "translation.metadata", getExistingTranslationId = (translationMetadata, targetLanguage) => translationMetadata?.translations?.length && translationMetadata.translations.find(
348
389
  (translation) => translation._key === targetLanguage
349
- )?.value._ref || null, TRANSLATION_METADATA_TYPE = "translation.metadata", getTranslationMetadata = async ({
390
+ )?.value._ref || null, checkTargetDrafts = async ({
391
+ client,
392
+ translationMetadata,
393
+ selectedTargetLanguages,
394
+ sourceDocumentId,
395
+ references
396
+ }) => {
397
+ const targetEntries = [];
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
+ }
433
+ }
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)
436
+ return devLog.debug("No existing translations found for selected languages", {
437
+ sourceDocumentId,
438
+ selectedTargetLanguages
439
+ }), [];
440
+ const draftIds = deduped.map((e) => `drafts.${e.targetId}`);
441
+ devLog.debug("Checking for stale drafts on target documents", {
442
+ sourceDocumentId,
443
+ draftIdsToCheck: draftIds.length,
444
+ referenceTargets: deduped.filter((e) => e.isReference).length
445
+ });
446
+ const drafts = await client.fetch(
447
+ "*[_id in $draftIds]{ _id, _type, title, name }",
448
+ { draftIds }
449
+ );
450
+ if (drafts.length === 0)
451
+ return devLog.debug("No stale drafts found", { sourceDocumentId }), [];
452
+ const draftMap = new Map(drafts.map((d) => [d._id, d])), results = [];
453
+ for (const entry of deduped) {
454
+ const draftId = `drafts.${entry.targetId}`, draft = draftMap.get(draftId);
455
+ draft && results.push({
456
+ targetId: entry.targetId,
457
+ draftId,
458
+ _type: draft._type,
459
+ title: draft.title || draft.name,
460
+ sourceDocumentId: entry.sourceDocumentId,
461
+ targetLanguage: entry.targetLanguage,
462
+ isReference: entry.isReference || void 0
463
+ });
464
+ }
465
+ return devLog.info("Stale drafts detected on target documents", {
466
+ sourceDocumentId,
467
+ staleDraftCount: results.length,
468
+ referenceDraftCount: results.filter((r) => r.isReference).length,
469
+ affectedLanguages: results.map((r) => r.targetLanguage)
470
+ }), results;
471
+ }, getTranslationMetadata = async ({
350
472
  client,
351
473
  sourceDocument,
352
474
  sourceLanguage
@@ -3001,6 +3123,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3001
3123
  excludeFields,
3002
3124
  unsetFields,
3003
3125
  releaseConfig,
3126
+ sourcePerspective,
3004
3127
  telemetryConfig
3005
3128
  }) => {
3006
3129
  const batchStartTime = Date.now(), logContext = {
@@ -3104,7 +3227,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3104
3227
  try {
3105
3228
  await translationSemaphore.withPermit(async () => {
3106
3229
  const document = await client.withConfig({
3107
- perspective: "drafts"
3230
+ perspective: sourcePerspective ?? "drafts"
3108
3231
  }).getDocument(documentId);
3109
3232
  if (document || devLog.error("\u274C SOURCE DOCUMENT NOT FOUND", { documentId }), document && releaseId) {
3110
3233
  const versionedTargetDocumentId = await translateDocument({
@@ -3275,24 +3398,189 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3275
3398
  ] })
3276
3399
  ] })
3277
3400
  ] }) });
3278
- }, ActionButtonBar = ({ setIsDialogOpen, hasValidationErrors }) => {
3401
+ }, DocumentOpenerButton = ({ documentId, documentType }) => {
3402
+ const publishedId = getPublishedId(documentId), openInNewPane = useOpenInNewPane(publishedId, documentType);
3403
+ return /* @__PURE__ */ jsx(
3404
+ Button,
3405
+ {
3406
+ mode: "bleed",
3407
+ icon: PanelRightIcon,
3408
+ onClick: () => openInNewPane(),
3409
+ "aria-label": "Open document",
3410
+ fontSize: 1,
3411
+ padding: 2
3412
+ }
3413
+ );
3414
+ }, VALIDATION_TIMEOUT_MS = 2e3, DocumentValidator = ({ documentId, documentType, onResult }) => {
3415
+ const publishedId = getPublishedId(documentId), { isValidating, validation } = useValidationStatus(publishedId, documentType), hasReceivedRealResult = useRef(!1), hasSettled = useRef(!1), [timedOut, setTimedOut] = useState(!1);
3416
+ return useEffect(() => {
3417
+ const timer = setTimeout(() => setTimedOut(!0), VALIDATION_TIMEOUT_MS);
3418
+ return () => clearTimeout(timer);
3419
+ }, []), useEffect(() => {
3420
+ if (hasSettled.current) return;
3421
+ (isValidating || validation.length > 0) && (hasReceivedRealResult.current = !0);
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,
3425
+ errors
3426
+ });
3427
+ }, [isValidating, validation, documentId, onResult, timedOut]), null;
3428
+ }, PreflightView = () => {
3279
3429
  const {
3280
3430
  document,
3281
3431
  config,
3282
- overallStatus,
3283
3432
  dispatch,
3284
3433
  references,
3285
3434
  selectedReferenceIds,
3286
3435
  selectedTargetLanguages,
3436
+ shouldTranslateReferences,
3437
+ translationMetadata,
3287
3438
  translationMode,
3288
- releaseConfig
3289
- } = useTranslationContext(), client = useClient({ apiVersion: "vX" }), [conflictContext, setConflictContext] = useState(null), [isCheckingConflicts, setIsCheckingConflicts] = useState(!1), filteredReferences = references.filter((ref) => selectedReferenceIds.has(ref._id)), totalTranslations = useMemo(() => (1 + filteredReferences.length) * selectedTargetLanguages.length, [filteredReferences.length, selectedTargetLanguages.length]);
3439
+ releaseConfig,
3440
+ documentSource
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(() => {
3442
+ const docs = [
3443
+ {
3444
+ _id: document._id,
3445
+ _type: document._type,
3446
+ title: document.title || document.name || document._id
3447
+ }
3448
+ ];
3449
+ if (shouldTranslateReferences)
3450
+ for (const ref of references)
3451
+ selectedReferenceIds.has(ref._id) && docs.push(ref);
3452
+ return docs;
3453
+ }, [document, references, selectedReferenceIds, shouldTranslateReferences]), handleValidationResult = useCallback((id, result) => {
3454
+ setValidationResults((prev) => {
3455
+ const next = new Map(prev);
3456
+ return next.set(id, result), next;
3457
+ });
3458
+ }, []), { hasValidationErrors, someStillValidating, documentsWithErrors } = useMemo(() => {
3459
+ const allValidated = documentsToValidate.every((doc) => validationResults.has(doc._id)), validating = [...validationResults.values()].some((r) => r.isValidating) || !allValidated, docsWithErrors = [];
3460
+ for (const doc of documentsToValidate) {
3461
+ const result = validationResults.get(doc._id);
3462
+ result && result.errors.length > 0 && docsWithErrors.push({ ...doc, errors: result.errors });
3463
+ }
3464
+ return {
3465
+ hasValidationErrors: docsWithErrors.length > 0,
3466
+ someStillValidating: validating,
3467
+ documentsWithErrors: docsWithErrors
3468
+ };
3469
+ }, [documentsToValidate, validationResults]), runSourceDraftCheck = useCallback(async () => {
3470
+ if (filteredReferences.length === 0) {
3471
+ setIsSourceDraftCheckLoading(!1);
3472
+ return;
3473
+ }
3474
+ setIsSourceDraftCheckLoading(!0);
3475
+ try {
3476
+ const results = await checkSourceDrafts({
3477
+ client,
3478
+ referenceIds: filteredReferences.map((ref) => ref._id)
3479
+ });
3480
+ setSourceRefsWithDrafts(results);
3481
+ } catch (error) {
3482
+ devLog.error(
3483
+ "Failed to check source reference drafts",
3484
+ error instanceof Error ? error : new Error(String(error)),
3485
+ { component: "PreflightView" }
3486
+ );
3487
+ } finally {
3488
+ setIsSourceDraftCheckLoading(!1);
3489
+ }
3490
+ }, [client, filteredReferences]), runDraftCheck = useCallback(async () => {
3491
+ setIsDraftCheckLoading(!0);
3492
+ try {
3493
+ const drafts = await checkTargetDrafts({
3494
+ client,
3495
+ translationMetadata,
3496
+ selectedTargetLanguages,
3497
+ sourceDocumentId: document._id,
3498
+ references: shouldTranslateReferences ? filteredReferences : void 0
3499
+ });
3500
+ setStaleDrafts(drafts), drafts.length === 0 && setDraftAction("none");
3501
+ } catch (error) {
3502
+ devLog.error("Failed to check target drafts", error instanceof Error ? error : new Error(String(error)), {
3503
+ sourceDocumentId: document._id,
3504
+ component: "PreflightView"
3505
+ });
3506
+ } finally {
3507
+ setIsDraftCheckLoading(!1);
3508
+ }
3509
+ }, [client, translationMetadata, selectedTargetLanguages, document._id, shouldTranslateReferences, filteredReferences]), runConflictCheck = useCallback(async () => {
3510
+ if (releaseConfig.mode === "new") {
3511
+ setIsCheckingConflicts(!1), setConflictContext(null);
3512
+ return;
3513
+ }
3514
+ if (releaseConfig.mode === "existing" && releaseConfig.existingReleaseId) {
3515
+ setIsCheckingConflicts(!0);
3516
+ const checkedReleaseId = releaseConfig.existingReleaseId, foundConflicts = await checkReleaseConflicts({
3517
+ client,
3518
+ releaseId: checkedReleaseId,
3519
+ sourceDocument: document,
3520
+ targetLanguages: selectedTargetLanguages,
3521
+ languageFieldName: config.languageFieldName,
3522
+ references: filteredReferences
3523
+ });
3524
+ foundConflicts.length > 0 ? setConflictContext({ releaseId: checkedReleaseId, conflicts: foundConflicts }) : setConflictContext(null), setIsCheckingConflicts(!1);
3525
+ }
3526
+ }, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]);
3290
3527
  useEffect(() => {
3291
- releaseConfig.mode === "new" && conflictContext && setConflictContext(null);
3292
- }, [releaseConfig.mode, conflictContext]);
3293
- const startTranslation = useCallback(
3528
+ runSourceDraftCheck(), runDraftCheck(), runConflictCheck();
3529
+ }, [scanKey]);
3530
+ const handleDiscardDrafts = useCallback(async () => {
3531
+ setIsDraftActionInProgress(!0);
3532
+ try {
3533
+ const draftIds = staleDrafts.map((d) => d.draftId);
3534
+ devLog.info("Discarding stale drafts", { draftIds, component: "PreflightView" });
3535
+ const transaction = client.transaction();
3536
+ for (const id of draftIds)
3537
+ transaction.delete(id);
3538
+ await transaction.commit(), devLog.info("Successfully discarded stale drafts", {
3539
+ count: draftIds.length,
3540
+ component: "PreflightView"
3541
+ }), await runDraftCheck();
3542
+ } catch (error) {
3543
+ devLog.error("Failed to discard drafts", error instanceof Error ? error : new Error(String(error)), {
3544
+ component: "PreflightView"
3545
+ });
3546
+ } finally {
3547
+ setIsDraftActionInProgress(!1);
3548
+ }
3549
+ }, [staleDrafts, client, runDraftCheck]), handlePublishDrafts = useCallback(async () => {
3550
+ setIsDraftActionInProgress(!0);
3551
+ try {
3552
+ devLog.info("Publishing stale drafts", {
3553
+ count: staleDrafts.length,
3554
+ component: "PreflightView"
3555
+ });
3556
+ for (const draft of staleDrafts)
3557
+ await client.request({
3558
+ method: "POST",
3559
+ uri: "/data/actions/mutate",
3560
+ body: {
3561
+ actions: [
3562
+ {
3563
+ actionType: "sanity.action.document.publish",
3564
+ draftId: draft.draftId,
3565
+ publishedId: draft.targetId
3566
+ }
3567
+ ]
3568
+ }
3569
+ });
3570
+ devLog.info("Successfully published stale drafts", {
3571
+ count: staleDrafts.length,
3572
+ component: "PreflightView"
3573
+ }), await runDraftCheck();
3574
+ } catch (error) {
3575
+ devLog.error("Failed to publish drafts", error instanceof Error ? error : new Error(String(error)), {
3576
+ component: "PreflightView"
3577
+ });
3578
+ } finally {
3579
+ setIsDraftActionInProgress(!1);
3580
+ }
3581
+ }, [staleDrafts, client, runDraftCheck]), startTranslation = useCallback(
3294
3582
  (replaceExisting, overrideReleaseId, skipConflicts) => {
3295
- setConflictContext(null), translateDocumentSet({
3583
+ translateDocumentSet({
3296
3584
  sourceDocument: document,
3297
3585
  targetLanguages: selectedTargetLanguages,
3298
3586
  client,
@@ -3310,10 +3598,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3310
3598
  ...releaseConfig,
3311
3599
  replaceExisting,
3312
3600
  skipConflicts,
3313
- // Use the override release ID if provided (from conflict context),
3314
- // otherwise use the current releaseConfig value
3315
3601
  existingReleaseId: overrideReleaseId ?? releaseConfig.existingReleaseId
3316
3602
  },
3603
+ sourcePerspective: documentSource === "published" ? "published" : "drafts",
3317
3604
  telemetryConfig: config.telemetry
3318
3605
  });
3319
3606
  },
@@ -3322,58 +3609,194 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3322
3609
  selectedTargetLanguages,
3323
3610
  client,
3324
3611
  dispatch,
3325
- config.languageFieldName,
3326
- config.schemaId,
3327
- config.maxConcurrentTranslationJobs,
3328
- config.maxReferenceDepth,
3329
- config.openai,
3330
- config.excludeFields,
3331
- config.unsetFields,
3332
- config.telemetry,
3612
+ config,
3333
3613
  filteredReferences,
3334
3614
  translationMode,
3335
- releaseConfig
3615
+ releaseConfig,
3616
+ documentSource
3336
3617
  ]
3337
- ), handleTranslate = useCallback(async () => {
3338
- if (releaseConfig.mode === "new") {
3339
- startTranslation(!1);
3340
- return;
3341
- }
3342
- if (releaseConfig.mode === "existing" && releaseConfig.existingReleaseId) {
3343
- const checkedReleaseId = releaseConfig.existingReleaseId;
3344
- setIsCheckingConflicts(!0);
3345
- const foundConflicts = await checkReleaseConflicts({
3346
- client,
3347
- releaseId: checkedReleaseId,
3348
- sourceDocument: document,
3349
- targetLanguages: selectedTargetLanguages,
3350
- languageFieldName: config.languageFieldName,
3351
- references: filteredReferences
3352
- });
3353
- if (setIsCheckingConflicts(!1), foundConflicts.length > 0) {
3354
- setConflictContext({
3355
- releaseId: checkedReleaseId,
3356
- conflicts: foundConflicts
3357
- });
3358
- return;
3359
- }
3360
- }
3361
- startTranslation(!1);
3362
- }, [
3363
- releaseConfig,
3364
- client,
3365
- document,
3366
- selectedTargetLanguages,
3367
- config.languageFieldName,
3368
- startTranslation,
3369
- filteredReferences
3370
- ]), handleCancelConflict = useCallback(() => {
3618
+ ), handleStartTranslation = useCallback(() => {
3619
+ conflictContext || startTranslation(!1);
3620
+ }, [conflictContext, startTranslation]), handleBack = useCallback(() => {
3621
+ dispatch({ type: "SET_OVERALL_STATUS", payload: "idle" });
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(
3625
+ () => new Set(sourceRefsWithDrafts.map((d) => d.publishedId)),
3626
+ [sourceRefsWithDrafts]
3627
+ ), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleCancelConflict = useCallback(() => {
3371
3628
  setConflictContext(null);
3372
3629
  }, []), handleReplaceConflict = useCallback(() => {
3373
3630
  conflictContext && startTranslation(!0, conflictContext.releaseId);
3374
3631
  }, [startTranslation, conflictContext]), handleSkipConflicts = useCallback(() => {
3375
3632
  conflictContext && startTranslation(!1, conflictContext.releaseId, conflictContext.conflicts);
3376
- }, [startTranslation, conflictContext]), handleSelectAll = useCallback(() => {
3633
+ }, [startTranslation, conflictContext]), totalTranslations = useMemo(() => (1 + filteredReferences.length) * selectedTargetLanguages.length, [filteredReferences.length, selectedTargetLanguages.length]);
3634
+ return conflictContext ? /* @__PURE__ */ jsx(Stack, { space: 4, children: /* @__PURE__ */ jsx(
3635
+ ReleaseConflictWarning,
3636
+ {
3637
+ conflicts: conflictContext.conflicts,
3638
+ totalTranslations,
3639
+ onCancel: handleCancelConflict,
3640
+ onReplace: handleReplaceConflict,
3641
+ onSkipConflicts: handleSkipConflicts
3642
+ }
3643
+ ) }) : /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3644
+ documentsToValidate.map((doc) => /* @__PURE__ */ jsx(
3645
+ DocumentValidator,
3646
+ {
3647
+ documentId: doc._id,
3648
+ documentType: doc._type,
3649
+ onResult: handleValidationResult
3650
+ },
3651
+ `${doc._id}-${scanKey}`
3652
+ )),
3653
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", size: 2, children: "Pre-flight Checks" }),
3654
+ /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
3655
+ "Using ",
3656
+ documentSource,
3657
+ " version of all documents \u2192 ",
3658
+ selectedTargetLanguages.length,
3659
+ " language",
3660
+ selectedTargetLanguages.length !== 1 ? "s" : "",
3661
+ " \xB7 ",
3662
+ 1 + filteredReferences.length,
3663
+ " document",
3664
+ filteredReferences.length !== 0 ? "s" : ""
3665
+ ] }),
3666
+ /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasValidationErrors ? "critical" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3667
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3668
+ someStillValidating ? /* @__PURE__ */ jsx(Spinner, { muted: !0 }) : hasValidationErrors ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }) : /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }),
3669
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: someStillValidating ? "Validating documents\u2026" : hasValidationErrors ? `Validation errors in ${documentsWithErrors.length} document${documentsWithErrors.length !== 1 ? "s" : ""}` : "All documents pass validation" })
3670
+ ] }),
3671
+ documentsWithErrors.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
3672
+ /* @__PURE__ */ jsx(Text, { size: 1, children: "Documents must pass validation before they can be translated. Click a document to open it and fix the errors." }),
3673
+ /* @__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: documentsWithErrors.map((doc) => {
3674
+ const publishedId = getPublishedId(doc._id), displayName = doc._id === document._id ? "This document" : doc.title || doc._id, hasUnpublishedChanges = sourceRefDraftIds.has(publishedId);
3675
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3676
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3677
+ /* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(
3678
+ IntentLink,
3679
+ {
3680
+ intent: "edit",
3681
+ params: { id: publishedId, type: doc._type },
3682
+ style: { color: "inherit" },
3683
+ children: displayName
3684
+ }
3685
+ ) }),
3686
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: doc._type }),
3687
+ hasUnpublishedChanges && /* @__PURE__ */ jsx(Badge, { tone: "caution", fontSize: 0, children: "unpublished changes" })
3688
+ ] }),
3689
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3690
+ /* @__PURE__ */ jsxs(Badge, { tone: "critical", fontSize: 0, children: [
3691
+ doc.errors.length,
3692
+ " error",
3693
+ doc.errors.length !== 1 ? "s" : ""
3694
+ ] }),
3695
+ /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: doc._id, documentType: doc._type })
3696
+ ] })
3697
+ ] }, doc._id);
3698
+ }) }) }) })
3699
+ ] })
3700
+ ] }) }),
3701
+ filteredReferences.length > 0 && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: sourceRefsWithDrafts.length > 0 ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3702
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3703
+ isSourceDraftCheckLoading ? /* @__PURE__ */ jsx(Spinner, { muted: !0 }) : sourceRefsWithDrafts.length > 0 ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(WarningOutlineIcon, {}) }) : /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }),
3704
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: isSourceDraftCheckLoading ? "Checking referenced documents\u2026" : sourceRefsWithDrafts.length > 0 ? `${sourceRefsWithDrafts.length} referenced document${sourceRefsWithDrafts.length !== 1 ? "s have" : " has"} unpublished changes` : "All referenced documents are in sync" })
3705
+ ] }),
3706
+ sourceRefsWithDrafts.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
3707
+ /* @__PURE__ */ jsx(Text, { size: 1, children: documentSource === "draft" ? 'You are translating from drafts. These unpublished changes will be included in the translation. If this is not intended, go back and switch to "Translate from published".' : 'You are translating from published. Unpublished changes on these documents will be ignored. If you want to include them, go back and switch to "Translate from drafts".' }),
3708
+ /* @__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: sourceRefsWithDrafts.map((ref) => /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3709
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3710
+ /* @__PURE__ */ jsx(Text, { size: 1, children: ref.title || ref.publishedId }),
3711
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: ref._type })
3712
+ ] }),
3713
+ /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: ref.publishedId, documentType: ref._type })
3714
+ ] }, ref.publishedId)) }) }) })
3715
+ ] })
3716
+ ] }) }),
3717
+ (isDraftCheckLoading || hasStaleDrafts) && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasStaleDrafts && !draftsDismissed ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3718
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3719
+ isDraftCheckLoading ? /* @__PURE__ */ jsx(Spinner, { muted: !0 }) : draftsDismissed ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(CheckmarkIcon, {}) }) : /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(WarningOutlineIcon, {}) }),
3720
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: isDraftCheckLoading ? "Checking for stale drafts on target documents\u2026" : draftsDismissed ? "Stale drafts acknowledged" : `${staleDrafts.length} stale draft${staleDrafts.length !== 1 ? "s" : ""} found on target documents` })
3721
+ ] }),
3722
+ hasStaleDrafts && !draftsDismissed && /* @__PURE__ */ jsxs(Fragment, { children: [
3723
+ /* @__PURE__ */ jsx(Text, { size: 1, children: "These target documents have unpublished drafts that may contain outdated content. Close and reopen the dialog after resolving, or use the actions below." }),
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: [
3725
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3726
+ /* @__PURE__ */ jsx(Text, { size: 1, children: draft.title || draft.targetId }),
3727
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage }),
3728
+ draft.isReference && /* @__PURE__ */ jsx(Badge, { fontSize: 0, children: "reference" })
3729
+ ] }),
3730
+ /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: draft.targetId, documentType: draft._type })
3731
+ ] }, draft.draftId)) }) }) }),
3732
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
3733
+ /* @__PURE__ */ jsx(
3734
+ Button,
3735
+ {
3736
+ mode: "ghost",
3737
+ tone: "caution",
3738
+ text: "Discard All Drafts",
3739
+ icon: CloseIcon,
3740
+ onClick: handleDiscardDrafts,
3741
+ disabled: isDraftActionInProgress
3742
+ }
3743
+ ),
3744
+ /* @__PURE__ */ jsx(
3745
+ Button,
3746
+ {
3747
+ mode: "ghost",
3748
+ tone: "caution",
3749
+ text: "Publish All Drafts",
3750
+ onClick: handlePublishDrafts,
3751
+ disabled: isDraftActionInProgress
3752
+ }
3753
+ ),
3754
+ /* @__PURE__ */ jsx(
3755
+ Button,
3756
+ {
3757
+ mode: "ghost",
3758
+ text: "Continue Anyway",
3759
+ onClick: () => setDraftAction("continue"),
3760
+ disabled: isDraftActionInProgress
3761
+ }
3762
+ )
3763
+ ] }),
3764
+ isDraftActionInProgress && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3765
+ /* @__PURE__ */ jsx(Spinner, { muted: !0 }),
3766
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Processing\u2026" })
3767
+ ] })
3768
+ ] })
3769
+ ] }) }),
3770
+ /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3771
+ /* @__PURE__ */ jsx(Button, { mode: "ghost", text: "Back", onClick: handleBack }),
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
+ ] })
3794
+ ] })
3795
+ ] });
3796
+ }, ActionButtonBar = ({ setIsDialogOpen }) => {
3797
+ const { document, config, overallStatus, dispatch, selectedTargetLanguages, releaseConfig } = useTranslationContext(), handleTranslate = useCallback(() => {
3798
+ dispatch({ type: "SET_OVERALL_STATUS", payload: "preflight" });
3799
+ }, [dispatch]), handleSelectAll = useCallback(() => {
3377
3800
  dispatch({
3378
3801
  type: "SET_SELECTED_TARGET_LANGUAGES",
3379
3802
  payload: config.supportedLanguages.map((lang) => lang.id).filter((id) => id !== document.language)
@@ -3384,16 +3807,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3384
3807
  payload: []
3385
3808
  });
3386
3809
  }, [dispatch]);
3387
- return conflictContext ? /* @__PURE__ */ jsx(Stack, { space: 4, children: /* @__PURE__ */ jsx(
3388
- ReleaseConflictWarning,
3389
- {
3390
- conflicts: conflictContext.conflicts,
3391
- totalTranslations,
3392
- onCancel: handleCancelConflict,
3393
- onReplace: handleReplaceConflict,
3394
- onSkipConflicts: handleSkipConflicts
3395
- }
3396
- ) }) : /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3810
+ return /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3397
3811
  /* @__PURE__ */ jsxs(Flex, { justify: "flex-start", gap: 2, children: [
3398
3812
  /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 2, onClick: handleSelectAll, children: "Select All" }),
3399
3813
  /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 2, onClick: handleSelectNone, children: "Select None" })
@@ -3407,12 +3821,61 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3407
3821
  tone: "positive",
3408
3822
  size: 2,
3409
3823
  onClick: handleTranslate,
3410
- disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId || isCheckingConflicts || hasValidationErrors,
3411
- children: isCheckingConflicts ? "Checking..." : "Translate"
3824
+ disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId,
3825
+ children: "Translate"
3412
3826
  }
3413
3827
  )
3414
3828
  ] })
3415
3829
  ] });
3830
+ }, DocumentSourcePanel = () => {
3831
+ const { hasDraft, hasPublished, documentSource, dispatch } = useTranslationContext();
3832
+ if (!hasDraft)
3833
+ return null;
3834
+ if (!hasPublished)
3835
+ return /* @__PURE__ */ jsx(Card, { tone: "transparent", padding: 4, border: !0, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This document has not been published yet. The current draft will be used for translation, along with drafts of any referenced documents." }) });
3836
+ const handleSourceChange = (source) => {
3837
+ dispatch({
3838
+ type: "SET_DOCUMENT_SOURCE",
3839
+ payload: source
3840
+ });
3841
+ };
3842
+ return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3843
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "This document has unpublished changes" }),
3844
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3845
+ /* @__PURE__ */ jsx(
3846
+ Button,
3847
+ {
3848
+ type: "button",
3849
+ mode: documentSource === "draft" ? "ghost" : "bleed",
3850
+ tone: documentSource === "draft" ? "primary" : "default",
3851
+ onClick: () => handleSourceChange("draft"),
3852
+ children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3853
+ /* @__PURE__ */ jsx(Radio, { checked: documentSource === "draft", readOnly: !0 }),
3854
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3855
+ /* @__PURE__ */ jsx(Text, { children: "Translate from drafts" }),
3856
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses the latest draft of this document and all referenced documents" })
3857
+ ] })
3858
+ ] })
3859
+ }
3860
+ ),
3861
+ /* @__PURE__ */ jsx(
3862
+ Button,
3863
+ {
3864
+ type: "button",
3865
+ mode: documentSource === "published" ? "ghost" : "bleed",
3866
+ tone: documentSource === "published" ? "primary" : "default",
3867
+ onClick: () => handleSourceChange("published"),
3868
+ children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3869
+ /* @__PURE__ */ jsx(Radio, { checked: documentSource === "published", readOnly: !0 }),
3870
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3871
+ /* @__PURE__ */ jsx(Text, { children: "Translate from published" }),
3872
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses only published content for this document and all referenced documents" })
3873
+ ] })
3874
+ ] })
3875
+ }
3876
+ )
3877
+ ] })
3878
+ ] }) });
3416
3879
  }, TRANSLATION_METADATA_QUERY = "*[_type == $translationMetadataType && references($documentId)][0]", useTranslationMetadata = ({ documentId }) => {
3417
3880
  const { dispatch } = useTranslationContext(), { data, loading, error } = useListeningQuery(TRANSLATION_METADATA_QUERY, {
3418
3881
  params: {
@@ -3855,108 +4318,6 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3855
4318
  )
3856
4319
  ] })
3857
4320
  ] }) });
3858
- }, DocumentValidator = ({ documentId, documentType, onResult }) => {
3859
- const publishedId = getPublishedId(documentId), { isValidating, validation } = useValidationStatus(publishedId, documentType), hasReceivedRealResult = useRef(!1);
3860
- return useEffect(() => {
3861
- (isValidating || validation.length > 0) && (hasReceivedRealResult.current = !0);
3862
- const stillWaitingForFirstResult = !hasReceivedRealResult.current, errors = validation.filter((v) => v.level === "error");
3863
- onResult(documentId, {
3864
- isValidating: isValidating || stillWaitingForFirstResult,
3865
- errors
3866
- });
3867
- }, [isValidating, validation, documentId, onResult]), null;
3868
- }, ValidationPanel = ({ onValidationChange }) => {
3869
- const { document, references, selectedReferenceIds, shouldTranslateReferences } = useTranslationContext(), [results, setResults] = useState(/* @__PURE__ */ new Map()), documentsToValidate = useMemo(() => {
3870
- const docs = [
3871
- {
3872
- _id: document._id,
3873
- _type: document._type,
3874
- title: document.title || document.name || document._id
3875
- }
3876
- ];
3877
- if (shouldTranslateReferences)
3878
- for (const ref of references)
3879
- selectedReferenceIds.has(ref._id) && docs.push(ref);
3880
- return docs;
3881
- }, [document, references, selectedReferenceIds, shouldTranslateReferences]), handleResult = useCallback((id, result) => {
3882
- setResults((prev) => {
3883
- const next = new Map(prev);
3884
- return next.set(id, result), next;
3885
- });
3886
- }, []), { shouldBlock, someStillValidating, documentsWithErrors } = useMemo(() => {
3887
- const allValidated = documentsToValidate.every((doc) => results.has(doc._id)), validating = [...results.values()].some((r) => r.isValidating) || !allValidated, docsWithErrors = [];
3888
- for (const doc of documentsToValidate) {
3889
- const result = results.get(doc._id);
3890
- result && result.errors.length > 0 && docsWithErrors.push({ ...doc, errors: result.errors });
3891
- }
3892
- const hasErrors = docsWithErrors.length > 0;
3893
- return {
3894
- shouldBlock: validating || hasErrors,
3895
- someStillValidating: validating,
3896
- documentsWithErrors: docsWithErrors
3897
- };
3898
- }, [documentsToValidate, results]);
3899
- useEffect(() => {
3900
- onValidationChange(shouldBlock);
3901
- }, [shouldBlock, onValidationChange]), useEffect(() => {
3902
- documentsWithErrors.length > 0 && devLog.warn("Documents have validation errors that block translation", {
3903
- documentsWithErrors: documentsWithErrors.map((d) => ({
3904
- id: d._id,
3905
- type: d._type,
3906
- errorCount: d.errors.length
3907
- })),
3908
- component: "ValidationPanel"
3909
- });
3910
- }, [documentsWithErrors]);
3911
- const totalErrors = useMemo(
3912
- () => documentsWithErrors.reduce((sum, doc) => sum + doc.errors.length, 0),
3913
- [documentsWithErrors]
3914
- );
3915
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3916
- documentsToValidate.map((doc) => /* @__PURE__ */ jsx(DocumentValidator, { documentId: doc._id, documentType: doc._type, onResult: handleResult }, doc._id)),
3917
- someStillValidating && documentsWithErrors.length === 0 && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: 2, children: [
3918
- /* @__PURE__ */ jsx(Spinner, { muted: !0 }),
3919
- /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Validating documents\u2026" })
3920
- ] }),
3921
- documentsWithErrors.length > 0 && /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3922
- /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3923
- /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }),
3924
- /* @__PURE__ */ jsxs(Text, { weight: "semibold", children: [
3925
- totalErrors,
3926
- " validation error",
3927
- totalErrors !== 1 ? "s" : "",
3928
- " found in ",
3929
- documentsWithErrors.length,
3930
- " ",
3931
- "document",
3932
- documentsWithErrors.length !== 1 ? "s" : ""
3933
- ] })
3934
- ] }),
3935
- /* @__PURE__ */ jsx(Text, { size: 1, children: "Documents must pass validation before they can be translated. Click a document to open it and fix the errors." }),
3936
- /* @__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: documentsWithErrors.map((doc) => {
3937
- const publishedId = getPublishedId(doc._id);
3938
- return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3939
- /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3940
- /* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(
3941
- IntentLink,
3942
- {
3943
- intent: "edit",
3944
- params: { id: publishedId, type: doc._type },
3945
- style: { color: "inherit" },
3946
- children: doc.title || doc._id
3947
- }
3948
- ) }),
3949
- /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: doc._type })
3950
- ] }),
3951
- /* @__PURE__ */ jsxs(Badge, { tone: "critical", fontSize: 0, children: [
3952
- doc.errors.length,
3953
- " error",
3954
- doc.errors.length !== 1 ? "s" : ""
3955
- ] })
3956
- ] }, doc._id);
3957
- }) }) }) })
3958
- ] }) })
3959
- ] });
3960
4321
  }, assembleReferences = async ({
3961
4322
  client,
3962
4323
  sourceDocument,
@@ -4210,31 +4571,26 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4210
4571
  !isLoadingReferences && references.length > 0 && showSelectionPanel && /* @__PURE__ */ jsx(ReferenceSelectionPanel, {})
4211
4572
  ] }) : null
4212
4573
  ] });
4213
- }, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => {
4214
- const [hasValidationErrors, setHasValidationErrors] = useState(!0), handleValidationChange = useCallback((shouldBlock) => {
4215
- setHasValidationErrors(shouldBlock);
4216
- }, []);
4217
- return /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4218
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4219
- /* @__PURE__ */ jsx(Label, { children: "Languages" }),
4220
- /* @__PURE__ */ jsx(LanguageSelectionPanel, {})
4221
- ] }),
4222
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4223
- /* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
4224
- /* @__PURE__ */ jsx(TranslationModePanel, {})
4225
- ] }),
4226
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4227
- /* @__PURE__ */ jsx(Label, { children: "Content Release" }),
4228
- /* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
4229
- ] }),
4230
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4231
- /* @__PURE__ */ jsx(Label, { children: "Settings" }),
4232
- /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
4233
- ] }),
4234
- /* @__PURE__ */ jsx(ValidationPanel, { onValidationChange: handleValidationChange }),
4235
- /* @__PURE__ */ jsx(ActionButtonBar, { setIsDialogOpen, hasValidationErrors })
4236
- ] });
4237
- }, TERMINAL_STATUSES = ["completed", "skipped", "error"], TranslationStatusPanel = () => {
4574
+ }, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4575
+ /* @__PURE__ */ jsx(DocumentSourcePanel, {}),
4576
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4577
+ /* @__PURE__ */ jsx(Label, { children: "Languages" }),
4578
+ /* @__PURE__ */ jsx(LanguageSelectionPanel, {})
4579
+ ] }),
4580
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4581
+ /* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
4582
+ /* @__PURE__ */ jsx(TranslationModePanel, {})
4583
+ ] }),
4584
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4585
+ /* @__PURE__ */ jsx(Label, { children: "Content Release" }),
4586
+ /* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
4587
+ ] }),
4588
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4589
+ /* @__PURE__ */ jsx(Label, { children: "Settings" }),
4590
+ /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
4591
+ ] }),
4592
+ /* @__PURE__ */ jsx(ActionButtonBar, { setIsDialogOpen })
4593
+ ] }), TERMINAL_STATUSES = ["completed", "skipped", "error"], TranslationStatusPanel = () => {
4238
4594
  const { overallStatus, translationJobs, dispatch, document } = useTranslationContext(), headline = useMemo(() => overallStatus === "completed" ? "Translation completed" : overallStatus === "error" ? "Translation failed" : "Translation in progress", [overallStatus]), jobs = useMemo(() => Array.from(translationJobs.values()), [translationJobs]), isTerminalState = overallStatus === "completed" || overallStatus === "error", handleReset = useCallback(() => {
4239
4595
  forceCleanupActiveTranslation(document._id), dispatch({ type: "RESET_TRANSLATION_STATE" });
4240
4596
  }, [dispatch, document._id]);
@@ -4356,7 +4712,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4356
4712
  const { overallStatus, document } = useTranslationContext();
4357
4713
  return useEffect(() => {
4358
4714
  onStatusChange?.(overallStatus);
4359
- }, [overallStatus, onStatusChange]), STATES_IN_WHICH_TO_SHOW_CONFIG.includes(overallStatus) ? /* @__PURE__ */ jsx(TranslationConfigView, { setIsDialogOpen }) : STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS.includes(overallStatus) ? /* @__PURE__ */ jsx(TranslationStatusPanel, {}) : STATES_IN_WHICH_TO_SHOW_ERROR.includes(overallStatus) ? /* @__PURE__ */ jsx("div", { children: "Error occurred" }) : (devLog.warn("Unhandled translation dialog state", {
4715
+ }, [overallStatus, onStatusChange]), STATES_IN_WHICH_TO_SHOW_CONFIG.includes(overallStatus) ? /* @__PURE__ */ jsx(TranslationConfigView, { setIsDialogOpen }) : overallStatus === "preflight" ? /* @__PURE__ */ jsx(PreflightView, {}) : STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS.includes(overallStatus) ? /* @__PURE__ */ jsx(TranslationStatusPanel, {}) : STATES_IN_WHICH_TO_SHOW_ERROR.includes(overallStatus) ? /* @__PURE__ */ jsx("div", { children: "Error occurred" }) : (devLog.warn("Unhandled translation dialog state", {
4360
4716
  documentId: document._id,
4361
4717
  overallStatus
4362
4718
  }), /* @__PURE__ */ jsx("div", { children: "Unhandled overall state" }));
@@ -4396,7 +4752,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4396
4752
  type: "dialog",
4397
4753
  onClose: handleClose,
4398
4754
  header: "Translate Document",
4399
- content: /* @__PURE__ */ jsx(TranslationContextProvider, { document, config, children: /* @__PURE__ */ jsx(TranslationDialog, { setIsDialogOpen, onStatusChange: handleStatusChange }) })
4755
+ content: /* @__PURE__ */ jsx(
4756
+ TranslationContextProvider,
4757
+ {
4758
+ document,
4759
+ config,
4760
+ hasDraft: !!draft,
4761
+ hasPublished: !!published,
4762
+ children: /* @__PURE__ */ jsx(TranslationDialog, { setIsDialogOpen, onStatusChange: handleStatusChange })
4763
+ }
4764
+ )
4400
4765
  }
4401
4766
  } : (devLog.error("Missing config in TranslateAction"), null);
4402
4767
  }, translationGlossaryTerm = defineType({