@braze/sanity-plugin-ai-translation 0.2.87 → 0.3.1

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,9 +1,10 @@
1
- import { getPublishedId, pathToString, useClient, useWorkspace, validateDocument, defineType, defineField, definePlugin } from "sanity";
1
+ import { useClient, getPublishedId, pathToString, useWorkspace, validateDocument, defineType, defineField, definePlugin } from "sanity";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import { WarningOutlineIcon, ErrorOutlineIcon, CheckmarkIcon, SyncIcon, PanelRightIcon, SearchIcon, CircleIcon, ArrowRightIcon, ResetIcon, TranslateIcon } from "@sanity/icons";
4
- import { createContext, useReducer, useRef, useEffect, useContext, useMemo, useState, useCallback } from "react";
4
+ import { createContext, useEffect, useReducer, useRef, useContext, useMemo, useState, useCallback } from "react";
5
+ import { devLog, retryWithBackoff, isNetworkError, isSanityMutationError } from "./_chunks-es/retry.js";
5
6
  import defaults from "lodash.defaults";
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 { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Badge, Inline, Radio, Checkbox, Grid, TextInput, Select, Label } from "@sanity/ui";
7
8
  import { IntentLink } from "sanity/router";
8
9
  import { useOpenInNewPane, useListeningQuery } from "sanity-plugin-utils";
9
10
  import { toPlainText } from "@portabletext/react";
@@ -11,59 +12,57 @@ import { uuid } from "@sanity/uuid";
11
12
  import cloneDeep from "lodash.clonedeep";
12
13
  import get from "lodash.get";
