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

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, PanelRightIcon, SyncIcon, 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,81 @@ 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;
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;
347
388
  }, 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
+ }) => {
396
+ if (!translationMetadata?.translations?.length)
397
+ return devLog.debug("No translation metadata \u2014 skipping draft check", {
398
+ sourceDocumentId
399
+ }), [];
400
+ const targetEntries = [];
401
+ for (const lang of selectedTargetLanguages) {
402
+ const targetId = getExistingTranslationId(translationMetadata, lang);
403
+ targetId && targetEntries.push({ targetId, targetLanguage: lang });
404
+ }
405
+ if (targetEntries.length === 0)
406
+ return devLog.debug("No existing translations found for selected languages", {
407
+ sourceDocumentId,
408
+ selectedTargetLanguages
409
+ }), [];
410
+ const draftIds = targetEntries.map((e) => `drafts.${e.targetId}`);
411
+ devLog.debug("Checking for stale drafts on target documents", {
412
+ sourceDocumentId,
413
+ draftIdsToCheck: draftIds.length
414
+ });
415
+ const drafts = await client.fetch(
416
+ "*[_id in $draftIds]{ _id, _type, title, name }",
417
+ { draftIds }
418
+ );
419
+ if (drafts.length === 0)
420
+ return devLog.debug("No stale drafts found", { sourceDocumentId }), [];
421
+ const draftMap = new Map(drafts.map((d) => [d._id, d])), results = [];
422
+ for (const entry of targetEntries) {
423
+ const draftId = `drafts.${entry.targetId}`, draft = draftMap.get(draftId);
424
+ draft && results.push({
425
+ targetId: entry.targetId,
426
+ draftId,
427
+ _type: draft._type,
428
+ title: draft.title || draft.name,
429
+ sourceDocumentId,
430
+ targetLanguage: entry.targetLanguage
431
+ });
432
+ }
433
+ return devLog.info("Stale drafts detected on target documents", {
434
+ sourceDocumentId,
435
+ staleDraftCount: results.length,
436
+ affectedLanguages: results.map((r) => r.targetLanguage)
437
+ }), results;
438
+ }, TRANSLATION_METADATA_TYPE = "translation.metadata", getTranslationMetadata = async ({
350
439
  client,
351
440
  sourceDocument,
352
441
  sourceLanguage
@@ -3001,6 +3090,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3001
3090
  excludeFields,
3002
3091
  unsetFields,
3003
3092
  releaseConfig,
3093
+ sourcePerspective,
3004
3094
  telemetryConfig
3005
3095
  }) => {
3006
3096
  const batchStartTime = Date.now(), logContext = {
@@ -3104,7 +3194,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3104
3194
  try {
3105
3195
  await translationSemaphore.withPermit(async () => {
3106
3196
  const document = await client.withConfig({
3107
- perspective: "drafts"
3197
+ perspective: sourcePerspective ?? "drafts"
3108
3198
  }).getDocument(documentId);
3109
3199
  if (document || devLog.error("\u274C SOURCE DOCUMENT NOT FOUND", { documentId }), document && releaseId) {
3110
3200
  const versionedTargetDocumentId = await translateDocument({
@@ -3275,24 +3365,187 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3275
3365
  ] })
3276
3366
  ] })
3277
3367
  ] }) });
3278
- }, ActionButtonBar = ({ setIsDialogOpen, hasValidationErrors }) => {
3368
+ }, DocumentOpenerButton = ({ documentId, documentType }) => {
3369
+ const publishedId = getPublishedId(documentId), openInNewPane = useOpenInNewPane(publishedId, documentType);
3370
+ return /* @__PURE__ */ jsx(
3371
+ Button,
3372
+ {
3373
+ mode: "bleed",
3374
+ icon: PanelRightIcon,
3375
+ onClick: () => openInNewPane(),
3376
+ "aria-label": "Open document",
3377
+ fontSize: 1,
3378
+ padding: 2
3379
+ }
3380
+ );
3381
+ }, 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);
3383
+ return useEffect(() => {
3384
+ const timer = setTimeout(() => setTimedOut(!0), VALIDATION_TIMEOUT_MS);
3385
+ return () => clearTimeout(timer);
3386
+ }, []), useEffect(() => {
3387
+ (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,
3391
+ errors
3392
+ });
3393
+ }, [isValidating, validation, documentId, onResult, timedOut]), null;
3394
+ }, PreflightView = () => {
3279
3395
  const {
3280
3396
  document,
3281
3397
  config,
3282
- overallStatus,
3283
3398
  dispatch,
3284
3399
  references,
3285
3400
  selectedReferenceIds,
3286
3401
  selectedTargetLanguages,
3402
+ shouldTranslateReferences,
3403
+ translationMetadata,
3287
3404
  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]);