13
14
  import set from "lodash.set";
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
- info: (message, context) => {
16
- isLoggingEnabled() && console.info("[AI Translation]", message, context ? JSON.stringify(context, null, 2) : "");
17
- },
18
- warn: (message, context) => {
19
- isLoggingEnabled() && console.warn("[AI Translation]", message, context ? JSON.stringify(context, null, 2) : "");
20
- },
21
- error: (message, error, context) => {
22
- isLoggingEnabled() && console.error("[AI Translation]", message, error, context ? JSON.stringify(context, null, 2) : "");
23
- },
24
- debug: (message, data, context) => {
25
- isLoggingEnabled() && console.debug("[AI Translation]", message, data, context ? JSON.stringify(context, null, 2) : "");
26
- },
27
- group: (label, context) => {
28
- isLoggingEnabled() && console.group(`[AI Translation] ${label}`, context ? JSON.stringify(context, null, 2) : "");
29
- },
30
- groupEnd: () => {
31
- isLoggingEnabled() && console.groupEnd();
32
- },
33
- time: (label) => {
34
- isLoggingEnabled() && console.time(`[AI Translation] ${label}`);
35
- },
36
- timeEnd: (label) => {
37
- isLoggingEnabled() && console.timeEnd(`[AI Translation] ${label}`);
38
- }
39
- }, validateOpenAIApiKey = async (apiKey, model = "gpt-5") => {
15
+ import { Code } from "@sanity/ui/code";
16
+ import { Tooltip } from "@sanity/ui/tooltip";
17
+ const DEFAULT_TRANSLATION_FUNCTION_NAME = "ai-translation", DEFAULT_MODEL = "gpt-5", TRANSLATION_FUNCTION_TIMEOUT_MS = 9e5, isOpenAIConfigured = (openaiConfig) => !!(openaiConfig?.enabled && typeof openaiConfig.stackId == "string" && openaiConfig.stackId.trim() !== ""), supportsFunctionInvoke = (client) => typeof client.functions?.invoke == "function", toNumber = (value) => typeof value == "number" && Number.isFinite(value) ? value : 0, parseResult = (result, functionName) => {
18
+ const value = result ?? {};
19
+ if (typeof value.content != "string")
20
+ throw new Error(`OpenAI translation function "${functionName}" returned no translated content`);
21
+ const tokensUsed = value.tokensUsed ?? {};
22
+ return {
23
+ content: value.content,
24
+ tokensUsed: {
25
+ prompt: toNumber(tokensUsed.prompt),
26
+ completion: toNumber(tokensUsed.completion),
27
+ reasoning: toNumber(tokensUsed.reasoning),
28
+ total: toNumber(tokensUsed.total),
29
+ cached: toNumber(tokensUsed.cached)
30
+ }
31
+ };
32
+ }, invokeTranslationFunction = async ({
33
+ client,
34
+ openaiConfig,
35
+ messages
36
+ }) => {
37
+ if (!isOpenAIConfigured(openaiConfig))
38
+ throw new Error("OpenAI mode requires openai.enabled and openai.stackId");
39
+ if (!supportsFunctionInvoke(client))
40
+ throw new Error("OpenAI mode requires @sanity/client 8.1 or newer in Studio to invoke the translation function");
41
+ const functionName = openaiConfig.functionName || DEFAULT_TRANSLATION_FUNCTION_NAME, payload = {
42
+ model: openaiConfig.model || DEFAULT_MODEL,
43
+ messages,
44
+ reasoningEffort: openaiConfig.reasoningEffort
45
+ };
46
+ devLog.debug("Invoking OpenAI translation function", {
47
+ functionName,
48
+ stackId: openaiConfig.stackId,
49
+ model: payload.model,
50
+ reasoningEffort: payload.reasoningEffort,
51
+ messages: messages.map((m) => ({ role: m.role, contentLength: m.content.length }))
52
+ });
53
+ let result;
40
54
  try {
41
- const response = await fetch("https://api.openai.com/v1/models", {
42
- method: "GET",
43
- headers: {
44
- Authorization: `Bearer ${apiKey}`
45
- }
46
- });
47
- if (!response.ok)
48
- return {
49
- valid: !1,
50
- error: (await response.json()).error?.message || "Invalid API key"
51
- };
52
- const availableModels = (await response.json()).data.map((m) => m.id);
53
- return availableModels.includes(model) ? {
54
- valid: !0,
55
- modelAvailable: availableModels
56
- } : {
57
- valid: !1,
58
- error: `Model ${model} not available. Try: ${availableModels.slice(0, 5).join(", ")}`
59
- };
55
+ result = await client.functions.invoke(
56
+ functionName,
57
+ { event: { data: payload }, stackId: openaiConfig.stackId, timeout: TRANSLATION_FUNCTION_TIMEOUT_MS },
58
+ { sync: !0 }
59
+ );
60
60
  } catch (error) {
61
- return {
62
- valid: !1,
63
- error: error instanceof Error ? error.message : "Network error"
64
- };
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ throw new Error(`OpenAI translation function "${functionName}" failed: ${message}`);
65
63
  }
66
- }, undraftId = (id) => id.replace(/^drafts\./, ""), activeTranslations = /* @__PURE__ */ new Map(), registerActiveTranslation = (documentId, releaseId) => {
64
+ return parseResult(result, functionName);
65
+ }, undraftId = (id) => id.replace(/^drafts\./, ""), draftId = (id) => `drafts.${undraftId(id)}`, activeTranslations = /* @__PURE__ */ new Map(), registerActiveTranslation = (documentId, releaseId) => {
67
66
  const entry = {
68
67
  state: {
69
68
  documentId,
@@ -102,7 +101,7 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
102
101
  try {
103
102
  subscriber(entry.state);
104
103
  } catch (error) {
105
- console.error("Error in active translation subscriber:", error);
104
+ devLog.error("Error in active translation subscriber", error);
106
105
  }
107
106
  }, forceCleanupActiveTranslation = (documentId) => {
108
107
  activeTranslations.delete(documentId);
@@ -253,7 +252,14 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
253
252
  supportedLanguages: config.supportedLanguages?.map((l) => l.id),
254
253
  component: "TranslationContextProvider"
255
254
  });
256
- const defaultReleaseTitle = generateDefaultReleaseTitle(document), activeTranslation = getActiveTranslation(document._id), initialContext = {
255
+ const defaultReleaseTitle = generateDefaultReleaseTitle(document), client = useClient({ apiVersion: "vX" }), openaiConfigured = isOpenAIConfigured(config?.openai), openaiAvailable = openaiConfigured && supportsFunctionInvoke(client);
256
+ useEffect(() => {
257
+ openaiConfigured && !openaiAvailable && devLog.warn("OpenAI mode is configured but the Studio client cannot invoke functions", {
258
+ requires: "@sanity/client 8.1 or newer",
259
+ component: "TranslationContextProvider"
260
+ });
261
+ }, [openaiConfigured, openaiAvailable]);
262
+ const activeTranslation = getActiveTranslation(document._id), initialContext = {
257
263
  document,
258
264
  config,
259
265
  // If there's an active translation, restore its state
@@ -266,8 +272,8 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
266
272
  selectedReferenceIds: /* @__PURE__ */ new Set(),
267
273
  // Restore jobs from active translation if available
268
274
  translationJobs: activeTranslation?.translationJobs ?? /* @__PURE__ */ new Map(),
269
- translationMode: config?.openai?.defaultMode || "sanity-only",
270
- openaiValidated: !1,
275
+ translationMode: openaiAvailable && config.openai?.defaultMode || "sanity-only",
276
+ openaiValidated: openaiAvailable,
271
277
  releaseConfig: {
272
278
  mode: "new",
273
279
  newReleaseTitle: defaultReleaseTitle
@@ -302,30 +308,6 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
302
308
  unsubscribe();
303
309
  };
304
310
  }, [document._id]), useEffect(() => {
305
- (async () => {
306
- if (config?.openai?.enabled && config?.openai?.apiKey)
307
- try {
308
- const validation = await validateOpenAIApiKey(config.openai.apiKey, config.openai.model || "gpt-5");
309
- dispatch({
310
- type: "SET_OPENAI_VALIDATED",
311
- payload: validation.valid
312
- }), validation.valid ? devLog.info("OpenAI API key validated successfully", {
313
- model: config.openai.model || "gpt-5",
314
- component: "TranslationContextProvider"
315
- }) : devLog.error("OpenAI API key validation failed", {
316
- error: validation.error,
317
- component: "TranslationContextProvider"
318
- });
319
- } catch (error) {
320
- devLog.error("OpenAI validation error", error instanceof Error ? error : new Error(String(error)), {
321
- component: "TranslationContextProvider"
322
- }), dispatch({
323
- type: "SET_OPENAI_VALIDATED",
324
- payload: !1
325
- });
326
- }
327
- })();
328
- }, [config?.openai?.apiKey, config?.openai?.enabled, config?.openai?.model]), useEffect(() => {
329
311
  const currentRefsKey = context.references.map((ref) => ref._id).sort().join(",");
330
312
  if (currentRefsKey !== lastProcessedRefsRef.current) {
331
313
  if (context.references.length > 0) {
@@ -361,7 +343,12 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
361
343
  "useTranslationContext must be used within a TranslationContextProvider with document and config props"
362
344
  );
363
345
  return context;
364
- }, checkSourceDrafts = async ({
346
+ };
347
+ async function fetchSourceDocument(client, documentId, perspective) {
348
+ const publishedId = undraftId(documentId), [draft, published] = await client.getDocuments([draftId(publishedId), publishedId]);
349
+ return perspective === "published" ? published : draft ?? published;
350
+ }
351
+ const checkSourceDrafts = async ({
365
352
  client,
366
353
  referenceIds
367
354
  }) => {
@@ -449,10 +436,10 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
449
436
  return devLog.debug("No stale drafts found", { sourceDocumentId }), [];
450
437
  const draftMap = new Map(drafts.map((d) => [d._id, d])), results = [];
451
438
  for (const entry of deduped) {
452
- const draftId = `drafts.${entry.targetId}`, draft = draftMap.get(draftId);
439
+ const draftId2 = `drafts.${entry.targetId}`, draft = draftMap.get(draftId2);
453
440
  draft && results.push({
454
441
  targetId: entry.targetId,
455
- draftId,
442
+ draftId: draftId2,
456
443
  _type: draft._type,
457
444
  title: draft.title || draft.name,
458
445
  sourceDocumentId: entry.sourceDocumentId,
@@ -498,7 +485,7 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
498
485
  translationHistory: []
499
486
  });
500
487
  } catch (error) {
501
- throw console.error(sourceDocument._id, "Error creating translation metadata:", error), error;
488
+ throw devLog.error("Error creating translation metadata", error, { documentId: sourceDocument._id }), error;
502
489
  }
503
490
  }, checkDocumentConflicts = async ({
504
491
  client,
@@ -587,115 +574,6 @@ const isLoggingEnabled = () => process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLAT
587
574
  }
588
575
  return allConflicts;
589
576
  };
590
- function isNetworkError(error) {
591
- if (!error) return !1;
592
- if (error instanceof TypeError) {
593
- const message = error.message.toLowerCase();
594
- return message.includes("fetch") || message.includes("network") || message.includes("connection") || message.includes("failed to fetch") || message.includes("networkerror");
595
- }
596
- if (error && typeof error == "object" && "code" in error) {
597
- const code = error.code;
598
- return ["ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "ECONNREFUSED", "EHOSTUNREACH"].includes(code);
599
- }
600
- if (error && typeof error == "object" && "response" in error) {
601
- const response = error.response;
602
- if (response?.status)
603
- return response.status >= 500 || response.status === 429;
604
- }
605
- if (error instanceof Error) {
606
- const message = error.message.toLowerCase();
607
- if (/\b(50[0-9]|429)\b/.test(message) || message.includes("rate limit"))
608
- return !0;
609
- }
610
- return !1;
611
- }
612
- function isSanityMutationError(error) {
613
- if (!error) return !1;
614
- if (error instanceof Error) {
615
- const message = error.message.toLowerCase();
616
- return [
617
- "validation",
618
- "schema",
619
- "invalid document",
620
- "invalid field",
621
- "type mismatch",
622
- "reference",
623
- "patch",
624
- "mutation",
625
- "document structure",
626
- "required field",
627
- "field type",
628
- "sanity"
629
- ].some((pattern) => message.includes(pattern));
630
- }
631
- if (error && typeof error == "object") {
632
- const errorObj = error;
633
- if (("statusCode" in errorObj || "response" in errorObj || "details" in errorObj) && "statusCode" in errorObj) {
634
- const statusCode = errorObj.statusCode;
635
- if (statusCode >= 400 && statusCode < 500 && statusCode !== 429)
636
- return !0;
637
- }
638
- }
639
- return !1;
640
- }
641
- const DEFAULT_RETRY_OPTIONS = {
642
- maxAttempts: 2,
643
- initialDelayMs: 0,
644
- maxDelayMs: 5e3,
645
- backoffMultiplier: 2,
646
- retryableErrors: isNetworkError,
647
- logRetries: !0
648
- };
649
- function calculateDelay(attempt, options) {
650
- const delay = options.initialDelayMs * options.backoffMultiplier ** attempt;
651
- return Math.min(delay, options.maxDelayMs);
652
- }
653
- function sleep(ms) {
654
- return new Promise((resolve) => setTimeout(resolve, ms));
655
- }
656
- async function retryWithBackoff(operation, options = {}) {
657
- const opts = {
658
- ...DEFAULT_RETRY_OPTIONS,
659
- ...options,
660
- retryableErrors: options.retryableErrors || DEFAULT_RETRY_OPTIONS.retryableErrors
661
- };
662
- let lastError;
663
- const maxAttempts = opts.maxAttempts + 1;
664
- for (let attempt = 1; attempt <= maxAttempts; attempt++)
665
- try {
666
- const result = await operation();
667
- return attempt > 1 && opts.logRetries && devLog.info(`Operation succeeded after ${attempt} attempts`, {
668
- ...opts.context,
669
- attempt,
670
- totalAttempts: attempt
671
- }), result;
672
- } catch (error) {
673
- if (lastError = error, !opts.retryableErrors(error))
674
- throw opts.logRetries && devLog.warn("Error is not retryable, aborting retries", {
675
- ...opts.context,
676
- attempt,
677
- error: error instanceof Error ? error.message : String(error)
678
- }), error;
679
- if (attempt >= maxAttempts)
680
- throw opts.logRetries && devLog.error(
681
- `Operation failed after ${maxAttempts} attempts`,
682
- error instanceof Error ? error : new Error(String(error)),
683
- {
684
- ...opts.context,
685
- totalAttempts: maxAttempts
686
- }
687
- ), error;
688
- const delay = calculateDelay(attempt - 1, opts);
689
- opts.logRetries && devLog.warn(`Operation failed, retrying in ${delay}ms (attempt ${attempt}/${maxAttempts})`, {
690
- ...opts.context,
691
- attempt,
692
- totalAttempts: maxAttempts,
693
- delayMs: delay,
694
- error: error instanceof Error ? error.message : String(error)
695
- }), delay > 0 && await sleep(delay);
696
- }
697
- throw lastError;
698
- }
699
577
  class Semaphore {
700
578
  permits;
701
579
  queue = [];
@@ -769,7 +647,10 @@ function documentToPlainText(document) {
769
647
  ) ? result[key] = toPlainText(value) : result[key] = value;
770
648
  return JSON.stringify(result);
771
649
  }
772
- const DEFAULTS = {
650
+ let glossaryDataset;
651
+ const setGlossaryDataset = (dataset) => {
652
+ glossaryDataset = typeof dataset == "string" && dataset.trim() ? dataset.trim() : void 0;
653
+ }, getGlossaryDataset = () => glossaryDataset, DEFAULTS = {
773
654
  windowSlack: 2,
774
655
  minTokenMatchRatio: 1,
775
656
  stopwords: [
@@ -885,7 +766,11 @@ const fetchTranslationPrompts = async ({
885
766
  protectedPhrasesCount: translationSettings2?.protectedPhrases?.length,
886
767
  targetLanguages
887
768
  });
888
- const translationGlossaryTerms = await client.fetch(
769
+ const glossaryDataset2 = getGlossaryDataset(), glossaryClient = glossaryDataset2 ? client.withConfig({ dataset: glossaryDataset2 }) : client;
770
+ devLog.debug("Fetching glossary terms", {
771
+ glossaryDataset: glossaryDataset2 || "document dataset"
772
+ });
773
+ const translationGlossaryTerms = await glossaryClient.fetch(
889
774
  `*[
890
775
  _type == "translationGlossaryTerm"
891
776
  && count(translations[_key in $targetLanguages]) > 0
@@ -1662,127 +1547,7 @@ class TransformRegistry {
1662
1547
  }), matches[0];
1663
1548
  }
1664
1549
  }
1665
- const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKey, request) => (request.model || "gpt-5").startsWith("gpt-5") ? await callGPT5ResponsesAPI(apiKey, request) : await callChatCompletionsAPI(apiKey, request), callGPT5ResponsesAPI = async (apiKey, request) => {
1666
- const model = request.model || "gpt-5", requestBody = {
1667
- model: model === "gpt-5" ? "gpt-5.1" : model,
1668
- // Map gpt-5 to gpt-5.1, use others as-is
1669
- input: request.messages
1670
- // Uses 'input' instead of 'messages'
1671
- };
1672
- request.reasoningEffort && (requestBody.reasoning = {
1673
- effort: request.reasoningEffort,
1674
- summary: "auto"
1675
- // Enable automatic reasoning summaries
1676
- }), devLog.debug("OpenAI GPT-5 Request", {
1677
- model: requestBody.model,
1678
- reasoningEffort: request.reasoningEffort,
1679
- messages: request.messages.map((m) => ({
1680
- role: m.role,
1681
- contentLength: m.content.length,
1682
- contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? "..." : "")
1683
- }))
1684
- }), devLog.debug("OpenAI GPT-5 Full Prompt", {
1685
- messages: request.messages
1686
- });
1687
- const response = await retryWithBackoff(
1688
- async () => {
1689
- const fetchResponse = await fetch("https://api.openai.com/v1/responses", {
1690
- method: "POST",
1691
- headers: {
1692
- "Content-Type": "application/json",
1693
- Authorization: `Bearer ${apiKey}`
1694
- },
1695
- body: JSON.stringify(requestBody)
1696
- });
1697
- if (!fetchResponse.ok) {
1698
- const errorMessage = `GPT-5 API error: ${(await fetchResponse.json()).error?.message || "Unknown error"}`, errorObj = new Error(errorMessage);
1699
- throw errorObj.response = { status: fetchResponse.status }, errorObj;
1700
- }
1701
- return fetchResponse;
1702
- },
1703
- {
1704
- maxAttempts: 2,
1705
- initialDelayMs: 0,
1706
- retryableErrors: isNetworkError,
1707
- context: { model: requestBody.model, endpoint: "responses" }
1708
- }
1709
- );
1710
- if (!response.ok) {
1711
- const error = await response.json();
1712
- throw new Error(`GPT-5 API error: ${error.error?.message || "Unknown error"}`);
1713
- }
1714
- const result = await response.json(), tokensUsed = {
1715
- prompt: result.usage?.input_tokens || 0,
1716
- completion: result.usage?.output_tokens || 0,
1717
- reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
1718
- total: result.usage?.total_tokens || 0,
1719
- cached: result.usage?.input_tokens_details?.cached_tokens || 0
1720
- };
1721
- let content = "";
1722
- if (result.output && Array.isArray(result.output)) {
1723
- const messageItem = result.output.find((item) => item.type === "message");
1724
- messageItem?.content && Array.isArray(messageItem.content) && (content = messageItem.content.find((item) => item.type === "output_text")?.text || "");
1725
- }
1726
- return content || (content = result.output_text || result.text || ""), {
1727
- content,
1728
- tokensUsed
1729
- };
1730
- }, callChatCompletionsAPI = async (apiKey, request) => {
1731
- const model = request.model || "gpt-4o", requestBody = {
1732
- model,
1733
- messages: request.messages,
1734
- temperature: request.temperature ?? 0.3
1735
- };
1736
- devLog.debug("OpenAI Chat Completions Request", {
1737
- model,
1738
- temperature: request.temperature ?? 0.3,
1739
- messages: request.messages.map((m) => ({
1740
- role: m.role,
1741
- contentLength: m.content.length,
1742
- contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? "..." : "")
1743
- }))
1744
- }), devLog.debug("OpenAI Chat Completions Full Prompt", {
1745
- messages: request.messages
1746
- });
1747
- const response = await retryWithBackoff(
1748
- async () => {
1749
- const fetchResponse = await fetch("https://api.openai.com/v1/chat/completions", {
1750
- method: "POST",
1751
- headers: {
1752
- "Content-Type": "application/json",
1753
- Authorization: `Bearer ${apiKey}`
1754
- },
1755
- body: JSON.stringify(requestBody)
1756
- });
1757
- if (!fetchResponse.ok) {
1758
- const errorMessage = `OpenAI API error: ${(await fetchResponse.json()).error?.message || "Unknown error"}`, errorObj = new Error(errorMessage);
1759
- throw errorObj.response = { status: fetchResponse.status }, errorObj;
1760
- }
1761
- return fetchResponse;
1762
- },
1763
- {
1764
- maxAttempts: 2,
1765
- initialDelayMs: 0,
1766
- retryableErrors: isNetworkError,
1767
- context: { model, endpoint: "chat/completions" }
1768
- }
1769
- );
1770
- if (!response.ok) {
1771
- const error = await response.json();
1772
- throw new Error(`OpenAI API error: ${error.error?.message || "Unknown error"}`);
1773
- }
1774
- const result = await response.json(), usage = result.usage || {}, tokensUsed = {
1775
- prompt: usage.prompt_tokens || 0,
1776
- completion: usage.completion_tokens || 0,
1777
- reasoning: usage.reasoning_tokens || 0,
1778
- total: usage.total_tokens || 0,
1779
- cached: usage.prompt_tokens_details?.cached_tokens || 0
1780
- };
1781
- return {
1782
- content: result.choices[0].message.content || "",
1783
- tokensUsed
1784
- };
1785
- }, deserializeDocument = (originalDoc, reviewedText) => {
1550
+ const globalTransformRegistry = new TransformRegistry(), deserializeDocument = (originalDoc, reviewedText) => {
1786
1551
  devLog.debug("Raw GPT-5 response received", {
1787
1552
  length: reviewedText.length,
1788
1553
  preview: reviewedText.substring(0, 1e3),
@@ -2382,6 +2147,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2382
2147
  }
2383
2148
  }, getOpenAITranslation = async ({
2384
2149
  dispatch,
2150
+ client,
2385
2151
  sourceDocument,
2386
2152
  languageFieldName,
2387
2153
  translationPrompts,
@@ -2410,16 +2176,16 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2410
2176
  targetLanguage,
2411
2177
  translationPrompts,
2412
2178
  errorContext
2413
- }), response = await callGPT5(openaiConfig.apiKey, {
2179
+ }), response = await invokeTranslationFunction({
2180
+ client,
2181
+ openaiConfig,
2414
2182
  messages: [
2415
2183
  {
2416
2184
  role: "developer",
2417
2185
  content: "You are a translation quality reviewer. Review and refine translations while preserving structure."
2418
2186
  },
2419
2187
  { role: "user", content: prompt }
2420
- ],
2421
- model: openaiConfig.model,
2422
- reasoningEffort: openaiConfig.reasoningEffort
2188
+ ]
2423
2189
  });
2424
2190
  openaiResponseContent = response.content, devLog.info("OpenAI translation response received", {
2425
2191
  ...logContext,
@@ -2727,25 +2493,18 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2727
2493
  languageFieldName
2728
2494
  });
2729
2495
  const documentAfterUnset = unsetDocumentFields(sourceDocument, safeUnsetFields), { preserveFieldPatterns } = normalizeUnsetFields(safeUnsetFields);
2730
- let documentWithPreserved = documentAfterUnset;
2496
+ let preservedValues = {};
2731
2497
  if (preserveFieldPatterns.length > 0 && existingTranslationId)
2732
2498
  try {
2733
2499
  const publishedTarget = await client.getDocument(existingTranslationId);
2734
- if (publishedTarget) {
2735
- const { excludedValues: preserveValues } = filterExcludedFields(
2736
- publishedTarget,
2737
- preserveFieldPatterns
2738
- );
2739
- Object.keys(preserveValues).length > 0 && (documentWithPreserved = restoreExcludedFields(documentAfterUnset, preserveValues), devLog.info("Preserved fields from published target during re-translation", {
2740
- documentId: sourceDocument._id,
2741
- existingTranslationId,
2742
- preservedFields: Object.keys(preserveValues)
2743
- }));
2744
- } else
2745
- devLog.debug("Published target not found for preserveExisting fields", {
2746
- documentId: sourceDocument._id,
2747
- existingTranslationId
2748
- });
2500
+ publishedTarget ? (preservedValues = filterExcludedFields(publishedTarget, preserveFieldPatterns).excludedValues, Object.keys(preservedValues).length > 0 && devLog.info("Preserving fields from published target during re-translation", {
2501
+ documentId: sourceDocument._id,
2502
+ existingTranslationId,
2503
+ preservedFields: Object.keys(preservedValues)
2504
+ })) : devLog.debug("Published target not found for preserveExisting fields", {
2505
+ documentId: sourceDocument._id,
2506
+ existingTranslationId
2507
+ });
2749
2508
  } catch (error) {
2750
2509
  devLog.warn("Failed to fetch published target for preserveExisting fields", {
2751
2510
  documentId: sourceDocument._id,
@@ -2764,10 +2523,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2764
2523
  documentType: sourceDocument._type,
2765
2524
  excludeFields: safeExcludeFields,
2766
2525
  excludeFieldsLength: safeExcludeFields?.length ?? 0,
2767
- sourceHasTitle: "title" in documentWithPreserved,
2768
- sourceTitleValue: documentWithPreserved.title
2526
+ sourceHasTitle: "title" in documentAfterUnset,
2527
+ sourceTitleValue: documentAfterUnset.title
2769
2528
  });
2770
- const { filteredDocument, excludedValues } = filterExcludedFields(documentWithPreserved, safeExcludeFields);
2529
+ const { filteredDocument, excludedValues } = filterExcludedFields(documentAfterUnset, safeExcludeFields);
2771
2530
  devLog.info("After filtering excluded fields", {
2772
2531
  documentId: sourceDocument._id,
2773
2532
  filteredHasTitle: "title" in filteredDocument,
@@ -2830,7 +2589,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2830
2589
  }
2831
2590
  }
2832
2591
  case "openai-only": {
2833
- if (!openaiConfig?.enabled || !openaiConfig?.apiKey)
2592
+ if (!isOpenAIConfigured(openaiConfig))
2834
2593
  throw new Error("OpenAI is not configured but openai-only mode was selected");
2835
2594
  setJobStatus(
2836
2595
  dispatch,
@@ -2842,6 +2601,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2842
2601
  );
2843
2602
  const result = await getOpenAITranslation({
2844
2603
  dispatch,
2604
+ client,
2845
2605
  sourceDocument: documentToTranslate,
2846
2606
  targetLanguage,
2847
2607
  languageFieldName,
@@ -2861,32 +2621,39 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2861
2621
  };
2862
2622
  if (translatedDoc = (await performTranslation()).document, !translatedDoc)
2863
2623
  return null;
2864
- let transformedResult;
2865
- Object.keys(originalStructure).length > 0 ? (devLog.debug("Restoring field transforms after translation", {
2866
- documentId: sourceDocument._id,
2867
- documentType: sourceDocument._type
2868
- }), transformedResult = restoreTransforms({
2869
- translatedDocument: translatedDoc,
2870
- originalStructure,
2871
- registry: globalTransformRegistry,
2872
- schemaType: sourceDocument._type
2873
- })) : transformedResult = translatedDoc;
2874
2624
  const excludedValuesToRestore = { ...excludedValues };
2875
- delete excludedValuesToRestore[languageFieldName], devLog.info("Before restoring excluded fields", {
2876
- documentId: sourceDocument._id,
2877
- excludedFieldCount: Object.keys(excludedValues).length,
2878
- excludedValuesKeys: Object.keys(excludedValues),
2879
- languageFieldExcluded: languageFieldName in excludedValues,
2880
- transformedResultHasTitle: "title" in transformedResult,
2881
- transformedResultTitle: transformedResult.title
2882
- });
2883
- const finalDoc = restoreExcludedFields(transformedResult, excludedValuesToRestore);
2884
- devLog.info("After restoring excluded fields", {
2885
- documentId: sourceDocument._id,
2886
- finalDocHasTitle: "title" in finalDoc,
2887
- finalDocTitle: finalDoc.title
2888
- });
2889
- const targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
2625
+ delete excludedValuesToRestore[languageFieldName];
2626
+ const finalizeDocument = (translated) => {
2627
+ let transformedResult;
2628
+ Object.keys(originalStructure).length > 0 ? (devLog.debug("Restoring field transforms after translation", {
2629
+ documentId: sourceDocument._id,
2630
+ documentType: sourceDocument._type
2631
+ }), transformedResult = restoreTransforms({
2632
+ translatedDocument: translated,
2633
+ originalStructure,
2634
+ registry: globalTransformRegistry,
2635
+ schemaType: sourceDocument._type
2636
+ })) : transformedResult = translated;
2637
+ const withoutUnsetFields = unsetDocumentFields(transformedResult, safeUnsetFields);
2638
+ devLog.info("Before restoring excluded fields", {
2639
+ documentId: sourceDocument._id,
2640
+ excludedFieldCount: Object.keys(excludedValues).length,
2641
+ excludedValuesKeys: Object.keys(excludedValues),
2642
+ languageFieldExcluded: languageFieldName in excludedValues,
2643
+ transformedResultHasTitle: "title" in withoutUnsetFields,
2644
+ transformedResultTitle: withoutUnsetFields.title
2645
+ });
2646
+ const finalDoc2 = restoreExcludedFields(
2647
+ restoreExcludedFields(withoutUnsetFields, excludedValuesToRestore),
2648
+ preservedValues
2649
+ );
2650
+ return devLog.info("After restoring excluded fields", {
2651
+ documentId: sourceDocument._id,
2652
+ finalDocHasTitle: "title" in finalDoc2,
2653
+ finalDocTitle: finalDoc2.title,
2654
+ preservedFields: Object.keys(preservedValues)
2655
+ }), finalDoc2;
2656
+ }, finalDoc = finalizeDocument(translatedDoc), targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
2890
2657
  ...finalDoc,
2891
2658
  _id: targetDocumentVersionId
2892
2659
  };
@@ -2925,15 +2692,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2925
2692
  const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`;
2926
2693
  if (translatedDoc = (await performTranslation(errorContext)).document, !translatedDoc)
2927
2694
  throw new Error("Translation retry failed: no document returned");
2928
- let retryTransformedResult;
2929
- Object.keys(originalStructure).length > 0 ? retryTransformedResult = restoreTransforms({
2930
- translatedDocument: translatedDoc,
2931
- originalStructure,
2932
- registry: globalTransformRegistry,
2933
- schemaType: sourceDocument._type
2934
- }) : retryTransformedResult = translatedDoc;
2935
2695
  const retryNewDoc = {
2936
- ...restoreExcludedFields(retryTransformedResult, excludedValuesToRestore),
2696
+ ...finalizeDocument(translatedDoc),
2937
2697
  _id: targetDocumentVersionId
2938
2698
  };
2939
2699
  return await retryWithBackoff(
@@ -3224,9 +2984,11 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3224
2984
  }
3225
2985
  try {
3226
2986
  await translationSemaphore.withPermit(async () => {
3227
- const document = await client.withConfig({
3228
- perspective: sourcePerspective ?? "drafts"
3229
- }).getDocument(documentId);
2987
+ const document = await fetchSourceDocument(
2988
+ client,
2989
+ documentId,
2990
+ sourcePerspective ?? "drafts"
2991
+ );
3230
2992
  if (document || devLog.error("\u274C SOURCE DOCUMENT NOT FOUND", { documentId }), document && releaseId) {
3231
2993
  const versionedTargetDocumentId = await translateDocument({
3232
2994
  dispatch,
@@ -3343,7 +3105,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3343
3105
  onSkipConflicts
3344
3106
  }) => {
3345
3107
  const uniqueDocuments = useMemo(() => new Set(conflicts.map((c) => c.sourceDocumentId)).size, [conflicts]), nonConflictingCount = totalTranslations - conflicts.length, hasNonConflicting = nonConflictingCount > 0, summaryText = useMemo(() => conflicts.length === 1 ? "1 translation" : uniqueDocuments === 1 ? `${conflicts.length} translations` : `${conflicts.length} translations across ${uniqueDocuments} documents`, [conflicts.length, uniqueDocuments]);
3346
- return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3108
+ return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3347
3109
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3348
3110
  /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(WarningOutlineIcon, {}) }),
3349
3111
  /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "This release already contains translations for this content and target language(s)." })
@@ -3353,13 +3115,13 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3353
3115
  summaryText,
3354
3116
  " that will be overwritten if you continue:"
3355
3117
  ] }),
3356
- /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "500px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 2, children: conflicts.map((conflict) => /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
3118
+ /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "500px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: conflicts.map((conflict) => /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
3357
3119
  conflict.sourceDocumentTitle || conflict.sourceDocumentId,
3358
3120
  conflict.sourceDocumentType && ` (${conflict.sourceDocumentType})`,
3359
3121
  " \u2192 ",
3360
3122
  conflict.targetLanguage
3361
3123
  ] }, conflict.existingVersionId)) }) }) }),
3362
- /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3124
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3363
3125
  /* @__PURE__ */ jsx(Heading, { size: 0, children: "Choose an action:" }),
3364
3126
  /* @__PURE__ */ jsxs(Text, { size: 1, children: [
3365
3127
  /* @__PURE__ */ jsx("strong", { children: "Cancel" }),
@@ -3415,12 +3177,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3415
3177
  hasRun.current || (hasRun.current = !0, (async () => {
3416
3178
  onResult(documentId, { isValidating: !0, errors: [] });
3417
3179
  try {
3418
- const publishedId = getPublishedId(documentId), draftId = `drafts.${publishedId}`;
3419
- let doc;
3420
- if (perspective === "published" ? doc = await client.fetch("*[_id == $publishedId][0]", { publishedId }) : doc = await client.fetch(
3421
- "*[_id in [$draftId, $publishedId]] | order(_id desc) [0]",
3422
- { draftId, publishedId }
3423
- ), !doc) {
3180
+ const doc = await fetchSourceDocument(client, documentId, perspective);
3181
+ if (!doc) {
3424
3182
  devLog.warn("Document not found for validation", { documentId, perspective }), onResult(documentId, { isValidating: !1, errors: [] });
3425
3183
  return;
3426
3184
  }
@@ -3436,14 +3194,13 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3436
3194
  component: "DocumentValidator"
3437
3195
  }), onResult(documentId, { isValidating: !1, errors });
3438
3196
  } catch (error) {
3439
- devLog.error(
3440
- "Failed to validate document",
3441
- error instanceof Error ? error : new Error(String(error)),
3442
- { documentId, component: "DocumentValidator" }
3443
- ), onResult(documentId, { isValidating: !1, errors: [] });
3197
+ devLog.error("Failed to validate document", error instanceof Error ? error : new Error(String(error)), {
3198
+ documentId,
3199
+ component: "DocumentValidator"
3200
+ }), onResult(documentId, { isValidating: !1, errors: [] });
3444
3201
  }
3445
3202
  })());
3446
- }, [documentId, documentType, perspective, onResult, client, workspace]), null;
3203
+ }, [documentId, perspective, onResult, client, workspace]), null;
3447
3204
  }, PreflightView = () => {
3448
3205
  const {
3449
3206
  document,
@@ -3605,23 +3362,21 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3605
3362
  }, []), sourceRefDraftIds = useMemo(
3606
3363
  () => new Set(sourceRefsWithDrafts.map((d) => d.publishedId)),
3607
3364
  [sourceRefsWithDrafts]
3608
- ), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleCancelConflict = useCallback(() => {
3609
- setConflictContext(null);
3610
- }, []), handleReplaceConflict = useCallback(() => {
3365
+ ), isLoading = someStillValidating || isSourceDraftCheckLoading || isDraftCheckLoading || isCheckingConflicts, hasStaleDrafts = staleDrafts.length > 0, draftsDismissed = draftAction === "continue", canStartTranslation = !isLoading && !hasValidationErrors && !conflictContext && (!hasStaleDrafts || draftsDismissed), handleReplaceConflict = useCallback(() => {
3611
3366
  conflictContext && startTranslation(!0, conflictContext.releaseId);
3612
3367
  }, [startTranslation, conflictContext]), handleSkipConflicts = useCallback(() => {
3613
3368
  conflictContext && startTranslation(!1, conflictContext.releaseId, conflictContext.conflicts);
3614
3369
  }, [startTranslation, conflictContext]), totalTranslations = useMemo(() => (1 + filteredReferences.length) * selectedTargetLanguages.length, [filteredReferences.length, selectedTargetLanguages.length]);
3615
- return conflictContext ? /* @__PURE__ */ jsx(Stack, { space: 4, children: /* @__PURE__ */ jsx(
3370
+ return conflictContext ? /* @__PURE__ */ jsx(Stack, { gap: 4, children: /* @__PURE__ */ jsx(
3616
3371
  ReleaseConflictWarning,
3617
3372
  {
3618
3373
  conflicts: conflictContext.conflicts,
3619
3374
  totalTranslations,
3620
- onCancel: handleCancelConflict,
3375
+ onCancel: handleBack,
3621
3376
  onReplace: handleReplaceConflict,
3622
3377
  onSkipConflicts: handleSkipConflicts
3623
3378
  }
3624
- ) }) : /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
3379
+ ) }) : /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3625
3380
  activeValidators.map((doc) => /* @__PURE__ */ jsx(
3626
3381
  DocumentValidator,
3627
3382
  {
@@ -3645,14 +3400,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3645
3400
  " document",
3646
3401
  filteredReferences.length !== 0 ? "s" : ""
3647
3402
  ] }),
3648
- /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasValidationErrors ? "critical" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3403
+ /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasValidationErrors ? "critical" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
3649
3404
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3650
3405
  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, {}) }),