3405
+ releaseConfig,
3406
+ 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(() => {
3408
+ const docs = [
3409
+ {
3410
+ _id: document._id,
3411
+ _type: document._type,
3412
+ title: document.title || document.name || document._id
3413
+ }
3414
+ ];
3415
+ if (shouldTranslateReferences)
3416
+ for (const ref of references)
3417
+ selectedReferenceIds.has(ref._id) && docs.push(ref);
3418
+ return docs;
3419
+ }, [document, references, selectedReferenceIds, shouldTranslateReferences]), handleValidationResult = useCallback((id, result) => {
3420
+ setValidationResults((prev) => {
3421
+ const next = new Map(prev);
3422
+ return next.set(id, result), next;
3423
+ });
3424
+ }, []), { hasValidationErrors, someStillValidating, documentsWithErrors } = useMemo(() => {
3425
+ const allValidated = documentsToValidate.every((doc) => validationResults.has(doc._id)), validating = [...validationResults.values()].some((r) => r.isValidating) || !allValidated, docsWithErrors = [];
3426
+ for (const doc of documentsToValidate) {
3427
+ const result = validationResults.get(doc._id);
3428
+ result && result.errors.length > 0 && docsWithErrors.push({ ...doc, errors: result.errors });
3429
+ }
3430
+ return {
3431
+ hasValidationErrors: docsWithErrors.length > 0,
3432
+ someStillValidating: validating,
3433
+ documentsWithErrors: docsWithErrors
3434
+ };
3435
+ }, [documentsToValidate, validationResults]), runSourceDraftCheck = useCallback(async () => {
3436
+ if (filteredReferences.length === 0) {
3437
+ setIsSourceDraftCheckLoading(!1);
3438
+ return;
3439
+ }
3440
+ setIsSourceDraftCheckLoading(!0);
3441
+ try {
3442
+ const results = await checkSourceDrafts({
3443
+ client,
3444
+ referenceIds: filteredReferences.map((ref) => ref._id)
3445
+ });
3446
+ setSourceRefsWithDrafts(results);
3447
+ } catch (error) {
3448
+ devLog.error(
3449
+ "Failed to check source reference drafts",
3450
+ error instanceof Error ? error : new Error(String(error)),
3451
+ { component: "PreflightView" }
3452
+ );
3453
+ } finally {
3454
+ setIsSourceDraftCheckLoading(!1);
3455
+ }
3456
+ }, [client, filteredReferences]), runDraftCheck = useCallback(async () => {
3457
+ setIsDraftCheckLoading(!0);
3458
+ try {
3459
+ const drafts = await checkTargetDrafts({
3460
+ client,
3461
+ translationMetadata,
3462
+ selectedTargetLanguages,
3463
+ sourceDocumentId: document._id
3464
+ });
3465
+ setStaleDrafts(drafts), drafts.length === 0 && setDraftAction("none");
3466
+ } catch (error) {
3467
+ devLog.error("Failed to check target drafts", error instanceof Error ? error : new Error(String(error)), {
3468
+ sourceDocumentId: document._id,
3469
+ component: "PreflightView"
3470
+ });
3471
+ } finally {
3472
+ setIsDraftCheckLoading(!1);
3473
+ }
3474
+ }, [client, translationMetadata, selectedTargetLanguages, document._id]), runConflictCheck = useCallback(async () => {
3475
+ if (releaseConfig.mode === "new") {
3476
+ setIsCheckingConflicts(!1), setConflictContext(null);
3477
+ return;
3478
+ }
3479
+ if (releaseConfig.mode === "existing" && releaseConfig.existingReleaseId) {
3480
+ setIsCheckingConflicts(!0);
3481
+ const checkedReleaseId = releaseConfig.existingReleaseId, foundConflicts = await checkReleaseConflicts({
3482
+ client,
3483
+ releaseId: checkedReleaseId,
3484
+ sourceDocument: document,
3485
+ targetLanguages: selectedTargetLanguages,
3486
+ languageFieldName: config.languageFieldName,
3487
+ references: filteredReferences
3488
+ });
3489
+ foundConflicts.length > 0 ? setConflictContext({ releaseId: checkedReleaseId, conflicts: foundConflicts }) : setConflictContext(null), setIsCheckingConflicts(!1);
3490
+ }
3491
+ }, [releaseConfig, client, document, selectedTargetLanguages, config.languageFieldName, filteredReferences]), hasRunChecks = useRef(!1);
3290
3492
  useEffect(() => {
3291
- releaseConfig.mode === "new" && conflictContext && setConflictContext(null);
3292
- }, [releaseConfig.mode, conflictContext]);
3293
- const startTranslation = useCallback(
3493
+ hasRunChecks.current || (hasRunChecks.current = !0, runSourceDraftCheck(), runDraftCheck(), runConflictCheck());
3494
+ }, [runSourceDraftCheck, runDraftCheck, runConflictCheck]);
3495
+ const handleDiscardDrafts = useCallback(async () => {
3496
+ setIsDraftActionInProgress(!0);
3497
+ try {
3498
+ const draftIds = staleDrafts.map((d) => d.draftId);
3499
+ devLog.info("Discarding stale drafts", { draftIds, component: "PreflightView" });
3500
+ const transaction = client.transaction();
3501
+ for (const id of draftIds)
3502
+ transaction.delete(id);
3503
+ await transaction.commit(), devLog.info("Successfully discarded stale drafts", {
3504
+ count: draftIds.length,
3505
+ component: "PreflightView"
3506
+ }), await runDraftCheck();
3507
+ } catch (error) {
3508
+ devLog.error("Failed to discard drafts", error instanceof Error ? error : new Error(String(error)), {
3509
+ component: "PreflightView"
3510
+ });
3511
+ } finally {
3512
+ setIsDraftActionInProgress(!1);
3513
+ }
3514
+ }, [staleDrafts, client, runDraftCheck]), handlePublishDrafts = useCallback(async () => {
3515
+ setIsDraftActionInProgress(!0);
3516
+ try {
3517
+ devLog.info("Publishing stale drafts", {
3518
+ count: staleDrafts.length,
3519
+ component: "PreflightView"
3520
+ });
3521
+ for (const draft of staleDrafts)
3522
+ await client.request({
3523
+ method: "POST",
3524
+ uri: "/data/actions/mutate",
3525
+ body: {
3526
+ actions: [
3527
+ {
3528
+ actionType: "sanity.action.document.publish",
3529
+ draftId: draft.draftId,
3530
+ publishedId: draft.targetId
3531
+ }
3532
+ ]
3533
+ }
3534
+ });
3535
+ devLog.info("Successfully published stale drafts", {
3536
+ count: staleDrafts.length,
3537
+ component: "PreflightView"
3538
+ }), await runDraftCheck();
3539
+ } catch (error) {
3540
+ devLog.error("Failed to publish drafts", error instanceof Error ? error : new Error(String(error)), {
3541
+ component: "PreflightView"
3542
+ });
3543
+ } finally {
3544
+ setIsDraftActionInProgress(!1);
3545
+ }
3546
+ }, [staleDrafts, client, runDraftCheck]), startTranslation = useCallback(
3294
3547
  (replaceExisting, overrideReleaseId, skipConflicts) => {
3295
- setConflictContext(null), translateDocumentSet({
3548
+ translateDocumentSet({
3296
3549
  sourceDocument: document,
3297
3550
  targetLanguages: selectedTargetLanguages,
3298
3551
  client,
@@ -3310,10 +3563,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3310
3563
  ...releaseConfig,
3311
3564
  replaceExisting,
3312
3565
  skipConflicts,
3313
- // Use the override release ID if provided (from conflict context),
3314
- // otherwise use the current releaseConfig value
3315
3566
  existingReleaseId: overrideReleaseId ?? releaseConfig.existingReleaseId
3316
3567
  },
3568
+ sourcePerspective: documentSource === "published" ? "published" : "drafts",
3317
3569
  telemetryConfig: config.telemetry
3318
3570
  });
3319
3571
  },
@@ -3322,58 +3574,179 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3322
3574
  selectedTargetLanguages,
3323
3575
  client,
3324
3576
  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,
3577
+ config,
3333
3578
  filteredReferences,
3334
3579
  translationMode,
3335
- releaseConfig
3580
+ releaseConfig,
3581
+ documentSource
3336
3582
  ]
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(() => {
3583
+ ), handleStartTranslation = useCallback(() => {
3584
+ conflictContext || startTranslation(!1);
3585
+ }, [conflictContext, startTranslation]), handleBack = useCallback(() => {
3586
+ dispatch({ type: "SET_OVERALL_STATUS", payload: "idle" });
3587
+ }, [dispatch]), sourceRefDraftIds = useMemo(
3588
+ () => new Set(sourceRefsWithDrafts.map((d) => d.publishedId)),
3589
+ [sourceRefsWithDrafts]
3590
+ ), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleCancelConflict = useCallback(() => {
3371
3591
  setConflictContext(null);
3372
3592
  }, []), handleReplaceConflict = useCallback(() => {
3373
3593
  conflictContext && startTranslation(!0, conflictContext.releaseId);
3374
3594
  }, [startTranslation, conflictContext]), handleSkipConflicts = useCallback(() => {
3375
3595
  conflictContext && startTranslation(!1, conflictContext.releaseId, conflictContext.conflicts);
3376
- }, [startTranslation, conflictContext]), handleSelectAll = useCallback(() => {
3596
+ }, [startTranslation, conflictContext]), totalTranslations = useMemo(() => (1 + filteredReferences.length) * selectedTargetLanguages.length, [filteredReferences.length, selectedTargetLanguages.length]);
3597
+ return conflictContext ? /* @__PURE__ */ jsx(Stack, { space: 4, children: /* @__PURE__ */ jsx(
3598
+ ReleaseConflictWarning,
3599
+ {
3600
+ conflicts: conflictContext.conflicts,
3601
+ totalTranslations,
3602
+ onCancel: handleCancelConflict,
3603
+ onReplace: handleReplaceConflict,
3604
+ onSkipConflicts: handleSkipConflicts
3605
+ }
3606
+ ) }) : /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3607
+ documentsToValidate.map((doc) => /* @__PURE__ */ jsx(
3608
+ DocumentValidator,
3609
+ {
3610
+ documentId: doc._id,
3611
+ documentType: doc._type,
3612
+ onResult: handleValidationResult
3613
+ },
3614
+ doc._id
3615
+ )),
3616
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", size: 2, children: "Pre-flight Checks" }),
3617
+ /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
3618
+ "Using ",
3619
+ documentSource,
3620
+ " version of all documents \u2192 ",
3621
+ selectedTargetLanguages.length,
3622
+ " language",
3623
+ selectedTargetLanguages.length !== 1 ? "s" : "",
3624
+ " \xB7 ",
3625
+ 1 + filteredReferences.length,
3626
+ " document",
3627
+ filteredReferences.length !== 0 ? "s" : ""
3628
+ ] }),
3629
+ /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasValidationErrors ? "critical" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3630
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3631
+ 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, {}) }),
3632
+ /* @__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" })
3633
+ ] }),
3634
+ documentsWithErrors.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
3635
+ /* @__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." }),
3636
+ /* @__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) => {
3637
+ const publishedId = getPublishedId(doc._id), displayName = doc._id === document._id ? "This document" : doc.title || doc._id, hasUnpublishedChanges = sourceRefDraftIds.has(publishedId);
3638
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3639
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3640
+ /* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(
3641
+ IntentLink,
3642
+ {
3643
+ intent: "edit",
3644
+ params: { id: publishedId, type: doc._type },
3645
+ style: { color: "inherit" },
3646
+ children: displayName
3647
+ }
3648
+ ) }),
3649
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: doc._type }),
3650
+ hasUnpublishedChanges && /* @__PURE__ */ jsx(Badge, { tone: "caution", fontSize: 0, children: "unpublished changes" })
3651
+ ] }),
3652
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3653
+ /* @__PURE__ */ jsxs(Badge, { tone: "critical", fontSize: 0, children: [
3654
+ doc.errors.length,
3655
+ " error",
3656
+ doc.errors.length !== 1 ? "s" : ""
3657
+ ] }),
3658
+ /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: doc._id, documentType: doc._type })
3659
+ ] })
3660
+ ] }, doc._id);
3661
+ }) }) }) })
3662
+ ] })
3663
+ ] }) }),
3664
+ 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: [
3665
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3666
+ 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, {}) }),
3667
+ /* @__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" })
3668
+ ] }),
3669
+ sourceRefsWithDrafts.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
3670
+ /* @__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".' }),
3671
+ /* @__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: [
3672
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3673
+ /* @__PURE__ */ jsx(Text, { size: 1, children: ref.title || ref.publishedId }),
3674
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: ref._type })
3675
+ ] }),
3676
+ /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: ref.publishedId, documentType: ref._type })
3677
+ ] }, ref.publishedId)) }) }) })
3678
+ ] })
3679
+ ] }) }),
3680
+ (isDraftCheckLoading || hasStaleDrafts) && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasStaleDrafts && !draftsDismissed ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3681
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3682
+ 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, {}) }),
3683
+ /* @__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` })
3684
+ ] }),
3685
+ hasStaleDrafts && !draftsDismissed && /* @__PURE__ */ jsxs(Fragment, { children: [
3686
+ /* @__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." }),
3687
+ /* @__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
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3689
+ /* @__PURE__ */ jsx(Text, { size: 1, children: draft.title || draft.targetId }),
3690
+ /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage })
3691
+ ] }),
3692
+ /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: draft.targetId, documentType: draft._type })
3693
+ ] }, draft.draftId)) }) }) }),
3694
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
3695
+ /* @__PURE__ */ jsx(
3696
+ Button,
3697
+ {
3698
+ mode: "ghost",
3699
+ tone: "caution",
3700
+ text: "Discard All Drafts",
3701
+ icon: CloseIcon,
3702
+ onClick: handleDiscardDrafts,
3703
+ disabled: isDraftActionInProgress
3704
+ }
3705
+ ),
3706
+ /* @__PURE__ */ jsx(
3707
+ Button,
3708
+ {
3709
+ mode: "ghost",
3710
+ tone: "caution",
3711
+ text: "Publish All Drafts",
3712
+ onClick: handlePublishDrafts,
3713
+ disabled: isDraftActionInProgress
3714
+ }
3715
+ ),
3716
+ /* @__PURE__ */ jsx(
3717
+ Button,
3718
+ {
3719
+ mode: "ghost",
3720
+ text: "Continue Anyway",
3721
+ onClick: () => setDraftAction("continue"),
3722
+ disabled: isDraftActionInProgress
3723
+ }
3724
+ )
3725
+ ] }),
3726
+ isDraftActionInProgress && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3727
+ /* @__PURE__ */ jsx(Spinner, { muted: !0 }),
3728
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Processing\u2026" })
3729
+ ] })
3730
+ ] })
3731
+ ] }) }),
3732
+ /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3733
+ /* @__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
+ )
3744
+ ] })
3745
+ ] });
3746
+ }, ActionButtonBar = ({ setIsDialogOpen }) => {
3747
+ const { document, config, overallStatus, dispatch, selectedTargetLanguages, releaseConfig } = useTranslationContext(), handleTranslate = useCallback(() => {
3748
+ dispatch({ type: "SET_OVERALL_STATUS", payload: "preflight" });
3749
+ }, [dispatch]), handleSelectAll = useCallback(() => {
3377
3750
  dispatch({
3378
3751
  type: "SET_SELECTED_TARGET_LANGUAGES",
3379
3752
  payload: config.supportedLanguages.map((lang) => lang.id).filter((id) => id !== document.language)
@@ -3384,16 +3757,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3384
3757
  payload: []
3385
3758
  });
3386
3759
  }, [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: [
3760
+ return /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3397
3761
  /* @__PURE__ */ jsxs(Flex, { justify: "flex-start", gap: 2, children: [
3398
3762
  /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 2, onClick: handleSelectAll, children: "Select All" }),
3399
3763
  /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 2, onClick: handleSelectNone, children: "Select None" })