3651
3406
  /* @__PURE__ */ jsx(Text, { weight: "semibold", children: someStillValidating ? `Validating documents (${validationResults.size}/${documentsToValidate.length})\u2026` : hasValidationErrors ? `Validation errors in ${documentsWithErrors.length} document${documentsWithErrors.length !== 1 ? "s" : ""}` : "All documents pass validation" })
3652
3407
  ] }),
3653
3408
  documentsWithErrors.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
3654
3409
  /* @__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." }),
3655
- /* @__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) => {
3410
+ /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: documentsWithErrors.map((doc) => {
3656
3411
  const publishedId = getPublishedId(doc._id), displayName = doc._id === document._id ? "This document" : doc.title || doc._id, hasUnpublishedChanges = sourceRefDraftIds.has(publishedId);
3657
3412
  return /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3658
3413
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
@@ -3680,14 +3435,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3680
3435
  }) }) }) })
3681
3436
  ] })
3682
3437
  ] }) }),
3683
- 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: [
3438
+ filteredReferences.length > 0 && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: sourceRefsWithDrafts.length > 0 ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
3684
3439
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3685
3440
  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, {}) }),
3686
3441
  /* @__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" })
3687
3442
  ] }),
3688
3443
  sourceRefsWithDrafts.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
3689
3444
  /* @__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".' }),
3690
- /* @__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: [
3445
+ /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: sourceRefsWithDrafts.map((ref) => /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3691
3446
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3692
3447
  /* @__PURE__ */ jsx(Text, { size: 1, children: ref.title || ref.publishedId }),
3693
3448
  /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: ref._type })
@@ -3696,14 +3451,14 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3696
3451
  ] }, ref.publishedId)) }) }) })
3697
3452
  ] })
3698
3453
  ] }) }),
3699
- (isDraftCheckLoading || hasStaleDrafts) && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasStaleDrafts && !draftsDismissed ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3454
+ (isDraftCheckLoading || hasStaleDrafts) && /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, radius: 2, tone: hasStaleDrafts && !draftsDismissed ? "caution" : void 0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
3700
3455
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
3701
3456
  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, {}) }),
3702
3457
  /* @__PURE__ */ jsx(Text, { weight: "semibold", children: isDraftCheckLoading ? "Checking for stale drafts on target documents\u2026" : draftsDismissed ? "Stale drafts acknowledged" : `${staleDrafts.length} draft${staleDrafts.length !== 1 ? "s" : ""} found on existing translated document${staleDrafts.length !== 1 ? "s" : ""}` })
3703
3458
  ] }),
3704
3459
  hasStaleDrafts && !draftsDismissed && /* @__PURE__ */ jsxs(Fragment, { children: [
3705
3460
  /* @__PURE__ */ jsx(Text, { size: 1, children: "These existing translations have unpublished changes that will be out-of-date after this translation is completed. Use the links below to either publish each draft or discard the changes." }),
3706
- /* @__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: [
3461
+ /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 3, children: staleDrafts.map((draft) => /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 2, children: [
3707
3462
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, flex: 1, style: { minWidth: 0 }, children: [
3708
3463
  /* @__PURE__ */ jsx(Text, { size: 1, children: draft.title || draft.targetId }),
3709
3464
  /* @__PURE__ */ jsx(Badge, { tone: "primary", fontSize: 0, children: draft.targetLanguage }),
@@ -3711,14 +3466,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3711
3466
  ] }),