@@ -3407,12 +3771,61 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3407
3771
  tone: "positive",
3408
3772
  size: 2,
3409
3773
  onClick: handleTranslate,
3410
- disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId || isCheckingConflicts || hasValidationErrors,
3411
- children: isCheckingConflicts ? "Checking..." : "Translate"
3774
+ disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId,
3775
+ children: "Translate"
3412
3776
  }
3413
3777
  )
3414
3778
  ] })
3415
3779
  ] });
3780
+ }, DocumentSourcePanel = () => {
3781
+ const { hasDraft, hasPublished, documentSource, dispatch } = useTranslationContext();
3782
+ if (!hasDraft)
3783
+ return null;
3784
+ if (!hasPublished)
3785
+ 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." }) });
3786
+ const handleSourceChange = (source) => {
3787
+ dispatch({
3788
+ type: "SET_DOCUMENT_SOURCE",
3789
+ payload: source
3790
+ });
3791
+ };
3792
+ return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3793
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "This document has unpublished changes" }),
3794
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3795
+ /* @__PURE__ */ jsx(
3796
+ Button,
3797
+ {
3798
+ type: "button",
3799
+ mode: documentSource === "draft" ? "ghost" : "bleed",
3800
+ tone: documentSource === "draft" ? "primary" : "default",
3801
+ onClick: () => handleSourceChange("draft"),
3802
+ children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3803
+ /* @__PURE__ */ jsx(Radio, { checked: documentSource === "draft", readOnly: !0 }),
3804
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3805
+ /* @__PURE__ */ jsx(Text, { children: "Translate from drafts" }),
3806
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses the latest draft of this document and all referenced documents" })
3807
+ ] })
3808
+ ] })
3809
+ }
3810
+ ),
3811
+ /* @__PURE__ */ jsx(
3812
+ Button,
3813
+ {
3814
+ type: "button",
3815
+ mode: documentSource === "published" ? "ghost" : "bleed",
3816
+ tone: documentSource === "published" ? "primary" : "default",
3817
+ onClick: () => handleSourceChange("published"),
3818
+ children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3819
+ /* @__PURE__ */ jsx(Radio, { checked: documentSource === "published", readOnly: !0 }),
3820
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3821
+ /* @__PURE__ */ jsx(Text, { children: "Translate from published" }),
3822
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses only published content for this document and all referenced documents" })
3823
+ ] })
3824
+ ] })
3825
+ }
3826
+ )
3827
+ ] })
3828
+ ] }) });
3416
3829
  }, TRANSLATION_METADATA_QUERY = "*[_type == $translationMetadataType && references($documentId)][0]", useTranslationMetadata = ({ documentId }) => {
3417
3830
  const { dispatch } = useTranslationContext(), { data, loading, error } = useListeningQuery(TRANSLATION_METADATA_QUERY, {
3418
3831
  params: {
@@ -3855,104 +4268,6 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3855
4268
  )
3856
4269
  ] })