3712
3467
  /* @__PURE__ */ jsx(DocumentOpenerButton, { documentId: draft.targetId, documentType: draft._type })
3713
3468
  ] }, draft.draftId)) }) }) }),
3714
- /* @__PURE__ */ jsx(
3715
- Button,
3716
- {
3717
- mode: "ghost",
3718
- text: "Continue Anyway",
3719
- onClick: () => setDraftAction("continue")
3720
- }
3721
- )
3469
+ /* @__PURE__ */ jsx(Button, { mode: "ghost", text: "Continue Anyway", onClick: () => setDraftAction("continue") })
3722
3470
  ] })
3723
3471
  ] }) }),
3724
3472
  /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
@@ -3754,17 +3502,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3754
3502
  }, [dispatch]);
3755
3503
  return /* @__PURE__ */ jsxs(Flex, { justify: "space-between", gap: 2, children: [
3756
3504
  /* @__PURE__ */ jsxs(Flex, { justify: "flex-start", gap: 2, children: [
3757
- /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 2, onClick: handleSelectAll, children: "Select All" }),
3758
- /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 2, onClick: handleSelectNone, children: "Select None" })
3505
+ /* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectAll, children: "Select All" }),
3506
+ /* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectNone, children: "Select None" })
3759
3507
  ] }),
3760
3508
  /* @__PURE__ */ jsxs(Flex, { justify: "flex-end", gap: 2, children: [
3761
- /* @__PURE__ */ jsx(Button, { onClick: () => setIsDialogOpen(!1), mode: "ghost", size: 2, children: "Cancel" }),
3509
+ /* @__PURE__ */ jsx(Button, { onClick: () => setIsDialogOpen(!1), mode: "ghost", children: "Cancel" }),
3762
3510
  /* @__PURE__ */ jsx(
3763
3511
  Button,
3764
3512
  {
3765
3513
  mode: "ghost",
3766
3514
  tone: "positive",
3767
- size: 2,
3768
3515
  onClick: handleTranslate,
3769
3516
  disabled: selectedTargetLanguages.length === 0 || !["idle", "completed"].includes(overallStatus) || releaseConfig.mode === "existing" && !releaseConfig.existingReleaseId,
3770
3517
  children: "Translate"
@@ -3784,9 +3531,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3784
3531
  payload: source
3785
3532
  });
3786
3533
  };
3787
- return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3534
+ return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
3788
3535
  /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "This document has unpublished changes" }),
3789
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3536
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3790
3537
  /* @__PURE__ */ jsx(
3791
3538
  Button,
3792
3539
  {
@@ -3794,9 +3541,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3794
3541
  mode: documentSource === "draft" ? "ghost" : "bleed",
3795
3542
  tone: documentSource === "draft" ? "primary" : "default",
3796
3543
  onClick: () => handleSourceChange("draft"),
3797
- children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3544
+ children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
3798
3545
  /* @__PURE__ */ jsx(Radio, { checked: documentSource === "draft", readOnly: !0 }),
3799
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3546
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3800
3547
  /* @__PURE__ */ jsx(Text, { children: "Translate from drafts" }),
3801
3548
  /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses the latest draft of this document and all referenced documents" })
3802
3549
  ] })
@@ -3810,9 +3557,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3810
3557
  mode: documentSource === "published" ? "ghost" : "bleed",
3811
3558
  tone: documentSource === "published" ? "primary" : "default",
3812
3559
  onClick: () => handleSourceChange("published"),
3813
- children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3560
+ children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
3814
3561
  /* @__PURE__ */ jsx(Radio, { checked: documentSource === "published", readOnly: !0 }),
3815
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3562
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3816
3563
  /* @__PURE__ */ jsx(Text, { children: "Translate from published" }),
3817
3564
  /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Uses only published content for this document and all referenced documents" })
3818
3565
  ] })
@@ -3857,17 +3604,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3857
3604
  payload: updatedLanguages
3858
3605
  });
3859
3606
  };