3857
4270
  ] }) });
3858
- }, DocumentValidator = ({ documentId, documentType, onResult }) => {
3859
- const publishedId = getPublishedId(documentId), { isValidating, validation } = useValidationStatus(publishedId, documentType);
3860
- return useEffect(() => {
3861
- const errors = validation.filter((v) => v.level === "error");
3862
- onResult(documentId, { isValidating, errors });
3863
- }, [isValidating, validation, documentId, onResult]), null;
3864
- }, ValidationPanel = ({ onValidationChange }) => {
3865
- const { document, references, selectedReferenceIds, shouldTranslateReferences } = useTranslationContext(), [results, setResults] = useState(/* @__PURE__ */ new Map()), documentsToValidate = useMemo(() => {
3866
- const docs = [
3867
- {
3868
- _id: document._id,
3869
- _type: document._type,
3870
- title: document.title || document.name || document._id
3871
- }
3872
- ];
3873
- if (shouldTranslateReferences)
3874
- for (const ref of references)
3875
- selectedReferenceIds.has(ref._id) && docs.push(ref);
3876
- return docs;
3877
- }, [document, references, selectedReferenceIds, shouldTranslateReferences]), handleResult = useCallback((id, result) => {
3878
- setResults((prev) => {
3879
- const next = new Map(prev);
3880
- return next.set(id, result), next;
3881
- });
3882
- }, []), { shouldBlock, someStillValidating, documentsWithErrors } = useMemo(() => {
3883
- const allValidated = documentsToValidate.every((doc) => results.has(doc._id)), validating = [...results.values()].some((r) => r.isValidating) || !allValidated, docsWithErrors = [];
3884
- for (const doc of documentsToValidate) {
3885
- const result = results.get(doc._id);
3886
- result && result.errors.length > 0 && docsWithErrors.push({ ...doc, errors: result.errors });
3887
- }
3888
- const hasErrors = docsWithErrors.length > 0;
3889
- return {
3890
- shouldBlock: validating || hasErrors,
3891
- someStillValidating: validating,
3892
- documentsWithErrors: docsWithErrors
3893
- };
3894
- }, [documentsToValidate, results]);
3895
- useEffect(() => {
3896
- onValidationChange(shouldBlock);
3897
- }, [shouldBlock, onValidationChange]), useEffect(() => {
3898
- documentsWithErrors.length > 0 && devLog.warn("Documents have validation errors that block translation", {
3899
- documentsWithErrors: documentsWithErrors.map((d) => ({
3900
- id: d._id,
3901
- type: d._type,
3902
- errorCount: d.errors.length
3903
- })),
3904
- component: "ValidationPanel"
3905
- });
3906
- }, [documentsWithErrors]);
3907
- const totalErrors = useMemo(
3908
- () => documentsWithErrors.reduce((sum, doc) => sum + doc.errors.length, 0),
3909
- [documentsWithErrors]
3910
- );
3911
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3912
- documentsToValidate.map((doc) => /* @__PURE__ */ jsx(DocumentValidator, { documentId: doc._id, documentType: doc._type, onResult: handleResult }, doc._id)),
3913
- someStillValidating && documentsWithErrors.length === 0 && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, padding: 2, children: [
3914
- /* @__PURE__ */ jsx(Spinner, { muted: !0 }),
3915
- /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Validating documents\u2026" })
3916
- ] }),
3917
- documentsWithErrors.length > 0 && /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3918
- /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3919
- /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, {}) }),
3920
- /* @__PURE__ */ jsxs(Text, { weight: "semibold", children: [
3921
- totalErrors,
3922
- " validation error",
3923
- totalErrors !== 1 ? "s" : "",
3924
- " found in ",
3925
- documentsWithErrors.length,
3926
- " ",
3927
- "document",
3928
- documentsWithErrors.length !== 1 ? "s" : ""
3929
- ] })
3930
- ] }),
3931
- /* @__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." }),
3932
- /* @__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) => {
3933
- const publishedId = getPublishedId(doc._id);
3934
- return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3935
- /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3936
- /* @__PURE__ */ jsx(Text, { size: 1, children: /* @__PURE__ */ jsx(
3937
- IntentLink,
3938
- {
3939
- intent: "edit",
3940
- params: { id: publishedId, type: doc._type },
3941
- style: { color: "inherit" },
3942
- children: doc.title || doc._id
3943
- }
3944
- ) }),
3945
- /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: doc._type })
3946
- ] }),
3947
- /* @__PURE__ */ jsxs(Badge, { tone: "critical", fontSize: 0, children: [
3948
- doc.errors.length,
3949
- " error",
3950
- doc.errors.length !== 1 ? "s" : ""
3951
- ] })
3952
- ] }, doc._id);
3953
- }) }) }) })
3954
- ] }) })
3955
- ] });
3956
4271
  }, assembleReferences = async ({
3957
4272
  client,
3958
4273
  sourceDocument,
@@ -4206,31 +4521,26 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4206
4521
  !isLoadingReferences && references.length > 0 && showSelectionPanel && /* @__PURE__ */ jsx(ReferenceSelectionPanel, {})
4207
4522
  ] }) : null