3860
- return isLoading ? /* @__PURE__ */ jsx(Button, { type: "button", mode: "bleed", size: 2, onClick: handleChange, disabled: !0, muted: !0, children: /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", paddingY: 3, children: /* @__PURE__ */ jsx(Spinner, { size: 1 }) }) }) : /* @__PURE__ */ jsx(
3607
+ return isLoading ? /* @__PURE__ */ jsx(Button, { type: "button", mode: "bleed", onClick: handleChange, disabled: !0, muted: !0, children: /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", paddingY: 3, children: /* @__PURE__ */ jsx(Spinner, { size: 1 }) }) }) : /* @__PURE__ */ jsx(
3861
3608
  Button,
3862
3609
  {
3863
3610
  type: "button",
3864
3611
  mode: isChecked ? "ghost" : "bleed",
3865
- size: 2,
3866
3612
  onClick: handleChange,
3867
3613
  disabled: isDisabled,
3868
3614
  tone: isChecked ? "primary" : "default",
3869
3615
  muted: !0,
3870
- children: /* @__PURE__ */ jsx(Flex, { align: "center", gap: 2, style: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Inline, { space: 3, children: [
3616
+ children: /* @__PURE__ */ jsx(Flex, { align: "center", gap: 2, style: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Inline, { gap: 3, children: [
3871
3617
  /* @__PURE__ */ jsx(Checkbox, { checked: isChecked, onChange: handleChange, label: lang.title, disabled: isDisabled }),
3872
3618
  /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
3873
3619
  /* @__PURE__ */ jsx(Text, { children: lang.title }),
@@ -3880,7 +3626,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3880
3626
  const { document, config } = useTranslationContext(), { loading } = useTranslationMetadata({
3881
3627
  documentId: document._id
3882
3628
  });
3883
- return /* @__PURE__ */ jsx(Card, { padding: 4, muted: !0, border: !0, children: /* @__PURE__ */ jsx(Grid, { columns: [1, 2], gap: 3, children: config?.supportedLanguages.map((lang) => /* @__PURE__ */ jsx(LanguageCheckbox, { lang, isLoading: loading }, lang.id)) }) });
3629
+ return /* @__PURE__ */ jsx(Card, { padding: 4, muted: !0, border: !0, children: /* @__PURE__ */ jsx(Grid, { gridTemplateColumns: [1, 2], gap: 3, children: config?.supportedLanguages.map((lang) => /* @__PURE__ */ jsx(LanguageCheckbox, { lang, isLoading: loading }, lang.id)) }) });
3884
3630
  }, reconcileReleaseReferences = async ({
3885
3631
  client,
3886
3632
  releaseId,
@@ -4023,9 +3769,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4023
3769
  if (releaseConfig.mode !== "existing" || !releaseId)
4024
3770
  return null;
4025
3771
  const isTranslating = overallStatus === "translating" || overallStatus === "linking_references", isReconciling = reconcilingStatus === "reconciling";
4026
- return /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, tone: reconcilingStatus === "error" ? "critical" : "default", children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3772
+ return /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, tone: reconcilingStatus === "error" ? "critical" : "default", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
4027
3773
  /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
4028
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3774
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4029
3775
  /* @__PURE__ */ jsx(Text, { size: 1, weight: "medium", children: "Reconcile References" }),
4030
3776
  /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: isLoadingCount ? "Loading..." : documentCount === null ? "Unable to load document count" : documentCount === 0 ? "No documents in release" : /* @__PURE__ */ jsxs(Fragment, { children: [
4031
3777
  documentCount,
@@ -4115,8 +3861,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4115
3861
  day: "numeric",
4116
3862
  year: "numeric"
4117
3863
  });
4118
- return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4119
- /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3864
+ return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3865
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
4120
3866
  /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, as: "label", style: { cursor: "pointer" }, onClick: () => handleModeChange("new"), children: [
4121
3867
  /* @__PURE__ */ jsx(Radio, { checked: releaseConfig.mode === "new", readOnly: !0 }),
4122
3868
  /* @__PURE__ */ jsx(Text, { weight: "medium", children: "Create new release" })
@@ -4144,7 +3890,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4144
3890
  placeholder: "Release title..."
4145
3891
  }
4146
3892
  ),
4147
- releaseConfig.mode === "existing" && /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3893
+ releaseConfig.mode === "existing" && /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
4148
3894
  isLoading && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
4149
3895
  /* @__PURE__ */ jsx(Spinner, { muted: !0 }),
4150
3896
  /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Loading..." })
@@ -4161,7 +3907,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4161
3907
  placeholder: "Search releases..."
4162
3908
  }
4163
3909
  ),
4164
- /* @__PURE__ */ jsx(Box, { style: { maxHeight: "200px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 1, children: filteredReleases.length === 0 ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No releases match your search." }) : filteredReleases.map((release) => {
3910
+ /* @__PURE__ */ jsx(Box, { style: { maxHeight: "200px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 1, children: filteredReleases.length === 0 ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No releases match your search." }) : filteredReleases.map((release) => {
4165
3911
  const releaseName = release._id.replace("_.releases.", ""), isSelected = releaseConfig.existingReleaseId === releaseName;
4166
3912
  return /* @__PURE__ */ jsx(
4167
3913
  Card,
@@ -4207,12 +3953,12 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4207
3953
  ] }) });
4208
3954
  }, TranslationModePanel = () => {
4209
3955
  const { config, translationMode, openaiValidated, dispatch } = useTranslationContext();
4210
- if (!config?.openai?.enabled || !config?.openai?.apiKey)
3956
+ if (!isOpenAIConfigured(config?.openai))
4211
3957
  return null;
4212
3958
  if (!openaiValidated)
4213
- return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4214
- /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "OpenAI Configuration" }),
4215
- /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Validating API key..." })
3959
+ return /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3960
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "OpenAI Unavailable" }),
3961
+ /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This Studio's Sanity client cannot invoke the translation function. OpenAI mode requires @sanity/client 8.1 or newer." })
4216
3962
  ] }) });
4217
3963
  const handleModeChange = (mode) => {
4218
3964
  dispatch({
@@ -4220,12 +3966,12 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4220
3966
  payload: mode
4221
3967
  });
4222
3968
  };
4223
- return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
4224
- /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3969
+ return /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
3970
+ /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4225
3971
  /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "Translation Mode" }),
4226
3972
  /* @__PURE__ */ jsx(Badge, { tone: "positive", children: "OpenAI Available" })
4227
3973
  ] }),
4228
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3974
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4229
3975
  /* @__PURE__ */ jsx(
4230
3976
  Button,
4231
3977
  {
@@ -4233,9 +3979,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4233
3979
  mode: translationMode === "sanity-only" ? "ghost" : "bleed",
4234
3980
  tone: translationMode === "sanity-only" ? "primary" : "default",
4235
3981
  onClick: () => handleModeChange("sanity-only"),
4236
- children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3982
+ children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4237
3983
  /* @__PURE__ */ jsx(Radio, { checked: translationMode === "sanity-only", readOnly: !0 }),
4238
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3984
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4239
3985
  /* @__PURE__ */ jsx(Text, { children: "Sanity Agent Only" }),
4240
3986
  /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Fast, reliable, and included with Sanity." })
4241
3987
  ] })
@@ -4249,10 +3995,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4249
3995
  mode: translationMode === "openai-only" ? "ghost" : "bleed",
4250
3996
  tone: translationMode === "openai-only" ? "primary" : "default",
4251
3997
  onClick: () => handleModeChange("openai-only"),
4252
- children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
3998
+ children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4253
3999
  /* @__PURE__ */ jsx(Radio, { checked: translationMode === "openai-only", readOnly: !0 }),
4254
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4255
- /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
4000
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4001
+ /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4256
4002
  /* @__PURE__ */ jsx(Text, { children: "OpenAI Only" }),
4257
4003
  /* @__PURE__ */ jsx(Badge, { tone: "caution", children: "Beta" })
4258
4004
  ] }),
@@ -4371,7 +4117,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4371
4117
  payload: newSelection
4372
4118
  });
4373
4119
  };
4374
- return references.length === 0 ? null : /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
4120
+ return references.length === 0 ? null : /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
4375
4121
  /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", gap: 2, children: [