4208
4523
  ] });
4209
- }, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => {
4210
- const [hasValidationErrors, setHasValidationErrors] = useState(!0), handleValidationChange = useCallback((shouldBlock) => {
4211
- setHasValidationErrors(shouldBlock);
4212
- }, []);
4213
- return /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4214
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4215
- /* @__PURE__ */ jsx(Label, { children: "Languages" }),
4216
- /* @__PURE__ */ jsx(LanguageSelectionPanel, {})
4217
- ] }),
4218
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4219
- /* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
4220
- /* @__PURE__ */ jsx(TranslationModePanel, {})
4221
- ] }),
4222
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4223
- /* @__PURE__ */ jsx(Label, { children: "Content Release" }),
4224
- /* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
4225
- ] }),
4226
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4227
- /* @__PURE__ */ jsx(Label, { children: "Settings" }),
4228
- /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
4229
- ] }),
4230
- /* @__PURE__ */ jsx(ValidationPanel, { onValidationChange: handleValidationChange }),
4231
- /* @__PURE__ */ jsx(ActionButtonBar, { setIsDialogOpen, hasValidationErrors })
4232
- ] });
4233
- }, TERMINAL_STATUSES = ["completed", "skipped", "error"], TranslationStatusPanel = () => {
4524
+ }, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4525
+ /* @__PURE__ */ jsx(DocumentSourcePanel, {}),
4526
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4527
+ /* @__PURE__ */ jsx(Label, { children: "Languages" }),
4528
+ /* @__PURE__ */ jsx(LanguageSelectionPanel, {})
4529
+ ] }),
4530
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4531
+ /* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
4532
+ /* @__PURE__ */ jsx(TranslationModePanel, {})
4533
+ ] }),
4534
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4535
+ /* @__PURE__ */ jsx(Label, { children: "Content Release" }),
4536
+ /* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
4537
+ ] }),
4538
+ /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4539
+ /* @__PURE__ */ jsx(Label, { children: "Settings" }),
4540
+ /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
4541
+ ] }),
4542
+ /* @__PURE__ */ jsx(ActionButtonBar, { setIsDialogOpen })
4543
+ ] }), TERMINAL_STATUSES = ["completed", "skipped", "error"], TranslationStatusPanel = () => {
4234
4544
  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(() => {
4235
4545
  forceCleanupActiveTranslation(document._id), dispatch({ type: "RESET_TRANSLATION_STATE" });
4236
4546
  }, [dispatch, document._id]);
@@ -4352,7 +4662,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4352
4662
  const { overallStatus, document } = useTranslationContext();
4353
4663
  return useEffect(() => {
4354
4664
  onStatusChange?.(overallStatus);
4355
- }, [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", {
4665
+ }, [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", {
4356
4666
  documentId: document._id,
4357
4667
  overallStatus
4358
4668
  }), /* @__PURE__ */ jsx("div", { children: "Unhandled overall state" }));
@@ -4392,7 +4702,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4392
4702
  type: "dialog",
4393
4703
  onClose: handleClose,
4394
4704
  header: "Translate Document",
4395
- content: /* @__PURE__ */ jsx(TranslationContextProvider, { document, config, children: /* @__PURE__ */ jsx(TranslationDialog, { setIsDialogOpen, onStatusChange: handleStatusChange }) })
4705
+ content: /* @__PURE__ */ jsx(
4706
+ TranslationContextProvider,
4707
+ {
4708
+ document,
4709
+ config,
4710
+ hasDraft: !!draft,
4711
+ hasPublished: !!published,
4712
+ children: /* @__PURE__ */ jsx(TranslationDialog, { setIsDialogOpen, onStatusChange: handleStatusChange })
4713
+ }
4714
+ )
4396
4715
  }
4397
4716
  } : (devLog.error("Missing config in TranslateAction"), null);
4398
4717
  }, translationGlossaryTerm = defineType({