4376
4122
  /* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
4377
4123
  /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Select References" }),
@@ -4384,8 +4130,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4384
4130
  ] })
4385
4131
  ] }),
4386
4132
  /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
4387
- /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 1, onClick: handleSelectAll, text: "Select All" }),
4388
- /* @__PURE__ */ jsx(Button, { mode: "ghost", size: 1, onClick: handleSelectNone, text: "Select None" })
4133
+ /* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectAll, text: "Select All" }),
4134
+ /* @__PURE__ */ jsx(Button, { mode: "ghost", onClick: handleSelectNone, text: "Select None" })
4389
4135
  ] })
4390
4136
  ] }),
4391
4137
  documentTypes.length > 1 && /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
@@ -4415,7 +4161,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4415
4161
  }
4416
4162
  )
4417
4163
  ] }),
4418
- /* @__PURE__ */ jsx(Box, { style: { maxHeight: "400px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { space: 2, children: filteredReferences.length === 0 ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No references match the selected filter." }) : filteredReferences.map((reference) => {
4164
+ /* @__PURE__ */ jsx(Box, { style: { maxHeight: "400px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: filteredReferences.length === 0 ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "No references match the selected filter." }) : filteredReferences.map((reference) => {
4419
4165
  const isSelected = selectedReferenceIds.has(reference._id), displayTitle = reference.title || reference._id;
4420
4166
  return /* @__PURE__ */ jsx(Card, { padding: 3, border: !0, radius: 2, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
4421
4167
  /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => handleToggleReference(reference._id) }),
@@ -4460,17 +4206,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4460
4206
  payload: !shouldTranslateReferences
4461
4207
  });
4462
4208
  };
4463
- return /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4209
+ return /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4464
4210
  /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
4465
4211
  /* @__PURE__ */ jsx(
4466
4212
  Button,
4467
4213
  {
4468
4214
  type: "button",
4469
4215
  mode: shouldTranslateReferences ? "ghost" : "bleed",
4470
- size: 2,
4471
4216
  onClick: handleShouldTranslateChange,
4472
4217
  tone: shouldTranslateReferences ? "primary" : "neutral",
4473
- children: /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
4218
+ children: /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4474
4219
  /* @__PURE__ */ jsx(
4475
4220
  Checkbox,
4476
4221
  {
@@ -4483,7 +4228,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4483
4228
  ] })
4484
4229
  }
4485
4230
  ),
4486
- shouldTranslateReferences ? /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
4231
+ shouldTranslateReferences ? /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4487
4232
  /* @__PURE__ */ jsx(Text, { children: "Max Depth:" }),
4488
4233
  /* @__PURE__ */ jsx(
4489
4234
  Select,
@@ -4507,7 +4252,6 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4507
4252
  Button,
4508
4253
  {
4509
4254
  mode: "ghost",
4510
- size: 1,
4511
4255
  text: showSelectionPanel ? "Hide selection" : "Select specific references...",
4512
4256
  onClick: () => setShowSelectionPanel(!showSelectionPanel)
4513
4257
  }
@@ -4516,21 +4260,21 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4516
4260
  !isLoadingReferences && references.length > 0 && showSelectionPanel && /* @__PURE__ */ jsx(ReferenceSelectionPanel, {})
4517
4261
  ] }) : null
4518
4262
  ] });
4519
- }, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4263
+ }, WriteSettingsPanel = () => /* @__PURE__ */ jsx(ReferenceConfigPanel, {}), TranslationConfigView = ({ setIsDialogOpen }) => /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
4520
4264
  /* @__PURE__ */ jsx(DocumentSourcePanel, {}),
4521
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4265
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4522
4266
  /* @__PURE__ */ jsx(Label, { children: "Languages" }),
4523
4267
  /* @__PURE__ */ jsx(LanguageSelectionPanel, {})
4524
4268
  ] }),
4525
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4269
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4526
4270
  /* @__PURE__ */ jsx(Label, { children: "Translation Method" }),
4527
4271
  /* @__PURE__ */ jsx(TranslationModePanel, {})
4528
4272
  ] }),
4529
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4273
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4530
4274
  /* @__PURE__ */ jsx(Label, { children: "Content Release" }),
4531
4275
  /* @__PURE__ */ jsx(ReleaseSelectionPanel, {})
4532
4276
  ] }),
4533
- /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4277
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4534
4278
  /* @__PURE__ */ jsx(Label, { children: "Settings" }),
4535
4279
  /* @__PURE__ */ jsx(Card, { padding: 4, border: !0, muted: !0, children: /* @__PURE__ */ jsx(WriteSettingsPanel, {}) })
4536
4280
  ] }),
@@ -4539,16 +4283,16 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4539
4283
  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(() => {
4540
4284
  forceCleanupActiveTranslation(document._id), dispatch({ type: "RESET_TRANSLATION_STATE" });
4541
4285
  }, [dispatch, document._id]);
4542
- return /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
4286
+ return /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
4543
4287
  /* @__PURE__ */ jsx(Heading, { size: 2, children: headline }),
4544
4288
  /* @__PURE__ */ jsx(ProgressBar, { progress: jobs.filter((job) => TERMINAL_STATUSES.includes(job.status)).length, max: jobs.length }),
4545
- /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
4289
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
4546
4290
  /* @__PURE__ */ jsx(Label, { children: "Translations" }),
4547
- /* @__PURE__ */ jsx(Card, { padding: 2, tone: "transparent", children: /* @__PURE__ */ jsx(Stack, { space: 2, children: /* @__PURE__ */ jsx("ul", { style: { listStyleType: "none", paddingInlineStart: "0" }, children: jobs.map((job) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
4548
- /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
4291
+ /* @__PURE__ */ jsx(Card, { padding: 2, tone: "transparent", children: /* @__PURE__ */ jsx(Stack, { gap: 2, children: /* @__PURE__ */ jsx("ul", { style: { listStyleType: "none", paddingInlineStart: "0" }, children: jobs.map((job) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", align: "center", children: [
4292
+ /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4549
4293
  /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", style: { width: "24px", height: "24px" }, children: ["completed", "skipped"].includes(job.status) ? /* @__PURE__ */ jsx(CheckmarkIcon, { fontSize: 24 }) : job.status === "error" ? /* @__PURE__ */ jsx(Box, { style: { color: "var(--card-critical-fg-color)" }, children: /* @__PURE__ */ jsx(ErrorOutlineIcon, { fontSize: 24 }) }) : job.status === "queued" ? /* @__PURE__ */ jsx(CircleIcon, { fontSize: 24 }) : /* @__PURE__ */ jsx(Spinner, {}) }),
4550
- /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
4551
- /* @__PURE__ */ jsxs(Inline, { space: 2, children: [
4294
+ /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
4295
+ /* @__PURE__ */ jsxs(Inline, { gap: 2, children: [
4552
4296
  /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: job.title || "Untitled Document" }),
4553
4297
  /* @__PURE__ */ jsx(ArrowRightIcon, { fontSize: 18 }),
4554
4298
  /* @__PURE__ */ jsx(Code, { size: 1, children: job.language })
@@ -4584,7 +4328,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
4584
4328
  ] });
4585
4329
  }, ProgressBar = ({ progress, max = 100 }) => {
4586
4330
  const percentage = Math.min(progress / max * 100, 100);
4587
- return /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
4331
+ return /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4588
4332
  /* @__PURE__ */ jsx(
4589
4333
  "div",
4590
4334
  {
@@ -5437,7 +5181,7 @@ function getDocumentLanguage(document) {
5437
5181
  }
5438
5182
  const aiTranslationPlugin = definePlugin((config) => (config.fieldTransforms?.length && (devLog.info(`Registering ${config.fieldTransforms.length} custom field transforms`), config.fieldTransforms.forEach((transform) => {
5439
5183
  globalTransformRegistry.register(transform);
5440
- })), {
5184
+ })), setGlossaryDataset(config.glossaryDataset), {
5441
5185
  name: "sanity-plugin-ai-translation",
5442
5186
  document: {
5443
5187
  actions: [getTranslateAction(config)]