@braze/sanity-plugin-ai-translation 0.2.71 → 0.2.73

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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/utils/common/devLog.ts","../src/utils/openai/validateApiKey.ts","../src/utils/document/undraftId.ts","../src/utils/state/activeTranslations.ts","../src/hooks/useTranslationContext/TranslationContext.ts","../src/hooks/useTranslationContext/translationReducer.ts","../src/hooks/useTranslationContext/TranslationContextProvider.tsx","../src/hooks/useTranslationContext/useTranslationContext.ts","../src/utils/metadata/getExistingTranslationId.ts","../src/utils/metadata/constants.ts","../src/utils/metadata/getTranslationMetadata.ts","../src/utils/releases/checkReleaseConflicts.ts","../src/utils/common/retry.ts","../src/utils/common/Semaphore.ts","../src/utils/document/documentToPlainText.ts","../src/utils/matching/matchTermInDocument.ts","../src/utils/matching/fetchTranslationPrompts.ts","../src/utils/common/findKeyValues/findKeyValues.ts","../src/utils/references/localizeReferences.ts","../src/utils/references/updateBackwardReferences.ts","../src/utils/state/setupTranslationJobStatuses.ts","../src/utils/telemetry/telemetry.ts","../src/utils/metadata/updateTranslationMetadata.ts","../src/utils/state/setJobStatus.ts","../src/utils/document/excludeFields.ts","../src/utils/transforms/applyTransforms.ts","../src/utils/transforms/restoreTransforms.ts","../src/utils/transforms/transformRegistry.ts","../src/utils/openai/callGPT5.ts","../src/utils/openai/deserializeDocument.ts","../src/utils/openai/serializeDocument.ts","../src/utils/openai/validateStructure.ts","../src/utils/translation/getOpenAITranslation.ts","../src/utils/document/assembleLanguageSpecs.ts","../src/utils/translation/assembleTranslationPrompt.ts","../src/utils/translation/getSanityAgentTranslation.ts","../src/utils/translation/handleTranslation.ts","../src/utils/translation/translateDocument.ts","../src/utils/translation/translateDocumentSet.ts","../src/components/ReleaseConflictWarning.tsx","../src/components/ActionButtonBar.tsx","../src/hooks/useTranslationMetadata.ts","../src/components/LanguageCheckbox.tsx","../src/components/LanguageSelectionPanel.tsx","../src/utils/references/reconcileReleaseReferences.ts","../src/components/ReconcileReferencesButton.tsx","../src/components/ReleaseSelectionPanel.tsx","../src/components/TranslationModePanel.tsx","../src/utils/references/assembleReferences.ts","../src/components/ReferenceSelectionPanel.tsx","../src/components/ReferenceConfigPanel.tsx","../src/components/WriteSettingsPanel.tsx","../src/components/TranslationConfigPanel.tsx","../src/components/TranslationStatusPanel.tsx","../src/components/TranslationDialog.tsx","../src/components/actions/TranslateAction.tsx","../src/schemas/translationGlossaryTerm.schema.ts","../src/schemas/translationHistory.schema.ts","../src/schemas/translationSettings.schema.ts","../src/utils/transforms/builtIn/richTextTransform/richTextTransform.ts","../src/utils/transforms/builtIn/vttCaptionTransform/vttCaptionTransform.ts","../src/utils/common/isoLanguage.ts","../src/utils/transforms/builtIn/wistiaCaptionTransform/wistiaCaptionTransform.ts","../src/index.ts"],"sourcesContent":["/**\n * Development logging utility for AI Translation plugin troubleshooting\n * Only logs when SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING environment variable is \"true\"\n */\n\nconst isLoggingEnabled = (): boolean => {\n return (\n process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === 'true' ||\n process.env.SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === 'true'\n )\n}\n\nexport interface DevLogContext {\n component?: string\n action?: string\n documentId?: string\n language?: string\n jobId?: string\n // biome-ignore lint/suspicious/noExplicitAny: not worth the effort\n [key: string]: any\n}\n\nexport const devLog = {\n info: (message: string, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.info('[AI Translation]', message, context ? JSON.stringify(context, null, 2) : '')\n },\n\n warn: (message: string, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.warn('[AI Translation]', message, context ? JSON.stringify(context, null, 2) : '')\n },\n\n error: (message: string, error?: Error | unknown, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.error('[AI Translation]', message, error, context ? JSON.stringify(context, null, 2) : '')\n },\n\n debug: (message: string, data?: unknown, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.debug('[AI Translation]', message, data, context ? JSON.stringify(context, null, 2) : '')\n },\n\n group: (label: string, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.group(`[AI Translation] ${label}`, context ? JSON.stringify(context, null, 2) : '')\n },\n\n groupEnd: () => {\n if (!isLoggingEnabled()) return\n console.groupEnd()\n },\n\n time: (label: string) => {\n if (!isLoggingEnabled()) return\n console.time(`[AI Translation] ${label}`)\n },\n\n timeEnd: (label: string) => {\n if (!isLoggingEnabled()) return\n console.timeEnd(`[AI Translation] ${label}`)\n },\n}\n","export interface ValidationResult {\n valid: boolean\n error?: string\n modelAvailable?: string[]\n}\n\n/**\n * Validates an OpenAI API key by making a minimal API call.\n * Checks if the key is valid and if the requested model is available.\n */\nexport const validateOpenAIApiKey = async (apiKey: string, model = 'gpt-5'): Promise<ValidationResult> => {\n try {\n // Make a minimal API call to validate the key\n const response = await fetch('https://api.openai.com/v1/models', {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n })\n\n if (!response.ok) {\n const error = await response.json()\n return {\n valid: false,\n error: error.error?.message || 'Invalid API key',\n }\n }\n\n const data = await response.json()\n const availableModels = data.data.map((m: { id: string }) => m.id)\n\n // Check if requested model is available\n if (!availableModels.includes(model)) {\n return {\n valid: false,\n error: `Model ${model} not available. Try: ${availableModels.slice(0, 5).join(', ')}`,\n }\n }\n\n return {\n valid: true,\n modelAvailable: availableModels,\n }\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : 'Network error',\n }\n }\n}\n","// Remove `drafts.` from a string\nexport const undraftId = (id: string) => id.replace(/^drafts\\./, '')\n\nexport const draftId = (id: string) => `drafts.${undraftId(id)}`\n","/**\n * Module-level state store for tracking active translations.\n *\n * This allows the translation dialog to reconnect to an ongoing translation\n * if the user closes and reopens it. The state persists in memory but is\n * lost on page refresh.\n */\n\nimport type { OverallTranslationStatus, TranslationJob } from '../../types'\nimport { undraftId } from '../document/undraftId'\n\nexport interface ActiveTranslationState {\n documentId: string\n releaseId?: string\n overallStatus: OverallTranslationStatus\n translationJobs: Map<string, TranslationJob>\n startedAt: Date\n}\n\ntype Subscriber = (state: ActiveTranslationState) => void\n\ninterface ActiveTranslationEntry {\n state: ActiveTranslationState\n subscribers: Set<Subscriber>\n}\n\n/**\n * Map of source document ID -> active translation state\n * This is the module-level store that persists across dialog open/close cycles\n */\nconst activeTranslations = new Map<string, ActiveTranslationEntry>()\n\n/**\n * Register a new active translation for a document.\n * Called when translation starts.\n */\nexport const registerActiveTranslation = (documentId: string, releaseId?: string): void => {\n const entry: ActiveTranslationEntry = {\n state: {\n documentId,\n releaseId,\n overallStatus: 'translating',\n translationJobs: new Map(),\n startedAt: new Date(),\n },\n subscribers: new Set(),\n }\n activeTranslations.set(documentId, entry)\n}\n\n/**\n * Update the overall status of an active translation.\n */\nexport const updateActiveTranslationStatus = (documentId: string, status: OverallTranslationStatus): void => {\n const entry = activeTranslations.get(documentId)\n if (entry) {\n entry.state.overallStatus = status\n notifySubscribers(entry)\n }\n}\n\n/**\n * Update a specific job within an active translation.\n * Merges with existing job data to preserve fields like title and _type\n * that are set during initial job setup but not included in status updates.\n */\nexport const updateActiveTranslationJob = (documentId: string, job: TranslationJob): void => {\n const entry = activeTranslations.get(documentId)\n if (entry) {\n // Create a composite key for the job (documentId + language)\n // IMPORTANT: Must match the key format in translationReducer.ts\n // Normalize document ID to handle draft vs published versions consistently\n const normalizedId = undraftId(job._id)\n const jobKey = `${normalizedId}_${job.language}`\n\n // Merge with existing job to preserve title, _type, etc.\n // Use the same merge strategy as translationReducer: only overwrite with defined values\n const existingJob = entry.state.translationJobs.get(jobKey)\n // Filter out undefined values from job to prevent overwriting existing values\n // This must be done for both new and existing jobs to maintain consistency\n const jobWithDefinedValues = Object.fromEntries(\n Object.entries(job).filter(([_, value]) => value !== undefined),\n ) as TranslationJob\n const mergedJob = existingJob ? { ...existingJob, ...jobWithDefinedValues } : jobWithDefinedValues\n\n entry.state.translationJobs.set(jobKey, mergedJob)\n notifySubscribers(entry)\n }\n}\n\n/**\n * Get the current state of an active translation, if one exists.\n */\nexport const getActiveTranslation = (documentId: string): ActiveTranslationState | null => {\n const entry = activeTranslations.get(documentId)\n return entry?.state ?? null\n}\n\n/**\n * Check if there's an active (in-progress) translation for a document.\n */\nexport const hasActiveTranslation = (documentId: string): boolean => {\n const entry = activeTranslations.get(documentId)\n if (!entry) return false\n\n // Only consider it \"active\" if it's still in progress\n const inProgressStatuses: OverallTranslationStatus[] = ['translating', 'linking_references']\n return inProgressStatuses.includes(entry.state.overallStatus)\n}\n\n/**\n * Subscribe to updates for an active translation.\n * Returns an unsubscribe function.\n */\nexport const subscribeToActiveTranslation = (documentId: string, callback: Subscriber): (() => void) => {\n const entry = activeTranslations.get(documentId)\n if (!entry) {\n return () => {} // No-op unsubscribe if no active translation\n }\n\n entry.subscribers.add(callback)\n\n // Immediately call with current state\n callback(entry.state)\n\n // Return unsubscribe function\n return () => {\n entry.subscribers.delete(callback)\n }\n}\n\n/**\n * Clean up a completed translation after a delay.\n * We keep it around briefly so the dialog can show the final state.\n */\nexport const cleanupActiveTranslation = (documentId: string, delayMs = 60000): void => {\n setTimeout(() => {\n const entry = activeTranslations.get(documentId)\n // Only cleanup if still completed/error (not restarted)\n if (entry && ['completed', 'error'].includes(entry.state.overallStatus)) {\n activeTranslations.delete(documentId)\n }\n }, delayMs)\n}\n\n/**\n * Notify all subscribers of a state change.\n */\nconst notifySubscribers = (entry: ActiveTranslationEntry): void => {\n for (const subscriber of entry.subscribers) {\n try {\n subscriber(entry.state)\n } catch (error) {\n // Don't let one subscriber's error affect others\n console.error('Error in active translation subscriber:', error)\n }\n }\n}\n\n/**\n * Force cleanup of an active translation immediately (for testing).\n */\nexport const forceCleanupActiveTranslation = (documentId: string): void => {\n activeTranslations.delete(documentId)\n}\n","import { createContext } from 'react'\nimport type { TranslationJob, TranslationContext as TTranslationContext } from '../../types'\n\nexport const TranslationContext = createContext<TTranslationContext>({\n document: null,\n config: null,\n overallStatus: 'idle',\n selectedTargetLanguages: [],\n translationJobs: new Map<string, TranslationJob>(),\n shouldTranslateReferences: false,\n maxReferenceDepth: 1,\n references: [],\n selectedReferenceIds: new Set(),\n translationMetadata: null,\n translationMode: 'sanity-only',\n openaiValidated: false,\n releaseConfig: {\n mode: 'new',\n newReleaseTitle: '',\n },\n reconcilingStatus: 'idle',\n reconciliationProgress: undefined,\n dispatch: () => {},\n})\n","import defaults from 'lodash.defaults'\nimport type { TranslationAction, TranslationContext } from '../../types'\nimport { devLog } from '../../utils/common/devLog'\nimport { undraftId } from '../../utils/document/undraftId'\n\nexport const translationReducer = (\n context: Omit<TranslationContext, 'dispatch'>,\n action: TranslationAction,\n): Omit<TranslationContext, 'dispatch'> => {\n const logContext = {\n documentId: context.document?._id,\n actionType: action.type,\n component: 'translationReducer',\n }\n\n devLog.debug('Translation action dispatched', {\n ...logContext,\n payload: 'payload' in action ? action.payload : undefined,\n })\n\n switch (action.type) {\n case 'SET_OVERALL_STATUS':\n return { ...context, overallStatus: action.payload }\n\n case 'SET_TRANSLATION_METADATA':\n return { ...context, translationMetadata: action.payload }\n\n case 'SET_SELECTED_TARGET_LANGUAGES':\n return {\n ...context,\n selectedTargetLanguages: action.payload,\n }\n\n case 'SET_TRANSLATION_MODE':\n return {\n ...context,\n translationMode: action.payload,\n }\n\n case 'SET_OPENAI_VALIDATED':\n return {\n ...context,\n openaiValidated: action.payload,\n }\n\n case 'SET_SHOULD_TRANSLATE_REFERENCES':\n return {\n ...context,\n shouldTranslateReferences: action.payload,\n }\n\n case 'SET_MAX_REFERENCE_DEPTH':\n return {\n ...context,\n maxReferenceDepth: action.payload,\n }\n\n case 'SET_REFERENCES':\n return {\n ...context,\n references: action.payload,\n }\n\n case 'SET_SELECTED_REFERENCE_IDS':\n return {\n ...context,\n selectedReferenceIds: action.payload,\n }\n\n case 'SET_TRANSLATION_JOB_STATUS': {\n // Key combines document ID + language to support concurrent translations\n // Normalize document ID to handle draft vs published versions consistently\n if (!action.payload._id) {\n devLog.warn('SET_TRANSLATION_JOB_STATUS action missing _id', {\n ...logContext,\n payload: action.payload,\n })\n return context\n }\n const normalizedId = undraftId(action.payload._id)\n const jobKey = `${normalizedId}_${action.payload.language}`\n\n // New Map instance required for React to detect state change\n const newTranslationJobs = new Map(context.translationJobs)\n\n const existingJob = newTranslationJobs.get(jobKey)\n const mergedJob = defaults(action.payload, existingJob)\n\n newTranslationJobs.set(jobKey, mergedJob)\n\n return {\n ...context,\n translationJobs: newTranslationJobs,\n }\n }\n\n case 'SET_RELEASE_CONFIG':\n return {\n ...context,\n releaseConfig: {\n ...context.releaseConfig,\n ...action.payload,\n },\n }\n\n case 'RESET_TRANSLATION_STATE':\n devLog.info('Resetting translation state for new translation', {\n ...logContext,\n previousStatus: context.overallStatus,\n previousJobCount: context.translationJobs.size,\n })\n return {\n ...context,\n overallStatus: 'idle',\n translationJobs: new Map(),\n selectedReferenceIds: new Set(),\n // Clear any release conflicts from previous run\n releaseConfig: {\n ...context.releaseConfig,\n conflicts: undefined,\n replaceExisting: undefined,\n skipConflicts: undefined,\n },\n // Reset reconciliation state\n reconcilingStatus: 'idle',\n reconciliationProgress: undefined,\n }\n\n case 'SET_RECONCILING_STATUS':\n return {\n ...context,\n reconcilingStatus: action.payload,\n // Clear progress when returning to idle or on error\n reconciliationProgress:\n action.payload === 'idle' || action.payload === 'error' ? undefined : context.reconciliationProgress,\n }\n\n case 'SET_RECONCILIATION_PROGRESS':\n return {\n ...context,\n reconciliationProgress: action.payload,\n }\n\n default:\n return context\n }\n}\n","import { type FC, useEffect, useReducer, useRef } from 'react'\nimport type { PluginConfig, TranslatableDocument, TranslationContext as TTranslationContext } from '../../types'\nimport { devLog } from '../../utils/common/devLog'\nimport { validateOpenAIApiKey } from '../../utils/openai/validateApiKey'\nimport {\n type ActiveTranslationState,\n getActiveTranslation,\n subscribeToActiveTranslation,\n} from '../../utils/state/activeTranslations'\nimport { TranslationContext } from './TranslationContext'\nimport { translationReducer } from './translationReducer'\n\ninterface TranslationContextProviderProps {\n document: TranslatableDocument\n config: PluginConfig\n children: React.ReactNode\n}\n\n/**\n * Generates a default release title based on the document being translated.\n */\nexport const generateDefaultReleaseTitle = (document: TranslatableDocument): string => {\n const documentTitle =\n (document.title as string | undefined) ||\n (document.name as string | undefined) ||\n document._id.split('-').slice(0, 2).join('-')\n\n return `[AI Translation] ${documentTitle}`\n}\n\nexport const TranslationContextProvider: FC<TranslationContextProviderProps> = ({ document, config, children }) => {\n devLog.debug('TranslationContextProvider initialized', {\n documentId: document._id,\n documentType: document._type,\n supportedLanguages: config.supportedLanguages?.map((l) => l.id),\n component: 'TranslationContextProvider',\n })\n\n const defaultReleaseTitle = generateDefaultReleaseTitle(document)\n\n // Check if there's an active translation we can reconnect to\n const activeTranslation = getActiveTranslation(document._id)\n\n const initialContext: Omit<TTranslationContext, 'dispatch'> = {\n document,\n config,\n // If there's an active translation, restore its state\n overallStatus: activeTranslation?.overallStatus ?? 'idle',\n translationMetadata: null,\n selectedTargetLanguages: [],\n shouldTranslateReferences: false,\n maxReferenceDepth: 1,\n references: [],\n selectedReferenceIds: new Set(),\n // Restore jobs from active translation if available\n translationJobs: activeTranslation?.translationJobs ?? new Map(),\n translationMode: config?.openai?.defaultMode || 'sanity-only',\n openaiValidated: false,\n releaseConfig: {\n mode: 'new',\n newReleaseTitle: defaultReleaseTitle,\n },\n // Reconciliation state for existing releases\n reconcilingStatus: 'idle',\n reconciliationProgress: undefined,\n }\n\n if (activeTranslation) {\n devLog.info('Reconnecting to active translation', {\n documentId: document._id,\n status: activeTranslation.overallStatus,\n jobCount: activeTranslation.translationJobs.size,\n component: 'TranslationContextProvider',\n })\n }\n\n const [context, dispatch] = useReducer(translationReducer, initialContext)\n\n // Track the last reference IDs we processed to detect actual changes\n const lastProcessedRefsRef = useRef<string>('')\n\n // Subscribe to active translation updates so dialog stays in sync\n useEffect(() => {\n const handleActiveTranslationUpdate = (state: ActiveTranslationState) => {\n // Update overall status\n dispatch({\n type: 'SET_OVERALL_STATUS',\n payload: state.overallStatus,\n })\n\n // Update each job\n for (const job of state.translationJobs.values()) {\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload: job,\n })\n }\n }\n\n const unsubscribe = subscribeToActiveTranslation(document._id, handleActiveTranslationUpdate)\n\n return () => {\n unsubscribe()\n }\n }, [document._id])\n\n // Verify OpenAI key validity on mount to enable/disable OpenAI modes in UI\n useEffect(() => {\n const validateKey = async () => {\n if (config?.openai?.enabled && config?.openai?.apiKey) {\n try {\n const validation = await validateOpenAIApiKey(config.openai.apiKey, config.openai.model || 'gpt-5')\n\n dispatch({\n type: 'SET_OPENAI_VALIDATED',\n payload: validation.valid,\n })\n\n if (validation.valid) {\n devLog.info('OpenAI API key validated successfully', {\n model: config.openai.model || 'gpt-5',\n component: 'TranslationContextProvider',\n })\n } else {\n devLog.error('OpenAI API key validation failed', {\n error: validation.error,\n component: 'TranslationContextProvider',\n })\n }\n } catch (error) {\n devLog.error('OpenAI validation error', error instanceof Error ? error : new Error(String(error)), {\n component: 'TranslationContextProvider',\n })\n dispatch({\n type: 'SET_OPENAI_VALIDATED',\n payload: false,\n })\n }\n }\n }\n\n validateKey()\n }, [config?.openai?.apiKey, config?.openai?.enabled, config?.openai?.model])\n\n // Auto-select all references when references array changes (default behavior)\n // Only auto-select when references are first loaded or when new references are added\n // This prevents the effect from interfering with user's manual selections\n useEffect(() => {\n // Create a stable string representation of current reference IDs for comparison\n const currentRefsKey = context.references\n .map((ref) => ref._id)\n .sort()\n .join(',')\n\n // Only process if references actually changed (not just selection)\n if (currentRefsKey !== lastProcessedRefsRef.current) {\n if (context.references.length > 0) {\n const currentReferenceIds = new Set(context.references.map((ref) => ref._id))\n const previousReferenceIds = new Set(\n lastProcessedRefsRef.current ? lastProcessedRefsRef.current.split(',') : [],\n )\n\n // Check if there are any new references that weren't in the previous set\n const hasNewReferences = Array.from(currentReferenceIds).some((id) => !previousReferenceIds.has(id))\n\n // Only auto-select if selection is empty (initial load) or there are new references\n // We read selectedReferenceIds here but the effect won't re-run when it changes\n // because we only update lastProcessedRefsRef when references actually change\n const currentSelection = context.selectedReferenceIds\n const shouldAutoSelect = currentSelection.size === 0 || hasNewReferences\n\n if (shouldAutoSelect) {\n // Preserve existing selections and add any new references\n const newSelection = new Set(currentSelection)\n currentReferenceIds.forEach((id) => {\n newSelection.add(id)\n })\n\n // Only dispatch if selection actually changed\n const currentIds = Array.from(currentSelection).sort()\n const newIds = Array.from(newSelection).sort()\n if (currentIds.length !== newIds.length || !currentIds.every((id, i) => id === newIds[i])) {\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n }\n }\n\n // Update the ref to track what we've processed\n lastProcessedRefsRef.current = currentRefsKey\n }\n }, [context.references, context.selectedReferenceIds])\n\n devLog.debug('Translation context state initialized', {\n documentId: document._id,\n overallStatus: context.overallStatus,\n selectedLanguages: context.selectedTargetLanguages,\n translationMode: context.translationMode,\n openaiEnabled: config?.openai?.enabled,\n component: 'TranslationContextProvider',\n })\n\n return <TranslationContext.Provider value={{ ...context, dispatch }}>{children}</TranslationContext.Provider>\n}\n","import { useContext } from 'react'\nimport type { TranslationContext as TTranslationContext } from '../../types'\nimport { TranslationContext } from './TranslationContext'\n\ninterface ValidatedTranslationContext extends TTranslationContext {\n document: NonNullable<TTranslationContext['document']>\n config: NonNullable<TTranslationContext['config']>\n}\n\nexport const useTranslationContext = (): ValidatedTranslationContext => {\n const context = useContext(TranslationContext)\n\n if (!context.document || !context.config) {\n throw new Error(\n 'useTranslationContext must be used within a TranslationContextProvider with document and config props',\n )\n }\n\n return context as ValidatedTranslationContext\n}\n","import type { PluginConfig, TranslationMetadataDocument } from '../../types'\n\nexport const getExistingTranslationId = (\n translationMetadata: TranslationMetadataDocument,\n targetLanguage: PluginConfig['supportedLanguages'][number]['id'],\n): string | null => {\n if (!translationMetadata?.translations?.length) {\n return null\n }\n\n const existingTranslation = translationMetadata.translations.find(\n (translation) => translation._key === targetLanguage,\n )\n\n return existingTranslation?.value._ref || null\n}\n","/**\n * Schema type name for translation metadata documents\n */\nexport const TRANSLATION_METADATA_TYPE = 'translation.metadata'\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument, TranslationMetadataDocument } from '../../types'\nimport { undraftId } from '../document/undraftId'\nimport { TRANSLATION_METADATA_TYPE } from './constants'\n\ninterface PrepareTranslationMetadataParams {\n client: SanityClient\n sourceDocument: TranslatableDocument\n sourceLanguage: string\n}\n\nexport const getTranslationMetadata = async ({\n client,\n sourceDocument,\n sourceLanguage,\n}: PrepareTranslationMetadataParams): Promise<TranslationMetadataDocument> => {\n try {\n const existingDoc = await client.fetch<TranslationMetadataDocument | null>(\n '*[_type == $tmType && references($sourceDocumentId)][0]',\n {\n tmType: TRANSLATION_METADATA_TYPE,\n sourceDocumentId: undraftId(sourceDocument._id),\n },\n )\n\n if (existingDoc) {\n return existingDoc\n }\n\n const newDoc = (await client.create({\n _type: TRANSLATION_METADATA_TYPE,\n translations: [\n {\n _key: sourceLanguage,\n _type: 'internationalizedArrayReferenceValue',\n value: {\n _type: 'reference',\n _ref: undraftId(sourceDocument._id),\n _weak: true,\n _strengthenOnPublish: {\n _type: sourceDocument._type,\n _weak: false,\n },\n },\n },\n ],\n translationHistory: [],\n })) as TranslationMetadataDocument\n\n return newDoc\n } catch (error) {\n console.error(sourceDocument._id, 'Error creating translation metadata:', error)\n throw error\n }\n}\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { getExistingTranslationId } from '../metadata/getExistingTranslationId'\nimport { getTranslationMetadata } from '../metadata/getTranslationMetadata'\nimport type { AssembleReferencesResult } from '../references/assembleReferences'\n\nexport interface ReleaseConflict {\n sourceDocumentId: string\n sourceDocumentTitle?: string\n sourceDocumentType?: string\n targetLanguage: string\n existingVersionId: string\n}\n\nexport interface CheckReleaseConflictsParams {\n client: SanityClient\n releaseId: string\n sourceDocument: TranslatableDocument\n targetLanguages: string[]\n languageFieldName: string\n /** Referenced documents that will also be translated */\n references?: AssembleReferencesResult\n}\n\n/**\n * Checks a single document for conflicts in the specified release.\n */\nconst checkDocumentConflicts = async ({\n client,\n releaseId,\n documentId,\n documentTitle,\n documentType,\n targetLanguages,\n languageFieldName,\n}: {\n client: SanityClient\n releaseId: string\n documentId: string\n documentTitle?: string\n documentType?: string\n targetLanguages: string[]\n languageFieldName: string\n}): Promise<ReleaseConflict[]> => {\n const conflicts: ReleaseConflict[] = []\n\n // Fetch the document to get translation metadata\n const document = await client.withConfig({ perspective: 'drafts' }).getDocument<TranslatableDocument>(documentId)\n\n if (!document) {\n return conflicts\n }\n\n const sourceLanguage = document[languageFieldName] as string\n\n // Get translation metadata to find existing translation IDs\n const translationMetadata = await getTranslationMetadata({\n client,\n sourceDocument: document,\n sourceLanguage,\n })\n\n // Check each target language for existing versions in the release\n for (const targetLanguage of targetLanguages) {\n const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage)\n\n if (existingTranslationId) {\n // Check if a version of this document exists in the release\n const versionedId = `versions.${releaseId}.${existingTranslationId}`\n\n const existingVersion = await client.getDocument(versionedId)\n\n if (existingVersion) {\n conflicts.push({\n sourceDocumentId: documentId,\n sourceDocumentTitle: documentTitle || (document.title as string) || (document.name as string),\n sourceDocumentType: documentType || document._type,\n targetLanguage,\n existingVersionId: versionedId,\n })\n\n devLog.debug('Found existing version in release', {\n releaseId,\n sourceDocumentId: documentId,\n targetLanguage,\n versionedId,\n })\n }\n }\n }\n\n return conflicts\n}\n\n/**\n * Checks if any target translation documents already exist in the specified release.\n * Returns a list of conflicts that would occur if the translation proceeds.\n * Also checks referenced documents if they are included in the translation.\n */\nexport const checkReleaseConflicts = async ({\n client,\n releaseId,\n sourceDocument,\n targetLanguages,\n languageFieldName,\n references = [],\n}: CheckReleaseConflictsParams): Promise<ReleaseConflict[]> => {\n const allConflicts: ReleaseConflict[] = []\n\n try {\n // Build list of all documents to check (source + references)\n const documentsToCheck = [\n {\n _id: sourceDocument._id,\n _type: sourceDocument._type,\n title: (sourceDocument.title || sourceDocument.name) as string | undefined,\n },\n ...references.map((ref) => ({\n _id: ref._id,\n _type: ref._type,\n title: ref.title,\n })),\n ]\n\n // Check all documents in parallel\n const conflictChecks = await Promise.all(\n documentsToCheck.map((doc) =>\n checkDocumentConflicts({\n client,\n releaseId,\n documentId: doc._id,\n documentTitle: doc.title,\n documentType: doc._type,\n targetLanguages,\n languageFieldName,\n }),\n ),\n )\n\n // Flatten all conflicts\n for (const conflicts of conflictChecks) {\n allConflicts.push(...conflicts)\n }\n\n devLog.debug('Release conflict check complete', {\n releaseId,\n documentsChecked: documentsToCheck.length,\n conflictsFound: allConflicts.length,\n })\n } catch (error) {\n devLog.error('Error checking release conflicts', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n sourceDocumentId: sourceDocument._id,\n targetLanguages,\n referencesCount: references.length,\n })\n // Don't throw - return empty conflicts and let the translation proceed\n // The actual error will be caught during translation\n }\n\n return allConflicts\n}\n","import { devLog } from './devLog'\n\n/**\n * Error classification utilities for determining retry strategies\n */\n\n/**\n * Checks if an error is a network error that should be retried immediately\n */\nexport function isNetworkError(error: unknown): boolean {\n if (!error) return false\n\n // Check for fetch TypeError (network failures)\n if (error instanceof TypeError) {\n const message = error.message.toLowerCase()\n return (\n message.includes('fetch') ||\n message.includes('network') ||\n message.includes('connection') ||\n message.includes('failed to fetch') ||\n message.includes('networkerror')\n )\n }\n\n // Check for Node.js network error codes\n if (error && typeof error === 'object' && 'code' in error) {\n const code = (error as { code: string }).code\n return ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED', 'EHOSTUNREACH'].includes(code)\n }\n\n // Check for HTTP error responses\n if (error && typeof error === 'object' && 'response' in error) {\n const response = (error as { response: { status?: number } }).response\n if (response?.status) {\n // Retry on 5xx server errors and 429 rate limiting\n return response.status >= 500 || response.status === 429\n }\n }\n\n // Check for HTTP status in error message\n if (error instanceof Error) {\n const message = error.message\n // Check for 5xx or 429 in error message\n if (/\\b(50[0-9]|429)\\b/.test(message)) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Checks if an error is a Sanity API mutation error\n * These errors typically indicate invalid document structure or schema violations\n */\nexport function isSanityMutationError(error: unknown): boolean {\n if (!error) return false\n\n if (error instanceof Error) {\n const message = error.message.toLowerCase()\n\n // Common Sanity error patterns\n const sanityErrorPatterns = [\n 'validation',\n 'schema',\n 'invalid document',\n 'invalid field',\n 'type mismatch',\n 'reference',\n 'patch',\n 'mutation',\n 'document structure',\n 'required field',\n 'field type',\n 'sanity',\n ]\n\n return sanityErrorPatterns.some((pattern) => message.includes(pattern))\n }\n\n // Check for Sanity-specific error structure\n if (error && typeof error === 'object') {\n const errorObj = error as Record<string, unknown>\n // Sanity errors often have these properties\n if ('statusCode' in errorObj || 'response' in errorObj || 'details' in errorObj) {\n // Check if it's a client error (4xx) that's not a network issue\n if ('statusCode' in errorObj) {\n const statusCode = errorObj.statusCode as number\n if (statusCode >= 400 && statusCode < 500 && statusCode !== 429) {\n return true\n }\n }\n }\n }\n\n return false\n}\n\n/**\n * Checks if an error is an OpenAI API error\n */\nexport function isOpenAIError(error: unknown): boolean {\n if (!error) return false\n\n if (error instanceof Error) {\n const message = error.message.toLowerCase()\n return message.includes('openai') || message.includes('gpt-5 api error') || message.includes('api error')\n }\n\n return false\n}\n\n/**\n * Configuration options for retry logic\n */\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 2) */\n maxAttempts?: number\n /** Initial delay in milliseconds before first retry (default: 0 for immediate) */\n initialDelayMs?: number\n /** Maximum delay in milliseconds (default: 5000) */\n maxDelayMs?: number\n /** Backoff multiplier (default: 2) */\n backoffMultiplier?: number\n /** Custom function to determine if error is retryable (default: isNetworkError) */\n retryableErrors?: (error: unknown) => boolean\n /** Context for logging */\n context?: Record<string, unknown>\n /** Whether to log retry attempts */\n logRetries?: boolean\n}\n\n/**\n * Default retry options\n */\nconst DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, 'context' | 'retryableErrors'>> & {\n retryableErrors: (error: unknown) => boolean\n} = {\n maxAttempts: 2,\n initialDelayMs: 0,\n maxDelayMs: 5000,\n backoffMultiplier: 2,\n retryableErrors: isNetworkError,\n logRetries: true,\n}\n\n/**\n * Calculates delay for exponential backoff\n */\nfunction calculateDelay(\n attempt: number,\n options: Required<Omit<RetryOptions, 'context' | 'retryableErrors'>> & {\n retryableErrors: (error: unknown) => boolean\n },\n): number {\n const delay = options.initialDelayMs * options.backoffMultiplier ** attempt\n return Math.min(delay, options.maxDelayMs)\n}\n\n/**\n * Waits for the specified number of milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Retries an operation with exponential backoff\n *\n * @param operation - The async operation to retry\n * @param options - Retry configuration options\n * @returns The result of the operation\n * @throws The last error if all retries are exhausted\n *\n * @example\n * ```typescript\n * const result = await retryWithBackoff(\n * () => fetch('https://api.example.com/data'),\n * {\n * maxAttempts: 3,\n * initialDelayMs: 100,\n * context: { documentId: 'doc123' }\n * }\n * )\n * ```\n */\nexport async function retryWithBackoff<T>(operation: () => Promise<T>, options: RetryOptions = {}): Promise<T> {\n const opts = {\n ...DEFAULT_RETRY_OPTIONS,\n ...options,\n retryableErrors: options.retryableErrors || DEFAULT_RETRY_OPTIONS.retryableErrors,\n }\n\n let lastError: unknown\n const maxAttempts = opts.maxAttempts + 1 // +1 for initial attempt\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n const result = await operation()\n if (attempt > 1 && opts.logRetries) {\n devLog.info(`Operation succeeded after ${attempt} attempts`, {\n ...opts.context,\n attempt,\n totalAttempts: attempt,\n })\n }\n return result\n } catch (error) {\n lastError = error\n\n // Check if error is retryable\n if (!opts.retryableErrors(error)) {\n if (opts.logRetries) {\n devLog.warn('Error is not retryable, aborting retries', {\n ...opts.context,\n attempt,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n throw error\n }\n\n // If this was the last attempt, throw the error\n if (attempt >= maxAttempts) {\n if (opts.logRetries) {\n devLog.error(\n `Operation failed after ${maxAttempts} attempts`,\n error instanceof Error ? error : new Error(String(error)),\n {\n ...opts.context,\n totalAttempts: maxAttempts,\n },\n )\n }\n throw error\n }\n\n // Calculate delay and wait before retrying\n const delay = calculateDelay(attempt - 1, opts)\n if (opts.logRetries) {\n devLog.warn(`Operation failed, retrying in ${delay}ms (attempt ${attempt}/${maxAttempts})`, {\n ...opts.context,\n attempt,\n totalAttempts: maxAttempts,\n delayMs: delay,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n if (delay > 0) {\n await sleep(delay)\n }\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError\n}\n","/**\n * Semaphore class for rate limiting concurrent operations\n * Ensures that only a specified number of operations can run simultaneously\n */\nexport class Semaphore {\n private permits: number\n private queue: Array<() => void> = []\n\n constructor(permits: number) {\n this.permits = permits\n }\n\n /**\n * Acquire a permit from the semaphore\n * If no permits are available, the caller will wait until one becomes available\n */\n async acquire(): Promise<void> {\n if (this.permits > 0) {\n this.permits--\n return Promise.resolve()\n }\n\n // Wait for a permit to become available\n return new Promise<void>((resolve) => {\n this.queue.push(resolve)\n })\n }\n\n /**\n * Release a permit back to the semaphore\n * If there are waiting callers, one will be woken up\n */\n release(): void {\n const next = this.queue.shift()\n if (next) {\n // Give the permit to the next waiting caller\n next()\n } else {\n // No one is waiting, increment the available permits\n this.permits++\n }\n }\n\n /**\n * Execute a function with semaphore protection\n * Automatically acquires and releases the permit\n */\n async withPermit<T>(fn: () => Promise<T>): Promise<T> {\n await this.acquire()\n try {\n return await fn()\n } finally {\n this.release()\n }\n }\n\n /**\n * Get the number of available permits\n */\n availablePermits(): number {\n return this.permits\n }\n\n /**\n * Get the number of waiting callers\n */\n queueLength(): number {\n return this.queue.length\n }\n\n /**\n * Update the number of permits\n * If increasing permits, waiting callers will be notified\n * If decreasing permits below current available, available permits will be reduced\n */\n updatePermits(newPermits: number): void {\n const currentTotal = this.permits + this.queue.length\n const difference = newPermits - currentTotal\n\n if (difference > 0) {\n // If permits increased, resolve waiting promises or add to available permits\n for (let i = 0; i < difference && this.queue.length > 0; i++) {\n const next = this.queue.shift()\n if (next) next()\n }\n // Add any remaining permits to available count\n const remainingIncrease = difference - (currentTotal - this.permits - this.queue.length)\n if (remainingIncrease > 0) {\n this.permits += remainingIncrease\n }\n } else if (difference < 0) {\n // Decreasing permits - reduce available permits (but don't interrupt running operations)\n this.permits = Math.max(0, this.permits + difference)\n }\n }\n}\n","import { type PortableTextBlock, toPlainText } from '@portabletext/react'\nimport type { TranslatableDocument } from '../../types'\n\n/**\n * Function to convert a sanity document to plain text.\n * Returns an object following these rules:\n * - All top-level fields are included.\n * - If a field is an array AND contains at least one object with `_type === 'block'`, it is converted to plain text with `toPlainText`.\n */\nexport function documentToPlainText(document?: TranslatableDocument): string {\n if (!document) return ''\n\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(document)) {\n // Check if the value is an array\n if (Array.isArray(value)) {\n // Check if the array contains at least one Portable Text block\n const hasBlockType = value.some(\n (item) => typeof item === 'object' && item !== null && '_type' in item && item._type === 'block',\n )\n\n if (hasBlockType) {\n // Convert Portable Text blocks to plain text\n result[key] = toPlainText(value as PortableTextBlock[])\n } else {\n // Keep the array as-is\n result[key] = value\n }\n } else {\n // Keep non-array values as-is\n result[key] = value\n }\n }\n\n return JSON.stringify(result)\n}\n","/**\n * Fuzzy glossary-term matching against a single source document.\n *\n * Design goals:\n * - Tokenize the source ONCE, then test many glossary terms cheaply.\n * - Order-agnostic “bag of words” matching inside a sliding window.\n * - Tolerates filler words and a small number of extra words (“window slack”).\n * - Case-insensitive, punctuation-insensitive, and diacritic-insensitive.\n * - Prefers recall (false positives are more acceptable than false negatives).\n */\n\nexport type Match =\n | { matched: true; term: string; windowStart: number; windowEnd: number; matchedRatio: number }\n | { matched: false; term: string; reason: string }\n\nexport interface MatchOptions {\n /**\n * Extra tokens allowed on top of the term length for the sliding window.\n * e.g., term has 3 tokens → default window size = 3 + windowSlack.\n * Larger slack → more forgiving (higher recall, lower precision).\n */\n windowSlack?: number\n\n /**\n * Ratio [0..1] of term tokens that must be satisfied inside a window.\n * 1.0 == all term tokens present (default).\n * 0.8 == at least 80% of term tokens present (useful for partials/variants).\n */\n minTokenMatchRatio?: number\n\n /**\n * Stop/filler words to ignore in term tokenization (e.g., “a”, “the”, “to”).\n * These are removed from the term ONLY (not from the source tokens),\n * so that “navigate using a map” still matches “navigate using the digital map”.\n */\n stopwords?: string[]\n\n /**\n * If true, we’ll also drop very short tokens from the term (length <= 2),\n * after stopword removal. Helps avoid spurious matches on tiny tokens.\n */\n dropVeryShortTermTokens?: boolean\n\n /**\n * If provided, only these characters are considered word chars in tokenization.\n * By default we use a broad “letters/digits/underscore” definition.\n */\n allowedWordCharsRegex?: RegExp\n}\n\nexport interface SourceContext {\n /** Tokenized, normalized source text. */\n tokens: string[]\n}\n\n/** Reasonable defaults tuned for translation glossary recall. */\nconst DEFAULTS: Required<MatchOptions> = {\n windowSlack: 2,\n minTokenMatchRatio: 1.0,\n stopwords: [\n // common English fillers — extend per your locales\n 'a',\n 'an',\n 'the',\n 'and',\n 'or',\n 'to',\n 'of',\n 'for',\n 'in',\n 'on',\n 'at',\n 'by',\n 'with',\n 'using',\n 'from',\n 'into',\n 'over',\n 'under',\n 'as',\n 'is',\n 'are',\n 'be',\n ],\n dropVeryShortTermTokens: true,\n allowedWordCharsRegex: /[\\p{L}\\p{N}_]+/gu, // letters/numbers/underscore (Unicode)\n}\n\n/** Normalize to improve matching across punctuation/case/diacritics. */\nfunction normalizeForMatch(s: string): string {\n // Convert to lowercase, normalize Unicode, strip diacritics, and trim\n return s\n .toLowerCase()\n .normalize('NFKD')\n .replace(/\\p{M}+/gu, '') // remove diacritic marks\n .trim()\n}\n\n/** Tokenize a string into normalized word tokens. */\nexport function tokenize(text: string, allowedWordChars = DEFAULTS.allowedWordCharsRegex): string[] {\n const norm = normalizeForMatch(text)\n // Extract “words” per allowedWordChars; fallback to split on whitespace\n const matches = norm.match(allowedWordChars)\n if (!matches) return []\n return matches\n}\n\n/** Build a SourceContext once for a given document. */\nexport function preprocessSource(\n sourceText: string,\n opts?: Pick<MatchOptions, 'allowedWordCharsRegex'>,\n): SourceContext {\n const tokens = tokenize(sourceText, opts?.allowedWordCharsRegex ?? DEFAULTS.allowedWordCharsRegex)\n return { tokens }\n}\n\n/** Prepare a term: tokenize, remove stopwords, optionally drop very short tokens. */\nfunction prepareTermTokens(term: string, opts: Required<MatchOptions>): string[] {\n const raw = tokenize(term, opts.allowedWordCharsRegex)\n const filtered = raw.filter((t) => !opts.stopwords.includes(t))\n if (!opts.dropVeryShortTermTokens) return filtered\n return filtered.filter((t) => t.length > 2)\n}\n\n/** Build a frequency map for tokens. */\nfunction freqMap(tokens: string[]): Map<string, number> {\n const m = new Map<string, number>()\n for (const t of tokens) m.set(t, (m.get(t) ?? 0) + 1)\n return m\n}\n\n/**\n * Core matcher: bag-of-words inside a sliding window.\n * We only count tokens that appear in the (prepared) term.\n *\n * This function can be used for matching glossary terms, protected phrases,\n * and other text patterns within documents.\n */\nexport function matchTermInDocument(term: string, source: SourceContext, options?: MatchOptions): Match {\n const opts: Required<MatchOptions> = { ...DEFAULTS, ...(options ?? {}) }\n\n const termTokens = prepareTermTokens(term, opts)\n if (termTokens.length === 0) {\n return { matched: false, term, reason: 'No substantive tokens after filtering.' }\n }\n\n const termFreq = freqMap(termTokens)\n const requiredCount = Math.ceil(termTokens.length * opts.minTokenMatchRatio)\n const windowSize = Math.max(1, termTokens.length + opts.windowSlack)\n\n // Cache matching tokens to minimize window calculations\n const interesting = new Set(termFreq.keys())\n\n // Sliding window counts ONLY for tokens that are in termFreq.\n const windowCounts = new Map<string, number>()\n let satisfiedCount = 0 // sum over tokens: min(windowCounts[token], termFreq[token])\n\n // Helper to add a token to the window (if interesting), updating satisfiedCount.\n const addToken = (tok: string) => {\n if (!interesting.has(tok)) return\n const before = windowCounts.get(tok) ?? 0\n const after = before + 1\n windowCounts.set(tok, after)\n // biome-ignore lint/style/noNonNullAssertion: this is safe\n const cap = termFreq.get(tok)!\n // Count token matches up to term's frequency requirement only\n if (before < cap) satisfiedCount += 1\n }\n\n // Helper to remove a token from the window, updating satisfiedCount.\n const removeToken = (tok: string) => {\n if (!interesting.has(tok)) return\n const before = windowCounts.get(tok) ?? 0\n if (before === 0) return\n const after = before - 1\n windowCounts.set(tok, after)\n\n // biome-ignore lint/style/noNonNullAssertion: this is safe\n const cap = termFreq.get(tok)!\n // Decrement satisfied count when token falls below frequency cap\n if (before <= cap) satisfiedCount -= 1\n }\n\n const { tokens } = source\n // Prime the first window\n for (let i = 0; i < Math.min(windowSize, tokens.length); i++) addToken(tokens[i])\n\n if (satisfiedCount >= requiredCount) {\n return {\n matched: true,\n term,\n windowStart: 0,\n windowEnd: Math.min(windowSize, tokens.length) - 1,\n matchedRatio: satisfiedCount / termTokens.length,\n }\n }\n\n // Slide the window across the source\n for (let start = 1; start + windowSize <= tokens.length; start++) {\n const prev = tokens[start - 1]\n const next = tokens[start + windowSize - 1]\n removeToken(prev)\n addToken(next)\n\n if (satisfiedCount >= requiredCount) {\n return {\n matched: true,\n term,\n windowStart: start,\n windowEnd: start + windowSize - 1,\n matchedRatio: satisfiedCount / termTokens.length,\n }\n }\n }\n\n return { matched: false, term, reason: 'No window satisfied the token threshold.' }\n}\n\n/**\n * Batch check: evaluate many terms against the same preprocessed source.\n * Returns match results in the same order as input terms.\n * Can be used for glossary terms, protected phrases, or other text patterns.\n */\nexport function matchAllTermsInDocument(terms: string[], source: SourceContext, options?: MatchOptions): Match[] {\n return terms.map((t) => matchTermInDocument(t, source, options))\n}\n\n/**\n * @deprecated Use matchTermInDocument instead\n */\nexport const matchGlossaryTerm = matchTermInDocument\n\n/**\n * @deprecated Use matchAllTermsInDocument instead\n */\nexport const matchAllGlossaryTerms = matchAllTermsInDocument\n\n/* =========================\n Example usage (pseudo):\n =========================\n\nconst sourceText = `\n You can navigate using the digital map, or rely on landmarks.\n Turn-by-turn directions are available.\n`;\n\nconst source = preprocessSource(sourceText);\n\nconst terms = [\n \"navigate using a map\",\n \"turn by turn directions\",\n \"offline routing\",\n];\n\nconst results = matchAllTermsInDocument(terms, source, {\n windowSlack: 3,\n minTokenMatchRatio: 1.0, // require all term tokens present (after filtering)\n stopwords: [...DEFAULTS.stopwords, \"a\"], // extend if desired\n});\n\nconsole.log(results);\n\n*/\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument, TranslationGlossaryTermDocument, TranslationSettingsDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { documentToPlainText } from '../document/documentToPlainText'\nimport { matchTermInDocument, preprocessSource } from './matchTermInDocument'\n\ninterface FetchTranslationPromptsOptions {\n client: SanityClient\n sourceLanguage?: string\n sourceDocument?: TranslatableDocument\n targetLanguages: Array<string>\n}\n\nexport interface FetchTranslationPromptsResult {\n globalPrompt?: string\n protectedPhrases?: Array<string>\n localeInstructions: Record<\n string,\n {\n prompt?: string\n glossaryTerms?: string\n }\n >\n}\n\nexport const fetchTranslationPrompts = async ({\n client,\n sourceLanguage,\n sourceDocument,\n targetLanguages,\n}: FetchTranslationPromptsOptions): Promise<FetchTranslationPromptsResult> => {\n const sourceDocumentString = documentToPlainText(sourceDocument)\n\n const translationSettings = await client.fetch<TranslationSettingsDocument | null>(\n `*[_id == \"translation-settings\"][0] {\n globalPrompt,\n localePrompts[_key in $targetLanguages],\n protectedPhrases\n }`,\n { targetLanguages },\n )\n\n devLog.debug('Fetched translation settings', {\n hasSettings: !!translationSettings,\n hasGlobalPrompt: !!translationSettings?.globalPrompt,\n globalPromptLength: translationSettings?.globalPrompt?.length,\n localePromptsCount: translationSettings?.localePrompts?.length,\n localePrompts: translationSettings?.localePrompts,\n protectedPhrasesCount: translationSettings?.protectedPhrases?.length,\n targetLanguages,\n })\n\n // Fetch all translation glossary terms that have a translation in both the source and target languages\n const translationGlossaryTerms =\n (await client.fetch<Array<NonNullable<TranslationGlossaryTermDocument['translations']>> | null>(\n `*[\n _type == \"translationGlossaryTerm\"\n && count(translations[_key in $targetLanguages]) > 0\n && count(translations[_key == $sourceLanguage]) > 0\n ].translations`,\n { targetLanguages, sourceLanguage },\n )) || []\n\n const glossaryTermsByLocale: Record<string, Array<{ from: string; to: string }>> = {}\n\n for (const targetLanguage of targetLanguages) {\n const relevantTerms = translationGlossaryTerms.filter((termTranslations) =>\n termTranslations.some((translation) => translation._key === targetLanguage),\n )\n\n interface GlossaryTermMapping {\n from: string\n to: string\n }\n\n let relevantTranslations = relevantTerms\n .map((termTranslations): GlossaryTermMapping | null => {\n const from = termTranslations.find((translation) => translation._key === sourceLanguage)?.value\n const to = termTranslations.find((translation) => translation._key === targetLanguage)?.value\n if (!from || !to) {\n return null\n }\n return { from, to }\n })\n .filter((item): item is GlossaryTermMapping => item !== null)\n\n // Filter to only include terms that appear in the source document\n if (sourceDocument) {\n relevantTranslations = relevantTranslations.filter(({ from }) =>\n glossaryTermAppearsInDocument(from, sourceDocumentString),\n )\n }\n\n glossaryTermsByLocale[targetLanguage] = relevantTranslations\n }\n\n // Filter protected phrases to only include those that appear in the source document\n let filteredProtectedPhrases = translationSettings?.protectedPhrases\n if (sourceDocument && filteredProtectedPhrases) {\n devLog.debug('Filtering protected phrases', {\n totalPhrases: filteredProtectedPhrases.length,\n phrases: filteredProtectedPhrases,\n sourceDocumentStringLength: sourceDocumentString.length,\n sourceDocumentPreview: sourceDocumentString.substring(0, 500),\n })\n\n filteredProtectedPhrases = filteredProtectedPhrases.filter((phrase) => {\n const appears = protectedPhraseAppearsInDocument(phrase, sourceDocumentString)\n devLog.debug('Protected phrase match result', {\n phrase,\n appears,\n })\n return appears\n })\n\n devLog.debug('Filtered protected phrases result', {\n remainingCount: filteredProtectedPhrases.length,\n remainingPhrases: filteredProtectedPhrases,\n })\n }\n\n return {\n globalPrompt: translationSettings?.globalPrompt,\n protectedPhrases: filteredProtectedPhrases,\n localeInstructions: Object.fromEntries(\n targetLanguages.map((locale) => [\n locale,\n {\n prompt: translationSettings?.localePrompts?.find((lp) => lp._key === locale)?.value,\n glossaryTerms: formatGlossaryTermInstruction(glossaryTermsByLocale[locale]),\n },\n ]),\n ),\n }\n}\n\n/**\n * Checks if a glossary term appears anywhere in the document content using fuzzy matching.\n * Uses order-agnostic bag-of-words matching.\n */\nconst glossaryTermAppearsInDocument = (term: string, documentString: string): boolean => {\n // Handle colon-delimited terms by checking the base term (before the colon)\n const baseTerm = term.split(':')[0].trim()\n\n // Preprocess the document string once for efficient matching\n const source = preprocessSource(documentString)\n\n // Match the term against the document with default options\n // (windowSlack: 10, minTokenMatchRatio: 1.0, stopwords included)\n const result = matchTermInDocument(baseTerm, source, {\n windowSlack: 10,\n })\n\n return result.matched\n}\n\n/**\n * Checks if a protected phrase appears anywhere in the document content using fuzzy matching.\n * Uses order-agnostic bag-of-words matching.\n */\nconst protectedPhraseAppearsInDocument = (phrase: string, documentString: string): boolean => {\n // Preprocess the document string once for efficient matching\n const source = preprocessSource(documentString)\n\n // Match the phrase against the document with the same options as glossary terms\n const result = matchTermInDocument(phrase, source, {\n windowSlack: 10,\n })\n\n return result.matched\n}\n\nconst formatGlossaryTermInstruction = (terms: Array<{ from: string; to: string }> | undefined) => {\n if (!terms || terms.length === 0) {\n return undefined\n }\n\n const instructions = [\n '## Glossary Terms',\n 'Use the translations below whenever the matching source term appears. Treat these mappings as required terminology.',\n 'Guidelines:',\n '- Apply glossary terms when the meaning matches, even if there are slight variations in wording, punctuation, or word order.',\n '- Preserve surrounding punctuation and keep the sentence natural.',\n '- Match the source casing when possible; only adjust when required for correct grammar.',\n '- If the source term meaning is absent, do not introduce a translation from this list.',\n '- When a term includes a colon, translate the text before the colon for general use; use the text after the colon when the feminine form is required.',\n '- Do not remove important words or phrases from the source text when they are essential to the meaning.',\n '- Never add punctuation or symbols that are not present in the source text.',\n '- Never apply glossary terms in code examples.',\n ]\n\n terms.forEach(({ from, to }) => {\n instructions.push(\n `When text conveys the same meaning as \"${from}\", translate it as \"${to}\" unless the term is used in a code example.`,\n )\n })\n\n return instructions.join('\\n')\n}\n","export type RecursiveSearchable =\n | null\n | undefined\n | string\n | number\n | boolean\n | RecursiveSearchable[]\n | { [key: string]: RecursiveSearchable }\n\nexport type FindKeyValueResult<T = unknown> = {\n value: T\n path: Array<string | number>\n}\n\n/**\n * Recursively searches through an object/array structure to find all values\n * associated with a specific key name, along with their paths.\n *\n * @param obj - The object or array to search through\n * @param targetKey - The key name to search for\n * @returns An array of objects containing the value and path for each occurrence of the target key\n */\nexport const findKeyValues = <T = unknown>(obj: RecursiveSearchable, targetKey: string): FindKeyValueResult<T>[] => {\n const results: FindKeyValueResult<T>[] = []\n\n const recursiveSearch = (current: RecursiveSearchable, currentPath: Array<string | number>): void => {\n // Handle null/undefined\n if (current === null || current === undefined) {\n return\n }\n\n // Recursively search each array element for the target key\n if (Array.isArray(current)) {\n current.forEach((item, index) => {\n recursiveSearch(item, [...currentPath, index])\n })\n return\n }\n\n // For objects, check properties for the target key\n if (typeof current === 'object') {\n for (const [key, value] of Object.entries(current)) {\n const newPath = [...currentPath, key]\n\n // Record matching value and its path for caller\n if (key === targetKey) {\n results.push({\n value: value as T,\n path: newPath,\n })\n }\n\n // Continue searching recursively\n recursiveSearch(value, newPath)\n }\n }\n }\n\n recursiveSearch(obj, [])\n return results\n}\n","import { getPublishedId, pathToString, type SanityClient } from 'sanity'\nimport type { PluginConfig } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { type FindKeyValueResult, findKeyValues } from '../common/findKeyValues'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\nimport { TRANSLATION_METADATA_TYPE } from '../metadata/constants'\n\ninterface LocalizeReferenceParams {\n client: SanityClient\n documentId: string\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n}\n\n/**\n * Localizes all references in a translated document to point to the appropriate\n * localized versions of referenced documents.\n *\n * This function:\n * 1. Fetches the document and extracts its target language\n * 2. Finds all `_ref` fields within the document\n * 3. Batch fetches translation IDs for all references\n * 4. Updates references to point to localized versions\n * 5. Sets weak references with strengthenOnPublish behavior\n *\n * @param params - Configuration for localizing references\n * @param params.client - Sanity client instance\n * @param params.documentId - ID of the document whose references should be localized\n * @param params.languageFieldName - Name of the field containing the language identifier\n *\n * @example\n * ```typescript\n * await localizeReferences({\n * client,\n * documentId: 'post-es',\n * languageFieldName: 'language'\n * })\n * // Updates all references in post-es to point to Spanish versions\n * ```\n */\nexport const localizeReferences = async ({\n client,\n documentId,\n sourceLanguage,\n languageFieldName,\n}: LocalizeReferenceParams) => {\n try {\n const doc = await client.fetch('*[_id == $documentId][0]', { documentId })\n\n if (!doc) {\n devLog.error('Document not found', new Error('Document not found'), {\n documentId,\n component: 'localizeReferences',\n })\n return\n }\n\n const targetLanguage = doc[languageFieldName]\n\n if (!targetLanguage) {\n devLog.error('No value found for language field', new Error('Missing language field'), {\n documentId,\n languageFieldName,\n component: 'localizeReferences',\n })\n return\n }\n\n const refs = findKeyValues<string>(doc, '_ref')\n\n if (!refs?.length) {\n return\n }\n\n await updateReferences(documentId, client, refs, sourceLanguage, targetLanguage)\n } catch (error) {\n devLog.error('Error localizing references', error instanceof Error ? error : new Error(String(error)), {\n documentId,\n component: 'localizeReferences',\n })\n }\n}\n\n/**\n * Updates document references to their localized versions in a single batch operation.\n *\n * This function performs efficient batch processing:\n * 1. Batch fetches all translation IDs in one query (avoids N+1 problem)\n * 2. Builds a translation map for quick lookup\n * 3. Applies all patches in a single commit\n *\n * References are set as weak with `_strengthenOnPublish` to ensure they become\n * strong references when the document is published.\n *\n * @param documentId - ID of the document to update\n * @param client - Sanity client instance\n * @param refs - Array of reference results from findKeyValues\n * @param targetLanguage - Target language to find localized references for\n *\n * @example\n * ```typescript\n * const refs = findKeyValues(document, '_ref')\n * await updateReferences('doc-es', client, refs, 'es')\n * // All references in doc-es now point to Spanish versions\n * ```\n */\nexport const updateReferences = async (\n documentId: string,\n client: SanityClient,\n refs: Array<FindKeyValueResult<string>>,\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id'],\n targetLanguage: PluginConfig['supportedLanguages'][number]['id'],\n) => {\n if (!refs?.length) {\n return []\n }\n\n // Step 1: Batch fetch all reference translations\n const refIds = refs.map((r) => getPublishedId(r.value))\n const translations = await client.fetch<Array<{ sourceId: string; targetId: string; targetType: string }>>(\n `*[_type == $translationMetadataSchemaType && array::intersects(translations[].value._ref, $refIds)] {\n \"sourceId\": translations[_key == $sourceLanguage][0].value._ref,\n \"targetId\": translations[_key == $targetLanguage][0].value._ref,\n \"targetType\": translations[_key == $sourceLanguage][0].value -> _type\n }`,\n {\n translationMetadataSchemaType: TRANSLATION_METADATA_TYPE,\n refIds,\n sourceLanguage,\n targetLanguage,\n },\n )\n\n // Step 2: Build translation map\n const translationMap = new Map<string, string>()\n for (const t of translations) {\n if (t.sourceId && t.targetId) {\n translationMap.set(t.sourceId, t.targetId)\n }\n }\n\n const patch = client.patch(documentId)\n let hasChanges = false\n\n for (const ref of refs) {\n const sourceId = getPublishedId(ref.value)\n const newId = translationMap.get(sourceId)\n\n if (!newId) {\n // Skip references without translations—document may not be translatable or translated yet\n continue\n }\n\n patch.set({ [pathToString(ref.path)]: getPublishedId(newId) })\n // Versions handle weak reference conversion; explicit _weak setting not needed\n patch.set({\n [`${pathToString(ref.path.slice(0, -1))}._strengthenOnPublish`]: {\n type: translations.find((t) => t.targetId === getPublishedId(newId))?.targetType || undefined,\n },\n })\n hasChanges = true\n }\n\n if (hasChanges) {\n await retryWithBackoff(\n async () => {\n await patch.commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'localizeReferences',\n documentId,\n referenceCount: refs.length,\n },\n },\n )\n devLog.info('Updated references in batch', {\n documentId,\n count: refs.length,\n translatedCount: translationMap.size,\n component: 'updateReferences',\n })\n }\n}\n","import { getPublishedId, type SanityClient } from 'sanity'\nimport type { PluginConfig } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { localizeReferences } from './localizeReferences'\n\ninterface UpdateBackwardReferencesParams {\n client: SanityClient\n releaseId: string\n /** Source document IDs that were just translated */\n sourceDocumentIds: string[]\n /** Target languages that were translated to */\n targetLanguages: string[]\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n /** Newly translated versioned document IDs (already processed by localizeReferences) */\n excludeDocumentIds: string[]\n}\n\n/**\n * Updates references in existing documents within a release that point to\n * source documents that were just translated.\n *\n * This handles the \"backward reference\" problem where:\n * 1. Doc A (which references Doc B) was translated earlier\n * 2. Doc B is translated later to the same release\n * 3. Doc A's reference to B should now point to the translated B\n *\n * @example\n * ```typescript\n * // After translating Doc B to Spanish in a release:\n * await updateBackwardReferences({\n * client,\n * releaseId: 'r123',\n * sourceDocumentIds: ['docB'],\n * targetLanguages: ['es'],\n * sourceLanguage: 'en',\n * languageFieldName: 'language',\n * excludeDocumentIds: ['versions.r123.docB-es'], // Already processed\n * })\n * // This finds Doc A (Spanish) in the release and updates its reference to Doc B\n * ```\n */\nexport const updateBackwardReferences = async ({\n client,\n releaseId,\n sourceDocumentIds,\n targetLanguages,\n sourceLanguage,\n languageFieldName,\n excludeDocumentIds,\n}: UpdateBackwardReferencesParams): Promise<void> => {\n if (!sourceDocumentIds.length || !targetLanguages.length) {\n return\n }\n\n // Normalize source document IDs to published IDs for consistent matching\n const normalizedSourceIds = sourceDocumentIds.map((id) => getPublishedId(id))\n\n devLog.debug('Starting backward reference update', {\n releaseId,\n sourceDocumentCount: normalizedSourceIds.length,\n targetLanguages,\n excludeCount: excludeDocumentIds.length,\n component: 'updateBackwardReferences',\n })\n\n try {\n // For each target language, find existing documents in the release that:\n // 1. Have that target language\n // 2. Reference any of the source documents we just translated\n // 3. Are NOT in the exclude list (newly translated docs already processed)\n for (const targetLanguage of targetLanguages) {\n // Build a dynamic query that checks for references to any of the source documents\n // We use array::intersects to efficiently check if any references match\n const existingDocsWithStaleRefs = await client.fetch<Array<{ _id: string }>>(\n `*[\n _id match \"versions.\" + $releaseId + \".*\" &&\n @[$languageFieldName] == $targetLanguage &&\n !(_id in $excludeIds)\n ] {\n _id,\n \"hasStaleRefs\": count(*[\n _id in $sourceDocumentIds && \n ^references(@._id)\n ]) > 0\n }[hasStaleRefs == true]`,\n {\n releaseId,\n targetLanguage,\n languageFieldName,\n excludeIds: excludeDocumentIds,\n sourceDocumentIds: normalizedSourceIds,\n },\n )\n\n if (existingDocsWithStaleRefs.length === 0) {\n devLog.debug('No existing documents with stale references found', {\n releaseId,\n targetLanguage,\n component: 'updateBackwardReferences',\n })\n continue\n }\n\n devLog.info('Found existing documents with stale references', {\n releaseId,\n targetLanguage,\n count: existingDocsWithStaleRefs.length,\n documentIds: existingDocsWithStaleRefs.map((d) => d._id),\n component: 'updateBackwardReferences',\n })\n\n // Run localizeReferences on each document to update the stale references\n await Promise.all(\n existingDocsWithStaleRefs.map(async ({ _id }) => {\n await localizeReferences({\n client,\n documentId: _id,\n sourceLanguage,\n languageFieldName,\n })\n }),\n )\n\n devLog.info('Updated backward references', {\n releaseId,\n targetLanguage,\n updatedCount: existingDocsWithStaleRefs.length,\n component: 'updateBackwardReferences',\n })\n }\n } catch (error) {\n devLog.error('Error updating backward references', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n sourceDocumentIds: normalizedSourceIds,\n targetLanguages,\n component: 'updateBackwardReferences',\n })\n // Don't throw - backward reference updates are best-effort\n // The manual reconcile action can be used to fix any issues\n }\n}\n","import type { Dispatch } from 'react'\nimport type { SetTranslationJobStatusAction, TranslationAction } from '../../types'\nimport { updateActiveTranslationJob } from './activeTranslations'\n\nexport const setupTranslationJobStatuses = (\n dispatch: Dispatch<TranslationAction>,\n sourceDocuments: Array<{\n _id: string\n _type: string\n title?: string\n }>,\n targetLanguages: Array<string>,\n /** Main source document ID for updating active translations store */\n mainSourceDocumentId?: string,\n) => {\n for (const sourceDocument of sourceDocuments) {\n for (const lang of targetLanguages) {\n const payload: SetTranslationJobStatusAction['payload'] = {\n _id: sourceDocument._id,\n _type: sourceDocument._type,\n language: lang,\n status: 'queued',\n title: sourceDocument.title,\n }\n\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload,\n })\n\n // Also update the module-level active translations store\n if (mainSourceDocumentId) {\n updateActiveTranslationJob(mainSourceDocumentId, payload)\n }\n }\n }\n}\n","import type { SanityDocument } from 'sanity'\nimport type {\n TelemetryConfig,\n TelemetryEvent,\n TranslationErrorBundle,\n TranslationErrorType,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\n\n/**\n * Emits a telemetry event if the onEvent callback is configured.\n * Safe to call even if telemetry is not configured.\n *\n * @param config - Telemetry configuration (may be undefined)\n * @param event - The telemetry event to emit\n */\nexport const emitTelemetryEvent = (config: TelemetryConfig | undefined, event: TelemetryEvent): void => {\n if (!config?.onEvent) {\n return\n }\n\n try {\n config.onEvent(event)\n devLog.debug('Telemetry event emitted', { eventName: event.name })\n } catch (error) {\n // Don't let telemetry errors break the translation flow\n devLog.warn('Failed to emit telemetry event', {\n eventName: event.name,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n}\n\n/**\n * Emits a telemetry error bundle if the onError callback is configured.\n * Safe to call even if telemetry is not configured.\n *\n * @param config - Telemetry configuration (may be undefined)\n * @param bundle - The error bundle to emit\n */\nexport const emitTelemetryError = (config: TelemetryConfig | undefined, bundle: TranslationErrorBundle): void => {\n if (!config?.onError) {\n return\n }\n\n try {\n config.onError(bundle)\n devLog.debug('Telemetry error emitted', {\n documentId: bundle.documentId,\n errorType: bundle.error.type,\n })\n } catch (error) {\n // Don't let telemetry errors break the translation flow\n devLog.warn('Failed to emit telemetry error', {\n documentId: bundle.documentId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n}\n\n/**\n * Parameters for building an error bundle\n */\nexport interface BuildErrorBundleParams {\n /** Document ID being translated */\n documentId: string\n /** Document schema type */\n documentType: string\n /** Target language for translation */\n targetLanguage: string\n /** Source language of the document */\n sourceLanguage: string\n /** Translation mode used */\n translationMode: TranslationMode\n /** The error that occurred */\n error: Error\n /** Categorized error type */\n errorType: TranslationErrorType\n /** Start time of the translation attempt (for duration calculation) */\n startTime: number\n /** Full source document */\n originalDocument: SanityDocument\n /** XML-serialized document sent to OpenAI (if OpenAI mode) */\n serializedDocument?: string\n /** Raw API response from OpenAI (if OpenAI mode) */\n openaiResponse?: string\n /** Translation prompts used */\n translationPrompts?: TranslationErrorBundle['debug']['translationPrompts']\n /** Validation errors if structure validation failed */\n validationErrors?: TranslationErrorBundle['debug']['validationErrors']\n}\n\n/**\n * Builds a TranslationErrorBundle from the given parameters.\n * Centralizes the construction of error bundles for consistency.\n *\n * @param params - Parameters for building the error bundle\n * @returns A complete TranslationErrorBundle\n */\nexport const buildErrorBundle = (params: BuildErrorBundleParams): TranslationErrorBundle => {\n const durationMs = Date.now() - params.startTime\n\n return {\n documentId: params.documentId,\n documentType: params.documentType,\n targetLanguage: params.targetLanguage,\n sourceLanguage: params.sourceLanguage,\n translationMode: params.translationMode,\n error: {\n message: params.error.message,\n stack: params.error.stack,\n type: params.errorType,\n },\n debug: {\n originalDocument: params.originalDocument,\n serializedDocument: params.serializedDocument,\n openaiResponse: params.openaiResponse,\n translationPrompts: params.translationPrompts,\n validationErrors: params.validationErrors,\n },\n timestamp: new Date().toISOString(),\n durationMs,\n }\n}\n\n/**\n * Helper to create a timestamp string for telemetry events\n */\nexport const createTimestamp = (): string => new Date().toISOString()\n\n/**\n * Classifies an error into a TranslationErrorType based on error properties and context.\n *\n * @param error - The error to classify\n * @param context - Optional context about where the error occurred\n * @returns The classified error type\n */\nexport const classifyError = (\n error: Error,\n context?: 'serialization' | 'deserialization' | 'openai_api' | 'sanity_agent' | 'validation',\n): TranslationErrorType => {\n // If context is provided, use it\n if (context) {\n return context\n }\n\n const message = error.message.toLowerCase()\n\n // Check for network errors\n if (\n message.includes('network') ||\n message.includes('fetch') ||\n message.includes('timeout') ||\n message.includes('econnrefused') ||\n message.includes('enotfound')\n ) {\n return 'network'\n }\n\n // Check for OpenAI API errors\n if (message.includes('openai') || message.includes('gpt') || message.includes('api error')) {\n return 'openai_api'\n }\n\n // Check for validation errors\n if (message.includes('validation') || message.includes('structure') || message.includes('invalid')) {\n return 'validation'\n }\n\n // Check for deserialization errors (check before serialization since \"deserialize\" contains \"serialize\")\n if (message.includes('deserialize') || message.includes('parse')) {\n return 'deserialization'\n }\n\n // Check for serialization errors\n if (message.includes('serialize') || message.includes('xml')) {\n return 'serialization'\n }\n\n // Check for Sanity agent errors\n if (message.includes('agent') || message.includes('sanity')) {\n return 'sanity_agent'\n }\n\n return 'unknown'\n}\n","import { getPublishedId, type SanityClient, type SanityDocument } from 'sanity'\nimport type { TranslationMetadataDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\ninterface UpdateTranslationMetadataParams {\n client: SanityClient\n sourceDocument: SanityDocument\n sourceLanguage: string\n newDocument: SanityDocument\n targetLanguage: string\n translationMetadata: TranslationMetadataDocument\n isNewTranslation: boolean\n}\n\nexport const updateTranslationMetadata = async ({\n client,\n targetLanguage,\n sourceDocument,\n sourceLanguage,\n newDocument,\n translationMetadata,\n isNewTranslation,\n}: UpdateTranslationMetadataParams) => {\n try {\n const refToNewTranslation: TranslationMetadataDocument['translations'][number] = {\n _key: targetLanguage,\n _type: 'internationalizedArrayReferenceValue',\n value: {\n _type: 'reference',\n _ref: getPublishedId(newDocument._id),\n _weak: true,\n _strengthenOnPublish: {\n _type: 'reference',\n _weak: false,\n },\n },\n }\n\n const newTranslationHistory: TranslationMetadataDocument['translationHistory'][number] = {\n _key: targetLanguage,\n language: targetLanguage,\n sourceId: getPublishedId(sourceDocument._id),\n sourceRev: sourceDocument._rev,\n sourceLanguage,\n sourceSnapshot: JSON.stringify(sourceDocument),\n targetId: newDocument._id,\n targetRev: newDocument._rev,\n targetSnapshot: JSON.stringify(newDocument),\n }\n\n const translationPatch = client\n .patch(translationMetadata._id)\n // Initialize translations array if missing\n .setIfMissing({ translations: [] })\n\n if (isNewTranslation) {\n await retryWithBackoff(\n async () => {\n await translationPatch.insert('after', 'translations[-1]', [refToNewTranslation]).commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.translations',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewTranslation: true,\n },\n },\n )\n } else {\n await retryWithBackoff(\n async () => {\n await translationPatch\n .insert('replace', `translations[_key == \"${targetLanguage}\"]`, [refToNewTranslation])\n .commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.translations',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewTranslation: false,\n },\n },\n )\n }\n\n const historyPatch = client.patch(translationMetadata._id).setIfMissing({ translationHistory: [] })\n const translationHistoryExists = !!translationMetadata.translationHistory?.some(\n (history) => history.language === targetLanguage,\n )\n\n if (!translationHistoryExists) {\n await retryWithBackoff(\n async () => {\n await historyPatch.insert('after', 'translationHistory[-1]', [newTranslationHistory]).commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.history',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewHistory: true,\n },\n },\n )\n } else {\n await retryWithBackoff(\n async () => {\n await historyPatch\n .insert('replace', `translationHistory[_key == \"${targetLanguage}\"]`, [newTranslationHistory])\n .commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.history',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewHistory: false,\n },\n },\n )\n }\n } catch (error) {\n devLog.error('Error updating translation metadata', error instanceof Error ? error : new Error(String(error)), {\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n newDocumentId: newDocument._id,\n component: 'updateTranslationMetadata',\n })\n throw error\n }\n}\n","import defaults from 'lodash.defaults'\nimport type { Dispatch } from 'react'\nimport type { SetTranslationJobStatusAction, TranslationAction, TranslationJob } from '../../types'\nimport { updateActiveTranslationJob } from './activeTranslations'\n\nexport const setJobStatus = (\n dispatch: Dispatch<TranslationAction>,\n _id: string,\n language: string,\n status: TranslationJob['status'],\n payload?: Partial<SetTranslationJobStatusAction['payload']>,\n /** Source document ID for updating active translations store */\n sourceDocumentId?: string,\n) => {\n const job = defaults(payload, {\n _id,\n language,\n status,\n }) as TranslationJob\n\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload: job,\n })\n\n // Also update the module-level active translations store\n if (sourceDocumentId) {\n updateActiveTranslationJob(sourceDocumentId, job)\n }\n}\n","import { devLog } from '../common/devLog'\n\n/**\n * Result of filtering excluded fields from a document\n * @internal\n */\nexport interface FilteredDocument<T = any> {\n /** Document with excluded fields removed */\n filteredDocument: T\n /** Excluded field values that were removed */\n excludedValues: Record<string, any>\n}\n\n/**\n * Checks if a field path matches any of the exclusion patterns\n * @param fieldPath - The field path to check (e.g., 'status', 'meta.category', 'body[0].imageType')\n * @param excludeFields - Array of field patterns to exclude\n * @returns True if the field should be excluded\n */\nfunction shouldExcludeField(fieldPath: string, excludeFields: string[]): boolean {\n for (const pattern of excludeFields) {\n // Exact match for full path (including array notation)\n if (fieldPath === pattern) {\n return true\n }\n\n // Check if pattern starts with '**.' - this means match field name anywhere in hierarchy\n if (pattern.startsWith('**.')) {\n const fieldName = pattern.substring(3) // Remove '**.'\n const segments = fieldPath.split('.')\n const lastSegment = segments[segments.length - 1]\n // Remove array notation if present: \"items[0]\" -> \"items\"\n const actualFieldName = lastSegment?.split('[')[0]\n if (actualFieldName === fieldName) {\n return true\n }\n }\n\n // Check if pattern is just a field name (no dots) - match anywhere in hierarchy\n else if (!pattern.includes('.') && !pattern.includes('[')) {\n const segments = fieldPath.split('.')\n const lastSegment = segments[segments.length - 1]\n // Remove array notation if present: \"items[0]\" -> \"items\"\n const actualFieldName = lastSegment?.split('[')[0]\n if (actualFieldName === pattern) {\n return true\n }\n }\n\n // Pattern with dots or array notation - check for pattern match\n else {\n // Support wildcard array indexing: \"body[*].imageType\" matches \"body[0].imageType\", \"body[1].imageType\", etc.\n if (pattern.includes('[*]')) {\n const regexPattern = pattern\n .replace(/\\[/g, '\\\\[')\n .replace(/\\]/g, '\\\\]')\n .replace(/\\\\\\[\\*\\\\\\]/g, '\\\\[\\\\d+\\\\]')\n const regex = new RegExp(`^${regexPattern}$`)\n if (regex.test(fieldPath)) {\n return true\n }\n }\n\n // Exact path match (already handled above, but included for clarity)\n }\n }\n\n return false\n}\n\n/**\n * Recursively filters out excluded fields from a document\n * @param document - The document to filter\n * @param excludeFields - Array of field patterns to exclude. Supports:\n * - Field names: 'status' (matches any field named 'status' anywhere)\n * - Exact paths: 'meta.category' (matches only 'category' under 'meta')\n * - Array paths: 'body[0].imageType' (matches 'imageType' in first array item)\n * - Wildcard arrays: 'body[*].imageType' (matches 'imageType' in any array item)\n * - Hierarchical: '**.status' (explicitly matches 'status' anywhere - same as just 'status')\n * @param currentPath - Current path in the document (used internally)\n * @returns Filtered document and excluded values\n * @internal\n */\nexport function filterExcludedFields<T extends Record<string, any>>(\n document: T,\n excludeFields: string[] = [],\n currentPath = '',\n): FilteredDocument<T> {\n if (!excludeFields.length) {\n return {\n filteredDocument: document,\n excludedValues: {},\n }\n }\n\n const filteredDocument = {} as T\n const excludedValues: Record<string, any> = {}\n let hasExclusions = false\n\n // Process each field in the document\n for (const [key, value] of Object.entries(document)) {\n const fieldPath = currentPath ? `${currentPath}.${key}` : key\n\n if (shouldExcludeField(fieldPath, excludeFields)) {\n // This field should be excluded\n excludedValues[fieldPath] = value\n hasExclusions = true\n\n devLog.debug('Excluding field from translation', {\n fieldPath,\n fieldType: typeof value,\n value: typeof value === 'string' ? value : '[object]',\n })\n\n continue\n }\n\n // If it's an object (but not an array), recursively process it\n if (value && typeof value === 'object' && !Array.isArray(value) && value._type !== 'reference') {\n const nested = filterExcludedFields(value, excludeFields, fieldPath)\n filteredDocument[key as keyof T] = nested.filteredDocument\n\n // Merge nested exclusions\n Object.assign(excludedValues, nested.excludedValues)\n if (Object.keys(nested.excludedValues).length > 0) {\n hasExclusions = true\n }\n }\n // If it's an array, recursively process each element to handle custom blocks in portable text\n else if (Array.isArray(value)) {\n const filteredArray: any[] = []\n let arrayHasExclusions = false\n\n for (let i = 0; i < value.length; i++) {\n const arrayItem = value[i]\n const itemPath = `${fieldPath}[${i}]`\n\n // If the array item is an object, recursively process it\n if (\n arrayItem &&\n typeof arrayItem === 'object' &&\n !Array.isArray(arrayItem) &&\n arrayItem._type !== 'reference'\n ) {\n const nested = filterExcludedFields(arrayItem, excludeFields, itemPath)\n filteredArray[i] = nested.filteredDocument\n\n // Merge nested exclusions\n Object.assign(excludedValues, nested.excludedValues)\n if (Object.keys(nested.excludedValues).length > 0) {\n arrayHasExclusions = true\n }\n } else {\n // For primitives, references, or other arrays, keep as-is\n filteredArray[i] = arrayItem\n }\n }\n\n filteredDocument[key as keyof T] = filteredArray as any\n if (arrayHasExclusions) {\n hasExclusions = true\n }\n } else {\n // Keep the field as-is (primitives, references, etc.)\n filteredDocument[key as keyof T] = value\n }\n }\n\n if (hasExclusions) {\n devLog.info('Filtered excluded fields from document', {\n documentId: document._id || 'unknown',\n excludedFieldCount: Object.keys(excludedValues).length,\n excludedFields: Object.keys(excludedValues),\n })\n }\n\n return {\n filteredDocument,\n excludedValues,\n }\n}\n\n/**\n * Restores previously excluded fields to a translated document\n * @param translatedDocument - The translated document\n * @param excludedValues - The excluded field values to restore\n * @returns Document with excluded fields restored\n * @internal\n */\nexport function restoreExcludedFields<T extends Record<string, any>>(\n translatedDocument: T,\n excludedValues: Record<string, any>,\n): T {\n if (!Object.keys(excludedValues).length) {\n return translatedDocument\n }\n\n // Clone the translated document to avoid mutations\n const result = { ...translatedDocument }\n\n // Restore each excluded field\n for (const [fieldPath, value] of Object.entries(excludedValues)) {\n setNestedValue(result, fieldPath, value)\n }\n\n devLog.debug('Restored excluded fields to translated document', {\n documentId: result._id || 'unknown',\n restoredFieldCount: Object.keys(excludedValues).length,\n restoredFields: Object.keys(excludedValues),\n })\n\n return result\n}\n\n/**\n * Sets a nested value in an object using a dot-separated path with array support\n * @param obj - The object to modify\n * @param path - Path with dots and array notation (e.g., 'meta.category', 'body[0].imageType')\n * @param value - Value to set\n */\nfunction setNestedValue(obj: any, path: string, value: any): void {\n const segments = path.split('.')\n let current = obj\n\n // Navigate to the parent object/array\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i]\n\n // Handle array notation: \"body[0]\" -> field \"body\", index 0\n const arrayMatch = segment.match(/^(.+)\\[(\\d+)\\]$/)\n if (arrayMatch) {\n const [, fieldName, indexStr] = arrayMatch\n const index = Number.parseInt(indexStr, 10)\n\n if (!(fieldName in current) || !Array.isArray(current[fieldName])) {\n current[fieldName] = []\n }\n\n // Ensure array is long enough\n while (current[fieldName].length <= index) {\n current[fieldName].push({})\n }\n\n current = current[fieldName][index]\n } else {\n // Regular object property\n if (!(segment in current) || typeof current[segment] !== 'object') {\n current[segment] = {}\n }\n current = current[segment]\n }\n }\n\n // Set the final value\n const finalSegment = segments[segments.length - 1]\n const arrayMatch = finalSegment.match(/^(.+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, fieldName, indexStr] = arrayMatch\n const index = Number.parseInt(indexStr, 10)\n\n if (!(fieldName in current) || !Array.isArray(current[fieldName])) {\n current[fieldName] = []\n }\n\n // Ensure array is long enough\n while (current[fieldName].length <= index) {\n current[fieldName].push(null)\n }\n\n current[fieldName][index] = value\n } else {\n current[finalSegment] = value\n }\n}\n\n/**\n * Common enum field names that are typically excluded from translation\n * @internal\n */\nexport const COMMON_ENUM_FIELDS = [\n 'status',\n 'state',\n 'type',\n 'category',\n 'priority',\n 'visibility',\n 'role',\n 'permission',\n 'level',\n 'mode',\n 'format',\n 'variant',\n 'size',\n 'theme',\n 'kind',\n 'phase',\n 'stage',\n] as const\n\n/**\n * Predefined exclusion lists for common use cases\n * @internal\n */\nexport const PREDEFINED_EXCLUSIONS = {\n /** Common enum and status fields */\n enums: [...COMMON_ENUM_FIELDS],\n\n /** All metadata and system fields (starting with _) */\n metadata: ['_id', '_type', '_rev', '_createdAt', '_updatedAt'],\n\n /** Common technical and configuration fields */\n technical: [\n 'slug',\n 'handle',\n 'key',\n 'code',\n 'id',\n 'uuid',\n 'token',\n 'hash',\n 'checksum',\n 'version',\n 'config',\n 'settings',\n ],\n\n /** SEO and URL-related fields that often shouldn't be translated */\n seo: ['slug', 'handle', 'permalink', 'canonical', 'redirect', 'robots', 'sitemap'],\n} as const\n","import type { TransformContext, TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { TransformRegistry } from './transformRegistry'\n\n/**\n * Stores original field values and their transforms for later restoration.\n * Keys are field paths (e.g., \"content\", \"metadata.description\").\n */\nexport interface OriginalStructure {\n [path: string]: {\n value: any\n transform: any\n }\n}\n\ninterface ApplyTransformsResult {\n transformedDocument: any\n originalStructure: OriginalStructure\n}\n\n/**\n * Recursively traverses a document and applies transforms to matching fields.\n * @param document - The document to transform\n * @param registry - The transform registry to use\n * @param schemaType - The document schema type\n * @param parentPath - Parent path (used for recursion)\n * @returns The transformed document and original structure\n */\nexport function applyTransforms({\n document,\n registry,\n schemaType,\n parentPath = '',\n}: {\n document: any\n registry: TransformRegistry\n schemaType: string\n parentPath?: string\n}): ApplyTransformsResult {\n if (!document) {\n return { transformedDocument: document, originalStructure: {} }\n }\n\n // Handle primitive values (no transforms needed)\n if (typeof document !== 'object') {\n return { transformedDocument: document, originalStructure: {} }\n }\n\n // Handle arrays\n if (Array.isArray(document)) {\n const originalStructure: OriginalStructure = {}\n const transformedArray = document.map((item, index) => {\n const itemPath = parentPath ? `${parentPath}[${index}]` : `[${index}]`\n const { transformedDocument: transformedItem, originalStructure: itemOriginalStructure } = applyTransforms({\n document: item,\n registry,\n schemaType,\n parentPath: itemPath,\n })\n\n // Merge original structure from items\n Object.assign(originalStructure, itemOriginalStructure)\n\n return transformedItem\n })\n\n return { transformedDocument: transformedArray, originalStructure }\n }\n\n // Handle objects\n const transformedObj: any = {}\n const originalStructure: OriginalStructure = {}\n\n // Process each property\n for (const [key, value] of Object.entries(document)) {\n const fieldPath = parentPath ? `${parentPath}.${key}` : key\n\n // Skip internal fields (starting with _) from transformation\n if (key.startsWith('_') && key !== '_type') {\n transformedObj[key] = value\n continue\n }\n\n // Check if there's a transform for this field\n // Pass value and document for value-based matching\n const transform = registry.findTransform(schemaType, fieldPath, value, document)\n\n if (transform) {\n devLog.debug('Applying transform', { fieldPath, schemaType })\n\n const context: TransformContext = {\n fieldPath,\n schemaType,\n document: document as TranslatableDocument,\n }\n\n // Apply transform\n const transformedValue = transform.toTranslatable(value, context)\n transformedObj[key] = transformedValue\n\n // Store original value for later restoration\n originalStructure[fieldPath] = {\n value,\n transform,\n }\n } else {\n // No transform found, process recursively\n const { transformedDocument: transformedValue, originalStructure: valueOriginalStructure } = applyTransforms({\n document: value,\n registry,\n schemaType,\n parentPath: fieldPath,\n })\n\n // Merge original structure from nested fields\n Object.assign(originalStructure, valueOriginalStructure)\n\n transformedObj[key] = transformedValue\n }\n }\n\n return { transformedDocument: transformedObj, originalStructure }\n}\n","import type { TransformContext, TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { OriginalStructure } from './applyTransforms'\nimport type { TransformRegistry } from './transformRegistry'\n\n// Type for values that can be translated and restored\ntype TranslatableValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | TranslatableValue[]\n | { [key: string]: TranslatableValue }\n | TranslatableDocument\n\n/**\n * Restores original structure with translated content.\n * @param translatedDocument - The translated document\n * @param originalStructure - Original structure with transform information\n * @param registry - The transform registry\n * @param schemaType - The document schema type\n * @param parentPath - Parent path (used for recursion)\n * @returns The restored document\n */\nexport function restoreTransforms({\n translatedDocument,\n originalStructure,\n registry,\n schemaType,\n parentPath = '',\n}: {\n translatedDocument: TranslatableValue\n originalStructure: OriginalStructure\n registry: TransformRegistry\n schemaType: string\n parentPath?: string\n}): TranslatableValue {\n if (translatedDocument === null || translatedDocument === undefined) {\n return translatedDocument\n }\n\n // Handle primitive values\n if (typeof translatedDocument !== 'object') {\n return translatedDocument\n }\n\n // Handle arrays\n if (Array.isArray(translatedDocument)) {\n return translatedDocument.map((item, index) => {\n const itemPath = parentPath ? `${parentPath}[${index}]` : `[${index}]`\n return restoreTransforms({\n translatedDocument: item,\n originalStructure,\n registry,\n schemaType,\n parentPath: itemPath,\n })\n })\n }\n\n // Check if current path has stored original structure\n const originalData = originalStructure[parentPath]\n if (originalData) {\n const { value: originalValue, transform } = originalData\n const context: TransformContext = {\n fieldPath: parentPath,\n schemaType,\n document: translatedDocument as TranslatableDocument,\n }\n\n devLog.debug('Restoring transform', { fieldPath: parentPath, schemaType })\n\n // Apply reverse transform\n return transform.fromTranslatable(translatedDocument, originalValue, context)\n }\n\n // Handle objects - at this point we know translatedDocument is a non-array object\n const restoredObj: Record<string, TranslatableValue> = {}\n const translatedObj = translatedDocument as Record<string, TranslatableValue>\n\n for (const [key, value] of Object.entries(translatedObj)) {\n const fieldPath = parentPath ? `${parentPath}.${key}` : key\n\n // Check if this specific field path has an original structure\n const fieldOriginalData = originalStructure[fieldPath]\n\n if (fieldOriginalData) {\n const { value: originalValue, transform } = fieldOriginalData\n const context: TransformContext = {\n fieldPath,\n schemaType,\n document: translatedDocument as TranslatableDocument,\n }\n\n devLog.debug('Restoring transform for field', { fieldPath, schemaType })\n\n // Apply reverse transform\n const restoredValue = transform.fromTranslatable(value, originalValue, context)\n restoredObj[key] = restoredValue\n } else {\n // Process recursively\n restoredObj[key] = restoreTransforms({\n translatedDocument: value,\n originalStructure,\n registry,\n schemaType,\n parentPath: fieldPath,\n })\n }\n }\n\n return restoredObj\n}\n","import type { AnyFieldTransform } from '../../types'\nimport { devLog } from '../common/devLog'\n\n/**\n * Registry for managing and applying field transforms.\n * @public\n */\nexport class TransformRegistry {\n private transforms: AnyFieldTransform[] = []\n\n /**\n * Register a transform with the registry.\n * @param transform - Field transform to register\n */\n register(transform: AnyFieldTransform): void {\n devLog.debug('Registering field transform', {\n matcher: transform.matcher,\n })\n this.transforms.push(transform)\n }\n\n /**\n * Find a matching transform for the given field.\n * If multiple transforms match, returns the first one (order matters!).\n * @param schemaType - Document schema type\n * @param fieldPath - Path to the field\n * @param value - Optional field value for value-based matching\n * @param document - Optional full document for context\n * @returns Matching transform or undefined if none found\n */\n findTransform(\n schemaType: string,\n fieldPath: string,\n // biome-ignore lint/suspicious/noExplicitAny: Field values can be any type\n value?: any,\n // biome-ignore lint/suspicious/noExplicitAny: Documents can be any shape\n document?: any,\n ): AnyFieldTransform | undefined {\n const matches: AnyFieldTransform[] = []\n\n for (const transform of this.transforms) {\n const { matcher } = transform\n\n // Check schema type if specified\n if (matcher.schemaType && matcher.schemaType !== schemaType) {\n continue\n }\n\n // Check field path if specified\n if (matcher.fieldPath) {\n if (typeof matcher.fieldPath === 'string') {\n if (matcher.fieldPath !== fieldPath) {\n continue\n }\n } else if (matcher.fieldPath instanceof RegExp) {\n if (!matcher.fieldPath.test(fieldPath)) {\n continue\n }\n }\n }\n\n // Check field name if specified\n if (matcher.fieldName) {\n // Extract field name from path (last segment, handling nested paths and array notation)\n // Examples: \"captions\" -> \"captions\", \"video.captions\" -> \"captions\", \"items[0].name\" -> \"name\"\n const segments = fieldPath.split('.')\n const lastSegment = segments[segments.length - 1]\n // Remove array notation if present: \"items[0]\" -> \"items\"\n const fieldName = lastSegment?.split('[')[0]\n\n if (fieldName !== matcher.fieldName) {\n continue\n }\n }\n\n // Check value-based matcher if specified\n if (matcher.valueMatcher) {\n // Value matcher requires the actual value to be provided\n if (value === undefined) {\n continue\n }\n\n const context = {\n fieldPath,\n schemaType,\n document: document || {},\n }\n\n if (!matcher.valueMatcher(value, context)) {\n continue\n }\n }\n\n // Transform matches all criteria\n matches.push(transform)\n }\n\n // Warn if multiple transforms match the same field\n if (matches.length > 1) {\n devLog.warn('Multiple transforms match the same field - using first match', {\n schemaType,\n fieldPath,\n matchCount: matches.length,\n firstMatcher: matches[0].matcher,\n })\n }\n\n return matches[0]\n }\n}\n\n/**\n * Global transform registry instance.\n * @public\n */\nexport const globalTransformRegistry = new TransformRegistry()\n","import { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\nexport interface GPT5Request {\n messages: Array<{ role: 'developer' | 'user' | 'assistant'; content: string }>\n model?: string\n temperature?: number\n reasoningEffort?: 'low' | 'medium' | 'high'\n}\n\nexport interface GPT5Response {\n content: string\n tokensUsed: {\n prompt: number\n completion: number\n reasoning: number\n total: number\n cached: number\n }\n cost: number\n}\n\n/**\n * Calls the OpenAI API with the given request parameters.\n * All GPT-5 variants (gpt-5, gpt-5.1, gpt-5-mini, gpt-5-nano, gpt-5-pro) use the new /v1/responses endpoint.\n * GPT-4 models use the standard /v1/chat/completions endpoint.\n */\nexport const callGPT5 = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n const isGPT5Family = model.startsWith('gpt-5')\n\n // All GPT-5 variants require /v1/responses endpoint; GPT-4 uses /v1/chat/completions\n if (isGPT5Family) {\n return await callGPT5ResponsesAPI(apiKey, request)\n }\n\n // GPT-4 and other models use the standard chat completions endpoint\n return await callChatCompletionsAPI(apiKey, request)\n}\n\n/**\n * Calls all GPT-5 variants using the new /v1/responses endpoint.\n * This endpoint supports reasoning configuration for applicable models.\n */\nconst callGPT5ResponsesAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n\n // Format request for OpenAI Responses API (different from chat completions API)\n const requestBody: Record<string, unknown> = {\n model: model === 'gpt-5' ? 'gpt-5.1' : model, // Map gpt-5 to gpt-5.1, use others as-is\n input: request.messages, // Uses 'input' instead of 'messages'\n }\n\n // Include reasoning effort setting if provided by caller\n if (request.reasoningEffort) {\n requestBody.reasoning = {\n effort: request.reasoningEffort,\n summary: 'auto', // Enable automatic reasoning summaries\n }\n }\n\n // Log the prompt being sent to OpenAI\n devLog.debug('OpenAI GPT-5 Request', {\n model: requestBody.model,\n reasoningEffort: request.reasoningEffort,\n messages: request.messages.map((m) => ({\n role: m.role,\n contentLength: m.content.length,\n contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? '...' : ''),\n })),\n })\n devLog.debug('OpenAI GPT-5 Full Prompt', {\n messages: request.messages,\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/responses', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `GPT-5 API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model: requestBody.model, endpoint: 'responses' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`GPT-5 API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n // Parse token usage from Responses API response structure\n const tokensUsed = {\n prompt: result.usage?.input_tokens || 0,\n completion: result.usage?.output_tokens || 0,\n reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,\n total: result.usage?.total_tokens || 0,\n cached: result.usage?.input_tokens_details?.cached_tokens || 0,\n }\n\n const cost = calculateCost(tokensUsed, model)\n\n // Responses API wraps output in array with reasoning and message objects\n let content = ''\n\n if (result.output && Array.isArray(result.output)) {\n // Find the message item in the output array\n const messageItem = result.output.find((item: { type: string }) => item.type === 'message') as\n | { content?: Array<{ type: string; text?: string }> }\n | undefined\n\n if (messageItem?.content && Array.isArray(messageItem.content)) {\n // Find the output_text item in the content array\n const outputTextItem = messageItem.content.find((item) => item.type === 'output_text')\n content = outputTextItem?.text || ''\n }\n }\n\n // Fallback to other possible field names if output array approach didn't work\n if (!content) {\n content = result.output_text || result.text || ''\n }\n\n return {\n content,\n tokensUsed,\n cost,\n }\n}\n\n/**\n * Calls GPT-4 and other models using the standard /v1/chat/completions endpoint.\n */\nconst callChatCompletionsAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-4o'\n\n const requestBody: Record<string, unknown> = {\n model,\n messages: request.messages,\n temperature: request.temperature ?? 0.3,\n }\n\n // Log the prompt being sent to OpenAI\n devLog.debug('OpenAI Chat Completions Request', {\n model,\n temperature: request.temperature ?? 0.3,\n messages: request.messages.map((m) => ({\n role: m.role,\n contentLength: m.content.length,\n contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? '...' : ''),\n })),\n })\n devLog.debug('OpenAI Chat Completions Full Prompt', {\n messages: request.messages,\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `OpenAI API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model, endpoint: 'chat/completions' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`OpenAI API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n const usage = result.usage || {}\n const tokensUsed = {\n prompt: usage.prompt_tokens || 0,\n completion: usage.completion_tokens || 0,\n reasoning: usage.reasoning_tokens || 0,\n total: usage.total_tokens || 0,\n cached: usage.prompt_tokens_details?.cached_tokens || 0,\n }\n\n const cost = calculateCost(tokensUsed, model)\n const message = result.choices[0].message\n const messageContent = message.content || ''\n\n return {\n content: messageContent,\n tokensUsed,\n cost,\n }\n}\n\n/**\n * Calculates the cost of an OpenAI API call based on token usage.\n * Supports pricing for all GPT-5 variants, GPT-4o, GPT-4 Turbo, and GPT-4.\n */\nconst calculateCost = (\n tokens: { prompt: number; completion: number; reasoning: number; cached: number },\n model: string,\n): number => {\n const pricing: Record<string, { input: number; output: number; reasoning: number }> = {\n 'gpt-5': {\n input: 1.25 / 1000000,\n output: 10.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-5.1': {\n input: 1.25 / 1000000,\n output: 10.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-5-mini': {\n input: 0.25 / 1000000,\n output: 2.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-5-nano': {\n input: 0.05 / 1000000,\n output: 0.4 / 1000000,\n reasoning: 0,\n },\n 'gpt-5-pro': {\n input: 15.0 / 1000000,\n output: 120.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-4o': {\n input: 2.5 / 1000000,\n output: 10.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-4-turbo': {\n input: 10.0 / 1000000,\n output: 30.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-4': {\n input: 30.0 / 1000000,\n output: 60.0 / 1000000,\n reasoning: 0,\n },\n }\n\n const modelPricing = pricing[model] || pricing['gpt-5']\n\n // For GPT-5.1, cached input tokens are 10x cheaper (0.125 vs 1.25 per 1M tokens)\n const cachedInputRate = model === 'gpt-5.1' ? 0.125 / 1000000 : modelPricing.input\n const regularInputTokens = Math.max(0, tokens.prompt - tokens.cached)\n\n return (\n regularInputTokens * modelPricing.input +\n tokens.cached * cachedInputRate +\n tokens.completion * modelPricing.output +\n tokens.reasoning * modelPricing.reasoning\n )\n}\n","import cloneDeep from 'lodash.clonedeep'\nimport get from 'lodash.get'\nimport set from 'lodash.set'\nimport type { SanityDocument } from 'sanity'\nimport { devLog } from '../common/devLog'\n\ninterface ParsedNode {\n type: 'field' | 'block' | 'span'\n name?: string\n text?: string\n attributes: Record<string, string>\n children: ParsedNode[]\n}\n\n/**\n * Deserializes a reviewed text format back into a Sanity document.\n * Only applies text changes - all structure and metadata preserved.\n */\nexport const deserializeDocument = (originalDoc: SanityDocument, reviewedText: string): SanityDocument => {\n // Log raw response for debugging (first 1000 chars to avoid log spam)\n devLog.debug('Raw GPT-5 response received', {\n length: reviewedText.length,\n preview: reviewedText.substring(0, 1000),\n hasReasoning: reviewedText.includes('<reasoning>'),\n hasCodeBlocks: reviewedText.includes('```'),\n })\n\n // Preprocess: Remove reasoning tags and code block markers that GPT-5 may include\n const cleanedText = preprocessResponse(reviewedText)\n\n // Log cleaned response for debugging\n if (cleanedText.length !== reviewedText.length) {\n devLog.debug('Response after preprocessing', {\n originalLength: reviewedText.length,\n cleanedLength: cleanedText.length,\n preview: cleanedText.substring(0, 500),\n })\n }\n\n // Parse OpenAI's XML-structured response back into node tree\n const parsed = parseReviewFormat(cleanedText, originalDoc)\n\n devLog.debug('Parsed response structure', {\n rootNodeCount: parsed.length,\n rootNodeNames: parsed.map((n) => (n.type === 'field' && n.name ? n.name : 'unknown')),\n })\n\n // Deep clone original\n const result = cloneDeep(originalDoc)\n\n // Apply text changes from parsed structure\n applyTextChanges(result, parsed)\n\n return result\n}\n\n/**\n * Preprocesses GPT-5 response to remove reasoning tags and code block markers.\n * GPT-5 may wrap responses in <reasoning> tags and code blocks (```xml ... ```).\n */\nconst preprocessResponse = (text: string): string => {\n const originalLength = text.length\n let cleaned = text\n\n // Remove reasoning tags and their content\n // Matches <reasoning>...</reasoning> including multiline content\n const beforeReasoning = cleaned.length\n cleaned = cleaned.replace(/<reasoning>[\\s\\S]*?<\\/reasoning>/gi, '')\n const reasoningRemoved = beforeReasoning !== cleaned.length\n\n // Remove code block markers (```xml, ```, etc.)\n // Matches opening markers like ```xml, ```json, etc.\n const beforeCodeBlocks = cleaned.length\n cleaned = cleaned.replace(/^```[a-z]*\\s*$/gim, '')\n // Matches closing markers ```\n cleaned = cleaned.replace(/^```\\s*$/gim, '')\n const codeBlocksRemoved = beforeCodeBlocks !== cleaned.length\n\n const finalLength = cleaned.trim().length\n\n if (reasoningRemoved || codeBlocksRemoved) {\n devLog.debug('Preprocessed GPT-5 response', {\n originalLength,\n finalLength,\n reasoningRemoved,\n codeBlocksRemoved,\n preview: cleaned.trim().substring(0, 200),\n })\n }\n\n return cleaned.trim()\n}\n\nconst parseReviewFormat = (text: string, originalDoc: SanityDocument): ParsedNode[] => {\n const root: ParsedNode[] = []\n const stack: ParsedNode[] = []\n\n // Parse XML-like format\n const lines = text.split('\\n')\n\n devLog.debug('Parsing response text', {\n lineCount: lines.length,\n firstLines: lines.slice(0, 10).map((l, idx) => ({ line: idx, content: l.substring(0, 100) })),\n hasFieldTags: text.includes('<field'),\n hasBlockTags: text.includes('<block'),\n hasSpanTags: text.includes('<span'),\n textLength: text.length,\n textPreview: text.substring(0, 500),\n })\n\n let processedLineCount = 0\n let skippedEmptyLineCount = 0\n let matchedPatternCount = 0\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const trimmed = line.trim()\n if (!trimmed) {\n skippedEmptyLineCount++\n continue\n }\n processedLineCount++\n\n // Check for compact string format (fieldName: value) - legacy support\n const compactStringMatch = trimmed.match(/^(\\w+):\\s*(.*)$/)\n if (compactStringMatch && !trimmed.includes('<')) {\n matchedPatternCount++\n const [, fieldName, text] = compactStringMatch\n\n const node: ParsedNode = {\n type: 'field',\n name: fieldName,\n text: unescapeXml(text),\n attributes: { type: 'string' },\n children: [],\n }\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n continue\n }\n\n // Check for compact format markers (--- fieldName ---)\n const compactStartMatch = trimmed.match(/^---\\s+(\\w+)\\s+---$/)\n if (compactStartMatch) {\n matchedPatternCount++\n const fieldName = compactStartMatch[1]\n const contentLines: string[] = []\n i++ // Move to next line\n\n // Capture content until closing marker\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.trim().match(new RegExp(`^---\\\\s+/${fieldName}\\\\s+---$`))) {\n break\n }\n contentLines.push(currentLine)\n i++\n }\n\n const content = contentLines.join('\\n')\n\n // Try to get the actual type from the original document field\n // This ensures custom transforms work without hardcoding types\n let contentType: string | undefined\n try {\n const fieldValue = get(originalDoc, [fieldName])\n if (fieldValue && typeof fieldValue === 'object' && fieldValue !== null) {\n const fieldValueObj = fieldValue as Record<string, unknown>\n const fieldType = fieldValueObj._type\n if (typeof fieldType === 'string' && fieldType.startsWith('translatable.')) {\n contentType = fieldType\n }\n }\n } catch {\n // Field doesn't exist or path is invalid, fall through to content-based detection\n }\n\n // Fallback to content-based detection if we couldn't get type from original document\n if (!contentType) {\n if (content.includes('<block') || content.includes('<span')) {\n contentType = 'translatable.richtext'\n } else if (content.match(/^\\[\\d+\\]/m)) {\n // Content with [index] format - default to VTT for backward compatibility\n contentType = 'translatable.vtt.captions'\n } else {\n // Unknown format, default to VTT\n contentType = 'translatable.vtt.captions'\n }\n }\n\n const node: ParsedNode = {\n type: 'field',\n name: fieldName,\n text: content,\n attributes: { type: contentType },\n children: [],\n }\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n continue\n }\n\n // Opening tag\n const openMatch = trimmed.match(/^<(field|block|span)([^>]*)>(.*)$/)\n if (openMatch) {\n matchedPatternCount++\n devLog.debug('Matched opening tag', {\n line: i,\n tagName: openMatch[1],\n attributes: openMatch[2],\n contentPreview: openMatch[3]?.substring(0, 50),\n })\n const [, tagName, attrsStr, content] = openMatch\n const attributes = parseAttributes(attrsStr)\n\n const node: ParsedNode = {\n type: tagName as 'field' | 'block' | 'span',\n name: attributes.name,\n text: content ? unescapeXml(content) : undefined,\n attributes,\n children: [],\n }\n\n // Check if self-closing (has content on same line)\n const closingTag = `</${tagName}>`\n if (content.includes(closingTag)) {\n // Self-closing: extract text up to first closing tag; push back remainder (handles siblings on same line)\n const closeIdx = content.indexOf(closingTag)\n node.text = unescapeXml(content.substring(0, closeIdx))\n const remainder = content.substring(closeIdx + closingTag.length).trim()\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n if (remainder) {\n lines[i] = remainder\n i--\n }\n } else {\n // Special handling for transformed fields - capture raw content\n // Generic check for any translatable.* type\n if (attributes.type && typeof attributes.type === 'string' && attributes.type.startsWith('translatable.')) {\n // Capture all content until closing tag (break when line contains </field>, push back remainder)\n const contentLines: string[] = []\n i++ // Move to next line\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.includes('</field>')) {\n const idx = currentLine.indexOf('</field>')\n const before = currentLine.substring(0, idx).trimEnd()\n if (before.trim()) contentLines.push(before)\n const after = currentLine.substring(idx + '</field>'.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n node.text = contentLines.join('\\n')\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n } else if (attributes.type === 'string' && content && !content.includes(closingTag)) {\n // Multi-line string field with content starting on opening line\n // This handles GPT-5 responses where the content spans multiple lines\n // e.g., <field name=\"text\" type=\"string\">First line content\n //\n // More content on subsequent lines </field>\n const contentLines: string[] = [content]\n i++ // Move to next line\n while (i < lines.length) {\n const currentLine = lines[i]\n // Check if this line contains the closing tag (could be at end of content line)\n if (currentLine.includes(closingTag)) {\n const closingIndex = currentLine.indexOf(closingTag)\n const remainingContent = currentLine.substring(0, closingIndex).trimEnd()\n if (remainingContent) {\n contentLines.push(remainingContent)\n }\n const after = currentLine.substring(closingIndex + closingTag.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n node.text = unescapeXml(contentLines.join('\\n'))\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n } else if (attributes.type === 'string' && !content) {\n // Multi-line string field - capture content until closing tag (break when line contains </field>, push back remainder)\n const contentLines: string[] = []\n i++ // Move to next line\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.includes('</field>')) {\n const idx = currentLine.indexOf('</field>')\n const before = currentLine.substring(0, idx).trimEnd()\n if (before.trim()) contentLines.push(before)\n const after = currentLine.substring(idx + '</field>'.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n node.text = contentLines.join('\\n').trim()\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n } else {\n // Opening tag for container\n stack.push(node)\n }\n }\n continue\n }\n\n // Closing tag\n // Match closing tags even when there's extra content on the same line (comments, whitespace, etc.)\n // Consume all closing tags on this line in a loop to avoid infinite loop when multiple </field> on one line\n const closeMatch = trimmed.match(/^<\\/(field|block|span)>/)\n if (closeMatch) {\n let afterClosingTag = trimmed\n // Consume one or more closing tags from the start of the line\n for (;;) {\n const match = afterClosingTag.match(/^<\\/(field|block|span)>/)\n if (!match) break\n if (stack.length === 0) {\n devLog.warn('Closing tag found but stack is empty', {\n line: i,\n tagName: match[1],\n trimmed: afterClosingTag,\n })\n afterClosingTag = afterClosingTag.substring(match[0].length).trim()\n afterClosingTag = afterClosingTag.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n continue\n }\n matchedPatternCount++\n devLog.debug('Matched closing tag', {\n line: i,\n tagName: match[1],\n trimmed: afterClosingTag,\n stackLength: stack.length,\n stackTopName: stack[stack.length - 1]?.name,\n })\n const node = stack.pop()\n if (node) {\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n }\n afterClosingTag = afterClosingTag.substring(match[0].length).trim()\n afterClosingTag = afterClosingTag.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n }\n // If there's content after the closing tag(s), process opening tag or push back remainder\n if (afterClosingTag) {\n const remainingOpenMatch = afterClosingTag.match(/^<(field|block|span)([^>]*)>(.*)$/)\n if (remainingOpenMatch) {\n const [, tagName, attrsStr, content] = remainingOpenMatch\n const attributes = parseAttributes(attrsStr)\n\n const remainingNode: ParsedNode = {\n type: tagName as 'field' | 'block' | 'span',\n name: attributes.name,\n text: content ? unescapeXml(content) : undefined,\n attributes,\n children: [],\n }\n\n const remainingClosingTag = `</${tagName}>`\n if (content.includes(remainingClosingTag)) {\n // Self-closing on same line: extract text up to first closing tag, push back remainder\n const closeIdx = content.indexOf(remainingClosingTag)\n remainingNode.text = unescapeXml(content.substring(0, closeIdx))\n const remainder = content.substring(closeIdx + remainingClosingTag.length).trim()\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(remainingNode)\n } else {\n root.push(remainingNode)\n }\n if (remainder) {\n lines[i] = remainder\n i--\n }\n } else if (attributes.type === 'string' && content) {\n remainingNode.text = unescapeXml(content)\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(remainingNode)\n } else {\n root.push(remainingNode)\n }\n } else if (attributes.type === 'string' && !content) {\n const contentLines: string[] = []\n i++\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.includes(`</${tagName}>`)) {\n const idx = currentLine.indexOf(`</${tagName}>`)\n const before = currentLine.substring(0, idx).trimEnd()\n if (before.trim()) contentLines.push(before)\n const after = currentLine.substring(idx + `</${tagName}>`.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n remainingNode.text = contentLines.join('\\n').trim()\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(remainingNode)\n } else {\n root.push(remainingNode)\n }\n } else {\n stack.push(remainingNode)\n }\n } else {\n // Remainder is not an opening tag (e.g. more closing tags with different spacing); push back to reprocess\n lines[i] = afterClosingTag\n i--\n }\n }\n continue\n }\n\n // If we get here, the line didn't match any pattern\n // Check if it looks like a closing tag that didn't match\n if (trimmed.startsWith('</')) {\n const attemptedCloseMatch = trimmed.match(/^<\\/(field|block|span)>/)\n if (!attemptedCloseMatch) {\n devLog.warn('Line starts with </ but did not match closing tag pattern', {\n line: i,\n trimmed,\n stackLength: stack.length,\n stackTopName: stack[stack.length - 1]?.name,\n stackTopType: stack[stack.length - 1]?.type,\n })\n } else if (stack.length === 0) {\n devLog.warn('Closing tag matched but stack is empty (unmatched closing tag)', {\n line: i,\n tagName: attemptedCloseMatch[1],\n trimmed,\n })\n }\n }\n if (processedLineCount <= 20) {\n // Log first 20 non-empty lines that don't match\n devLog.debug('Line did not match any pattern', {\n line: i,\n trimmed,\n startsWithLessThan: trimmed.startsWith('<'),\n startsWithField: trimmed.startsWith('<field'),\n startsWithBacktick: trimmed.startsWith('`'),\n startsWithClosingTag: trimmed.startsWith('</'),\n })\n }\n }\n\n // If there are nodes remaining in the stack, add them to root\n // This handles cases where closing tags are missing or malformed\n if (stack.length > 0) {\n devLog.warn('Nodes remaining in stack after parsing complete', {\n stackLength: stack.length,\n remainingNodes: stack.map((n) => ({\n type: n.type,\n name: n.name,\n attributes: n.attributes,\n })),\n })\n // Add all remaining nodes to root (they should have been closed but weren't)\n while (stack.length > 0) {\n const node = stack.pop()\n if (node) {\n root.push(node)\n }\n }\n }\n\n devLog.debug('Parse complete', {\n totalLines: lines.length,\n processedLines: processedLineCount,\n skippedEmptyLines: skippedEmptyLineCount,\n rootNodeCount: root.length,\n stackLength: stack.length,\n matchedPatterns: matchedPatternCount,\n })\n\n return root\n}\n\nconst parseAttributes = (attrStr: string): Record<string, string> => {\n const attrs: Record<string, string> = {}\n const attrRegex = /(\\w+)=\"([^\"]*)\"/g\n let match: RegExpExecArray | null = attrRegex.exec(attrStr)\n\n while (match !== null) {\n attrs[match[1]] = unescapeXml(match[2])\n match = attrRegex.exec(attrStr)\n }\n\n return attrs\n}\n\nconst unescapeXml = (str: string): string => {\n return str\n .replace(/&apos;/g, \"'\")\n .replace(/&quot;/g, '\"')\n .replace(/&gt;/g, '>')\n .replace(/&lt;/g, '<')\n .replace(/&amp;/g, '&')\n}\n\nconst applyTextChanges = (doc: Record<string, unknown>, parsedNodes: ParsedNode[]): void => {\n devLog.debug('Applying text changes from parsed nodes', {\n nodeCount: parsedNodes.length,\n nodeNames: parsedNodes.map((n) => (n.type === 'field' && n.name ? n.name : 'unknown')),\n })\n\n for (const node of parsedNodes) {\n if (node.type === 'field' && node.name) {\n const path = [node.name]\n const beforeValue = get(doc, path)\n applyFieldChanges(doc, path, node)\n const afterValue = get(doc, path)\n if (beforeValue !== afterValue) {\n devLog.debug('Field updated during deserialization', {\n path: path.join('.'),\n before: typeof beforeValue === 'string' ? beforeValue.substring(0, 50) : beforeValue,\n after: typeof afterValue === 'string' ? afterValue.substring(0, 50) : afterValue,\n })\n }\n }\n }\n}\n\nconst applyFieldChanges = (doc: Record<string, unknown>, path: (string | number)[], node: ParsedNode): void => {\n // Generic handling for any transformed field with translatable.* type\n // This allows custom transforms to work without modifying deserialization code\n const nodeType = node.attributes.type\n if (typeof nodeType === 'string' && nodeType.startsWith('translatable.')) {\n const fieldValue = get(doc, path)\n if (fieldValue && typeof fieldValue === 'object' && fieldValue !== null) {\n const fieldValueObj = fieldValue as Record<string, unknown>\n const fieldType = fieldValueObj._type\n // Verify the field type matches what we expect (safety check)\n if (typeof fieldType === 'string' && fieldType === nodeType) {\n // The translated content is captured in node.text during parsing\n if (node.text) {\n // Update the translationText field with the translated content\n set(doc, [...path, 'translationText'], node.text)\n }\n }\n }\n return\n }\n\n if (node.attributes.type === 'array') {\n // Handle array fields\n node.children.forEach((child, idx) => {\n applyFieldChanges(doc, [...path, idx], child)\n })\n return\n }\n\n if (node.attributes.type === 'object') {\n // Handle object fields\n const currentValue = get(doc, path)\n\n // Verify that the current value is actually an object (safety check)\n if (currentValue && typeof currentValue === 'object' && !Array.isArray(currentValue)) {\n const currentValueObj = currentValue as Record<string, unknown>\n\n for (const child of node.children) {\n if (child.name) {\n // Check if the child name exists as a field in the actual object\n const childValue = currentValueObj[child.name]\n\n // If child name is numeric and the value doesn't exist, this is likely a parsing error\n // Skip it to avoid creating incorrect paths like \"questions.2.3.stem\"\n const numericName = /^\\d+$/.test(child.name)\n if (numericName && childValue === undefined) {\n devLog.warn('Skipping numeric child name in object field - field does not exist (likely parsing error)', {\n path: path.join('.'),\n childName: child.name,\n availableKeys: Object.keys(currentValueObj),\n childType: child.attributes.type,\n })\n continue\n }\n\n applyFieldChanges(doc, [...path, child.name], child)\n }\n }\n } else {\n devLog.warn('Object field type but value is not an object', {\n path: path.join('.'),\n actualType: Array.isArray(currentValue) ? 'array' : typeof currentValue,\n })\n }\n return\n }\n\n // Check for blocks (Portable Text)\n if (node.children.some((c) => c.type === 'block')) {\n const fieldValue = get(doc, path)\n if (Array.isArray(fieldValue)) {\n node.children.forEach((blockNode, idx) => {\n if (blockNode.type === 'block') {\n applyBlockChanges(fieldValue[idx], blockNode)\n }\n })\n }\n return\n }\n\n // Simple text field (including explicit string type)\n // Only update fields that exist in the original document - don't create new fields\n // This prevents OpenAI from adding fields that were intentionally excluded\n // CRITICAL: Only update if existing value is a string - never overwrite objects/arrays with strings\n // This prevents LLM malformed output (empty object tags, wrong types) from corrupting document structure\n if (node.text !== undefined && !node.attributes.readonly) {\n const existingValue = get(doc, path)\n if (existingValue !== undefined && typeof existingValue === 'string') {\n set(doc, path, node.text)\n } else if (existingValue !== undefined) {\n devLog.debug('Skipping field - existing value is not a string (would corrupt structure)', {\n path: path.join('.'),\n existingType: typeof existingValue,\n attemptedValue: node.text?.substring(0, 50),\n })\n } else {\n devLog.debug('Skipping field not present in original document', {\n path: path.join('.'),\n attemptedValue: node.text,\n })\n }\n }\n}\n\nconst applyBlockChanges = (block: Record<string, unknown>, blockNode: ParsedNode): void => {\n // Verify metadata matches (safety check)\n if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {\n devLog.warn('Block key mismatch, skipping', {\n expected: block._key,\n found: blockNode.attributes._key,\n component: 'deserializeDocument',\n })\n return\n }\n\n // Handle standard Portable Text blocks with span children\n const children = block.children\n if (Array.isArray(children) && blockNode.children.some((c) => c.type === 'span')) {\n blockNode.children.forEach((spanNode, idx) => {\n const childItem = (children as Record<string, unknown>[])[idx]\n if (spanNode.type === 'span' && childItem) {\n applySpanChanges(childItem, spanNode)\n }\n })\n } else if (blockNode.children.some((c) => c.type === 'field')) {\n // Handle custom block types with nested fields\n blockNode.children.forEach((fieldNode) => {\n if (fieldNode.type === 'field' && fieldNode.name) {\n applyBlockFieldChanges(block, fieldNode.name, fieldNode)\n }\n })\n }\n}\n\nconst applyBlockFieldChanges = (block: Record<string, unknown>, fieldName: string, fieldNode: ParsedNode): void => {\n const currentValue = block[fieldName]\n\n // Handle arrays\n if (fieldNode.attributes.type === 'array') {\n if (Array.isArray(currentValue)) {\n fieldNode.children.forEach((childNode, idx) => {\n if (childNode.type === 'block' && currentValue[idx]) {\n // Nested block in array\n applyBlockChanges(currentValue[idx] as Record<string, unknown>, childNode)\n } else if (childNode.type === 'field') {\n // Nested field in array\n applyBlockFieldChanges(currentValue[idx] as Record<string, unknown>, childNode.name || String(idx), childNode)\n }\n })\n }\n return\n }\n\n // Handle objects\n if (fieldNode.attributes.type === 'object') {\n if (currentValue && typeof currentValue === 'object') {\n fieldNode.children.forEach((childNode) => {\n if (childNode.name) {\n applyBlockFieldChanges(currentValue as Record<string, unknown>, childNode.name, childNode)\n }\n })\n }\n return\n }\n\n // Handle simple text fields\n // Only update fields that exist in the original block - don't create new fields\n // CRITICAL: Only update if existing value is a string - never overwrite objects/arrays with strings\n // This prevents LLM malformed output from corrupting block structure (e.g. figureDetails, children)\n if (fieldNode.text !== undefined && !fieldNode.attributes.readonly) {\n if (fieldName in block && typeof block[fieldName] === 'string') {\n block[fieldName] = fieldNode.text\n } else if (fieldName in block) {\n devLog.debug('Skipping block field - existing value is not a string (would corrupt structure)', {\n fieldName,\n existingType: typeof block[fieldName],\n attemptedValue: fieldNode.text?.substring(0, 50),\n })\n } else {\n devLog.debug('Skipping block field not present in original', {\n fieldName,\n attemptedValue: fieldNode.text,\n })\n }\n }\n}\n\nconst applySpanChanges = (span: Record<string, unknown>, spanNode: ParsedNode): void => {\n // Verify metadata matches\n if (spanNode.attributes._key && span._key !== spanNode.attributes._key) {\n devLog.warn('Span key mismatch, skipping', {\n expected: span._key,\n found: spanNode.attributes._key,\n component: 'deserializeDocument',\n })\n return\n }\n\n // ONLY update the text property\n if (spanNode.text !== undefined) {\n span.text = spanNode.text\n }\n}\n","import type { SanityDocument } from 'sanity'\n\n/**\n * Converts Sanity documents to a structured text format for OpenAI review.\n * The format is deterministic and preserves all structure and metadata.\n * Only text content can be modified during review.\n */\nexport const serializeDocument = (doc: SanityDocument) => {\n const lines: string[] = []\n\n const serialize = (value: unknown, path: (string | number)[] = [], indent = 0): void => {\n const indentStr = ' '.repeat(indent)\n\n // Skip Sanity metadata at root\n if (path.length === 0) {\n if (isPlainObject(value)) {\n for (const [key, val] of Object.entries(value)) {\n if (!key.startsWith('_') && key !== 'language') {\n serialize(val, [key], 0) // Always use indent 0 to reduce whitespace\n }\n }\n }\n return\n }\n\n const fieldName = path[path.length - 1]\n\n // Handle arrays\n if (Array.isArray(value)) {\n // Check if it's Portable Text\n const isPortableText = value.some((item) => item?._type === 'block')\n\n if (isPortableText) {\n lines.push(`${indentStr}<field name=\"${fieldName}\">`)\n value.forEach((block, idx) => {\n serializeBlock(block, [...path, idx], indent + 1, lines)\n })\n lines.push(`${indentStr}</field>`)\n } else {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"array\">`)\n value.forEach((item, idx) => {\n serialize(item, [...path, idx], indent + 1)\n })\n lines.push(`${indentStr}</field>`)\n }\n return\n }\n\n // Handle objects\n if (isPlainObject(value)) {\n // Generic handling for any transformed field with translatable.* type\n // This allows custom transforms to work without modifying serialization code\n const valueObj = value as Record<string, unknown>\n const valueType = valueObj._type\n const translationText = valueObj.translationText\n if (\n typeof valueType === 'string' &&\n valueType.startsWith('translatable.') &&\n typeof translationText === 'string'\n ) {\n // Output the translationText directly in compact format\n // Use a simple marker format without extra XML overhead\n lines.push(`${indentStr}--- ${fieldName} ---`)\n lines.push(translationText)\n lines.push(`${indentStr}--- /${fieldName} ---`)\n return\n }\n\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"object\">`)\n for (const [key, val] of Object.entries(value)) {\n if (!key.startsWith('_')) {\n serialize(val, [...path, key], indent + 1)\n }\n }\n lines.push(`${indentStr}</field>`)\n return\n }\n\n // Handle strings (actual translatable content)\n if (typeof value === 'string') {\n if (value.length > 0) {\n // Use explicit field markers for consistency with GPT-5\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"string\">${escapeXml(value)}</field>`)\n }\n return\n }\n\n // Other primitives (numbers, booleans) - include but mark readonly\n if (typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"${typeof value}\" readonly=\"true\">${value}</field>`)\n }\n }\n\n serialize(doc)\n return lines.join('\\n')\n}\n\nconst serializeBlock = (\n block: Record<string, unknown>,\n path: (string | number)[],\n indent: number,\n lines: string[],\n): void => {\n const indentStr = ' '.repeat(indent)\n\n // Store block metadata\n const metadata: Record<string, unknown> = {}\n if (block._type) metadata._type = block._type\n if (block._key) metadata._key = block._key\n if (block.style) metadata.style = block.style\n if (block.listItem) metadata.listItem = block.listItem\n if (block.level) metadata.level = block.level\n if (block.markDefs) metadata.markDefs = block.markDefs\n\n const metaStr = Object.entries(metadata)\n .map(([k, v]) => `${k}=\"${typeof v === 'string' ? escapeXml(v) : escapeXml(JSON.stringify(v))}\"`)\n .join(' ')\n\n lines.push(`${indentStr}<block ${metaStr}>`)\n\n // Handle standard Portable Text blocks with children (spans)\n if (Array.isArray(block.children) && block._type === 'block') {\n block.children.forEach((child: unknown, idx: number) => {\n serializeSpan(child as Record<string, unknown>, [...path, 'children', idx], indent + 1, lines)\n })\n } else {\n // Handle custom block types by recursively serializing all non-metadata fields\n for (const [key, value] of Object.entries(block)) {\n // Skip metadata fields already serialized in the block header\n if (key.startsWith('_') || key === 'style' || key === 'listItem' || key === 'level' || key === 'markDefs') {\n continue\n }\n\n // Recursively serialize the field content\n serializeBlockField(value, key, [...path, key], indent + 1, lines)\n }\n }\n\n lines.push(`${indentStr}</block>`)\n}\n\nconst serializeBlockField = (\n value: unknown,\n fieldName: string,\n path: (string | number)[],\n indent: number,\n lines: string[],\n): void => {\n const indentStr = ' '.repeat(indent)\n\n // Handle arrays\n if (Array.isArray(value)) {\n // Check if array contains blocks (nested Portable Text or custom blocks)\n const hasBlocks = value.some((item) => item?._type)\n\n if (hasBlocks) {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"array\">`)\n value.forEach((item, idx) => {\n if (item?._type === 'block' && item?.children) {\n // Standard Portable Text block\n serializeBlock(item, [...path, idx], indent + 1, lines)\n } else if (item?._type) {\n // Custom block type - treat as block\n serializeBlock(item, [...path, idx], indent + 1, lines)\n } else {\n // Non-block array item\n serializeBlockField(item, String(idx), [...path, idx], indent + 1, lines)\n }\n })\n lines.push(`${indentStr}</field>`)\n } else {\n // Simple array without blocks\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"array\">`)\n value.forEach((item, idx) => {\n serializeBlockField(item, String(idx), [...path, idx], indent + 1, lines)\n })\n lines.push(`${indentStr}</field>`)\n }\n return\n }\n\n // Handle objects\n if (isPlainObject(value)) {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"object\">`)\n for (const [key, val] of Object.entries(value)) {\n if (!key.startsWith('_')) {\n serializeBlockField(val, key, [...path, key], indent + 1, lines)\n }\n }\n lines.push(`${indentStr}</field>`)\n return\n }\n\n // Handle strings\n if (typeof value === 'string' && value.length > 0) {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"string\">${escapeXml(value)}</field>`)\n return\n }\n\n // Other primitives\n if (typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"${typeof value}\" readonly=\"true\">${value}</field>`)\n }\n}\n\nconst serializeSpan = (\n span: Record<string, unknown>,\n _path: (string | number)[],\n indent: number,\n lines: string[],\n): void => {\n const indentStr = ' '.repeat(indent)\n\n if (!span || typeof span !== 'object') return\n\n // Store span metadata\n const metadata: Record<string, unknown> = {}\n if (span._type) metadata._type = span._type\n if (span._key) metadata._key = span._key\n if (span.marks && Array.isArray(span.marks)) {\n metadata.marks = span.marks.join(',')\n }\n\n const metaStr = Object.entries(metadata)\n .map(([k, v]) => `${k}=\"${escapeXml(String(v))}\"`)\n .join(' ')\n\n const text = typeof span.text === 'string' ? span.text : ''\n\n if (metaStr) {\n lines.push(`${indentStr}<span ${metaStr}>${escapeXml(text)}</span>`)\n } else {\n lines.push(`${indentStr}<span>${escapeXml(text)}</span>`)\n }\n}\n\nconst escapeXml = (str: string): string => {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\nconst isPlainObject = (val: unknown): val is Record<string, unknown> => {\n return typeof val === 'object' && val !== null && !Array.isArray(val)\n}\n","import type { SanityDocument } from 'sanity'\n\nexport interface ValidationError {\n path: string[]\n expected: unknown\n actual: unknown\n type: 'missing' | 'type_mismatch' | 'extra' | 'metadata_changed'\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: ValidationError[]\n}\n\n/**\n * Validates that document structure has not been changed.\n * Extremely strict validation - any structural change is an error.\n * Only text content in 'text' fields is allowed to change.\n */\nexport const validateStructureIntegrity = (original: SanityDocument, modified: SanityDocument): ValidationResult => {\n const errors: ValidationError[] = []\n\n // Phase 1: Check critical metadata\n const criticalFields = ['_id', '_type', '_rev', '_createdAt', '_updatedAt']\n for (const field of criticalFields) {\n if (original[field] !== modified[field]) {\n errors.push({\n path: [field],\n expected: original[field],\n actual: modified[field],\n type: 'metadata_changed',\n })\n }\n }\n\n // Phase 2: Deep structure validation\n validateStructureRecursive(original, modified, [], errors)\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\nconst validateStructureRecursive = (\n original: unknown,\n modified: unknown,\n path: string[],\n errors: ValidationError[],\n): void => {\n // Type check\n if (typeof original !== typeof modified) {\n errors.push({\n path,\n expected: typeof original,\n actual: typeof modified,\n type: 'type_mismatch',\n })\n return\n }\n\n // Array validation\n if (Array.isArray(original)) {\n if (!Array.isArray(modified)) {\n errors.push({\n path,\n expected: 'array',\n actual: typeof modified,\n type: 'type_mismatch',\n })\n return\n }\n\n if (original.length !== modified.length) {\n errors.push({\n path,\n expected: `length ${original.length}`,\n actual: `length ${modified.length}`,\n type: 'type_mismatch',\n })\n return\n }\n\n original.forEach((item, idx) => {\n validateStructureRecursive(item, modified[idx], [...path, String(idx)], errors)\n })\n return\n }\n\n // Object validation\n if (typeof original === 'object' && original !== null && typeof modified === 'object' && modified !== null) {\n const origKeys = Object.keys(original as object).sort()\n const modKeys = Object.keys(modified as object).sort()\n\n // Check for missing keys\n for (const key of origKeys) {\n if (!(key in modified)) {\n errors.push({\n path: [...path, key],\n expected: 'present',\n actual: 'missing',\n type: 'missing',\n })\n }\n }\n\n // Check for extra keys\n for (const key of modKeys) {\n if (!(key in original)) {\n errors.push({\n path: [...path, key],\n expected: 'absent',\n actual: 'present',\n type: 'extra',\n })\n }\n }\n\n // Validate nested structure\n const origObj = original as Record<string, unknown>\n const modObj = modified as Record<string, unknown>\n for (const key of origKeys) {\n if (key in modObj) {\n // For metadata fields, require exact match\n if (key.startsWith('_') || key === 'marks' || key === 'style' || key === 'markDefs') {\n if (JSON.stringify(origObj[key]) !== JSON.stringify(modObj[key])) {\n errors.push({\n path: [...path, key],\n expected: origObj[key],\n actual: modObj[key],\n type: 'metadata_changed',\n })\n }\n } else if (key !== 'text') {\n // For non-text fields, validate structure recursively\n validateStructureRecursive(origObj[key], modObj[key], [...path, key], errors)\n }\n // Only 'text' fields can be modified; all other structure is immutable\n }\n }\n }\n}\n","import type { Dispatch } from 'react'\nimport type { PluginConfig, TelemetryConfig, TranslatableDocument, TranslationAction } from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { callGPT5 } from '../openai/callGPT5'\nimport { deserializeDocument } from '../openai/deserializeDocument'\nimport { serializeDocument } from '../openai/serializeDocument'\nimport { validateStructureIntegrity } from '../openai/validateStructure'\nimport { setJobStatus } from '../state/setJobStatus'\nimport { buildErrorBundle, classifyError, emitTelemetryError } from '../telemetry'\n\n/**\n * Result from OpenAI translation including document and metrics\n */\nexport interface OpenAITranslationResult {\n document: TranslatableDocument\n tokensUsed: number\n cost: number\n}\n\n/**\n * Translates or reviews a document using OpenAI.\n * Works in-place - receives a document, returns translated/reviewed document with metrics.\n */\nexport const getOpenAITranslation = async ({\n dispatch,\n sourceDocument,\n languageFieldName,\n translationPrompts,\n targetLanguage,\n openaiConfig,\n previousError: _previousError,\n errorContext,\n telemetryConfig,\n}: {\n dispatch: Dispatch<TranslationAction>\n sourceDocument: TranslatableDocument\n targetLanguage: string\n languageFieldName: string\n translationPrompts: FetchTranslationPromptsResult\n openaiConfig: NonNullable<PluginConfig['openai']>\n previousError?: Error\n errorContext?: string\n telemetryConfig?: TelemetryConfig\n}): Promise<OpenAITranslationResult | null> => {\n // Note: _previousError is available for future use in error recovery strategies\n const startTime = Date.now()\n const sourceLanguage = sourceDocument[languageFieldName] as string\n const logContext = {\n sourceId: sourceDocument._id,\n sourceLanguage,\n }\n\n // Variables for telemetry error bundle\n let serializedDocument: string | undefined\n let openaiResponseContent: string | undefined\n\n try {\n // Serialize documents\n const sourceText = serializeDocument(sourceDocument)\n serializedDocument = sourceText // Capture for error bundle\n\n devLog.info('Serialized document for OpenAI', {\n ...logContext,\n characterCount: sourceText.length,\n estimatedTokens: Math.ceil(sourceText.length / 4),\n preview: sourceText.substring(0, 500),\n })\n\n // Build prompt\n const prompt = buildTranslationPrompt({\n sourceText,\n sourceLanguage,\n targetLanguage,\n translationPrompts,\n errorContext,\n })\n\n const response = await callGPT5(openaiConfig.apiKey, {\n messages: [\n {\n role: 'developer',\n content: 'You are a translation quality reviewer. Review and refine translations while preserving structure.',\n },\n { role: 'user', content: prompt },\n ],\n model: openaiConfig.model,\n reasoningEffort: openaiConfig.reasoningEffort,\n })\n\n // Capture response content for error bundle\n openaiResponseContent = response.content\n\n devLog.info('OpenAI translation response received', {\n ...logContext,\n tokensUsed: response.tokensUsed,\n cost: response.cost,\n responseLength: response.content.length,\n responsePreview: response.content.substring(0, 500),\n })\n\n // Deserialize response - use the transformed/filtered document that was sent to GPT-5\n // This ensures the structure matches what GPT-5 translated\n const result = deserializeDocument(sourceDocument, response.content) as TranslatableDocument\n\n // Set the language field to the target language\n result[languageFieldName] = targetLanguage\n\n // Validate structure - compare against the source document we used for deserialization\n const validation = validateStructureIntegrity(sourceDocument, result)\n if (!validation.valid) {\n devLog.error('Structure validation failed', {\n ...logContext,\n errors: validation.errors,\n })\n\n const unexpectedErrors = validation.errors.filter(\n (err) => !(err.path.length === 1 && err.path[0] === languageFieldName),\n )\n\n if (unexpectedErrors.length > 0) {\n throw new Error(\n `Structure validation failed: ${unexpectedErrors.length} errors. First: ${unexpectedErrors[0].type} at ${unexpectedErrors[0].path.join('.')}`,\n )\n }\n }\n\n // Validate that important fields were translated - use filtered document for this check\n const translationValidation = validateTranslationCompleteness(sourceDocument, result, languageFieldName)\n if (!translationValidation.valid) {\n devLog.warn('Translation completeness warning', {\n ...logContext,\n skippedFields: translationValidation.skippedFields,\n warning: 'GPT-5 may have skipped some important fields',\n })\n\n // Log details but don't throw - this is a warning, not a failure\n devLog.info('Skipped field details', {\n ...logContext,\n skippedFields: translationValidation.skippedFields.map((field) => ({\n path: field.path,\n originalValue: field.originalValue,\n translatedValue: field.translatedValue,\n })),\n })\n }\n\n setJobStatus(dispatch, sourceDocument._id, targetLanguage, 'translating_with_openai', {\n tokensUsed: response.tokensUsed.total,\n estimatedCost: response.cost,\n })\n\n return {\n document: result,\n tokensUsed: response.tokensUsed.total,\n cost: response.cost,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n devLog.error('OpenAI translation failed', err, logContext)\n\n // Emit telemetry error bundle with full debug context\n emitTelemetryError(\n telemetryConfig,\n buildErrorBundle({\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n targetLanguage,\n sourceLanguage,\n translationMode: 'openai-only',\n error: err,\n errorType: classifyError(err, 'openai_api'),\n startTime,\n originalDocument: sourceDocument,\n serializedDocument,\n openaiResponse: openaiResponseContent,\n translationPrompts: {\n globalPrompt: translationPrompts.globalPrompt,\n localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,\n protectedPhrases: translationPrompts.protectedPhrases,\n glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms\n ? [translationPrompts.localeInstructions[targetLanguage].glossaryTerms]\n : undefined,\n },\n }),\n )\n\n throw error\n }\n}\n\nconst buildTranslationPrompt = ({\n sourceText,\n sourceLanguage,\n targetLanguage,\n translationPrompts,\n errorContext,\n}: {\n sourceText: string\n sourceLanguage: string\n targetLanguage: string\n translationPrompts: FetchTranslationPromptsResult\n errorContext?: string\n}): string => {\n const { globalPrompt, localeInstructions, protectedPhrases } = translationPrompts\n const localePrompt = localeInstructions[targetLanguage]?.prompt\n const glossaryTerms = localeInstructions[targetLanguage]?.glossaryTerms\n\n return `# TRANSLATION TASK\n\nTranslate this ${sourceLanguage} document into ${targetLanguage}.\n\n${errorContext ? `## ⚠️ IMPORTANT: Previous Attempt Error\\n\\n${errorContext}\\n\\nPlease carefully review the error above and ensure your translation fixes the issue.\\n\\n` : ''}## Guidelines\n\n${globalPrompt ? `### General Guidelines\\n${globalPrompt}\\n` : ''}\n${localePrompt ? `### ${targetLanguage} Guidelines\\n${localePrompt}\\n` : ''}\n${glossaryTerms ? `### Required Terminology\\n${glossaryTerms}\\n` : ''}\n${protectedPhrases?.length ? `### Protected Phrases (do not translate)\\n${protectedPhrases.join(', ')}\\n` : ''}\n\n## Source Document (${sourceLanguage})\n\n\\`\\`\\`\n${sourceText}\n\\`\\`\\`\n\n## Instructions\n\n1. **TRANSLATE ALL TEXT FIELDS**: Every field marked with type=\"string\" or containing text must be translated\n2. **DO NOT SKIP SIMPLE FIELDS**: Pay special attention to simple fields like \"title\", \"name\", \"description\" - these are often the most important\n3. Follow the guidelines and terminology requirements above\n4. Preserve protected phrases exactly as they appear\n5. Return the document in THE EXACT SAME FORMAT\n6. **CRITICAL**: Only modify text content between tags\n7. Never add, remove, or change any tags, attributes, or structure\n\n**Common fields that must always be translated:**\n- title, name, heading, label\n- description, caption, alt text\n- All text within <field> tags\n- All text within <span> tags\n\nIf you used significant reasoning to make translation decisions, you may include a brief summary:\n<reasoning>\nBrief summary of key translation decisions and reasoning\n</reasoning>\n\nThen return the translated document:`\n}\n\n/**\n * Validates that important translatable fields were actually translated by GPT-5.\n * This helps detect when GPT-5 inconsistently skips simple fields like \"title\".\n */\nconst validateTranslationCompleteness = (\n sourceDoc: TranslatableDocument,\n translatedDoc: TranslatableDocument,\n languageFieldName: string,\n): { valid: boolean; skippedFields: Array<{ path: string; originalValue: string; translatedValue: string }> } => {\n const skippedFields: Array<{ path: string; originalValue: string; translatedValue: string }> = []\n\n const checkField = (source: unknown, translated: unknown, path: string[] = []): void => {\n // Skip metadata fields and language field\n if (path.length > 0) {\n const fieldName = path[path.length - 1]\n if (fieldName.startsWith('_') || fieldName === languageFieldName) {\n return\n }\n }\n\n // Check string fields that should be translated\n if (typeof source === 'string' && typeof translated === 'string') {\n if (source.length > 0 && source === translated) {\n // Important fields that commonly get skipped\n const fieldName = path[path.length - 1] || 'unknown'\n const isImportantField = ['title', 'name', 'heading', 'label', 'description', 'caption', 'alt'].includes(\n fieldName,\n )\n\n if (isImportantField || source.length > 10) {\n // Flag important fields or longer text\n skippedFields.push({\n path: path.join('.'),\n originalValue: source,\n translatedValue: translated,\n })\n }\n }\n return\n }\n\n // Recursively check objects\n if (source && translated && typeof source === 'object' && typeof translated === 'object') {\n if (Array.isArray(source) && Array.isArray(translated)) {\n const maxLength = Math.min(source.length, translated.length)\n for (let i = 0; i < maxLength; i++) {\n checkField(source[i], translated[i], [...path, String(i)])\n }\n } else if (!Array.isArray(source) && !Array.isArray(translated)) {\n for (const key of Object.keys(source as Record<string, unknown>)) {\n if (!key.startsWith('_') && key !== languageFieldName) {\n checkField((source as Record<string, unknown>)[key], (translated as Record<string, unknown>)[key], [\n ...path,\n key,\n ])\n }\n }\n }\n }\n }\n\n checkField(sourceDoc, translatedDoc)\n\n return {\n valid: skippedFields.length === 0,\n skippedFields,\n }\n}\n","import type { TranslatableDocument } from '../../types'\n\ninterface AssembleLanguageSpecsParams {\n sourceDocument: TranslatableDocument\n languageFieldName: string\n targetLanguage: string\n}\n\n/**\n * Assembles language specification objects for translation operations.\n * Extracts the source language from the document and formats both source and target\n * languages into objects suitable for Sanity Agent Action translation calls.\n *\n * @param params - Configuration for assembling language specs\n * @param params.sourceDocument - The document being translated (contains source language ID)\n * @param params.languageFieldName - Name of the field containing the language identifier\n * @param params.targetLanguage - Target language ID to translate to\n * @returns Object containing fromLanguage and toLanguage specifications\n * @throws Error if source language ID is missing from the document\n *\n * @example\n * ```typescript\n * const specs = assembleLanguageSpecs({\n * sourceDocument: { language: 'en', ... },\n * languageFieldName: 'language',\n * targetLanguage: 'es'\n * })\n * // Returns: { fromLanguage: { id: 'en', title: 'en' }, toLanguage: { id: 'es', title: 'es' } }\n * ```\n */\nexport const assembleLanguageSpecs = ({\n sourceDocument,\n languageFieldName,\n targetLanguage,\n}: AssembleLanguageSpecsParams): {\n toLanguage: { id: string; title: string }\n fromLanguage: { id: string; title: string }\n} => {\n const sourceLanugageId = sourceDocument[languageFieldName] as string | undefined\n\n if (!sourceLanugageId) {\n throw new Error('[assembleLanguages] Source language ID is missing')\n }\n\n return {\n fromLanguage: {\n id: sourceLanugageId,\n title: sourceLanugageId,\n },\n toLanguage: {\n id: targetLanguage,\n title: targetLanguage,\n },\n }\n}\n","export interface PromptComponents {\n globalPrompt?: string\n localePrompt?: string\n glossaryTerms?: string\n protectedPhrases?: string[]\n}\n\nexport interface BuildTranslationPromptParams {\n sourceLanguage: string\n targetLanguage: string\n components: PromptComponents\n isUpdate?: boolean\n}\n\n/**\n * Builds a unified translation prompt for both Sanity Agent and OpenAI.\n * No character limits - includes all provided content.\n */\nexport const buildTranslationPrompt = ({\n sourceLanguage,\n targetLanguage,\n components,\n isUpdate = false,\n}: BuildTranslationPromptParams): string => {\n const sections: string[] = []\n\n // Section 1: Agent workflow directives\n sections.push(buildAgentDirectivesSection(sourceLanguage, targetLanguage))\n\n // Section 2: Critical rules\n sections.push(buildCriticalRulesSection(sourceLanguage, targetLanguage))\n\n // Section 3: Update instructions (if applicable)\n if (isUpdate) {\n sections.push(buildUpdateInstructionsSection())\n }\n\n // Section 4: Glossary terms (if provided)\n if (components.glossaryTerms?.trim()) {\n sections.push(buildGlossarySection(components.glossaryTerms))\n }\n\n // Section 5: Protected phrases (if provided)\n if (components.protectedPhrases?.length) {\n sections.push(buildProtectedPhrasesSection(components.protectedPhrases))\n }\n\n // Section 6: Style guide (if provided)\n const styleSection = buildStyleGuideSection(components.globalPrompt, components.localePrompt)\n if (styleSection) {\n sections.push(styleSection)\n }\n\n return sections.join('\\n\\n---\\n\\n')\n}\n\n/**\n * SECTION 1: Agent workflow directives to align with best practices\n */\nconst buildAgentDirectivesSection = (sourceLanguage: string, targetLanguage: string): string => {\n const fromLanguage = sourceLanguage || 'the source language'\n const toLanguage = targetLanguage || 'the target language'\n\n return `# AGENT WORKFLOW DIRECTIVES\n\n- Use only the provided ${fromLanguage} source; never fetch outside context.\n- Scan each field once, then move directly into translation work.\n- Plan updates before editing so document structure remains intact.\n- Default to medium reasoning effort and escalate only for ambiguous passages.\n- Output just the ${toLanguage} translation and mirror the source formatting exactly.`\n}\n\n/**\n * SECTION 2: Critical rules that must never be violated\n */\nconst buildCriticalRulesSection = (sourceLanguage: string, targetLanguage: string): string => {\n const fromLanguage = sourceLanguage || 'the source language'\n const toLanguage = targetLanguage || 'the target language'\n\n return `# CRITICAL TRANSLATION RULES\n\nFollow these without exception:\n\n1. Translate from ${fromLanguage} into ${toLanguage}; every output token must be ${toLanguage} unless flagged as protected.\n2. Preserve meaning exactly—no additions, deletions, or tone shifts.\n3. Maintain all layout, markdown, rich-text marks, and data structure.\n4. Never invent metadata or field values that are absent in the source.\n5. Leave technical tokens (code, IDs, filenames) untouched.\n6. Keep punctuation, casing, and spacing unless ${toLanguage} grammar forces a change.`\n}\n\n/**\n * SECTION 3: Instructions for updating existing translations\n */\nconst buildUpdateInstructionsSection = (): string => {\n return `# UPDATING EXISTING TRANSLATION\n\nThis document already has a translation; only adjust fields that changed.\n\n- Compare the latest source to the existing target text.\n- Keep prior terminology and style unless the source demands a change.\n- Edit minimally while guaranteeing accuracy and completeness.`\n}\n\n/**\n * SECTION 4: Required glossary term translations\n */\nconst buildGlossarySection = (glossaryTerms: string): string => {\n return `# TERMINOLOGY RULES\n\nApply these mappings immediately after protected phrases and before style preferences.\n\n${glossaryTerms}`\n}\n\n/**\n * SECTION 5: Protected phrases that must never be translated\n */\nconst buildProtectedPhrasesSection = (protectedPhrases: string[]): string => {\n return `# PROTECTED PHRASES\n\nThese phrases must NEVER be translated. Keep them exactly as they appear:\n\n${protectedPhrases.join(', ')}`\n}\n\n/**\n * SECTION 6: Style guide and user preferences\n */\nconst buildStyleGuideSection = (globalPrompt?: string, localePrompt?: string): string | null => {\n const components: string[] = []\n\n if (globalPrompt || localePrompt) {\n components.push('# TRANSLATION STYLE GUIDE (LOWEST PRIORITY)')\n components.push(\n 'Apply these directions only after honoring critical rules, protected phrases, and glossary terminology.',\n )\n components.push('')\n }\n\n if (globalPrompt) {\n components.push('## General Guidelines')\n components.push(globalPrompt)\n components.push('')\n }\n\n if (localePrompt) {\n components.push('## Language-Specific Guidelines')\n components.push(localePrompt)\n }\n\n return components.length > 0 ? components.join('\\n') : null\n}\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\nimport { assembleLanguageSpecs } from '../document/assembleLanguageSpecs'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { buildTranslationPrompt } from './assembleTranslationPrompt'\n\ninterface GetSanityAgentTranslationParams {\n sourceDocument: TranslatableDocument\n targetLanguage: string\n languageFieldName: string\n schemaId: string\n client: SanityClient\n translationPrompts: FetchTranslationPromptsResult\n}\n\nexport const getSanityAgentTranslation = async ({\n sourceDocument,\n targetLanguage,\n languageFieldName,\n schemaId,\n client,\n translationPrompts,\n}: GetSanityAgentTranslationParams) => {\n // Build unified translation prompt (no character limit)\n const styleGuide = buildTranslationPrompt({\n sourceLanguage: sourceDocument[languageFieldName] as string,\n targetLanguage,\n isUpdate: false, // Always fresh translation in a version\n components: {\n globalPrompt: translationPrompts.globalPrompt,\n localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,\n glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms,\n protectedPhrases: translationPrompts.protectedPhrases,\n },\n })\n\n const { fromLanguage, toLanguage } = assembleLanguageSpecs({\n sourceDocument,\n languageFieldName,\n targetLanguage,\n })\n\n return (await retryWithBackoff(\n async () => {\n return await client.agent.action.translate({\n schemaId,\n toLanguage,\n fromLanguage,\n documentId: sourceDocument._id,\n noWrite: true, // Do not write to the document, just return the translated content\n languageFieldPath: [languageFieldName],\n styleGuide,\n protectedPhrases: translationPrompts.protectedPhrases,\n conditionalPaths: {\n defaultHidden: false,\n defaultReadOnly: false,\n },\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'agent.action.translate',\n documentId: sourceDocument._id,\n targetLanguage,\n schemaId,\n },\n },\n )) as TranslatableDocument | null\n}\n","import { uuid } from '@sanity/uuid'\nimport type { Dispatch } from 'react'\nimport type { SanityClient } from 'sanity'\nimport type {\n PluginConfig,\n TelemetryConfig,\n TranslatableDocument,\n TranslationAction,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, isSanityMutationError, retryWithBackoff } from '../common/retry'\nimport { filterExcludedFields, restoreExcludedFields } from '../document/excludeFields'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { setJobStatus } from '../state/setJobStatus'\nimport { buildErrorBundle, classifyError, emitTelemetryError } from '../telemetry'\nimport { applyTransforms } from '../transforms/applyTransforms'\nimport { restoreTransforms } from '../transforms/restoreTransforms'\nimport { globalTransformRegistry } from '../transforms/transformRegistry'\nimport { getOpenAITranslation } from './getOpenAITranslation'\nimport { getSanityAgentTranslation } from './getSanityAgentTranslation'\n\n/**\n * Result from handleTranslation including document and optional metrics\n */\nexport interface HandleTranslationResult {\n document: TranslatableDocument\n tokensUsed?: number\n cost?: number\n}\n\ninterface TranslateEntireDocumentOptions {\n dispatch: Dispatch<TranslationAction>\n client: SanityClient\n schemaId: string\n sourceDocument: TranslatableDocument\n targetLanguage: string\n existingTranslationId: string | null\n languageFieldName: string\n translationPrompts: FetchTranslationPromptsResult\n translationMode: TranslationMode\n openaiConfig?: PluginConfig['openai']\n releaseId: string\n excludeFields?: string[]\n /** Whether to replace an existing version in the release */\n replaceExisting?: boolean\n /** Main source document ID for tracking in active translations store */\n mainSourceDocumentId?: string\n /** Optional telemetry configuration */\n telemetryConfig?: TelemetryConfig\n}\n\n/**\n * Main translation orchestrator.\n * Creates a version in the release, then routes to the appropriate translation method.\n */\nexport const handleTranslation = async ({\n dispatch,\n client,\n schemaId,\n sourceDocument,\n targetLanguage,\n existingTranslationId,\n languageFieldName,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields = [],\n replaceExisting = false,\n mainSourceDocumentId,\n telemetryConfig,\n}: TranslateEntireDocumentOptions): Promise<HandleTranslationResult | null> => {\n const startTime = Date.now()\n const sourceLanguage = sourceDocument[languageFieldName] as string\n // 1. Filter out excluded fields before translation\n // CRITICAL: Never exclude the language field - it's required for translation\n const safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName)\n\n if (excludeFields.length !== safeExcludeFields.length) {\n devLog.warn('Language field was in excludeFields - removing it to prevent translation errors', {\n documentId: sourceDocument._id,\n languageFieldName,\n originalExcludeFields: excludeFields,\n safeExcludeFields,\n })\n }\n\n devLog.info('Filtering excluded fields before translation', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n excludeFields: safeExcludeFields,\n excludeFieldsLength: safeExcludeFields?.length ?? 0,\n sourceHasTitle: 'title' in sourceDocument,\n sourceTitleValue: (sourceDocument as any).title,\n })\n\n const { filteredDocument, excludedValues } = filterExcludedFields(sourceDocument, safeExcludeFields)\n\n devLog.info('After filtering excluded fields', {\n documentId: sourceDocument._id,\n filteredHasTitle: 'title' in filteredDocument,\n filteredTitleValue: (filteredDocument as any).title,\n excludedValuesKeys: Object.keys(excludedValues),\n excludedValues,\n })\n\n // 2. Apply transforms to document before translation (OpenAI mode only)\n // Note: Sanity Agent fetches the document directly from the database by documentId,\n // so transforms are not needed and would cause issues with portable text fields.\n let documentToTranslate: TranslatableDocument\n let originalStructure: Record<string, { value: unknown; transform: unknown }> = {}\n\n if (translationMode === 'openai-only') {\n devLog.debug('Applying field transforms before translation (OpenAI mode)', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n })\n\n const transformResult = applyTransforms({\n document: filteredDocument as TranslatableDocument,\n registry: globalTransformRegistry,\n schemaType: sourceDocument._type,\n })\n documentToTranslate = transformResult.transformedDocument\n originalStructure = transformResult.originalStructure\n } else {\n // For Sanity Agent mode, use filtered document directly (no transforms)\n devLog.debug('Skipping field transforms (Sanity Agent mode)', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n })\n documentToTranslate = filteredDocument as TranslatableDocument\n }\n\n // 3. Create translation in memory via Sanity Agent or ChatGPT\n // For OpenAI mode, we'll retry the entire translation if mutation fails\n let translatedDoc: TranslatableDocument | null\n let translationMetrics: { tokensUsed?: number; cost?: number } = {}\n let previousError: Error | null = null\n\n interface TranslationAttemptResult {\n document: TranslatableDocument | null\n tokensUsed?: number\n cost?: number\n }\n\n const performTranslation = async (errorContext?: string): Promise<TranslationAttemptResult> => {\n switch (translationMode) {\n case 'sanity-only': {\n setJobStatus(dispatch, sourceDocument._id, targetLanguage, 'translating', undefined, mainSourceDocumentId)\n try {\n const doc = await getSanityAgentTranslation({\n sourceDocument: documentToTranslate,\n targetLanguage,\n languageFieldName,\n schemaId,\n client,\n translationPrompts,\n })\n return { document: doc }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n\n // Emit telemetry error for Sanity Agent failures\n emitTelemetryError(\n telemetryConfig,\n buildErrorBundle({\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n targetLanguage,\n sourceLanguage,\n translationMode: 'sanity-only',\n error: err,\n errorType: classifyError(err, 'sanity_agent'),\n startTime,\n originalDocument: sourceDocument,\n translationPrompts: {\n globalPrompt: translationPrompts.globalPrompt,\n localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,\n protectedPhrases: translationPrompts.protectedPhrases,\n },\n }),\n )\n\n throw error\n }\n }\n case 'openai-only': {\n if (!openaiConfig?.enabled || !openaiConfig?.apiKey) {\n throw new Error('OpenAI is not configured but openai-only mode was selected')\n }\n\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'translating_with_openai',\n undefined,\n mainSourceDocumentId,\n )\n const result = await getOpenAITranslation({\n dispatch,\n sourceDocument: documentToTranslate,\n targetLanguage,\n languageFieldName,\n translationPrompts,\n openaiConfig,\n previousError: errorContext && previousError ? previousError : undefined,\n errorContext,\n telemetryConfig,\n })\n\n if (!result) {\n return { document: null }\n }\n\n return {\n document: result.document,\n tokensUsed: result.tokensUsed,\n cost: result.cost,\n }\n }\n default: {\n throw new Error(`Invalid translation mode: ${translationMode}`)\n }\n }\n }\n\n // Initial translation attempt\n const translationResult = await performTranslation()\n translatedDoc = translationResult.document\n translationMetrics = {\n tokensUsed: translationResult.tokensUsed,\n cost: translationResult.cost,\n }\n\n if (!translatedDoc) {\n return null\n }\n\n // 4. Restore original structure with translated content (only if transforms were applied)\n let transformedResult: TranslatableDocument\n\n if (Object.keys(originalStructure).length > 0) {\n devLog.debug('Restoring field transforms after translation', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n })\n\n transformedResult = restoreTransforms({\n translatedDocument: translatedDoc,\n originalStructure,\n registry: globalTransformRegistry,\n schemaType: sourceDocument._type,\n }) as TranslatableDocument\n } else {\n // No transforms were applied, use translated document directly\n transformedResult = translatedDoc\n }\n\n // 5. Restore excluded fields to final document\n // NOTE: The language field should never be in excludedValues (filtered out above),\n // but we double-check here as a safety measure to ensure it's not restored\n const excludedValuesToRestore = { ...excludedValues }\n delete excludedValuesToRestore[languageFieldName]\n\n devLog.info('Before restoring excluded fields', {\n documentId: sourceDocument._id,\n excludedFieldCount: Object.keys(excludedValues).length,\n excludedValuesKeys: Object.keys(excludedValues),\n languageFieldExcluded: languageFieldName in excludedValues,\n transformedResultHasTitle: 'title' in transformedResult,\n transformedResultTitle: (transformedResult as any).title,\n })\n\n const finalDoc = restoreExcludedFields(transformedResult, excludedValuesToRestore)\n\n devLog.info('After restoring excluded fields', {\n documentId: sourceDocument._id,\n finalDocHasTitle: 'title' in finalDoc,\n finalDocTitle: (finalDoc as any).title,\n })\n\n // 6. Create new version of the target document with the translated content\n // Overwrite the existing translation as base if it exists, otherwise create a new id\n const targetDocumentPublishedId = existingTranslationId || uuid()\n const targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`\n const newDoc = {\n ...finalDoc,\n _id: targetDocumentVersionId,\n }\n\n // If replaceExisting is true, delete the existing version first\n if (replaceExisting) {\n const existingVersion = await client.getDocument(targetDocumentVersionId)\n if (existingVersion) {\n devLog.info('Replacing existing version in release', {\n documentId: sourceDocument._id,\n targetDocumentVersionId,\n releaseId,\n })\n await client.delete(targetDocumentVersionId)\n }\n }\n\n // 6. Create version with retry logic\n // If mutation fails after OpenAI translation, retry the entire translation\n try {\n await retryWithBackoff(\n async () => {\n await client.action({\n actionType: 'sanity.action.document.version.create',\n publishedId: targetDocumentPublishedId,\n document: newDoc,\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n documentId: sourceDocument._id,\n targetLanguage,\n operation: 'version.create',\n },\n },\n )\n } catch (error) {\n // If this is a mutation error after OpenAI translation, retry the entire translation\n if (translationMode === 'openai-only' && isSanityMutationError(error)) {\n devLog.warn('Mutation error after OpenAI translation, retrying full translation', {\n documentId: sourceDocument._id,\n targetLanguage,\n error: error instanceof Error ? error.message : String(error),\n })\n\n previousError = error instanceof Error ? error : new Error(String(error))\n const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`\n\n // Retry the entire translation with error context\n const retryResult = await performTranslation(errorContext)\n translatedDoc = retryResult.document\n\n if (!translatedDoc) {\n throw new Error('Translation retry failed: no document returned')\n }\n\n // Update metrics from retry\n translationMetrics = {\n tokensUsed: retryResult.tokensUsed,\n cost: retryResult.cost,\n }\n\n // Rebuild the document with the new translation\n let retryTransformedResult: TranslatableDocument\n\n if (Object.keys(originalStructure).length > 0) {\n retryTransformedResult = restoreTransforms({\n translatedDocument: translatedDoc,\n originalStructure,\n registry: globalTransformRegistry,\n schemaType: sourceDocument._type,\n }) as TranslatableDocument\n } else {\n retryTransformedResult = translatedDoc\n }\n\n const retryFinalDoc = restoreExcludedFields(retryTransformedResult, excludedValuesToRestore)\n const retryNewDoc = {\n ...retryFinalDoc,\n _id: targetDocumentVersionId,\n }\n\n // Try creating the version again with the retried translation\n await retryWithBackoff(\n async () => {\n await client.action({\n actionType: 'sanity.action.document.version.create',\n publishedId: targetDocumentPublishedId,\n document: retryNewDoc,\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n documentId: sourceDocument._id,\n targetLanguage,\n operation: 'version.create',\n isRetry: true,\n },\n },\n )\n\n return {\n document: retryNewDoc as TranslatableDocument,\n tokensUsed: translationMetrics.tokensUsed,\n cost: translationMetrics.cost,\n }\n }\n\n // For other errors or Sanity Agent mode, just throw\n throw error\n }\n\n return {\n document: newDoc as TranslatableDocument,\n tokensUsed: translationMetrics.tokensUsed,\n cost: translationMetrics.cost,\n }\n}\n","import type { Dispatch } from 'react'\nimport type { SanityClient } from 'sanity'\nimport type {\n PluginConfig,\n TelemetryConfig,\n TranslatableDocument,\n TranslationAction,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { getExistingTranslationId } from '../metadata/getExistingTranslationId'\nimport { getTranslationMetadata } from '../metadata/getTranslationMetadata'\nimport { updateTranslationMetadata } from '../metadata/updateTranslationMetadata'\nimport { setJobStatus } from '../state/setJobStatus'\nimport { createTimestamp, emitTelemetryEvent } from '../telemetry'\nimport { handleTranslation } from './handleTranslation'\n\ninterface TranslateDocumentParams {\n dispatch: Dispatch<TranslationAction>\n client: SanityClient\n sourceDocument: TranslatableDocument\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n targetLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n schemaId: string\n translationPrompts: FetchTranslationPromptsResult\n translationMode: TranslationMode\n openaiConfig?: PluginConfig['openai']\n releaseId: string\n excludeFields?: string[]\n /** Whether to replace an existing version in the release */\n replaceExisting?: boolean\n /** Main source document ID for tracking in active translations store */\n mainSourceDocumentId?: string\n /** Optional telemetry configuration */\n telemetryConfig?: TelemetryConfig\n}\n\nexport const translateDocument = async ({\n dispatch,\n client,\n sourceDocument,\n sourceLanguage,\n targetLanguage,\n languageFieldName,\n schemaId,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields,\n replaceExisting,\n mainSourceDocumentId,\n telemetryConfig,\n}: TranslateDocumentParams) => {\n const jobStartTime = Date.now()\n const logContext = {\n documentId: sourceDocument._id,\n targetLanguage,\n schemaId,\n translationMode,\n }\n\n devLog.group('translateDocument', logContext)\n devLog.time('translateDocument')\n\n setJobStatus(dispatch, sourceDocument._id, targetLanguage, 'preparing_translation', undefined, mainSourceDocumentId)\n\n try {\n const translationMetadata = await getTranslationMetadata({\n client,\n sourceDocument,\n sourceLanguage,\n })\n\n // Get the existing translation id if it exists\n const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage)\n\n // Delegate to handleTranslation which orchestrates the translation\n const translationResult = await handleTranslation({\n dispatch,\n client,\n schemaId,\n sourceDocument,\n targetLanguage,\n existingTranslationId,\n languageFieldName,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields,\n replaceExisting,\n mainSourceDocumentId,\n telemetryConfig,\n })\n\n const newDocument = translationResult?.document\n\n if (!newDocument) {\n devLog.error('Translation failed: no document returned', new Error('Translation returned null'), logContext)\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'error',\n {\n errorMessage: 'Translation failed',\n },\n mainSourceDocumentId,\n )\n devLog.timeEnd('translateDocument')\n devLog.groupEnd()\n return undefined\n }\n\n // Store translation history and update metadata\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'updating_metadata',\n {\n targetId: newDocument._id,\n },\n mainSourceDocumentId,\n )\n\n await updateTranslationMetadata({\n client,\n sourceDocument,\n sourceLanguage,\n newDocument,\n targetLanguage,\n translationMetadata,\n isNewTranslation: !existingTranslationId,\n })\n\n // Mark translation complete with target document reference for UI\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'completed',\n {\n targetId: newDocument._id,\n tokensUsed: translationResult?.tokensUsed,\n estimatedCost: translationResult?.cost,\n },\n mainSourceDocumentId,\n )\n devLog.info('Translation completed successfully', {\n ...logContext,\n newDocumentId: newDocument._id,\n })\n\n // Emit job completed telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.job_completed',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguage,\n translationMode,\n durationMs: Date.now() - jobStartTime,\n tokensUsed: translationResult?.tokensUsed,\n estimatedCost: translationResult?.cost,\n },\n })\n\n devLog.timeEnd('translateDocument')\n devLog.groupEnd()\n return newDocument._id\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n devLog.error('Translation failed', error instanceof Error ? error : new Error(String(error)), logContext)\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'error',\n {\n errorMessage,\n },\n mainSourceDocumentId,\n )\n devLog.timeEnd('translateDocument')\n devLog.groupEnd()\n // Don't re-throw - let the batch continue with other translations\n return undefined\n }\n}\n","import type { Dispatch } from 'react'\nimport type { SanityClient } from 'sanity'\nimport type {\n PluginConfig,\n ReleaseConfig,\n TelemetryConfig,\n TranslatableDocument,\n TranslationAction,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\nimport { Semaphore } from '../common/Semaphore'\nimport { fetchTranslationPrompts } from '../matching/fetchTranslationPrompts'\nimport type { AssembleReferencesResult } from '../references/assembleReferences'\nimport { localizeReferences } from '../references/localizeReferences'\nimport { updateBackwardReferences } from '../references/updateBackwardReferences'\nimport {\n cleanupActiveTranslation,\n registerActiveTranslation,\n updateActiveTranslationStatus,\n} from '../state/activeTranslations'\nimport { setupTranslationJobStatuses } from '../state/setupTranslationJobStatuses'\nimport { createTimestamp, emitTelemetryEvent } from '../telemetry'\nimport { translateDocument } from './translateDocument'\n\ninterface TranslateDocumentsParams {\n sourceDocument: TranslatableDocument\n targetLanguages: Array<string>\n client: SanityClient\n dispatch: Dispatch<TranslationAction>\n languageFieldName: string\n schemaId: string\n references: AssembleReferencesResult\n maxConcurrentTranslationJobs?: number\n maxReferenceDepth?: number\n translationMode: TranslationMode\n openaiConfig?: PluginConfig['openai']\n excludeFields?: string[]\n releaseConfig: ReleaseConfig\n telemetryConfig?: TelemetryConfig\n}\n\nconst translationSemaphore = new Semaphore(2)\n\nexport const translateDocumentSet = async ({\n client,\n sourceDocument,\n targetLanguages,\n dispatch,\n languageFieldName,\n schemaId,\n references = [],\n maxConcurrentTranslationJobs,\n translationMode,\n openaiConfig,\n excludeFields,\n releaseConfig,\n telemetryConfig,\n}: TranslateDocumentsParams) => {\n const batchStartTime = Date.now()\n const logContext = {\n documentId: sourceDocument._id,\n documentRev: sourceDocument._rev,\n targetLanguages,\n referencesCount: references.length,\n maxConcurrentJobs: maxConcurrentTranslationJobs,\n translationMode,\n releaseMode: releaseConfig.mode,\n }\n\n let releaseId: string | undefined\n let createdNewRelease = false\n\n try {\n const sourceLanguage = sourceDocument[languageFieldName] as string\n\n if (!sourceLanguage) {\n devLog.error('Source language not found')\n throw new Error('Source language not found')\n }\n\n // Emit batch started telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.batch_started',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguages,\n translationMode,\n referenceCount: references.length,\n releaseMode: releaseConfig.mode,\n },\n })\n\n // Configure semaphore with max concurrent jobs if specified\n if (maxConcurrentTranslationJobs) {\n translationSemaphore.updatePermits(maxConcurrentTranslationJobs)\n }\n\n dispatch({\n type: 'SET_OVERALL_STATUS',\n payload: 'translating',\n })\n\n // Handle release creation based on releaseConfig\n if (releaseConfig.mode === 'existing' && releaseConfig.existingReleaseId) {\n // Use existing release\n releaseId = releaseConfig.existingReleaseId\n devLog.info('Using existing Content Release', {\n ...logContext,\n releaseId,\n })\n } else {\n // Create a new release with the user-specified or default title\n const releaseTitle = releaseConfig.newReleaseTitle\n const releaseDescription = `AI translation batch created on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}`\n\n const createResult = await retryWithBackoff(\n async () => {\n return await client.releases.create({\n metadata: {\n title: releaseTitle,\n description: releaseDescription,\n releaseType: 'undecided',\n },\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'releases.create',\n releaseTitle,\n ...logContext,\n },\n },\n )\n releaseId = createResult.releaseId\n createdNewRelease = true\n\n devLog.info('Content Release created', {\n ...logContext,\n releaseId,\n releaseTitle,\n })\n }\n\n // Register this translation in the module-level store so dialog can reconnect\n // Must happen after releaseId is determined above\n registerActiveTranslation(sourceDocument._id, releaseId)\n updateActiveTranslationStatus(sourceDocument._id, 'translating')\n\n const sourceDocuments = [\n {\n _id: sourceDocument._id,\n _type: sourceDocument._type,\n // TODO: Extract document title more robustly across schema types\n title: (sourceDocument.name || sourceDocument.title) as string | undefined,\n },\n ...references,\n ]\n\n const sourceDocumentIds = sourceDocuments.map((doc) => doc._id)\n // Array of versioned _ids for target documents. These follow the format `versions.<version_id>.<id_after_published>`\n const versionedTargetDocumentIds: Array<string> = []\n\n // Assemble prompts for translation task style guides\n const translationPrompts = await fetchTranslationPrompts({\n client,\n sourceLanguage,\n targetLanguages,\n sourceDocument,\n })\n\n // Dispatch new job statuses for each document\n setupTranslationJobStatuses(dispatch, sourceDocuments, targetLanguages, sourceDocument._id)\n\n // Helper to check if a document/language pair should be skipped\n const shouldSkipConflict = (documentId: string, targetLanguage: string): boolean => {\n if (!releaseConfig.skipConflicts || releaseConfig.skipConflicts.length === 0) {\n return false\n }\n return releaseConfig.skipConflicts.some(\n (conflict) => conflict.sourceDocumentId === documentId && conflict.targetLanguage === targetLanguage,\n )\n }\n\n // Calculate skip count upfront (avoids mutable counter in async callbacks)\n const skippedCount = sourceDocumentIds.reduce((count, documentId) => {\n return count + targetLanguages.filter((lang) => shouldSkipConflict(documentId, lang)).length\n }, 0)\n\n // Create translation tasks for all documents and all languages\n const translationTasks = sourceDocumentIds.flatMap((documentId) =>\n targetLanguages.map(async (targetLanguage) => {\n // Check if this document/language pair should be skipped due to existing conflict\n if (shouldSkipConflict(documentId, targetLanguage)) {\n const docInfo = sourceDocuments.find((d) => d._id === documentId)\n\n devLog.info('Skipping conflicting translation', {\n documentId,\n targetLanguage,\n reason: 'User chose to skip conflicts',\n })\n\n // Mark this job as skipped\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload: {\n _id: documentId,\n _type: docInfo?._type,\n language: targetLanguage,\n status: 'skipped',\n title: docInfo?.title,\n },\n })\n return\n }\n\n try {\n await translationSemaphore.withPermit(async () => {\n const document = await client\n .withConfig({\n perspective: 'drafts',\n })\n .getDocument<TranslatableDocument>(documentId)\n\n if (!document) {\n devLog.error('❌ SOURCE DOCUMENT NOT FOUND', { documentId })\n }\n\n if (document && releaseId) {\n const versionedTargetDocumentId = await translateDocument({\n dispatch,\n client,\n sourceDocument: document,\n sourceLanguage,\n targetLanguage,\n languageFieldName,\n schemaId,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields,\n replaceExisting: releaseConfig.replaceExisting,\n // Pass main source document ID for active translations tracking\n mainSourceDocumentId: sourceDocument._id,\n telemetryConfig,\n })\n\n if (versionedTargetDocumentId) {\n versionedTargetDocumentIds.push(versionedTargetDocumentId)\n }\n }\n })\n } catch (error) {\n // Log the error but don't fail the entire batch\n devLog.error(\n 'Individual translation task failed',\n error instanceof Error ? error : new Error(String(error)),\n {\n ...logContext,\n failedDocumentId: documentId,\n failedTargetLanguage: targetLanguage,\n },\n )\n }\n }),\n )\n\n const results = await Promise.allSettled(translationTasks)\n\n // Log summary of results (exclude skipped from succeeded/failed counts)\n const succeeded = results.filter((r) => r.status === 'fulfilled').length - skippedCount\n const failed = results.filter((r) => r.status === 'rejected').length\n\n devLog.info('Translation tasks completed', {\n ...logContext,\n total: results.length,\n succeeded,\n failed,\n skipped: skippedCount,\n targetDocuments: versionedTargetDocumentIds.length,\n })\n\n // Emit batch completed telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.batch_completed',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguages,\n translationMode,\n succeeded,\n failed,\n skipped: skippedCount,\n durationMs: Date.now() - batchStartTime,\n },\n })\n\n dispatch({\n type: 'SET_OVERALL_STATUS',\n payload: 'linking_references',\n })\n updateActiveTranslationStatus(sourceDocument._id, 'linking_references')\n\n // Localize references in newly translated documents\n await Promise.all(\n versionedTargetDocumentIds.map(async (documentId) => {\n await localizeReferences({\n client,\n documentId,\n sourceLanguage,\n languageFieldName,\n })\n }),\n )\n\n // Update backward references in existing release documents\n // This handles the case where Doc A (translated earlier) references Doc B (translated now)\n // Only needed for existing releases - new releases have no pre-existing documents\n if (releaseConfig.mode === 'existing' && releaseId) {\n await updateBackwardReferences({\n client,\n releaseId,\n sourceDocumentIds,\n targetLanguages,\n sourceLanguage,\n languageFieldName,\n excludeDocumentIds: versionedTargetDocumentIds,\n })\n }\n\n // Determine overall status based on individual job statuses\n const hasAnyFailures = failed > 0\n\n if (hasAnyFailures && succeeded === 0) {\n // All translations failed\n devLog.error('All translation tasks failed', undefined, logContext)\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'error' })\n updateActiveTranslationStatus(sourceDocument._id, 'error')\n cleanupActiveTranslation(sourceDocument._id)\n } else if (hasAnyFailures) {\n // Partial success - some translations failed but others succeeded\n devLog.warn('Translation completed with some failures', {\n ...logContext,\n succeeded,\n failed,\n })\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'completed' })\n updateActiveTranslationStatus(sourceDocument._id, 'completed')\n cleanupActiveTranslation(sourceDocument._id)\n } else {\n // All translations succeeded\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'completed' })\n updateActiveTranslationStatus(sourceDocument._id, 'completed')\n cleanupActiveTranslation(sourceDocument._id)\n }\n\n devLog.timeEnd('translateDocumentSet')\n devLog.groupEnd()\n } catch (error) {\n // This catch block should only catch unexpected errors that aren't handled above\n devLog.error(\n 'translateDocumentSet failed with unexpected error',\n error instanceof Error ? error : new Error(String(error)),\n logContext,\n )\n\n // Clean up release if fatal error occurred (only if we created a new release)\n if (releaseId && createdNewRelease) {\n try {\n await client.releases.delete({ releaseId })\n devLog.warn('Deleted release due to fatal error', {\n ...logContext,\n releaseId,\n })\n } catch (cleanupError) {\n devLog.error(\n 'Failed to cleanup release',\n cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError)),\n logContext,\n )\n }\n }\n\n devLog.timeEnd('translateDocumentSet')\n devLog.groupEnd()\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'error' })\n updateActiveTranslationStatus(sourceDocument._id, 'error')\n cleanupActiveTranslation(sourceDocument._id)\n throw error\n }\n}\n","import { WarningOutlineIcon } from '@sanity/icons'\nimport { Box, Button, Card, Flex, Heading, Stack, Text } from '@sanity/ui'\nimport { type FC, useMemo } from 'react'\nimport type { ReleaseConflictInfo } from '../types'\n\ninterface ReleaseConflictWarningProps {\n conflicts: ReleaseConflictInfo[]\n /** Total number of document/language translation pairs requested */\n totalTranslations: number\n onCancel: () => void\n onReplace: () => void\n onSkipConflicts: () => void\n}\n\nexport const ReleaseConflictWarning: FC<ReleaseConflictWarningProps> = ({\n conflicts,\n totalTranslations,\n onCancel,\n onReplace,\n onSkipConflicts,\n}) => {\n // Count unique documents with conflicts\n const uniqueDocuments = useMemo(() => {\n const docIds = new Set(conflicts.map((c) => c.sourceDocumentId))\n return docIds.size\n }, [conflicts])\n\n const nonConflictingCount = totalTranslations - conflicts.length\n const hasNonConflicting = nonConflictingCount > 0\n\n const summaryText = useMemo(() => {\n if (conflicts.length === 1) {\n return '1 translation'\n }\n if (uniqueDocuments === 1) {\n return `${conflicts.length} translations`\n }\n return `${conflicts.length} translations across ${uniqueDocuments} documents`\n }, [conflicts.length, uniqueDocuments])\n\n return (\n <Card tone=\"caution\" padding={4} border radius={2}>\n <Stack space={4}>\n <Flex align=\"center\" gap={2}>\n <Text size={2}>\n <WarningOutlineIcon />\n </Text>\n <Text weight=\"semibold\">\n This release already contains translations for this content and target language(s).\n </Text>\n </Flex>\n\n <Text size={1}>The selected release already has {summaryText} that will be overwritten if you continue:</Text>\n\n <Card padding={3} border radius={2}>\n <Box style={{ maxHeight: '500px', overflowY: 'auto' }}>\n <Stack space={2}>\n {conflicts.map((conflict) => (\n <Text key={conflict.existingVersionId} size={1} muted>\n {conflict.sourceDocumentTitle || conflict.sourceDocumentId}\n {conflict.sourceDocumentType && ` (${conflict.sourceDocumentType})`} → {conflict.targetLanguage}\n </Text>\n ))}\n </Stack>\n </Box>\n </Card>\n\n <Stack space={4}>\n <Heading size={0}>Choose an action:</Heading>\n <Text size={1}>\n <strong>Cancel</strong> — Go back and select a different release, or create a new one.\n </Text>\n <Text size={1}>\n <strong>Replace All</strong> — Delete the existing translations in this release and create new ones.\n </Text>\n <Text size={1}>\n <strong>Skip Conflicts</strong> — Translate only the {nonConflictingCount} non-conflicting translation\n {nonConflictingCount !== 1 ? 's' : ''}, leaving existing ones unchanged.\n {!hasNonConflicting && ' (All translations have conflicts)'}\n </Text>\n </Stack>\n\n <Flex gap={2} justify=\"space-between\">\n <Button mode=\"ghost\" text=\"Cancel\" onClick={onCancel} />\n <Flex gap={2} justify=\"flex-end\">\n <Button mode=\"bleed\" tone=\"caution\" text=\"Replace All\" onClick={onReplace} />\n <Button\n mode=\"ghost\"\n tone=\"caution\"\n text=\"Skip Conflicts\"\n onClick={onSkipConflicts}\n disabled={!hasNonConflicting}\n />\n </Flex>\n </Flex>\n </Stack>\n </Card>\n )\n}\n","import { Button, Flex, Stack } from '@sanity/ui'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { ReleaseConflictInfo } from '../types'\nimport { checkReleaseConflicts } from '../utils/releases/checkReleaseConflicts'\nimport { translateDocumentSet } from '../utils/translation/translateDocumentSet'\nimport { ReleaseConflictWarning } from './ReleaseConflictWarning'\n\ninterface ActionButtonBarProps {\n setIsDialogOpen: (isOpen: boolean) => void\n}\n\n/**\n * Stores the release ID alongside conflicts to prevent race conditions.\n * When the user changes release selection during async conflict checking,\n * we need to use the originally checked release ID, not the current one.\n */\ninterface ConflictContext {\n releaseId: string\n conflicts: ReleaseConflictInfo[]\n}\n\nexport const ActionButtonBar = ({ setIsDialogOpen }: ActionButtonBarProps) => {\n const {\n document,\n config,\n overallStatus,\n dispatch,\n references,\n selectedReferenceIds,\n selectedTargetLanguages,\n translationMode,\n releaseConfig,\n } = useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n\n const [conflictContext, setConflictContext] = useState<ConflictContext | null>(null)\n const [isCheckingConflicts, setIsCheckingConflicts] = useState(false)\n\n // Filter references to only include selected ones\n const filteredReferences = references.filter((ref) => selectedReferenceIds.has(ref._id))\n\n // Calculate total number of document/language translation pairs\n // This is (1 source + selected references) * target languages\n const totalTranslations = useMemo(() => {\n const documentCount = 1 + filteredReferences.length\n return documentCount * selectedTargetLanguages.length\n }, [filteredReferences.length, selectedTargetLanguages.length])\n\n // Dismiss conflict warning if user switches to \"create new release\" mode\n // since new releases can't have conflicts\n useEffect(() => {\n if (releaseConfig.mode === 'new' && conflictContext) {\n setConflictContext(null)\n }\n }, [releaseConfig.mode, conflictContext])\n\n const startTranslation = useCallback(\n (replaceExisting: boolean, overrideReleaseId?: string, skipConflicts?: ReleaseConflictInfo[]) => {\n // Clear conflict context and start translation\n setConflictContext(null)\n\n translateDocumentSet({\n sourceDocument: document,\n targetLanguages: selectedTargetLanguages,\n client,\n dispatch,\n languageFieldName: config.languageFieldName,\n schemaId: config.schemaId,\n references: filteredReferences,\n maxConcurrentTranslationJobs: config.maxConcurrentTranslationJobs,\n maxReferenceDepth: config.maxReferenceDepth,\n translationMode,\n openaiConfig: config.openai,\n excludeFields: config.excludeFields,\n releaseConfig: {\n ...releaseConfig,\n replaceExisting,\n skipConflicts,\n // Use the override release ID if provided (from conflict context),\n // otherwise use the current releaseConfig value\n existingReleaseId: overrideReleaseId ?? releaseConfig.existingReleaseId,\n },\n telemetryConfig: config.telemetry,\n })\n },\n [\n document,\n selectedTargetLanguages,\n client,\n dispatch,\n config.languageFieldName,\n config.schemaId,\n config.maxConcurrentTranslationJobs,\n config.maxReferenceDepth,\n config.openai,\n config.excludeFields,\n config.telemetry,\n filteredReferences,\n translationMode,\n releaseConfig,\n ],\n )\n\n const handleTranslate = useCallback(async () => {\n // If using a new release, no conflicts possible - start translation directly\n if (releaseConfig.mode === 'new') {\n startTranslation(false)\n return\n }\n\n // Check for conflicts when using an existing release\n if (releaseConfig.mode === 'existing' && releaseConfig.existingReleaseId) {\n // Capture the release ID at the start of the async operation\n const checkedReleaseId = releaseConfig.existingReleaseId\n\n setIsCheckingConflicts(true)\n\n const foundConflicts = await checkReleaseConflicts({\n client,\n releaseId: checkedReleaseId,\n sourceDocument: document,\n targetLanguages: selectedTargetLanguages,\n languageFieldName: config.languageFieldName,\n references: filteredReferences,\n })\n\n setIsCheckingConflicts(false)\n\n if (foundConflicts.length > 0) {\n // Store both conflicts AND the release ID they were checked against\n // This prevents race conditions if user changes release selection during check\n setConflictContext({\n releaseId: checkedReleaseId,\n conflicts: foundConflicts,\n })\n return\n }\n }\n\n // No conflicts - start translation\n startTranslation(false)\n }, [\n releaseConfig,\n client,\n document,\n selectedTargetLanguages,\n config.languageFieldName,\n startTranslation,\n filteredReferences,\n ])\n\n const handleCancelConflict = useCallback(() => {\n setConflictContext(null)\n }, [])\n\n const handleReplaceConflict = useCallback(() => {\n // Use the stored release ID from when conflicts were checked,\n // not the current releaseConfig (which may have changed)\n if (conflictContext) {\n startTranslation(true, conflictContext.releaseId)\n }\n }, [startTranslation, conflictContext])\n\n const handleSkipConflicts = useCallback(() => {\n // Skip the conflicting document/language pairs and translate only non-conflicting ones\n if (conflictContext) {\n startTranslation(false, conflictContext.releaseId, conflictContext.conflicts)\n }\n }, [startTranslation, conflictContext])\n\n const handleSelectAll = useCallback(() => {\n dispatch({\n type: 'SET_SELECTED_TARGET_LANGUAGES',\n payload: config.supportedLanguages.map((lang) => lang.id).filter((id) => id !== document.language),\n })\n }, [dispatch, config.supportedLanguages, document.language])\n\n const handleSelectNone = useCallback(() => {\n dispatch({\n type: 'SET_SELECTED_TARGET_LANGUAGES',\n payload: [],\n })\n }, [dispatch])\n\n // Show conflict warning if conflicts were detected\n if (conflictContext) {\n return (\n <Stack space={4}>\n <ReleaseConflictWarning\n conflicts={conflictContext.conflicts}\n totalTranslations={totalTranslations}\n onCancel={handleCancelConflict}\n onReplace={handleReplaceConflict}\n onSkipConflicts={handleSkipConflicts}\n />\n </Stack>\n )\n }\n\n return (\n <Flex justify=\"space-between\" gap={2}>\n <Flex justify=\"flex-start\" gap={2}>\n <Button mode=\"ghost\" size={2} onClick={handleSelectAll}>\n Select All\n </Button>\n <Button mode=\"ghost\" size={2} onClick={handleSelectNone}>\n Select None\n </Button>\n </Flex>\n <Flex justify=\"flex-end\" gap={2}>\n <Button onClick={() => setIsDialogOpen(false)} mode=\"ghost\" size={2}>\n Cancel\n </Button>\n <Button\n mode=\"ghost\"\n tone=\"positive\"\n size={2}\n onClick={handleTranslate}\n disabled={\n selectedTargetLanguages.length === 0 ||\n !['idle', 'completed'].includes(overallStatus) ||\n (releaseConfig.mode === 'existing' && !releaseConfig.existingReleaseId) ||\n isCheckingConflicts\n }\n >\n {isCheckingConflicts ? 'Checking...' : 'Translate'}\n </Button>\n </Flex>\n </Flex>\n )\n}\n","import { useEffect } from 'react'\nimport { useListeningQuery } from 'sanity-plugin-utils'\nimport type { TranslationMetadataDocument } from '../types'\nimport { undraftId } from '../utils/document/undraftId'\nimport { TRANSLATION_METADATA_TYPE } from '../utils/metadata/constants'\nimport { useTranslationContext } from './useTranslationContext'\n\nconst TRANSLATION_METADATA_QUERY = '*[_type == $translationMetadataType && references($documentId)][0]'\n\ninterface UseTranslationMetadataProps {\n documentId: string\n}\n\nexport const useTranslationMetadata = ({ documentId }: UseTranslationMetadataProps) => {\n const { dispatch } = useTranslationContext()\n\n const { data, loading, error } = useListeningQuery<TranslationMetadataDocument | null>(TRANSLATION_METADATA_QUERY, {\n params: {\n documentId: undraftId(documentId),\n translationMetadataType: TRANSLATION_METADATA_TYPE,\n },\n initialValue: null,\n })\n\n useEffect(() => {\n if (loading) {\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'fetching_metadata' })\n return\n }\n\n if (error) {\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'error' })\n return\n }\n\n if (data) {\n dispatch({ type: 'SET_TRANSLATION_METADATA', payload: data as TranslationMetadataDocument })\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'idle' })\n return\n }\n\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'idle' })\n }, [data, loading, error, dispatch])\n\n return {\n data,\n loading,\n error,\n }\n}\n","import { Button, Checkbox, Flex, Inline, Spinner, Text } from '@sanity/ui'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { PluginConfig } from '../types'\n\ninterface LanguageCheckboxProps {\n lang: PluginConfig['supportedLanguages'][number]\n isLoading: boolean\n}\n\nexport const LanguageCheckbox = ({ lang, isLoading }: LanguageCheckboxProps) => {\n const { document, dispatch, selectedTargetLanguages } = useTranslationContext()\n const isDisabled = lang.id === document.language\n const isChecked = selectedTargetLanguages.includes(lang.id)\n\n const handleChange = () => {\n if (isDisabled) return\n\n const updatedLanguages = isChecked\n ? selectedTargetLanguages.filter((langId) => langId !== lang.id)\n : [...selectedTargetLanguages, lang.id]\n\n dispatch({\n type: 'SET_SELECTED_TARGET_LANGUAGES',\n payload: updatedLanguages,\n })\n }\n\n if (isLoading) {\n return (\n <Button type=\"button\" mode=\"bleed\" size={2} onClick={handleChange} disabled muted>\n <Flex align=\"center\" justify=\"center\" paddingY={3}>\n <Spinner size={1} />\n </Flex>\n </Button>\n )\n }\n\n return (\n <Button\n type=\"button\"\n mode={isChecked ? 'ghost' : 'bleed'}\n size={2}\n onClick={handleChange}\n disabled={isDisabled}\n tone={isChecked ? 'primary' : 'default'}\n muted\n >\n <Flex align=\"center\" gap={2} style={{ cursor: 'pointer' }}>\n <Inline space={3}>\n <Checkbox checked={isChecked} onChange={handleChange} label={lang.title} disabled={isDisabled} />\n <Flex direction=\"column\" gap={3}>\n <Text>{lang.title}</Text>\n {isDisabled && (\n <Text size={1} muted>\n The current language\n </Text>\n )}\n </Flex>\n </Inline>\n </Flex>\n </Button>\n )\n}\n","import { Card, Grid } from '@sanity/ui'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport { useTranslationMetadata } from '../hooks/useTranslationMetadata'\nimport { LanguageCheckbox } from './LanguageCheckbox'\n\nexport const LanguageSelectionPanel = () => {\n // Get the configuration from the context\n const { document, config } = useTranslationContext()\n\n const { loading } = useTranslationMetadata({\n documentId: document._id,\n })\n\n return (\n <Card padding={4} muted border>\n <Grid columns={[1, 2]} gap={3}>\n {config?.supportedLanguages.map((lang) => (\n <LanguageCheckbox key={lang.id} lang={lang} isLoading={loading} />\n ))}\n </Grid>\n </Card>\n )\n}\n","import type { SanityClient } from 'sanity'\nimport type { PluginConfig } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { localizeReferences } from './localizeReferences'\n\ninterface ReconcileReleaseReferencesParams {\n client: SanityClient\n releaseId: string\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n /** Optional callback for progress updates */\n onProgress?: (current: number, total: number) => void\n}\n\ninterface ReconcileReleaseReferencesResult {\n /** Total documents processed */\n total: number\n /** Number of documents that had references updated */\n updated: number\n /** Number of documents that failed to process */\n errors: number\n}\n\n/**\n * Reconciles all references in a Content Release.\n *\n * This function runs `localizeReferences` on every document in the release,\n * ensuring all references point to translated versions when available.\n *\n * Use this for:\n * - Manual cleanup of stale references\n * - Edge cases where automatic backward reference updates missed something\n * - Releases with documents added through other means (manual, API, etc.)\n *\n * @example\n * ```typescript\n * const result = await reconcileReleaseReferences({\n * client,\n * releaseId: 'r123',\n * sourceLanguage: 'en',\n * languageFieldName: 'language',\n * onProgress: (current, total) => {\n * console.log(`Processing ${current}/${total}`)\n * },\n * })\n * console.log(`Updated ${result.updated} of ${result.total} documents`)\n * ```\n */\nexport const reconcileReleaseReferences = async ({\n client,\n releaseId,\n sourceLanguage,\n languageFieldName,\n onProgress,\n}: ReconcileReleaseReferencesParams): Promise<ReconcileReleaseReferencesResult> => {\n devLog.info('Starting release reference reconciliation', {\n releaseId,\n sourceLanguage,\n component: 'reconcileReleaseReferences',\n })\n\n try {\n // Fetch all versioned documents in the release\n const docsInRelease = await client.fetch<Array<{ _id: string; language: string }>>(\n `*[_id match \"versions.\" + $releaseId + \".*\"] {\n _id,\n \"language\": @[$languageFieldName]\n }`,\n { releaseId, languageFieldName },\n )\n\n if (docsInRelease.length === 0) {\n devLog.info('No documents found in release', {\n releaseId,\n component: 'reconcileReleaseReferences',\n })\n return { total: 0, updated: 0, errors: 0 }\n }\n\n devLog.info('Found documents in release', {\n releaseId,\n count: docsInRelease.length,\n component: 'reconcileReleaseReferences',\n })\n\n let updated = 0\n let errors = 0\n\n // Process each document\n for (let i = 0; i < docsInRelease.length; i++) {\n const doc = docsInRelease[i]\n\n // Report progress\n if (onProgress) {\n onProgress(i + 1, docsInRelease.length)\n }\n\n try {\n // Skip documents that don't have a language (shouldn't happen, but be safe)\n if (!doc.language) {\n devLog.warn('Document missing language field, skipping', {\n documentId: doc._id,\n component: 'reconcileReleaseReferences',\n })\n continue\n }\n\n // Run localizeReferences on the document\n await localizeReferences({\n client,\n documentId: doc._id,\n sourceLanguage,\n languageFieldName,\n })\n\n updated++\n } catch (error) {\n errors++\n devLog.error(\n 'Failed to reconcile references for document',\n error instanceof Error ? error : new Error(String(error)),\n {\n documentId: doc._id,\n component: 'reconcileReleaseReferences',\n },\n )\n }\n }\n\n const result = {\n total: docsInRelease.length,\n updated,\n errors,\n }\n\n devLog.info('Release reference reconciliation complete', {\n releaseId,\n ...result,\n component: 'reconcileReleaseReferences',\n })\n\n return result\n } catch (error) {\n devLog.error('Failed to reconcile release references', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n component: 'reconcileReleaseReferences',\n })\n throw error\n }\n}\n","import { SyncIcon } from '@sanity/icons'\nimport { Button, Card, Flex, Spinner, Stack, Text } from '@sanity/ui'\nimport { type FC, useCallback, useEffect, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { ReconciliationStatus } from '../types'\nimport { devLog } from '../utils/common/devLog'\nimport { reconcileReleaseReferences } from '../utils/references/reconcileReleaseReferences'\n\ninterface ReconcileReferencesButtonProps {\n /** Source language for reference localization */\n sourceLanguage: string\n}\n\n/**\n * Button component that triggers reference reconciliation for an existing release.\n *\n * This component:\n * - Only appears when an existing release is selected\n * - Shows the count of documents in the release\n * - Displays progress during reconciliation\n * - Is disabled during active translation\n */\nexport const ReconcileReferencesButton: FC<ReconcileReferencesButtonProps> = ({ sourceLanguage }) => {\n const { releaseConfig, overallStatus, config } = useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n\n const [documentCount, setDocumentCount] = useState<number | null>(null)\n const [isLoadingCount, setIsLoadingCount] = useState(false)\n\n // Local state for reconciliation (not shared across releases)\n const [reconcilingStatus, setReconcilingStatus] = useState<ReconciliationStatus>('idle')\n const [reconciliationProgress, setReconciliationProgress] = useState<{ current: number; total: number } | undefined>(\n undefined,\n )\n\n const releaseId = releaseConfig.existingReleaseId\n const languageFieldName = config?.languageFieldName ?? 'language'\n\n // Fetch document count when release is selected\n useEffect(() => {\n if (!releaseId || releaseConfig.mode !== 'existing') {\n setDocumentCount(null)\n return\n }\n\n const fetchCount = async () => {\n setIsLoadingCount(true)\n try {\n const count = await client.fetch<number>(`count(*[_id match \"versions.\" + $releaseId + \".*\"])`, { releaseId })\n setDocumentCount(count)\n devLog.debug('Fetched release document count', {\n releaseId,\n count,\n component: 'ReconcileReferencesButton',\n })\n } catch (error) {\n devLog.error(\n 'Failed to fetch release document count',\n error instanceof Error ? error : new Error(String(error)),\n {\n releaseId,\n component: 'ReconcileReferencesButton',\n },\n )\n setDocumentCount(null)\n } finally {\n setIsLoadingCount(false)\n }\n }\n\n fetchCount()\n }, [releaseId, releaseConfig.mode, client])\n\n const handleReconcile = useCallback(async () => {\n if (!releaseId) return\n\n devLog.info('Starting reference reconciliation', {\n releaseId,\n sourceLanguage,\n component: 'ReconcileReferencesButton',\n })\n\n setReconcilingStatus('reconciling')\n\n try {\n const result = await reconcileReleaseReferences({\n client,\n releaseId,\n sourceLanguage,\n languageFieldName,\n onProgress: (current, total) => {\n setReconciliationProgress({ current, total })\n },\n })\n\n devLog.info('Reference reconciliation completed', {\n releaseId,\n ...result,\n component: 'ReconcileReferencesButton',\n })\n\n setReconcilingStatus('completed')\n\n // Reset to idle after a short delay to show completion\n setTimeout(() => {\n setReconcilingStatus('idle')\n setReconciliationProgress(undefined)\n }, 2000)\n } catch (error) {\n devLog.error('Reference reconciliation failed', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n component: 'ReconcileReferencesButton',\n })\n setReconcilingStatus('error')\n setReconciliationProgress(undefined)\n }\n }, [releaseId, sourceLanguage, languageFieldName, client])\n\n // Only show when an existing release is selected\n if (releaseConfig.mode !== 'existing' || !releaseId) {\n return null\n }\n\n const isTranslating = overallStatus === 'translating' || overallStatus === 'linking_references'\n const isReconciling = reconcilingStatus === 'reconciling'\n const isDisabled = isTranslating || isReconciling || isLoadingCount || documentCount === 0\n\n return (\n <Card padding={3} border radius={2} tone={reconcilingStatus === 'error' ? 'critical' : 'default'}>\n <Stack space={3}>\n <Flex align=\"center\" justify=\"space-between\" gap={3}>\n <Stack space={2}>\n <Text size={1} weight=\"medium\">\n Reconcile References\n </Text>\n <Text size={1} muted>\n {isLoadingCount ? (\n 'Loading...'\n ) : documentCount === null ? (\n 'Unable to load document count'\n ) : documentCount === 0 ? (\n 'No documents in release'\n ) : (\n <>\n {documentCount} document{documentCount !== 1 ? 's' : ''} in release\n </>\n )}\n </Text>\n </Stack>\n\n <Button\n icon={isReconciling ? Spinner : SyncIcon}\n text={\n reconcilingStatus === 'completed'\n ? 'Done!'\n : reconcilingStatus === 'error'\n ? 'Retry'\n : isReconciling\n ? 'Reconciling...'\n : 'Reconcile'\n }\n tone={\n reconcilingStatus === 'completed' ? 'positive' : reconcilingStatus === 'error' ? 'critical' : 'default'\n }\n mode=\"ghost\"\n onClick={handleReconcile}\n disabled={isDisabled && reconcilingStatus !== 'error'}\n />\n </Flex>\n\n {/* Progress indicator */}\n {isReconciling && reconciliationProgress && (\n <Flex align=\"center\" gap={2}>\n <Text size={1} muted>\n Processing {reconciliationProgress.current} of {reconciliationProgress.total}...\n </Text>\n </Flex>\n )}\n\n {/* Error state */}\n {reconcilingStatus === 'error' && (\n <Text size={1} muted>\n Reconciliation failed. Click Retry to try again.\n </Text>\n )}\n\n {/* Help text */}\n {reconcilingStatus === 'idle' && documentCount !== null && documentCount > 0 && (\n <Text size={1} muted>\n Updates all document references to point to their translated versions in this release.\n </Text>\n )}\n </Stack>\n </Card>\n )\n}\n","import { SearchIcon, SyncIcon } from '@sanity/icons'\nimport { Box, Button, Card, Flex, Radio, Spinner, Stack, Text, TextInput } from '@sanity/ui'\nimport { type FC, useCallback, useEffect, useMemo, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { ReleaseSelectionMode } from '../types'\nimport { devLog } from '../utils/common/devLog'\nimport { ReconcileReferencesButton } from './ReconcileReferencesButton'\n\ninterface SanityRelease {\n _id: string\n _createdAt: string\n metadata: {\n title: string\n description?: string\n releaseType: 'asap' | 'scheduled' | 'undecided'\n }\n state: 'active' | 'scheduled' | 'publishing' | 'published' | 'archived'\n}\n\nexport const ReleaseSelectionPanel: FC = () => {\n const { dispatch, releaseConfig, document, config } = useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n\n const languageFieldName = config?.languageFieldName ?? 'language'\n const sourceLanguage = document?.[languageFieldName] as string | undefined\n\n const [releases, setReleases] = useState<SanityRelease[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [searchQuery, setSearchQuery] = useState('')\n const [showReconcileUI, setShowReconcileUI] = useState(false)\n\n // Fetch active releases\n useEffect(() => {\n const fetchReleases = async () => {\n setIsLoading(true)\n setError(null)\n\n try {\n const result = await client.fetch<SanityRelease[]>(\n `releases::all()[state == \"active\"] | order(_createdAt desc)`,\n )\n setReleases(result)\n devLog.debug('Fetched active releases', {\n count: result.length,\n component: 'ReleaseSelectionPanel',\n })\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Failed to fetch releases'\n setError(errorMessage)\n devLog.error('Failed to fetch releases', err instanceof Error ? err : new Error(errorMessage), {\n component: 'ReleaseSelectionPanel',\n })\n } finally {\n setIsLoading(false)\n }\n }\n\n fetchReleases()\n }, [client])\n\n const handleModeChange = useCallback(\n (mode: ReleaseSelectionMode) => {\n dispatch({\n type: 'SET_RELEASE_CONFIG',\n payload: { mode },\n })\n },\n [dispatch],\n )\n\n const handleTitleChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n dispatch({\n type: 'SET_RELEASE_CONFIG',\n payload: { newReleaseTitle: event.target.value },\n })\n },\n [dispatch],\n )\n\n const handleSelectRelease = useCallback(\n (releaseId: string) => {\n dispatch({\n type: 'SET_RELEASE_CONFIG',\n payload: { existingReleaseId: releaseId },\n })\n },\n [dispatch],\n )\n\n const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(event.target.value)\n }, [])\n\n const filteredReleases = useMemo(() => {\n if (!searchQuery.trim()) {\n return releases\n }\n const query = searchQuery.toLowerCase()\n return releases.filter((release) => release.metadata.title.toLowerCase().includes(query))\n }, [releases, searchQuery])\n\n const formatDate = (dateString: string) => {\n const date = new Date(dateString)\n return date.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n }\n\n return (\n <Card padding={4} border>\n <Stack space={4}>\n {/* Radio Options */}\n <Stack space={3}>\n <Flex align=\"center\" gap={3} as=\"label\" style={{ cursor: 'pointer' }} onClick={() => handleModeChange('new')}>\n <Radio checked={releaseConfig.mode === 'new'} readOnly />\n <Text weight=\"medium\">Create new release</Text>\n </Flex>\n\n <Flex\n align=\"center\"\n gap={3}\n as=\"label\"\n style={{ cursor: 'pointer' }}\n onClick={() => handleModeChange('existing')}\n >\n <Radio checked={releaseConfig.mode === 'existing'} readOnly />\n <Text weight=\"medium\">Add to existing release</Text>\n </Flex>\n </Stack>\n\n {/* New Release Title Input */}\n {releaseConfig.mode === 'new' && (\n <TextInput\n value={releaseConfig.newReleaseTitle}\n onChange={handleTitleChange}\n placeholder=\"Release title...\"\n />\n )}\n\n {/* Existing Release Selector */}\n {releaseConfig.mode === 'existing' && (\n <Stack space={3}>\n {isLoading && (\n <Flex align=\"center\" gap={2}>\n <Spinner muted />\n <Text size={1} muted>\n Loading...\n </Text>\n </Flex>\n )}\n\n {error && (\n <Text size={1} muted>\n {error}\n </Text>\n )}\n\n {!isLoading && !error && releases.length === 0 && (\n <Text size={1} muted>\n No active releases found.\n </Text>\n )}\n\n {!isLoading && !error && releases.length > 0 && (\n <>\n <TextInput\n icon={SearchIcon}\n value={searchQuery}\n onChange={handleSearchChange}\n placeholder=\"Search releases...\"\n />\n <Box style={{ maxHeight: '200px', overflowY: 'auto' }}>\n <Stack space={1}>\n {filteredReleases.length === 0 ? (\n <Text size={1} muted>\n No releases match your search.\n </Text>\n ) : (\n filteredReleases.map((release) => {\n const releaseName = release._id.replace('_.releases.', '')\n const isSelected = releaseConfig.existingReleaseId === releaseName\n\n return (\n <Card\n key={release._id}\n padding={3}\n radius={2}\n tone={isSelected ? 'primary' : 'default'}\n style={{ cursor: 'pointer' }}\n onClick={() => {\n handleSelectRelease(releaseName)\n // Reset reconcile UI when selecting a different release\n if (!isSelected) {\n setShowReconcileUI(false)\n }\n }}\n border\n muted={isSelected}\n >\n <Flex direction=\"column\" gap={3}>\n <Flex justify=\"space-between\" align=\"center\">\n <Flex direction=\"column\" gap={2}>\n <Text size={1}>{release.metadata.title}</Text>\n <Text size={1} muted>\n {formatDate(release._createdAt)}\n </Text>\n </Flex>\n {isSelected && sourceLanguage && (\n <Button\n icon={SyncIcon}\n text={showReconcileUI ? 'Hide' : 'Reconcile References'}\n mode=\"bleed\"\n tone=\"primary\"\n fontSize={0}\n padding={2}\n onClick={(e) => {\n e.stopPropagation()\n setShowReconcileUI(!showReconcileUI)\n }}\n />\n )}\n </Flex>\n {/* Reconcile References UI - shown when user clicks the Reconcile button */}\n {isSelected && sourceLanguage && showReconcileUI && (\n <ReconcileReferencesButton sourceLanguage={sourceLanguage} />\n )}\n </Flex>\n </Card>\n )\n })\n )}\n </Stack>\n </Box>\n </>\n )}\n </Stack>\n )}\n </Stack>\n </Card>\n )\n}\n","import { Badge, Button, Card, Inline, Radio, Stack, Text } from '@sanity/ui'\nimport type { FC } from 'react'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { TranslationMode } from '../types'\n\nexport const TranslationModePanel: FC = () => {\n const { config, translationMode, openaiValidated, dispatch } = useTranslationContext()\n\n // Panel only relevant when OpenAI integration is configured\n if (!config?.openai?.enabled || !config?.openai?.apiKey) {\n return null\n }\n\n // Show validation status\n if (!openaiValidated) {\n return (\n <Card tone=\"caution\" padding={4} border>\n <Stack space={2}>\n <Text weight=\"semibold\">OpenAI Configuration</Text>\n <Text size={1} muted>\n Validating API key...\n </Text>\n </Stack>\n </Card>\n )\n }\n\n const handleModeChange = (mode: TranslationMode) => {\n dispatch({\n type: 'SET_TRANSLATION_MODE',\n payload: mode,\n })\n }\n\n return (\n <Card padding={4} border>\n <Stack space={3}>\n <Inline space={2}>\n <Text weight=\"semibold\">Translation Mode</Text>\n <Badge tone=\"positive\">OpenAI Available</Badge>\n </Inline>\n\n <Stack space={2}>\n {/* Sanity Only */}\n <Button\n type=\"button\"\n mode={translationMode === 'sanity-only' ? 'ghost' : 'bleed'}\n tone={translationMode === 'sanity-only' ? 'primary' : 'default'}\n onClick={() => handleModeChange('sanity-only')}\n >\n <Inline space={2}>\n <Radio checked={translationMode === 'sanity-only'} readOnly />\n <Stack space={2}>\n <Text>Sanity Agent Only</Text>\n <Text size={1} muted>\n Fast, reliable, and included with Sanity.\n </Text>\n </Stack>\n </Inline>\n </Button>\n\n {/* OpenAI Only */}\n <Button\n type=\"button\"\n mode={translationMode === 'openai-only' ? 'ghost' : 'bleed'}\n tone={translationMode === 'openai-only' ? 'primary' : 'default'}\n onClick={() => handleModeChange('openai-only')}\n >\n <Inline space={2}>\n <Radio checked={translationMode === 'openai-only'} readOnly />\n <Stack space={2}>\n <Inline space={2}>\n <Text>OpenAI Only</Text>\n <Badge tone=\"caution\">Beta</Badge>\n </Inline>\n <Text size={1} muted>\n Full style guide capacity with 1M token context window.\n </Text>\n </Stack>\n </Inline>\n </Button>\n </Stack>\n\n {/* Cost Warning */}\n {translationMode !== 'sanity-only' && (\n <Card tone=\"caution\" padding={3} border>\n <Text size={1}>\n 💰 <strong>OpenAI costs apply:</strong> You'll be charged for API usage based on tokens processed.\n </Text>\n </Card>\n )}\n </Stack>\n </Card>\n )\n}\n","import type { SanityClient, SanityDocument } from 'sanity'\nimport type { TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { findKeyValues, type RecursiveSearchable } from '../common/findKeyValues/findKeyValues'\n\ninterface AssembleReferencesParams {\n client: SanityClient\n sourceDocument: SanityDocument\n maxReferenceDepth: number\n translatableSchemaTypes?: Array<string>\n}\n\nexport type AssembleReferencesResult = Array<{\n _id: SanityDocument['_id']\n _type: SanityDocument['_type']\n title?: string\n}>\n\n// Sanity documents are objects of arbitrary shape, which can\n// contain references to other documents. References are always\n// represented as a `_ref` key with a value of the referenced document's\n// `_id`. This function takes a Sanity document, appends all `_ref` keys\n// within it to an array, then fetches the references documents and repeats\n// the process until the maximum reference depth is reached.\n\nexport const assembleReferences = async ({\n client,\n sourceDocument,\n maxReferenceDepth,\n translatableSchemaTypes = [],\n}: AssembleReferencesParams): Promise<AssembleReferencesResult> => {\n // Validate required parameters before processing\n if (!client) {\n throw new Error('assembleReferences: client is required')\n }\n\n if (!sourceDocument || !sourceDocument._id) {\n throw new Error('assembleReferences: sourceDocument with _id is required')\n }\n\n if (typeof maxReferenceDepth !== 'number' || maxReferenceDepth < 0) {\n throw new Error('assembleReferences: maxReferenceDepth must be a non-negative number')\n }\n\n const references = new Set<string>()\n const documentCache = new Map<string, TranslatableDocument | null>()\n const errors: Array<{ refId: string; error: Error }> = []\n\n let currentDepthDocuments = [sourceDocument]\n\n for (let depth = 0; depth < maxReferenceDepth && currentDepthDocuments.length > 0; depth++) {\n // Extract all _ref IDs from current depth to process recursively\n const allRefIds = new Set<string>()\n\n for (const document of currentDepthDocuments) {\n try {\n const refResults = findKeyValues<string>(document as RecursiveSearchable, '_ref')\n for (const result of refResults) {\n const refId = result.value\n if (typeof refId === 'string' && refId.length > 0 && !references.has(refId) && !documentCache.has(refId)) {\n allRefIds.add(refId)\n }\n }\n } catch (error) {\n devLog.error(\n 'Error finding references in document',\n error instanceof Error ? error : new Error(String(error)),\n {\n documentId: document._id,\n component: 'assembleReferences',\n },\n )\n }\n }\n\n if (allRefIds.size === 0) {\n break\n }\n\n // Batch fetch all references for this depth level\n const refIdArray = Array.from(allRefIds)\n try {\n const batchResults = await client.fetch<TranslatableDocument[]>('*[_id in $ids]', { ids: refIdArray })\n\n // Index documents by ID for O(1) reference lookup\n const fetchedDocuments = new Map<string, TranslatableDocument>()\n for (const doc of batchResults) {\n fetchedDocuments.set(doc._id, doc)\n }\n\n // Check each reference: if translatable and not cached, include it\n const nextDepthDocuments: TranslatableDocument[] = []\n\n for (const refId of refIdArray) {\n const fetchedDoc = fetchedDocuments.get(refId)\n\n if (fetchedDoc) {\n // Skip non-translatable documents\n if (translatableSchemaTypes.length > 0 && !translatableSchemaTypes.includes(fetchedDoc._type)) {\n documentCache.set(refId, null)\n continue\n }\n\n documentCache.set(refId, fetchedDoc)\n references.add(refId)\n nextDepthDocuments.push(fetchedDoc)\n } else {\n // Document not found\n documentCache.set(refId, null)\n devLog.warn(`Document with _id \"${refId}\" not found`, {\n refId,\n component: 'assembleReferences',\n })\n }\n }\n\n currentDepthDocuments = nextDepthDocuments\n } catch (fetchError) {\n // Log the error but continue processing\n const error = fetchError instanceof Error ? fetchError : new Error(String(fetchError))\n errors.push({ refId: `batch(${refIdArray.join(',')})`, error })\n devLog.error('Failed to batch fetch documents', error, {\n refIdCount: refIdArray.length,\n depth,\n component: 'assembleReferences',\n })\n break\n }\n }\n\n // Log summary of errors if any occurred\n if (errors.length > 0) {\n devLog.warn(`Encountered ${errors.length} error(s) while fetching references`, {\n errorCount: errors.length,\n errors: errors.map((e) => ({ refId: e.refId, message: e.error.message })),\n component: 'assembleReferences',\n })\n }\n\n // Convert the cached documents to the expected result format\n const result: AssembleReferencesResult = []\n for (const refId of references) {\n const cachedDoc = documentCache.get(refId)\n if (cachedDoc) {\n result.push({\n _id: cachedDoc._id,\n _type: cachedDoc._type,\n title: (cachedDoc.name || cachedDoc.title) as string | undefined,\n })\n }\n }\n\n return result\n}\n","import { Badge, Box, Button, Card, Checkbox, Flex, Select, Stack, Text } from '@sanity/ui'\nimport { useMemo, useState } from 'react'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\n\nexport const ReferenceSelectionPanel = () => {\n const { references, selectedReferenceIds, dispatch } = useTranslationContext()\n const [typeFilter, setTypeFilter] = useState<string>('all')\n\n // Get unique document types from references\n const documentTypes = useMemo(() => {\n const types = new Set(references.map((ref) => ref._type))\n return Array.from(types).sort()\n }, [references])\n\n // Filter references by selected type\n const filteredReferences = useMemo(() => {\n if (typeFilter === 'all') {\n return references\n }\n return references.filter((ref) => ref._type === typeFilter)\n }, [references, typeFilter])\n\n // Count selected references in filtered list\n const selectedCount = useMemo(() => {\n return filteredReferences.filter((ref) => selectedReferenceIds.has(ref._id)).length\n }, [filteredReferences, selectedReferenceIds])\n\n const handleToggleReference = (referenceId: string) => {\n const newSelection = new Set(selectedReferenceIds)\n if (newSelection.has(referenceId)) {\n newSelection.delete(referenceId)\n } else {\n newSelection.add(referenceId)\n }\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n\n const handleSelectAll = () => {\n // Add all filtered references to the existing selection\n const newSelection = new Set(selectedReferenceIds)\n filteredReferences.forEach((ref) => {\n newSelection.add(ref._id)\n })\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n\n const handleSelectNone = () => {\n // Remove only filtered references from the selection\n const newSelection = new Set(selectedReferenceIds)\n filteredReferences.forEach((ref) => {\n newSelection.delete(ref._id)\n })\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n\n if (references.length === 0) {\n return null\n }\n\n return (\n <Card padding={4} border muted>\n <Stack space={3}>\n {/* Header with filter and selection controls */}\n <Flex justify=\"space-between\" align=\"center\" gap={2}>\n <Flex gap={2} align=\"center\">\n <Text size={1} weight=\"semibold\">\n Select References\n </Text>\n <Text size={1} muted>\n ({selectedCount} of {filteredReferences.length} selected)\n </Text>\n </Flex>\n <Flex gap={2}>\n <Button mode=\"ghost\" size={1} onClick={handleSelectAll} text=\"Select All\" />\n <Button mode=\"ghost\" size={1} onClick={handleSelectNone} text=\"Select None\" />\n </Flex>\n </Flex>\n\n {/* Document type filter */}\n {documentTypes.length > 1 && (\n <Flex align=\"center\" gap={2}>\n <Text size={1} muted>\n Filter by type:\n </Text>\n <Select\n value={typeFilter}\n onChange={(event: React.ChangeEvent<HTMLSelectElement>) => setTypeFilter(event.target.value)}\n style={{ minWidth: '150px' }}\n >\n <option value=\"all\">All types ({references.length})</option>\n {documentTypes.map((type) => {\n const count = references.filter((ref) => ref._type === type).length\n return (\n <option key={type} value={type}>\n {type} ({count})\n </option>\n )\n })}\n </Select>\n </Flex>\n )}\n\n {/* Reference list */}\n <Box style={{ maxHeight: '400px', overflowY: 'auto' }}>\n <Stack space={2}>\n {filteredReferences.length === 0 ? (\n <Text size={1} muted>\n No references match the selected filter.\n </Text>\n ) : (\n filteredReferences.map((reference) => {\n const isSelected = selectedReferenceIds.has(reference._id)\n const displayTitle = reference.title || reference._id\n\n return (\n <Card key={reference._id} padding={3} border radius={2}>\n <Flex align=\"center\" gap={3}>\n <Checkbox checked={isSelected} onChange={() => handleToggleReference(reference._id)} />\n <Flex flex={1} align=\"center\" gap={2}>\n <Text size={1}>{displayTitle}</Text>\n <Badge tone=\"primary\" fontSize={0}>\n {reference._type}\n </Badge>\n </Flex>\n </Flex>\n </Card>\n )\n })\n )}\n </Stack>\n </Box>\n </Stack>\n </Card>\n )\n}\n","import { Button, Checkbox, Flex, Inline, Select, Stack, Text } from '@sanity/ui'\nimport { useEffect, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport { assembleReferences } from '../utils/references/assembleReferences'\nimport { ReferenceSelectionPanel } from './ReferenceSelectionPanel'\n\nexport const ReferenceConfigPanel = () => {\n const { dispatch, document, shouldTranslateReferences, maxReferenceDepth, config, references, selectedReferenceIds } =\n useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n const [isLoadingReferences, setIsLoadingReferences] = useState(false)\n const [showSelectionPanel, setShowSelectionPanel] = useState(false)\n const maxDepth = config.maxReferenceDepth || 3\n\n useEffect(() => {\n const updateReferences = async () => {\n setIsLoadingReferences(true)\n\n if (!shouldTranslateReferences) {\n dispatch({\n type: 'SET_REFERENCES',\n payload: [],\n })\n return\n }\n\n const newReferences = await assembleReferences({\n client,\n sourceDocument: document,\n maxReferenceDepth,\n translatableSchemaTypes: config.translatableSchemaTypes,\n })\n dispatch({\n type: 'SET_REFERENCES',\n payload: newReferences,\n })\n\n setIsLoadingReferences(false)\n }\n\n updateReferences()\n }, [document, client, dispatch, shouldTranslateReferences, maxReferenceDepth, config.translatableSchemaTypes])\n\n const handleMaxReferenceDepthChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {\n dispatch({\n type: 'SET_MAX_REFERENCE_DEPTH',\n payload: Number.parseInt(event.target.value),\n })\n }\n\n const handleShouldTranslateChange = () => {\n dispatch({\n type: 'SET_SHOULD_TRANSLATE_REFERENCES',\n payload: !shouldTranslateReferences,\n })\n }\n\n return (\n <Stack space={2}>\n <Flex justify=\"space-between\" align=\"center\">\n <Button\n type=\"button\"\n mode={shouldTranslateReferences ? 'ghost' : 'bleed'}\n size={2}\n onClick={handleShouldTranslateChange}\n tone={shouldTranslateReferences ? 'primary' : 'neutral'}\n >\n <Inline space={2}>\n <Checkbox\n checked={shouldTranslateReferences}\n onChange={handleShouldTranslateChange}\n label={shouldTranslateReferences ? 'Disable' : 'Enable'}\n />\n <Text>Translate References</Text>\n </Inline>\n </Button>\n {shouldTranslateReferences ? (\n <Inline space={2}>\n <Text>Max Depth:</Text>\n <Select\n disabled={!shouldTranslateReferences}\n placeholder=\"Depth\"\n onChange={handleMaxReferenceDepthChange}\n value={maxReferenceDepth.toString()}\n >\n {Array.from({ length: maxDepth }, (_, i) => i + 1).map((depth) => (\n <option key={depth} value={depth.toString()}>\n {depth}\n </option>\n ))}\n </Select>\n </Inline>\n ) : null}\n </Flex>\n {shouldTranslateReferences ? (\n <>\n <Flex padding={2} justify=\"space-between\" align=\"center\">\n <Text size={1} muted>\n {isLoadingReferences\n ? 'Loading references...'\n : references.length === 0\n ? 'No translatable references found.'\n : (() => {\n const selectedCount = references.filter((ref) => selectedReferenceIds.has(ref._id)).length\n const totalCount = references.length\n if (selectedCount === totalCount) {\n return `${totalCount} reference${totalCount === 1 ? '' : 's'} will be translated along with this document.`\n }\n return `${selectedCount} of ${totalCount} reference${totalCount === 1 ? '' : 's'} will be translated along with this document.`\n })()}\n </Text>\n {!isLoadingReferences && references.length > 0 && (\n <Button\n mode=\"ghost\"\n size={1}\n text={showSelectionPanel ? 'Hide selection' : 'Select specific references...'}\n onClick={() => setShowSelectionPanel(!showSelectionPanel)}\n />\n )}\n </Flex>\n {!isLoadingReferences && references.length > 0 && showSelectionPanel && <ReferenceSelectionPanel />}\n </>\n ) : null}\n </Stack>\n )\n}\n","import { ReferenceConfigPanel } from './ReferenceConfigPanel'\n\nexport const WriteSettingsPanel = () => {\n return <ReferenceConfigPanel />\n}\n","import { Card, Label, Stack } from '@sanity/ui'\nimport type { FC } from 'react'\nimport { ActionButtonBar } from './ActionButtonBar'\nimport { LanguageSelectionPanel } from './LanguageSelectionPanel'\nimport { ReleaseSelectionPanel } from './ReleaseSelectionPanel'\nimport { TranslationModePanel } from './TranslationModePanel'\nimport { WriteSettingsPanel } from './WriteSettingsPanel'\n\ninterface TranslationConfigViewProps {\n setIsDialogOpen: (isOpen: boolean) => void\n}\n\nexport const TranslationConfigView: FC<TranslationConfigViewProps> = ({ setIsDialogOpen }) => {\n return (\n <Stack space={4}>\n <Stack space={2}>\n <Label>Languages</Label>\n <LanguageSelectionPanel />\n </Stack>\n\n <Stack space={2}>\n <Label>Translation Method</Label>\n <TranslationModePanel />\n </Stack>\n\n <Stack space={2}>\n <Label>Content Release</Label>\n <ReleaseSelectionPanel />\n </Stack>\n\n <Stack space={2}>\n <Label>Settings</Label>\n <Card padding={4} border muted>\n <WriteSettingsPanel />\n </Card>\n </Stack>\n <ActionButtonBar setIsDialogOpen={setIsDialogOpen} />\n </Stack>\n )\n}\n","import { ArrowRightIcon, CheckmarkIcon, CircleIcon, ErrorOutlineIcon, PanelRightIcon, ResetIcon } from '@sanity/icons'\nimport { Badge, Box, Button, Card, Code, Flex, Heading, Inline, Label, Spinner, Stack, Text, Tooltip } from '@sanity/ui'\nimport { useCallback, useMemo } from 'react'\nimport { useOpenInNewPane } from 'sanity-plugin-utils'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport { forceCleanupActiveTranslation } from '../utils/state/activeTranslations'\n\nconst TERMINAL_STATUSES = ['completed', 'skipped', 'error']\n\nexport const TranslationStatusPanel = () => {\n const { overallStatus, translationJobs, translationMode, dispatch, document } = useTranslationContext()\n\n const headline = useMemo(() => {\n if (overallStatus === 'completed') return 'Translation completed'\n if (overallStatus === 'error') return 'Translation failed'\n return 'Translation in progress'\n }, [overallStatus])\n\n const jobs = useMemo(() => Array.from(translationJobs.values()), [translationJobs])\n\n const isTerminalState = overallStatus === 'completed' || overallStatus === 'error'\n\n const handleReset = useCallback(() => {\n // Clean up the active translation from module-level store\n forceCleanupActiveTranslation(document._id)\n // Reset the context state to allow a new translation\n dispatch({ type: 'RESET_TRANSLATION_STATE' })\n }, [dispatch, document._id])\n\n // Aggregate token usage and costs across completed OpenAI translation jobs\n const openaiStats = useMemo(() => {\n if (translationMode === 'sanity-only') return null\n\n const completedJobs = jobs.filter((j) => j.status === 'completed')\n const totalTokens = completedJobs.reduce((sum, job) => sum + (job.tokensUsed || 0), 0)\n const totalCost = completedJobs.reduce((sum, job) => sum + (job.estimatedCost || 0), 0)\n\n return { totalTokens, totalCost, jobsWithUsage: completedJobs.filter((j) => j.tokensUsed).length }\n }, [jobs, translationMode])\n\n return (\n <Stack space={4}>\n <Heading size={2}>{headline}</Heading>\n <ProgressBar progress={jobs.filter((job) => TERMINAL_STATUSES.includes(job.status)).length} max={jobs.length} />\n <Stack space={3}>\n <Label>Translations</Label>\n <Card padding={2} tone=\"transparent\">\n <Stack space={2}>\n <ul style={{ listStyleType: 'none', paddingInlineStart: '0' }}>\n {jobs.map((job) => (\n <li key={job._id}>\n <Box padding={3}>\n <Flex justify=\"space-between\" align=\"center\">\n <Inline space={2}>\n <Flex align=\"center\" justify=\"center\" style={{ width: '24px', height: '24px' }}>\n {['completed', 'skipped'].includes(job.status) ? (\n <CheckmarkIcon fontSize={24} />\n ) : job.status === 'error' ? (\n <Box style={{ color: 'var(--card-critical-fg-color)' }}>\n <ErrorOutlineIcon fontSize={24} />\n </Box>\n ) : job.status === 'queued' ? (\n <CircleIcon fontSize={24} />\n ) : (\n <Spinner />\n )}\n </Flex>\n <Stack space={3}>\n <Inline space={2}>\n <Text size={1} weight=\"semibold\">\n {job.title || 'Untitled Document'}\n </Text>\n <ArrowRightIcon fontSize={18} />\n <Code size={1}>{job.language}</Code>\n </Inline>\n <Code size={1}>\n {job._type} — {job._id?.slice(-6) || 'unknown'}\n </Code>\n {!['completed', 'skipped', 'error'].includes(job.status) && (\n <Text size={1} weight=\"semibold\">\n {getStatusLabel(job.status)}\n </Text>\n )}\n {job.status === 'completed' && job.tokensUsed ? (\n <Text size={1} muted>\n {job.tokensUsed.toLocaleString()} tokens (~${job.estimatedCost?.toFixed(4)})\n </Text>\n ) : null}\n {job.status === 'error' && job.errorMessage ? (\n <Card tone=\"critical\" padding={2} radius={2}>\n <Text size={1}>Error: {job.errorMessage}</Text>\n </Card>\n ) : null}\n </Stack>\n </Inline>\n <Flex gap={2} align=\"center\">\n {job.status === 'skipped' ? <SkippedBadge /> : null}\n {job.status === 'error' ? <ErrorBadge /> : null}\n {job.targetId ? (\n <TranslationOpenerButton\n target={{\n _id: job.targetId,\n _type: job._type as string,\n }}\n />\n ) : null}\n </Flex>\n </Flex>\n </Box>\n </li>\n ))}\n </ul>\n </Stack>\n </Card>\n </Stack>\n\n {/* OpenAI Usage Summary */}\n {openaiStats && openaiStats.jobsWithUsage > 0 && (\n <Card tone=\"primary\" padding={4} border>\n <Stack space={2}>\n <Text weight=\"semibold\">OpenAI Usage Summary</Text>\n <Inline space={4}>\n <Text size={1}>\n Total Tokens: <strong>{openaiStats.totalTokens.toLocaleString()}</strong>\n </Text>\n <Text size={1}>\n Total Cost: <strong>${openaiStats.totalCost.toFixed(4)}</strong>\n </Text>\n </Inline>\n </Stack>\n </Card>\n )}\n\n {/* Reset button for starting a new translation */}\n {isTerminalState && (\n <Flex justify=\"flex-end\">\n <Button mode=\"ghost\" tone=\"primary\" text=\"Translate More\" icon={ResetIcon} onClick={handleReset} />\n </Flex>\n )}\n </Stack>\n )\n}\n\nconst ProgressBar = ({ progress, max = 100 }: { progress: number; max?: number }) => {\n const percentage = Math.min((progress / max) * 100, 100)\n\n return (\n <Stack space={2}>\n <div\n style={{\n width: '100%',\n height: '8px',\n backgroundColor: 'var(--card-border-color)',\n borderRadius: '4px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n width: `${percentage}%`,\n height: '100%',\n backgroundColor: 'var(--card-accent-fg-color)',\n borderRadius: '4px',\n transition: 'width 0.3s ease-in-out',\n }}\n />\n </div>\n <Flex justify=\"space-between\" align=\"center\">\n <Text size={1} muted>\n {percentage.toFixed(0)}%\n </Text>\n <Text size={1} muted>\n {progress} / {max} jobs completed.\n </Text>\n </Flex>\n </Stack>\n )\n}\n\nconst TranslationOpenerButton = ({ target }: { target: { _id: string; _type: string } }) => {\n const openInNewPane = useOpenInNewPane(target._id, target._type)\n\n return <Button mode=\"bleed\" text=\"View\" iconRight={PanelRightIcon} onClick={() => openInNewPane()} />\n}\n\nconst SkippedBadge = () => (\n <Tooltip\n portal\n content={\n <Box padding={1}>\n <Text size={1} muted>\n There was already a fresh translation for this document and language.\n </Text>\n </Box>\n }\n >\n <Badge tone=\"neutral\">Skipped</Badge>\n </Tooltip>\n)\n\nconst ErrorBadge = () => (\n <Tooltip\n portal\n content={\n <Box padding={1}>\n <Text size={1} muted>\n This translation failed. Check the error message for details.\n </Text>\n </Box>\n }\n >\n <Badge tone=\"critical\">Failed</Badge>\n </Tooltip>\n)\n\nconst getStatusLabel = (status: string): string => {\n const labels: Record<string, string> = {\n idle: 'Idle',\n queued: 'Queued...',\n preparing_translation: 'Preparing translation...',\n translating: 'Translating with Sanity Agent...',\n translating_with_openai: 'Translating with OpenAI...',\n updating_metadata: 'Saving metadata...',\n error: 'Error',\n skipped: 'Skipped',\n completed: 'Completed',\n }\n return labels[status] || status\n}\n","import { type FC, useEffect } from 'react'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { OverallTranslationStatus } from '../types'\nimport { devLog } from '../utils/common/devLog'\nimport { TranslationConfigView } from './TranslationConfigPanel'\nimport { TranslationStatusPanel } from './TranslationStatusPanel'\n\ninterface TranslationDialogProps {\n setIsDialogOpen: (isOpen: boolean) => void\n /** Callback to notify parent of status changes (for close confirmation) */\n onStatusChange?: (status: OverallTranslationStatus) => void\n}\n\nconst STATES_IN_WHICH_TO_SHOW_CONFIG: Array<OverallTranslationStatus> = ['fetching_metadata', 'idle']\n\nconst STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS: Array<OverallTranslationStatus> = [\n 'translating',\n 'linking_references',\n 'completed',\n]\n\nconst STATES_IN_WHICH_TO_SHOW_ERROR: Array<OverallTranslationStatus> = ['error']\n\nexport const TranslationDialog: FC<TranslationDialogProps> = ({ setIsDialogOpen, onStatusChange }) => {\n const { overallStatus, document } = useTranslationContext()\n\n // Notify parent of status changes so it can show confirmation on close\n useEffect(() => {\n onStatusChange?.(overallStatus)\n }, [overallStatus, onStatusChange])\n\n if (STATES_IN_WHICH_TO_SHOW_CONFIG.includes(overallStatus)) {\n return <TranslationConfigView setIsDialogOpen={setIsDialogOpen} />\n }\n\n if (STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS.includes(overallStatus)) {\n return <TranslationStatusPanel />\n }\n\n if (STATES_IN_WHICH_TO_SHOW_ERROR.includes(overallStatus)) {\n return <div>Error occurred</div>\n }\n\n devLog.warn('Unhandled translation dialog state', {\n documentId: document._id,\n overallStatus,\n })\n\n return <div>Unhandled overall state</div>\n}\n","import { TranslateIcon } from '@sanity/icons'\nimport { useCallback, useState } from 'react'\nimport type { DocumentActionDescription, DocumentActionProps } from 'sanity'\nimport { TranslationContextProvider } from '../../hooks/useTranslationContext/TranslationContextProvider'\nimport type { OverallTranslationStatus, PluginConfig, TranslatableDocument } from '../../types'\nimport { devLog } from '../../utils/common/devLog'\nimport { TranslationDialog } from '../TranslationDialog'\n\ninterface TranslateActionProps extends DocumentActionProps {\n config: PluginConfig\n}\n\n/** Statuses that indicate translation is actively in progress */\nconst IN_PROGRESS_STATUSES: OverallTranslationStatus[] = ['translating', 'linking_references']\n\n/** Get the button label based on current translation status */\nconst getButtonLabel = (status: OverallTranslationStatus | null): string => {\n if (status === 'completed') return 'Translation Complete'\n if (status && IN_PROGRESS_STATUSES.includes(status)) return 'Translating...'\n return 'Translate'\n}\n\nexport const getTranslateAction = (config: PluginConfig) => {\n return ({ draft, published }: TranslateActionProps): DocumentActionDescription | null => {\n const document = draft ?? published\n const [isDialogOpen, setIsDialogOpen] = useState(false)\n const [currentStatus, setCurrentStatus] = useState<OverallTranslationStatus | null>(null)\n\n const isTranslating = currentStatus !== null && IN_PROGRESS_STATUSES.includes(currentStatus)\n\n // Callback for child to report translation status changes\n const handleStatusChange = useCallback((status: OverallTranslationStatus) => {\n setCurrentStatus(status)\n }, [])\n\n // Handle close with confirmation when translation is in progress\n const handleClose = useCallback(() => {\n if (isTranslating) {\n const confirmed = window.confirm(\n 'Translation is in progress. Closing this dialog will not stop the translation, ' +\n 'but refreshing the page will cancel the translation. You can reopen the ' +\n 'Translate dialog to see updated progress.',\n )\n if (!confirmed) {\n return\n }\n devLog.warn('User closed dialog during active translation', {\n documentId: document?._id,\n component: 'TranslateAction',\n })\n }\n setIsDialogOpen(false)\n }, [isTranslating, document?._id])\n\n devLog.debug('TranslateAction initialized', {\n documentId: document?._id,\n documentType: document?._type,\n })\n\n if (!document || !config.translatableSchemaTypes?.includes(document._type)) {\n devLog.warn('Document not in translatable schema types', {\n documentType: document?._type,\n translatableTypes: config.translatableSchemaTypes,\n })\n return null\n }\n\n if (!config) {\n devLog.error('Missing config in TranslateAction')\n return null\n }\n\n return {\n label: getButtonLabel(currentStatus),\n icon: TranslateIcon,\n onHandle: () => {\n devLog.info('Translation dialog opened', {\n documentId: document._id,\n documentType: document._type,\n component: 'TranslateAction',\n })\n setIsDialogOpen(true)\n },\n dialog: isDialogOpen && {\n type: 'dialog',\n onClose: handleClose,\n header: 'Translate Document',\n content: (\n <TranslationContextProvider document={document as TranslatableDocument} config={config}>\n <TranslationDialog setIsDialogOpen={setIsDialogOpen} onStatusChange={handleStatusChange} />\n </TranslationContextProvider>\n ),\n },\n }\n }\n}\n","import { TranslateIcon } from '@sanity/icons'\nimport { defineField, defineType } from 'sanity'\n\nexport const translationGlossaryTerm = defineType({\n name: 'translationGlossaryTerm',\n title: 'Translation Glossary Term',\n icon: TranslateIcon,\n type: 'document',\n fields: [\n defineField({\n name: 'translations',\n title: 'Translations',\n type: 'internationalizedArrayString',\n }),\n ],\n preview: {\n select: {\n translations: 'translations',\n },\n prepare({ translations }: { translations: { locale: string; value: string }[] }) {\n const enTranslation = translations?.find((t) => t.locale === 'en-us')\n const firstTranslation = translations?.[0]\n return {\n title: enTranslation?.value || firstTranslation?.value || 'No translation available',\n }\n },\n },\n})\n","import { defineField } from 'sanity'\n\nexport const translationHistory = defineField({\n name: 'translationHistory',\n type: 'array',\n of: [\n {\n name: 'targetLanguageHistory',\n type: 'object',\n fields: [\n defineField({\n name: 'language',\n title: 'Language',\n description: 'The language of the target document',\n type: 'string',\n }),\n defineField({\n name: 'sourceId',\n title: 'Source ID',\n description: 'The ID of the source document',\n type: 'string',\n }),\n defineField({\n name: 'sourceRev',\n title: 'Source Revision',\n description: 'The revision ID of the source document at the time of translation',\n type: 'string',\n }),\n defineField({\n name: 'sourceLanguage',\n title: 'Source Language',\n description: 'The language of the source document',\n type: 'string',\n }),\n defineField({\n name: 'sourceSnapshot',\n title: 'Source Snapshot',\n description: 'The snapshot of the source document at the time of translation',\n type: 'text',\n }),\n defineField({\n name: 'targetId',\n title: 'Target ID',\n description: 'The ID of the target document',\n type: 'string',\n }),\n ],\n },\n ],\n})\n","import { TranslateIcon } from '@sanity/icons'\nimport { defineField, defineType } from 'sanity'\n\nexport const translationSettings = defineType({\n name: 'translationSettings',\n title: 'Translation Settings',\n icon: TranslateIcon,\n type: 'document',\n groups: [\n {\n name: 'general',\n title: 'General',\n default: true,\n },\n {\n name: 'history',\n title: 'History',\n },\n ],\n fields: [\n defineField({\n name: 'globalPrompt',\n title: 'Global Translation Prompt',\n description: 'A prompt that will given to the Sanity AI Agent for all translation tasks.',\n type: 'text',\n rows: 5,\n group: 'general',\n }),\n defineField({\n name: 'localePrompts',\n title: 'Locale Prompts',\n description: 'Prompts that will be given to the Sanity AI Agent for each locale.',\n type: 'internationalizedArrayText',\n group: 'general',\n }),\n defineField({\n name: 'protectedPhrases',\n title: 'Protected Phrases',\n description: 'Phrases that will never be translated.',\n type: 'array',\n of: [{ type: 'string' }],\n group: 'general',\n }),\n defineField({\n name: 'changeHistory',\n title: 'Change History',\n description: 'History of changes made to translation settings.',\n type: 'array',\n readOnly: true,\n group: 'history',\n of: [\n {\n type: 'object',\n fields: [\n defineField({\n name: 'timestamp',\n title: 'Timestamp',\n type: 'datetime',\n }),\n defineField({\n name: 'changeType',\n title: 'Change Type',\n type: 'string',\n options: {\n list: [\n { title: 'Global Prompt', value: 'globalPrompt' },\n { title: 'Locale Prompt', value: 'localePrompt' },\n { title: 'Protected Phrase', value: 'protectedPhrase' },\n { title: 'Glossary Term', value: 'glossaryTerm' },\n ],\n },\n }),\n defineField({\n name: 'description',\n title: 'Description',\n type: 'text',\n rows: 3,\n }),\n defineField({\n name: 'locale',\n title: 'Locale',\n type: 'string',\n description: 'Target locale for locale-specific changes',\n }),\n ],\n preview: {\n select: {\n timestamp: 'timestamp',\n changeType: 'changeType',\n description: 'description',\n },\n prepare({ timestamp, changeType, description }) {\n return {\n title: `${changeType} - ${new Date(timestamp).toLocaleDateString()}`,\n subtitle: description,\n }\n },\n },\n },\n ],\n }),\n ],\n preview: {\n prepare: () => ({\n title: 'Translation Settings',\n }),\n },\n})\n","import type { SanityDocument } from 'sanity'\nimport type { FieldTransform } from '../../../../types'\nimport { devLog } from '../../../common/devLog'\nimport { deserializeDocument } from '../../../openai/deserializeDocument'\nimport { serializeDocument } from '../../../openai/serializeDocument'\n\n/**\n * Special type to track transformed rich text content.\n * This separates the serialized XML content from regular strings\n * so the main serialization can handle it correctly.\n */\ninterface TranslatableRichText {\n _type: 'translatable.richtext'\n // XML-formatted text content for translation\n translationText: string\n // Original value stored for fallback\n _originalValue: unknown[]\n}\n\n/**\n * Identifies if an array is a Portable Text field (block content)\n */\nfunction isPortableText(value: unknown[]): boolean {\n // biome-ignore lint/suspicious/noExplicitAny: Items in array can be any shape\n return Array.isArray(value) && value.some((item: any) => item?._type === 'block')\n}\n\n/**\n * Transform for handling Portable Text (rich text) content using XML-like serialization.\n * This leverages the existing serialization utilities to convert complex rich text\n * to a format that's easier for translation models to process.\n *\n * Uses value-based detection to identify Portable Text arrays (checking for _type === 'block').\n * The fieldPath pattern is used as a performance optimization to avoid checking every field,\n * but the valueMatcher is the definitive check.\n *\n * @public\n */\nexport const richTextTransform: FieldTransform<unknown[], TranslatableRichText> = {\n // Matcher combines field name pattern with value-based detection\n // fieldPath pattern acts as a pre-filter for performance\n // valueMatcher definitively checks if the value is actually Portable Text\n matcher: {\n // Pre-filter: Match all fields to enable detection based solely on value\n fieldPath: /.*/,\n\n // Definitive check: Verify the value is actually Portable Text\n valueMatcher: (value) => {\n return Array.isArray(value) && isPortableText(value)\n },\n },\n\n toTranslatable: (value, context) => {\n if (!Array.isArray(value) || !isPortableText(value)) {\n // Return a translatable wrapper even for non-portable text\n return {\n _type: 'translatable.richtext' as const,\n translationText: JSON.stringify(value),\n _originalValue: value,\n }\n }\n\n devLog.info('Applying rich text transform with XML serialization', {\n fieldPath: context.fieldPath,\n blockCount: value.length,\n })\n\n try {\n // Create a temporary document wrapper for the rich text\n const tempDoc = {\n _id: 'temp',\n _type: 'richTextWrapper',\n _createdAt: new Date().toISOString(),\n _updatedAt: new Date().toISOString(),\n _rev: 'temp',\n content: value,\n } as SanityDocument\n\n // Use the existing serialization\n const serialized = serializeDocument(tempDoc)\n devLog.debug('Serialized rich text document', {\n fieldPath: context.fieldPath,\n serializedLength: serialized.length,\n preview: serialized.substring(0, 200),\n })\n\n // Extract just the serialized rich text part\n // Use greedy match to capture everything until the LAST </field> tag (the outer one)\n // The content field is the only top-level field, so the last </field> is the right one\n const contentRegex = /<field name=\"content\"[^>]*>([\\s\\S]*)<\\/field>/s\n const match = serialized.match(contentRegex)\n\n if (match?.[1]) {\n // Return a special object type that the serializer recognizes\n // This prevents the XML content from being escaped as a regular string\n const result: TranslatableRichText = {\n _type: 'translatable.richtext',\n translationText: match[1].trim(),\n _originalValue: value,\n }\n devLog.debug('Successfully extracted and wrapped rich text', {\n fieldPath: context.fieldPath,\n translationTextLength: result.translationText.length,\n })\n return result\n }\n\n devLog.warn('Failed to serialize rich text content - regex did not match', {\n fieldPath: context.fieldPath,\n serializedPreview: serialized.substring(0, 500),\n })\n // Return a fallback translatable object\n return {\n _type: 'translatable.richtext' as const,\n translationText: JSON.stringify(value),\n _originalValue: value,\n }\n } catch (error) {\n devLog.error('Error serializing rich text', error, {\n fieldPath: context.fieldPath,\n })\n // Return a fallback translatable object\n return {\n _type: 'translatable.richtext' as const,\n translationText: JSON.stringify(value),\n _originalValue: value,\n }\n }\n },\n\n fromTranslatable: (translatedValue, originalValue, context) => {\n // Check if the value is our translatable format\n if (!translatedValue || translatedValue._type !== 'translatable.richtext' || !translatedValue.translationText) {\n devLog.debug('Value is not a translatable rich text object, returning original', {\n fieldPath: context.fieldPath,\n hasCorrectType: translatedValue._type === 'translatable.richtext',\n })\n return originalValue // Return original value as we're expecting unknown[]\n }\n\n devLog.info('Deserializing rich text content', {\n fieldPath: context.fieldPath,\n translationTextLength: translatedValue.translationText.length,\n })\n\n try {\n const { translationText, _originalValue } = translatedValue as TranslatableRichText\n\n // Create a wrapper document for deserialization\n const wrappedSerialized = `<field name=\"content\">\\n${translationText}\\n</field>`\n\n devLog.debug('Prepared wrapped serialized content', {\n fieldPath: context.fieldPath,\n wrappedLength: wrappedSerialized.length,\n preview: wrappedSerialized.substring(0, 300),\n })\n\n // Create original temp doc for structure\n const tempOriginalDoc = {\n _id: 'temp',\n _type: 'richTextWrapper',\n _createdAt: new Date().toISOString(),\n _updatedAt: new Date().toISOString(),\n _rev: 'temp',\n content: originalValue,\n } as SanityDocument\n\n // Use existing deserialization\n const result = deserializeDocument(tempOriginalDoc, wrappedSerialized)\n\n devLog.debug('Deserialization result', {\n fieldPath: context.fieldPath,\n hasContent: !!result.content,\n contentType: Array.isArray(result.content) ? 'array' : typeof result.content,\n contentLength: Array.isArray(result.content) ? result.content.length : undefined,\n })\n\n if (!result.content) {\n devLog.warn('Deserialization returned no content, returning original', {\n fieldPath: context.fieldPath,\n resultKeys: Object.keys(result),\n })\n return originalValue\n }\n\n return result.content as unknown[]\n } catch (error) {\n devLog.error('Failed to deserialize rich text', error, {\n fieldPath: context.fieldPath,\n })\n return originalValue\n }\n },\n}\n","import type { FieldTransform } from '../../../../types'\nimport { devLog } from '../../../common/devLog'\nimport { escapeXml, unescapeXml } from '../../xmlUtils'\n\n/**\n * Represents a VTT caption cue with timing and text content.\n */\ninterface VTTCaption {\n _key?: string\n _type?: string\n text?: string\n startTime?: number\n endTime?: number\n [key: string]: string | number | undefined\n}\n\n/**\n * VTT caption metadata without text content, used for preservation during translation.\n */\ninterface VTTCaptionMetadata {\n _key?: string\n _type?: string\n startTime?: number\n endTime?: number\n _originalIndex: number\n [key: string]: string | number | undefined\n}\n\n/**\n * Special type to track transformed VTT captions.\n * This combines metadata separation for token efficiency with XML format for translation.\n */\ninterface TranslatableVTTCaptions {\n _type: 'translatable.vtt.captions'\n // XML-formatted text content for translation\n translationText: string\n // Metadata stored separately to avoid sending to translation model\n _originalMetadata: VTTCaptionMetadata[]\n}\n\n/**\n * Transform for handling VTT captions in video documents using a hybrid approach.\n * This stores metadata separately for token efficiency while formatting\n * text content as XML for better translation results.\n *\n * @public\n */\nexport const vttCaptionTransform: FieldTransform<VTTCaption[], TranslatableVTTCaptions> = {\n matcher: {\n fieldName: 'captions',\n schemaType: 'video',\n },\n\n toTranslatable: (captionsArray, context) => {\n devLog.info('VTT captions toTranslatable - input:', {\n fieldPath: context.fieldPath,\n isArray: Array.isArray(captionsArray),\n count: Array.isArray(captionsArray) ? captionsArray.length : 0,\n firstCaption: Array.isArray(captionsArray) && captionsArray.length > 0 ? captionsArray[0] : null,\n })\n\n if (!Array.isArray(captionsArray)) {\n devLog.debug('VTT caption field is not an array, returning empty transform', {\n fieldPath: context.fieldPath,\n value: captionsArray,\n })\n // Return empty translatable format for non-array values\n return {\n _type: 'translatable.vtt.captions' as const,\n translationText: '',\n _originalMetadata: [],\n }\n }\n\n devLog.info('Applying VTT caption hybrid transform', {\n fieldPath: context.fieldPath,\n count: captionsArray.length,\n })\n\n // Extract text for XML format and metadata for separate storage\n const translationText = serializeVTTCaptionText(captionsArray)\n const originalMetadata = captionsArray.map((caption, index) => {\n // Remove text content but preserve all other properties\n const { text: _text, ...metadata } = caption\n return {\n ...metadata,\n _originalIndex: index,\n }\n })\n\n devLog.info('VTT captions serialized:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n metadataCount: originalMetadata.length,\n metadataExample: originalMetadata.length > 0 ? originalMetadata[0] : null,\n })\n\n // Return object with separated text and metadata\n const result: TranslatableVTTCaptions = {\n _type: 'translatable.vtt.captions',\n translationText,\n _originalMetadata: originalMetadata,\n }\n\n return result\n },\n\n fromTranslatable: (translatedValue, _originalValue, context) => {\n devLog.info('VTT captions fromTranslatable - input:', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n isCorrectType: translatedValue._type === 'translatable.vtt.captions',\n hasText: !!translatedValue.translationText,\n hasMetadata: Array.isArray(translatedValue._originalMetadata),\n })\n\n // Check if the value is our hybrid format\n if (\n !translatedValue ||\n translatedValue._type !== 'translatable.vtt.captions' ||\n !translatedValue.translationText ||\n !Array.isArray(translatedValue._originalMetadata)\n ) {\n devLog.debug('Value is not a translatable VTT caption object, skipping transform', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n translatedValue: translatedValue,\n })\n return [] // Return empty array as we're expecting VTTCaption[]\n }\n\n devLog.info('Processing translated VTT captions', {\n fieldPath: context.fieldPath,\n metadataCount: translatedValue._originalMetadata.length,\n })\n\n // Get translated text and metadata\n const { translationText, _originalMetadata } = translatedValue\n\n devLog.info('VTT captions translationText:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n })\n\n // Merge translated text with preserved metadata\n const result = deserializeVTTCaptionText(translationText, _originalMetadata)\n\n devLog.info('VTT captions final result:', {\n fieldPath: context.fieldPath,\n resultCount: result.length,\n resultExample: result.length > 0 ? result[0] : null,\n })\n\n return result\n },\n}\n\n/**\n * Serialize VTT captions to compact text format for translation\n * @param captions - Array of VTT caption objects\n * @returns Compact string with indexed text content\n */\nexport function serializeVTTCaptionText(captions: VTTCaption[]): string {\n devLog.debug('serializeVTTCaptionText - input:', {\n captionsCount: captions.length,\n firstCaption: captions.length > 0 ? captions[0] : null,\n })\n\n if (!Array.isArray(captions) || captions.length === 0) {\n return ''\n }\n\n // Use a compact format: [index] text\n // Escape newlines in text to prevent them from breaking the line-by-line parsing\n const lines: string[] = captions.map((caption, index) => {\n devLog.debug(`Processing caption ${index}:`, {\n text: caption.text,\n hasText: typeof caption.text === 'string',\n })\n // Replace actual newlines with escaped newlines to preserve multi-line cues\n const escapedText = (caption.text || '').replace(/\\n/g, '\\\\n')\n return `[${index}] ${escapedText}`\n })\n\n const result = lines.join('\\n')\n\n devLog.debug('serializeVTTCaptionText - output:', {\n resultLength: result.length,\n resultPreview: result.substring(0, 100) + (result.length > 100 ? '...' : ''),\n })\n\n return result\n}\n\n/**\n * Deserialize compact text format and merge with preserved metadata\n * @param translationText - Compact string with translated text\n * @param originalMetadata - Array of original caption metadata\n * @returns Reconstructed captions array with translated text\n */\nexport function deserializeVTTCaptionText(\n translationText: string,\n originalMetadata: VTTCaptionMetadata[],\n): VTTCaption[] {\n devLog.debug('deserializeVTTCaptionText - input:', {\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 100) + (translationText.length > 100 ? '...' : ''),\n metadataCount: originalMetadata.length,\n metadataExample: originalMetadata.length > 0 ? originalMetadata[0] : null,\n })\n\n if (!translationText || translationText.trim() === '') {\n return []\n }\n\n // Parse compact format: [index] text\n // Handle multi-line cues by restoring escaped newlines and continuation lines\n const result: VTTCaption[] = []\n const lines = translationText.split('\\n')\n const lineRegex = /^\\[(\\d+)\\]\\s*(.*)$/\n\n let currentIndex: number | null = null\n let currentTextParts: string[] = []\n\n for (const line of lines) {\n const match = line.match(lineRegex)\n\n if (match) {\n // Found a new caption - process the previous one if it exists\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = originalMetadata.find((m) => m._originalIndex === currentIndex)\n\n if (metadata) {\n const { _originalIndex, ...metadataWithoutIndex } = metadata\n result.push({\n ...metadataWithoutIndex,\n text,\n })\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n result.push({\n _type: 'vtt.cue',\n _key: `caption-${currentIndex}`,\n text,\n startTime: 0,\n endTime: 0,\n })\n }\n }\n\n // Start new caption\n currentIndex = Number.parseInt(match[1], 10)\n currentTextParts = [match[2]]\n } else if (currentIndex !== null) {\n // Continuation line for current caption (handles cases where translation service converted \\n to actual newlines)\n currentTextParts.push(line)\n } else {\n // Line doesn't match format and we're not in a caption - skip it\n devLog.warn('Failed to parse caption line', { line })\n }\n }\n\n // Process the last caption\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = originalMetadata.find((m) => m._originalIndex === currentIndex)\n\n if (metadata) {\n const { _originalIndex, ...metadataWithoutIndex } = metadata\n result.push({\n ...metadataWithoutIndex,\n text,\n })\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n result.push({\n _type: 'vtt.cue',\n _key: `caption-${currentIndex}`,\n text,\n startTime: 0,\n endTime: 0,\n })\n }\n }\n\n // Verify all captions were processed\n if (result.length < originalMetadata.length) {\n devLog.warn(`Missing captions in translation: found ${result.length} out of ${originalMetadata.length}`)\n }\n\n devLog.debug('deserializeVTTCaptionText - output:', {\n resultCount: result.length,\n resultExample: result.length > 0 ? result[0] : null,\n })\n\n return result\n}\n\n// Export the full serialization functions for testing and standalone usage\n/**\n * Legacy function for full XML serialization - used in tests\n */\nexport function serializeVTTCaptions(captions: VTTCaption[]): string {\n if (!Array.isArray(captions) || captions.length === 0) {\n return '<captions></captions>'\n }\n\n const lines: string[] = ['<captions>']\n\n captions.forEach((caption, index) => {\n const { text, _key, startTime, endTime, _type, ...otherProps } = caption\n\n // Build attributes string\n const attributes: string[] = [`start=\"${startTime}\"`, `end=\"${endTime}\"`]\n\n if (_key) {\n attributes.push(`key=\"${escapeXml(_key)}\"`)\n }\n\n if (_type) {\n attributes.push(`type=\"${escapeXml(_type)}\"`)\n }\n\n // Add index for matching during deserialization\n attributes.push(`index=\"${index}\"`)\n\n // Add any other properties as attributes (excluding objects)\n Object.entries(otherProps).forEach(([key, value]) => {\n if (!key.startsWith('_') && typeof value !== 'object') {\n attributes.push(`${key}=\"${escapeXml(String(value))}\"`)\n }\n })\n\n // Add the cue with its text content\n lines.push(` <cue ${attributes.join(' ')}>`)\n lines.push(` ${escapeXml(text ?? '')}`)\n lines.push(' </cue>')\n })\n\n lines.push('</captions>')\n return lines.join('\\n')\n}\n\n/**\n * Legacy function for full XML deserialization - used in tests\n */\nexport function deserializeVTTCaptions(\n serialized: string,\n originalCaptions: Array<Record<string, unknown>>,\n): Array<Record<string, unknown>> {\n // Create maps for looking up original captions\n const originalByKey = new Map<string, { caption: Record<string, unknown>; index: number }>()\n const originalByIndex = [...originalCaptions]\n\n originalCaptions.forEach((caption, index) => {\n if (caption._key && typeof caption._key === 'string') {\n originalByKey.set(caption._key, { caption, index })\n }\n })\n\n // Extract captions from XML\n const result: Array<Record<string, unknown>> = []\n const captionsRegex = /<captions>([\\s\\S]*?)<\\/captions>/\n const cueRegex = /<cue\\s+([^>]*)>([\\s\\S]*?)<\\/cue>/g\n const attrRegex = /([a-zA-Z0-9_-]+)=\"([^\"]*)\"/g\n\n const captionsMatch = serialized.match(captionsRegex)\n\n if (!captionsMatch) {\n // Return original if no match\n devLog.warn('Failed to parse serialized VTT captions', { serialized })\n return originalCaptions\n }\n\n const captionsContent = captionsMatch[1]\n let processedCount = 0\n\n // Use Array.from to iterate over regex matches without assignment in condition\n const cueMatches = Array.from(captionsContent.matchAll(cueRegex))\n\n for (const cueMatch of cueMatches) {\n const attributesStr = cueMatch[1]\n const text = unescapeXml(cueMatch[2].trim())\n const attributes: Record<string, string> = {}\n\n // Parse attributes using matchAll to avoid assignment in condition\n const attrMatches = Array.from(attributesStr.matchAll(new RegExp(attrRegex.source, 'g')))\n for (const attrMatch of attrMatches) {\n attributes[attrMatch[1]] = unescapeXml(attrMatch[2])\n }\n\n // Find the original caption (by key, index, or position)\n let original: Record<string, unknown> | null = null\n const index = Number.parseInt(attributes.index || '0')\n\n if (attributes.key && originalByKey.has(attributes.key)) {\n const originalData = originalByKey.get(attributes.key)\n if (originalData) {\n original = originalData.caption\n }\n } else if (index < originalByIndex.length) {\n original = originalByIndex[index]\n } else if (processedCount < originalByIndex.length) {\n original = originalByIndex[processedCount]\n }\n\n if (original) {\n // Merge translated text with preserved original metadata\n result.push({\n ...original,\n text,\n })\n } else {\n // Fall back to reconstructing original caption from metadata if not cached\n devLog.warn('Creating caption from attributes as original not found', {\n key: attributes.key,\n index,\n })\n\n result.push({\n _type: attributes.type || 'vtt.cue',\n _key: attributes.key || `caption-${processedCount}`,\n text,\n startTime: Number.parseFloat(attributes.start) || 0,\n endTime: Number.parseFloat(attributes.end) || 0,\n })\n }\n\n processedCount++\n }\n\n return result\n}\n","/**\n * ISO-639-2 language code utilities for converting locale codes to ISO language codes.\n * @public\n */\n\nexport const SUPPORTED_ISO_LANGUAGES = ['eng', 'spa', 'fra', 'ger', 'ita', 'por', 'rus', 'jpn', 'kor', 'chi'] as const\n\nexport type IsoLanguageCode = (typeof SUPPORTED_ISO_LANGUAGES)[number]\n\n// ISO-639-2 language codes\nexport const ISO_LANGUAGE_NAMES: Record<IsoLanguageCode, string> = {\n eng: 'English',\n spa: 'Spanish',\n fra: 'French',\n ger: 'German',\n ita: 'Italian',\n por: 'Portuguese',\n rus: 'Russian',\n jpn: 'Japanese',\n kor: 'Korean',\n chi: 'Chinese',\n}\n\n// Map from locale codes to ISO-639-2\nconst LOCALE_TO_ISO: Record<string, IsoLanguageCode> = {\n // English variants\n 'en-us': 'eng',\n 'en-US': 'eng',\n 'en-gb': 'eng',\n 'en-GB': 'eng',\n 'en-ca': 'eng',\n 'en-CA': 'eng',\n 'en-au': 'eng',\n 'en-AU': 'eng',\n\n // Spanish variants\n es: 'spa',\n 'es-es': 'spa',\n 'es-ES': 'spa',\n 'es-mx': 'spa',\n 'es-MX': 'spa',\n\n // French variants\n fr: 'fra',\n 'fr-fr': 'fra',\n 'fr-FR': 'fra',\n 'fr-ca': 'fra',\n 'fr-CA': 'fra',\n\n // German variants\n de: 'ger',\n 'de-de': 'ger',\n 'de-DE': 'ger',\n 'de-at': 'ger',\n 'de-AT': 'ger',\n\n // Italian variants\n it: 'ita',\n 'it-it': 'ita',\n 'it-IT': 'ita',\n\n // Portuguese variants\n pt: 'por',\n 'pt-br': 'por',\n 'pt-BR': 'por',\n 'pt-pt': 'por',\n 'pt-PT': 'por',\n\n // Russian variants\n ru: 'rus',\n 'ru-ru': 'rus',\n 'ru-RU': 'rus',\n\n // Japanese variants\n ja: 'jpn',\n 'ja-jp': 'jpn',\n 'ja-JP': 'jpn',\n\n // Korean variants\n ko: 'kor',\n 'ko-kr': 'kor',\n 'ko-KR': 'kor',\n\n // Chinese variants\n zh: 'chi',\n 'zh-cn': 'chi',\n 'zh-CN': 'chi',\n 'zh-tw': 'chi',\n 'zh-TW': 'chi',\n 'zh-hk': 'chi',\n 'zh-HK': 'chi',\n}\n\n/**\n * Converts a locale code (e.g., 'en-US') to its ISO-639-2 language code (e.g., 'eng')\n * @param code The locale code to convert\n * @returns The ISO-639-2 language code, or undefined if the locale is not supported\n * @public\n */\nexport const normalizeToIsoLanguageCode = (code: string): IsoLanguageCode | undefined => {\n const normalizedCode = code.toLowerCase()\n return LOCALE_TO_ISO[normalizedCode]\n}\n\n/**\n * Gets the display name for a locale code in English\n * @param code The locale code to get the display name for\n * @returns The display name in English, or the original code if not found\n * @public\n */\nexport const getIsoLanguageName = (code: string): string => {\n const normalizedCode = normalizeToIsoLanguageCode(code)\n return normalizedCode ? ISO_LANGUAGE_NAMES[normalizedCode] : code\n}\n","import type { FieldTransform } from '../../../../types'\nimport { devLog } from '../../../common/devLog'\nimport { normalizeToIsoLanguageCode } from '../../../common/isoLanguage'\n\n/**\n * Represents a Wistia captions object with SRT content and metadata.\n */\nexport interface WistiaCaptions {\n captionContent?: string\n captionFileName?: string\n isoLanguage?: string\n captionUploadStatus?: string\n captionUploadMessage?: string\n [key: string]: string | undefined\n}\n\n/**\n * Represents a parsed SRT cue with timing and text content.\n */\ninterface SRTCue {\n index: number\n startTime: string\n endTime: string\n text: string\n}\n\n/**\n * Wistia captions metadata without captionContent, used for preservation during translation.\n */\ninterface WistiaCaptionsMetadata {\n captionFileName?: string\n isoLanguage?: string\n captionUploadStatus?: string\n captionUploadMessage?: string\n _originalIndex: number\n [key: string]: string | number | undefined\n}\n\n/**\n * Special type to track transformed Wistia captions.\n * This combines metadata separation for token efficiency with compact text format for translation.\n */\ninterface TranslatableWistiaCaptions {\n _type: 'translatable.wistia.captions'\n // Compact text content for translation\n translationText: string\n // Metadata stored separately to avoid sending to translation model\n _originalMetadata: WistiaCaptionsMetadata\n // Store parsed SRT cues for reconstruction\n _srtCues: Array<{ index: number; startTime: string; endTime: string }>\n}\n\n/**\n * Transform for handling Wistia captions in documents using SRT format.\n * This stores metadata separately for token efficiency while formatting\n * text content in a compact indexed format for better translation results.\n *\n * @public\n */\nexport const wistiaCaptionTransform: FieldTransform<WistiaCaptions, TranslatableWistiaCaptions> = {\n matcher: {\n fieldName: 'wistiaCaptions',\n },\n\n toTranslatable: (wistiaCaptions, context) => {\n devLog.info('Wistia captions toTranslatable - input:', {\n fieldPath: context.fieldPath,\n hasCaptionContent: !!wistiaCaptions?.captionContent,\n captionContentLength: wistiaCaptions?.captionContent?.length ?? 0,\n isoLanguage: wistiaCaptions?.isoLanguage,\n })\n\n if (!wistiaCaptions || !wistiaCaptions.captionContent) {\n devLog.debug('Wistia caption field is missing or has no content, returning empty transform', {\n fieldPath: context.fieldPath,\n value: wistiaCaptions,\n })\n // Return empty translatable format for missing/invalid values\n return {\n _type: 'translatable.wistia.captions' as const,\n translationText: '',\n _originalMetadata: {\n captionFileName: wistiaCaptions?.captionFileName,\n isoLanguage: wistiaCaptions?.isoLanguage,\n captionUploadStatus: wistiaCaptions?.captionUploadStatus,\n captionUploadMessage: wistiaCaptions?.captionUploadMessage,\n _originalIndex: 0,\n },\n _srtCues: [],\n }\n }\n\n devLog.info('Applying Wistia caption transform', {\n fieldPath: context.fieldPath,\n captionContentPreview: wistiaCaptions.captionContent.substring(0, 200),\n })\n\n // Parse SRT content into structured cues\n const srtCues = parseSRTContent(wistiaCaptions.captionContent)\n\n devLog.debug('Parsed SRT cues:', {\n fieldPath: context.fieldPath,\n cueCount: srtCues.length,\n firstCue: srtCues.length > 0 ? srtCues[0] : null,\n })\n\n // Extract text for compact format and metadata for separate storage\n const translationText = serializeWistiaCaptionText(srtCues)\n const originalMetadata: WistiaCaptionsMetadata = {\n captionFileName: wistiaCaptions.captionFileName,\n isoLanguage: wistiaCaptions.isoLanguage,\n captionUploadStatus: wistiaCaptions.captionUploadStatus,\n captionUploadMessage: wistiaCaptions.captionUploadMessage,\n _originalIndex: 0,\n }\n\n // Store timing information for each cue\n const srtCuesMetadata = srtCues.map((cue) => ({\n index: cue.index,\n startTime: cue.startTime,\n endTime: cue.endTime,\n }))\n\n devLog.info('Wistia captions serialized:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n cueCount: srtCues.length,\n })\n\n // Return object with separated text and metadata\n const result: TranslatableWistiaCaptions = {\n _type: 'translatable.wistia.captions',\n translationText,\n _originalMetadata: originalMetadata,\n _srtCues: srtCuesMetadata,\n }\n\n return result\n },\n\n fromTranslatable: (translatedValue, originalValue, context) => {\n devLog.info('Wistia captions fromTranslatable - input:', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n isCorrectType: translatedValue._type === 'translatable.wistia.captions',\n hasText: !!translatedValue.translationText,\n hasMetadata: !!translatedValue._originalMetadata,\n hasCues: Array.isArray(translatedValue._srtCues),\n })\n\n // Check if the value is our transformed format\n if (\n !translatedValue ||\n translatedValue._type !== 'translatable.wistia.captions' ||\n !translatedValue._originalMetadata ||\n !Array.isArray(translatedValue._srtCues)\n ) {\n devLog.debug('Value is not a translatable Wistia caption object, returning original', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n translatedValue: translatedValue,\n })\n return originalValue || {}\n }\n\n devLog.info('Processing translated Wistia captions', {\n fieldPath: context.fieldPath,\n cueCount: translatedValue._srtCues.length,\n })\n\n // Get translated text and metadata\n const { translationText, _originalMetadata, _srtCues } = translatedValue\n\n devLog.info('Wistia captions translationText:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n })\n\n // Reconstruct SRT format from translated text and original timing\n const reconstructedCaptionContent = deserializeWistiaCaptionText(translationText, _srtCues)\n\n // Get the document's language field to update isoLanguage\n const documentLanguage = getDocumentLanguage(context.document)\n let updatedIsoLanguage = _originalMetadata.isoLanguage\n\n if (documentLanguage) {\n const isoCode = normalizeToIsoLanguageCode(documentLanguage)\n if (isoCode) {\n updatedIsoLanguage = isoCode\n devLog.debug('Updated isoLanguage based on document language', {\n fieldPath: context.fieldPath,\n documentLanguage,\n isoCode,\n })\n } else {\n devLog.debug('Could not convert document language to ISO code, preserving original', {\n fieldPath: context.fieldPath,\n documentLanguage,\n originalIsoLanguage: _originalMetadata.isoLanguage,\n })\n }\n } else {\n devLog.debug('Document language field not found, preserving original isoLanguage', {\n fieldPath: context.fieldPath,\n originalIsoLanguage: _originalMetadata.isoLanguage,\n })\n }\n\n // Merge translated content with preserved metadata\n const result: WistiaCaptions = {\n captionContent: reconstructedCaptionContent,\n captionFileName: _originalMetadata.captionFileName,\n isoLanguage: updatedIsoLanguage,\n captionUploadStatus: _originalMetadata.captionUploadStatus,\n captionUploadMessage: _originalMetadata.captionUploadMessage,\n }\n\n devLog.info('Wistia captions final result:', {\n fieldPath: context.fieldPath,\n hasCaptionContent: !!result.captionContent,\n captionContentLength: result.captionContent?.length ?? 0,\n isoLanguage: result.isoLanguage,\n })\n\n return result\n },\n}\n\n/**\n * Parses SRT format content into structured cues.\n * SRT format:\n * ```\n * 1\n * 00:00:00,160 --> 00:00:03,920\n * Text line 1\n * Text line 2\n *\n * 2\n * 00:00:03,920 --> 00:00:07,080\n * More text\n * ```\n * @param content - SRT format string\n * @returns Array of parsed SRT cues\n */\nexport function parseSRTContent(content: string): SRTCue[] {\n devLog.debug('parseSRTContent - input:', {\n contentLength: content.length,\n contentPreview: content.substring(0, 100) + (content.length > 100 ? '...' : ''),\n })\n\n if (!content || content.trim() === '') {\n return []\n }\n\n // Handle escaped newlines - unescape \\n sequences to actual newlines\n // This handles cases where SRT content is stored with escaped newlines (e.g., from JSON)\n let normalizedContent = content\n if (content.includes('\\\\n') && !content.includes('\\n')) {\n // Only unescape if we see escaped newlines but no actual newlines\n normalizedContent = content.replace(/\\\\n/g, '\\n')\n devLog.debug('Unescaped newlines in SRT content', {\n originalLength: content.length,\n normalizedLength: normalizedContent.length,\n })\n }\n\n const cues: SRTCue[] = []\n // Split by double newlines to get individual cues\n const cueBlocks = normalizedContent.split(/\\n\\s*\\n/)\n\n for (const block of cueBlocks) {\n const lines = block.trim().split('\\n')\n if (lines.length < 3) {\n // Need at least: number, timestamp, and one text line\n continue\n }\n\n // First line is the cue number\n const cueNumber = Number.parseInt(lines[0].trim(), 10)\n if (Number.isNaN(cueNumber)) {\n devLog.warn('Invalid SRT cue number', { line: lines[0] })\n continue\n }\n\n // Second line is the timestamp\n const timestampLine = lines[1].trim()\n const timestampMatch = timestampLine.match(/^(\\d{2}:\\d{2}:\\d{2}[,.]\\d{3})\\s*-->\\s*(\\d{2}:\\d{2}:\\d{2}[,.]\\d{3})$/)\n if (!timestampMatch) {\n devLog.warn('Invalid SRT timestamp format', { line: timestampLine })\n continue\n }\n\n // Normalize timestamps (convert periods to commas for consistency)\n const startTime = timestampMatch[1].replace('.', ',')\n const endTime = timestampMatch[2].replace('.', ',')\n\n // Remaining lines are the text content\n const text = lines.slice(2).join('\\n').trim()\n\n if (text) {\n cues.push({\n index: cueNumber,\n startTime,\n endTime,\n text,\n })\n }\n }\n\n devLog.debug('parseSRTContent - output:', {\n cueCount: cues.length,\n firstCue: cues.length > 0 ? cues[0] : null,\n })\n\n return cues\n}\n\n/**\n * Serialize Wistia captions to compact text format for translation\n * @param cues - Array of parsed SRT cues\n * @returns Compact string with indexed text content\n */\nexport function serializeWistiaCaptionText(cues: SRTCue[]): string {\n devLog.debug('serializeWistiaCaptionText - input:', {\n cuesCount: cues.length,\n firstCue: cues.length > 0 ? cues[0] : null,\n })\n\n if (!Array.isArray(cues) || cues.length === 0) {\n return ''\n }\n\n // Use a compact format: [index] text\n // Note: We use the cue's index (from SRT numbering) for matching\n // Escape newlines in text to prevent them from breaking the line-by-line parsing\n const lines: string[] = cues.map((cue) => {\n devLog.debug(`Processing cue ${cue.index}:`, {\n text: cue.text,\n hasText: typeof cue.text === 'string',\n })\n // Replace actual newlines with escaped newlines to preserve multi-line cues\n const escapedText = cue.text.replace(/\\n/g, '\\\\n')\n return `[${cue.index}] ${escapedText}`\n })\n\n const result = lines.join('\\n')\n\n devLog.debug('serializeWistiaCaptionText - output:', {\n resultLength: result.length,\n resultPreview: result.substring(0, 100) + (result.length > 100 ? '...' : ''),\n })\n\n return result\n}\n\n/**\n * Deserialize compact text format and merge with preserved timing metadata\n * @param translationText - Compact string with translated text\n * @param srtCuesMetadata - Array of original SRT cue timing metadata\n * @returns Reconstructed SRT format string\n */\nexport function deserializeWistiaCaptionText(\n translationText: string,\n srtCuesMetadata: Array<{ index: number; startTime: string; endTime: string }>,\n): string {\n devLog.debug('deserializeWistiaCaptionText - input:', {\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 100) + (translationText.length > 100 ? '...' : ''),\n metadataCount: srtCuesMetadata.length,\n metadataExample: srtCuesMetadata.length > 0 ? srtCuesMetadata[0] : null,\n })\n\n if (!translationText || translationText.trim() === '') {\n return ''\n }\n\n // Parse compact format: [index] text\n // Handle multi-line cues by restoring escaped newlines and continuation lines\n const result: string[] = []\n const lines = translationText.split('\\n')\n const lineRegex = /^\\[(\\d+)\\]\\s*(.*)$/\n\n // Create a map of metadata by index for quick lookup\n const metadataByIndex = new Map<number, { startTime: string; endTime: string }>()\n for (const metadata of srtCuesMetadata) {\n metadataByIndex.set(metadata.index, { startTime: metadata.startTime, endTime: metadata.endTime })\n }\n\n let currentIndex: number | null = null\n let currentTextParts: string[] = []\n\n for (const line of lines) {\n const match = line.match(lineRegex)\n\n if (match) {\n // Found a new caption - process the previous one if it exists\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = metadataByIndex.get(currentIndex)\n\n if (metadata) {\n result.push(`${currentIndex}`)\n result.push(`${metadata.startTime} --> ${metadata.endTime}`)\n result.push(text)\n result.push('') // Empty line between cues\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n }\n }\n\n // Start new caption\n currentIndex = Number.parseInt(match[1], 10)\n currentTextParts = [match[2]]\n } else if (currentIndex !== null) {\n // Continuation line for current caption (handles cases where translation service converted \\n to actual newlines)\n currentTextParts.push(line)\n } else {\n // Line doesn't match format and we're not in a caption - skip it\n devLog.warn('Failed to parse caption line', { line })\n }\n }\n\n // Process the last caption\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = metadataByIndex.get(currentIndex)\n\n if (metadata) {\n result.push(`${currentIndex}`)\n result.push(`${metadata.startTime} --> ${metadata.endTime}`)\n result.push(text)\n result.push('') // Empty line between cues\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n }\n }\n\n // Remove trailing empty line\n if (result.length > 0 && result[result.length - 1] === '') {\n result.pop()\n }\n\n const reconstructed = result.join('\\n')\n\n devLog.debug('deserializeWistiaCaptionText - output:', {\n resultLength: reconstructed.length,\n resultPreview: reconstructed.substring(0, 200) + (reconstructed.length > 200 ? '...' : ''),\n })\n\n return reconstructed\n}\n\n/**\n * Attempts to find the language field in a document.\n * Tries common field names: 'language', 'lang', 'locale'\n * @param document - The document to search\n * @returns The language value if found, undefined otherwise\n */\nfunction getDocumentLanguage(document: Record<string, unknown>): string | undefined {\n if (!document || typeof document !== 'object') {\n return undefined\n }\n\n // Try common language field names\n const languageFieldNames = ['language', 'lang', 'locale']\n for (const fieldName of languageFieldNames) {\n if (fieldName in document && typeof document[fieldName] === 'string') {\n return document[fieldName]\n }\n }\n\n return undefined\n}\n","import { definePlugin } from 'sanity'\nimport { getTranslateAction } from './components/actions'\nimport { translationGlossaryTerm } from './schemas/translationGlossaryTerm.schema'\nimport { translationHistory } from './schemas/translationHistory.schema'\nimport { translationSettings } from './schemas/translationSettings.schema'\nimport type { PluginConfig } from './types'\nimport { devLog } from './utils/common/devLog'\nimport { globalTransformRegistry } from './utils/transforms/transformRegistry'\n\nexport type {\n AnyFieldTransform,\n FieldMatcher,\n FieldTransform,\n InferOriginalType,\n InferTranslatableType,\n PluginConfig,\n TransformContext,\n} from './types'\n\n/**\n * Field exclusion utilities for preventing specific fields from being translated\n *\n * @public\n */\nexport {\n COMMON_ENUM_FIELDS,\n type FilteredDocument,\n filterExcludedFields,\n PREDEFINED_EXCLUSIONS,\n restoreExcludedFields,\n} from './utils/document/excludeFields'\n\n/**\n * AI translation plugin for Sanity Studio\n * See the PluginConfig export for configuration options\n *\n * @public\n */\nexport const aiTranslationPlugin = definePlugin<PluginConfig>((config) => {\n // Register custom transforms provided by plugin consumer\n if (config.fieldTransforms?.length) {\n devLog.info(`Registering ${config.fieldTransforms.length} custom field transforms`)\n config.fieldTransforms.forEach((transform) => {\n globalTransformRegistry.register(transform)\n })\n }\n\n return {\n name: 'sanity-plugin-ai-translation',\n document: {\n actions: [getTranslateAction(config)],\n },\n schema: {\n types: [translationSettings, translationGlossaryTerm],\n },\n }\n})\n\n/**\n * Schema for translation history\n *\n * @public\n */\nexport const translationHistorySchema = translationHistory\n\n/**\n * Export types for custom transform development\n *\n * @public\n */\n// Types are exported at the top of the file with additional helper types\n\n/**\n * Built-in field transform for Portable Text (rich text) content\n * Converts complex rich text structures to XML-like format for translation\n *\n * @public\n */\nexport { richTextTransform } from './utils/transforms/builtIn/richTextTransform'\n\n/**\n * Built-in field transform for VTT video captions\n * Converts VTT caption arrays to a compact format for translation\n * while preserving timing metadata\n *\n * @public\n */\nexport { vttCaptionTransform } from './utils/transforms/builtIn/vttCaptionTransform'\n\n/**\n * Built-in field transform for Wistia captions\n * Converts Wistia caption objects with SRT format content to a compact format for translation\n * while preserving timing metadata and updating isoLanguage based on document language\n *\n * @public\n */\nexport { wistiaCaptionTransform } from './utils/transforms/builtIn/wistiaCaptionTransform'\n"],"names":["translationSettings","arrayMatch","originalStructure","text","buildTranslationPrompt","result"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/common/devLog.ts","../src/utils/openai/validateApiKey.ts","../src/utils/document/undraftId.ts","../src/utils/state/activeTranslations.ts","../src/hooks/useTranslationContext/TranslationContext.ts","../src/hooks/useTranslationContext/translationReducer.ts","../src/hooks/useTranslationContext/TranslationContextProvider.tsx","../src/hooks/useTranslationContext/useTranslationContext.ts","../src/utils/metadata/getExistingTranslationId.ts","../src/utils/metadata/constants.ts","../src/utils/metadata/getTranslationMetadata.ts","../src/utils/releases/checkReleaseConflicts.ts","../src/utils/common/retry.ts","../src/utils/common/Semaphore.ts","../src/utils/document/documentToPlainText.ts","../src/utils/matching/matchTermInDocument.ts","../src/utils/matching/fetchTranslationPrompts.ts","../src/utils/common/findKeyValues/findKeyValues.ts","../src/utils/references/localizeReferences.ts","../src/utils/references/updateBackwardReferences.ts","../src/utils/state/setupTranslationJobStatuses.ts","../src/utils/telemetry/telemetry.ts","../src/utils/metadata/updateTranslationMetadata.ts","../src/utils/state/setJobStatus.ts","../src/utils/document/excludeFields.ts","../src/utils/transforms/applyTransforms.ts","../src/utils/transforms/restoreTransforms.ts","../src/utils/transforms/transformRegistry.ts","../src/utils/openai/callGPT5.ts","../src/utils/openai/deserializeDocument.ts","../src/utils/openai/serializeDocument.ts","../src/utils/openai/validateStructure.ts","../src/utils/translation/getOpenAITranslation.ts","../src/utils/document/assembleLanguageSpecs.ts","../src/utils/translation/assembleTranslationPrompt.ts","../src/utils/translation/getSanityAgentTranslation.ts","../src/utils/translation/handleTranslation.ts","../src/utils/translation/translateDocument.ts","../src/utils/translation/translateDocumentSet.ts","../src/components/ReleaseConflictWarning.tsx","../src/components/ActionButtonBar.tsx","../src/hooks/useTranslationMetadata.ts","../src/components/LanguageCheckbox.tsx","../src/components/LanguageSelectionPanel.tsx","../src/utils/references/reconcileReleaseReferences.ts","../src/components/ReconcileReferencesButton.tsx","../src/components/ReleaseSelectionPanel.tsx","../src/components/TranslationModePanel.tsx","../src/utils/references/assembleReferences.ts","../src/components/ReferenceSelectionPanel.tsx","../src/components/ReferenceConfigPanel.tsx","../src/components/WriteSettingsPanel.tsx","../src/components/TranslationConfigPanel.tsx","../src/components/TranslationStatusPanel.tsx","../src/components/TranslationDialog.tsx","../src/components/actions/TranslateAction.tsx","../src/schemas/translationGlossaryTerm.schema.ts","../src/schemas/translationHistory.schema.ts","../src/schemas/translationSettings.schema.ts","../src/utils/transforms/builtIn/richTextTransform/richTextTransform.ts","../src/utils/transforms/builtIn/vttCaptionTransform/vttCaptionTransform.ts","../src/utils/common/isoLanguage.ts","../src/utils/transforms/builtIn/wistiaCaptionTransform/wistiaCaptionTransform.ts","../src/index.ts"],"sourcesContent":["/**\n * Development logging utility for AI Translation plugin troubleshooting\n * Only logs when SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING environment variable is \"true\"\n */\n\nconst isLoggingEnabled = (): boolean => {\n return (\n process.env.NEXT_PUBLIC_SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === 'true' ||\n process.env.SANITY_STUDIO_AI_TRANSLATION_PLUGIN_LOGGING === 'true'\n )\n}\n\nexport interface DevLogContext {\n component?: string\n action?: string\n documentId?: string\n language?: string\n jobId?: string\n // biome-ignore lint/suspicious/noExplicitAny: not worth the effort\n [key: string]: any\n}\n\nexport const devLog = {\n info: (message: string, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.info('[AI Translation]', message, context ? JSON.stringify(context, null, 2) : '')\n },\n\n warn: (message: string, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.warn('[AI Translation]', message, context ? JSON.stringify(context, null, 2) : '')\n },\n\n error: (message: string, error?: Error | unknown, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.error('[AI Translation]', message, error, context ? JSON.stringify(context, null, 2) : '')\n },\n\n debug: (message: string, data?: unknown, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.debug('[AI Translation]', message, data, context ? JSON.stringify(context, null, 2) : '')\n },\n\n group: (label: string, context?: DevLogContext) => {\n if (!isLoggingEnabled()) return\n console.group(`[AI Translation] ${label}`, context ? JSON.stringify(context, null, 2) : '')\n },\n\n groupEnd: () => {\n if (!isLoggingEnabled()) return\n console.groupEnd()\n },\n\n time: (label: string) => {\n if (!isLoggingEnabled()) return\n console.time(`[AI Translation] ${label}`)\n },\n\n timeEnd: (label: string) => {\n if (!isLoggingEnabled()) return\n console.timeEnd(`[AI Translation] ${label}`)\n },\n}\n","export interface ValidationResult {\n valid: boolean\n error?: string\n modelAvailable?: string[]\n}\n\n/**\n * Validates an OpenAI API key by making a minimal API call.\n * Checks if the key is valid and if the requested model is available.\n */\nexport const validateOpenAIApiKey = async (apiKey: string, model = 'gpt-5'): Promise<ValidationResult> => {\n try {\n // Make a minimal API call to validate the key\n const response = await fetch('https://api.openai.com/v1/models', {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n },\n })\n\n if (!response.ok) {\n const error = await response.json()\n return {\n valid: false,\n error: error.error?.message || 'Invalid API key',\n }\n }\n\n const data = await response.json()\n const availableModels = data.data.map((m: { id: string }) => m.id)\n\n // Check if requested model is available\n if (!availableModels.includes(model)) {\n return {\n valid: false,\n error: `Model ${model} not available. Try: ${availableModels.slice(0, 5).join(', ')}`,\n }\n }\n\n return {\n valid: true,\n modelAvailable: availableModels,\n }\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : 'Network error',\n }\n }\n}\n","// Remove `drafts.` from a string\nexport const undraftId = (id: string) => id.replace(/^drafts\\./, '')\n\nexport const draftId = (id: string) => `drafts.${undraftId(id)}`\n","/**\n * Module-level state store for tracking active translations.\n *\n * This allows the translation dialog to reconnect to an ongoing translation\n * if the user closes and reopens it. The state persists in memory but is\n * lost on page refresh.\n */\n\nimport type { OverallTranslationStatus, TranslationJob } from '../../types'\nimport { undraftId } from '../document/undraftId'\n\nexport interface ActiveTranslationState {\n documentId: string\n releaseId?: string\n overallStatus: OverallTranslationStatus\n translationJobs: Map<string, TranslationJob>\n startedAt: Date\n}\n\ntype Subscriber = (state: ActiveTranslationState) => void\n\ninterface ActiveTranslationEntry {\n state: ActiveTranslationState\n subscribers: Set<Subscriber>\n}\n\n/**\n * Map of source document ID -> active translation state\n * This is the module-level store that persists across dialog open/close cycles\n */\nconst activeTranslations = new Map<string, ActiveTranslationEntry>()\n\n/**\n * Register a new active translation for a document.\n * Called when translation starts.\n */\nexport const registerActiveTranslation = (documentId: string, releaseId?: string): void => {\n const entry: ActiveTranslationEntry = {\n state: {\n documentId,\n releaseId,\n overallStatus: 'translating',\n translationJobs: new Map(),\n startedAt: new Date(),\n },\n subscribers: new Set(),\n }\n activeTranslations.set(documentId, entry)\n}\n\n/**\n * Update the overall status of an active translation.\n */\nexport const updateActiveTranslationStatus = (documentId: string, status: OverallTranslationStatus): void => {\n const entry = activeTranslations.get(documentId)\n if (entry) {\n entry.state.overallStatus = status\n notifySubscribers(entry)\n }\n}\n\n/**\n * Update a specific job within an active translation.\n * Merges with existing job data to preserve fields like title and _type\n * that are set during initial job setup but not included in status updates.\n */\nexport const updateActiveTranslationJob = (documentId: string, job: TranslationJob): void => {\n const entry = activeTranslations.get(documentId)\n if (entry) {\n // Create a composite key for the job (documentId + language)\n // IMPORTANT: Must match the key format in translationReducer.ts\n // Normalize document ID to handle draft vs published versions consistently\n const normalizedId = undraftId(job._id)\n const jobKey = `${normalizedId}_${job.language}`\n\n // Merge with existing job to preserve title, _type, etc.\n // Use the same merge strategy as translationReducer: only overwrite with defined values\n const existingJob = entry.state.translationJobs.get(jobKey)\n // Filter out undefined values from job to prevent overwriting existing values\n // This must be done for both new and existing jobs to maintain consistency\n const jobWithDefinedValues = Object.fromEntries(\n Object.entries(job).filter(([_, value]) => value !== undefined),\n ) as TranslationJob\n const mergedJob = existingJob ? { ...existingJob, ...jobWithDefinedValues } : jobWithDefinedValues\n\n entry.state.translationJobs.set(jobKey, mergedJob)\n notifySubscribers(entry)\n }\n}\n\n/**\n * Get the current state of an active translation, if one exists.\n */\nexport const getActiveTranslation = (documentId: string): ActiveTranslationState | null => {\n const entry = activeTranslations.get(documentId)\n return entry?.state ?? null\n}\n\n/**\n * Check if there's an active (in-progress) translation for a document.\n */\nexport const hasActiveTranslation = (documentId: string): boolean => {\n const entry = activeTranslations.get(documentId)\n if (!entry) return false\n\n // Only consider it \"active\" if it's still in progress\n const inProgressStatuses: OverallTranslationStatus[] = ['translating', 'linking_references']\n return inProgressStatuses.includes(entry.state.overallStatus)\n}\n\n/**\n * Subscribe to updates for an active translation.\n * Returns an unsubscribe function.\n */\nexport const subscribeToActiveTranslation = (documentId: string, callback: Subscriber): (() => void) => {\n const entry = activeTranslations.get(documentId)\n if (!entry) {\n return () => {} // No-op unsubscribe if no active translation\n }\n\n entry.subscribers.add(callback)\n\n // Immediately call with current state\n callback(entry.state)\n\n // Return unsubscribe function\n return () => {\n entry.subscribers.delete(callback)\n }\n}\n\n/**\n * Clean up a completed translation after a delay.\n * We keep it around briefly so the dialog can show the final state.\n */\nexport const cleanupActiveTranslation = (documentId: string, delayMs = 60000): void => {\n setTimeout(() => {\n const entry = activeTranslations.get(documentId)\n // Only cleanup if still completed/error (not restarted)\n if (entry && ['completed', 'error'].includes(entry.state.overallStatus)) {\n activeTranslations.delete(documentId)\n }\n }, delayMs)\n}\n\n/**\n * Notify all subscribers of a state change.\n */\nconst notifySubscribers = (entry: ActiveTranslationEntry): void => {\n for (const subscriber of entry.subscribers) {\n try {\n subscriber(entry.state)\n } catch (error) {\n // Don't let one subscriber's error affect others\n console.error('Error in active translation subscriber:', error)\n }\n }\n}\n\n/**\n * Force cleanup of an active translation immediately (for testing).\n */\nexport const forceCleanupActiveTranslation = (documentId: string): void => {\n activeTranslations.delete(documentId)\n}\n","import { createContext } from 'react'\nimport type { TranslationJob, TranslationContext as TTranslationContext } from '../../types'\n\nexport const TranslationContext = createContext<TTranslationContext>({\n document: null,\n config: null,\n overallStatus: 'idle',\n selectedTargetLanguages: [],\n translationJobs: new Map<string, TranslationJob>(),\n shouldTranslateReferences: false,\n maxReferenceDepth: 1,\n references: [],\n selectedReferenceIds: new Set(),\n translationMetadata: null,\n translationMode: 'sanity-only',\n openaiValidated: false,\n releaseConfig: {\n mode: 'new',\n newReleaseTitle: '',\n },\n reconcilingStatus: 'idle',\n reconciliationProgress: undefined,\n dispatch: () => {},\n})\n","import defaults from 'lodash.defaults'\nimport type { TranslationAction, TranslationContext } from '../../types'\nimport { devLog } from '../../utils/common/devLog'\nimport { undraftId } from '../../utils/document/undraftId'\n\nexport const translationReducer = (\n context: Omit<TranslationContext, 'dispatch'>,\n action: TranslationAction,\n): Omit<TranslationContext, 'dispatch'> => {\n const logContext = {\n documentId: context.document?._id,\n actionType: action.type,\n component: 'translationReducer',\n }\n\n devLog.debug('Translation action dispatched', {\n ...logContext,\n payload: 'payload' in action ? action.payload : undefined,\n })\n\n switch (action.type) {\n case 'SET_OVERALL_STATUS':\n return { ...context, overallStatus: action.payload }\n\n case 'SET_TRANSLATION_METADATA':\n return { ...context, translationMetadata: action.payload }\n\n case 'SET_SELECTED_TARGET_LANGUAGES':\n return {\n ...context,\n selectedTargetLanguages: action.payload,\n }\n\n case 'SET_TRANSLATION_MODE':\n return {\n ...context,\n translationMode: action.payload,\n }\n\n case 'SET_OPENAI_VALIDATED':\n return {\n ...context,\n openaiValidated: action.payload,\n }\n\n case 'SET_SHOULD_TRANSLATE_REFERENCES':\n return {\n ...context,\n shouldTranslateReferences: action.payload,\n }\n\n case 'SET_MAX_REFERENCE_DEPTH':\n return {\n ...context,\n maxReferenceDepth: action.payload,\n }\n\n case 'SET_REFERENCES':\n return {\n ...context,\n references: action.payload,\n }\n\n case 'SET_SELECTED_REFERENCE_IDS':\n return {\n ...context,\n selectedReferenceIds: action.payload,\n }\n\n case 'SET_TRANSLATION_JOB_STATUS': {\n // Key combines document ID + language to support concurrent translations\n // Normalize document ID to handle draft vs published versions consistently\n if (!action.payload._id) {\n devLog.warn('SET_TRANSLATION_JOB_STATUS action missing _id', {\n ...logContext,\n payload: action.payload,\n })\n return context\n }\n const normalizedId = undraftId(action.payload._id)\n const jobKey = `${normalizedId}_${action.payload.language}`\n\n // New Map instance required for React to detect state change\n const newTranslationJobs = new Map(context.translationJobs)\n\n const existingJob = newTranslationJobs.get(jobKey)\n const mergedJob = defaults(action.payload, existingJob)\n\n newTranslationJobs.set(jobKey, mergedJob)\n\n return {\n ...context,\n translationJobs: newTranslationJobs,\n }\n }\n\n case 'SET_RELEASE_CONFIG':\n return {\n ...context,\n releaseConfig: {\n ...context.releaseConfig,\n ...action.payload,\n },\n }\n\n case 'RESET_TRANSLATION_STATE':\n devLog.info('Resetting translation state for new translation', {\n ...logContext,\n previousStatus: context.overallStatus,\n previousJobCount: context.translationJobs.size,\n })\n return {\n ...context,\n overallStatus: 'idle',\n translationJobs: new Map(),\n selectedReferenceIds: new Set(),\n // Clear any release conflicts from previous run\n releaseConfig: {\n ...context.releaseConfig,\n conflicts: undefined,\n replaceExisting: undefined,\n skipConflicts: undefined,\n },\n // Reset reconciliation state\n reconcilingStatus: 'idle',\n reconciliationProgress: undefined,\n }\n\n case 'SET_RECONCILING_STATUS':\n return {\n ...context,\n reconcilingStatus: action.payload,\n // Clear progress when returning to idle or on error\n reconciliationProgress:\n action.payload === 'idle' || action.payload === 'error' ? undefined : context.reconciliationProgress,\n }\n\n case 'SET_RECONCILIATION_PROGRESS':\n return {\n ...context,\n reconciliationProgress: action.payload,\n }\n\n default:\n return context\n }\n}\n","import { type FC, useEffect, useReducer, useRef } from 'react'\nimport type { PluginConfig, TranslatableDocument, TranslationContext as TTranslationContext } from '../../types'\nimport { devLog } from '../../utils/common/devLog'\nimport { validateOpenAIApiKey } from '../../utils/openai/validateApiKey'\nimport {\n type ActiveTranslationState,\n getActiveTranslation,\n subscribeToActiveTranslation,\n} from '../../utils/state/activeTranslations'\nimport { TranslationContext } from './TranslationContext'\nimport { translationReducer } from './translationReducer'\n\ninterface TranslationContextProviderProps {\n document: TranslatableDocument\n config: PluginConfig\n children: React.ReactNode\n}\n\n/**\n * Generates a default release title based on the document being translated.\n */\nexport const generateDefaultReleaseTitle = (document: TranslatableDocument): string => {\n const documentTitle =\n (document.title as string | undefined) ||\n (document.name as string | undefined) ||\n document._id.split('-').slice(0, 2).join('-')\n\n return `[AI Translation] ${documentTitle}`\n}\n\nexport const TranslationContextProvider: FC<TranslationContextProviderProps> = ({ document, config, children }) => {\n devLog.debug('TranslationContextProvider initialized', {\n documentId: document._id,\n documentType: document._type,\n supportedLanguages: config.supportedLanguages?.map((l) => l.id),\n component: 'TranslationContextProvider',\n })\n\n const defaultReleaseTitle = generateDefaultReleaseTitle(document)\n\n // Check if there's an active translation we can reconnect to\n const activeTranslation = getActiveTranslation(document._id)\n\n const initialContext: Omit<TTranslationContext, 'dispatch'> = {\n document,\n config,\n // If there's an active translation, restore its state\n overallStatus: activeTranslation?.overallStatus ?? 'idle',\n translationMetadata: null,\n selectedTargetLanguages: [],\n shouldTranslateReferences: false,\n maxReferenceDepth: 1,\n references: [],\n selectedReferenceIds: new Set(),\n // Restore jobs from active translation if available\n translationJobs: activeTranslation?.translationJobs ?? new Map(),\n translationMode: config?.openai?.defaultMode || 'sanity-only',\n openaiValidated: false,\n releaseConfig: {\n mode: 'new',\n newReleaseTitle: defaultReleaseTitle,\n },\n // Reconciliation state for existing releases\n reconcilingStatus: 'idle',\n reconciliationProgress: undefined,\n }\n\n if (activeTranslation) {\n devLog.info('Reconnecting to active translation', {\n documentId: document._id,\n status: activeTranslation.overallStatus,\n jobCount: activeTranslation.translationJobs.size,\n component: 'TranslationContextProvider',\n })\n }\n\n const [context, dispatch] = useReducer(translationReducer, initialContext)\n\n // Track the last reference IDs we processed to detect actual changes\n const lastProcessedRefsRef = useRef<string>('')\n\n // Subscribe to active translation updates so dialog stays in sync\n useEffect(() => {\n const handleActiveTranslationUpdate = (state: ActiveTranslationState) => {\n // Update overall status\n dispatch({\n type: 'SET_OVERALL_STATUS',\n payload: state.overallStatus,\n })\n\n // Update each job\n for (const job of state.translationJobs.values()) {\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload: job,\n })\n }\n }\n\n const unsubscribe = subscribeToActiveTranslation(document._id, handleActiveTranslationUpdate)\n\n return () => {\n unsubscribe()\n }\n }, [document._id])\n\n // Verify OpenAI key validity on mount to enable/disable OpenAI modes in UI\n useEffect(() => {\n const validateKey = async () => {\n if (config?.openai?.enabled && config?.openai?.apiKey) {\n try {\n const validation = await validateOpenAIApiKey(config.openai.apiKey, config.openai.model || 'gpt-5')\n\n dispatch({\n type: 'SET_OPENAI_VALIDATED',\n payload: validation.valid,\n })\n\n if (validation.valid) {\n devLog.info('OpenAI API key validated successfully', {\n model: config.openai.model || 'gpt-5',\n component: 'TranslationContextProvider',\n })\n } else {\n devLog.error('OpenAI API key validation failed', {\n error: validation.error,\n component: 'TranslationContextProvider',\n })\n }\n } catch (error) {\n devLog.error('OpenAI validation error', error instanceof Error ? error : new Error(String(error)), {\n component: 'TranslationContextProvider',\n })\n dispatch({\n type: 'SET_OPENAI_VALIDATED',\n payload: false,\n })\n }\n }\n }\n\n validateKey()\n }, [config?.openai?.apiKey, config?.openai?.enabled, config?.openai?.model])\n\n // Auto-select all references when references array changes (default behavior)\n // Only auto-select when references are first loaded or when new references are added\n // This prevents the effect from interfering with user's manual selections\n useEffect(() => {\n // Create a stable string representation of current reference IDs for comparison\n const currentRefsKey = context.references\n .map((ref) => ref._id)\n .sort()\n .join(',')\n\n // Only process if references actually changed (not just selection)\n if (currentRefsKey !== lastProcessedRefsRef.current) {\n if (context.references.length > 0) {\n const currentReferenceIds = new Set(context.references.map((ref) => ref._id))\n const previousReferenceIds = new Set(\n lastProcessedRefsRef.current ? lastProcessedRefsRef.current.split(',') : [],\n )\n\n // Check if there are any new references that weren't in the previous set\n const hasNewReferences = Array.from(currentReferenceIds).some((id) => !previousReferenceIds.has(id))\n\n // Only auto-select if selection is empty (initial load) or there are new references\n // We read selectedReferenceIds here but the effect won't re-run when it changes\n // because we only update lastProcessedRefsRef when references actually change\n const currentSelection = context.selectedReferenceIds\n const shouldAutoSelect = currentSelection.size === 0 || hasNewReferences\n\n if (shouldAutoSelect) {\n // Preserve existing selections and add any new references\n const newSelection = new Set(currentSelection)\n currentReferenceIds.forEach((id) => {\n newSelection.add(id)\n })\n\n // Only dispatch if selection actually changed\n const currentIds = Array.from(currentSelection).sort()\n const newIds = Array.from(newSelection).sort()\n if (currentIds.length !== newIds.length || !currentIds.every((id, i) => id === newIds[i])) {\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n }\n }\n\n // Update the ref to track what we've processed\n lastProcessedRefsRef.current = currentRefsKey\n }\n }, [context.references, context.selectedReferenceIds])\n\n devLog.debug('Translation context state initialized', {\n documentId: document._id,\n overallStatus: context.overallStatus,\n selectedLanguages: context.selectedTargetLanguages,\n translationMode: context.translationMode,\n openaiEnabled: config?.openai?.enabled,\n component: 'TranslationContextProvider',\n })\n\n return <TranslationContext.Provider value={{ ...context, dispatch }}>{children}</TranslationContext.Provider>\n}\n","import { useContext } from 'react'\nimport type { TranslationContext as TTranslationContext } from '../../types'\nimport { TranslationContext } from './TranslationContext'\n\ninterface ValidatedTranslationContext extends TTranslationContext {\n document: NonNullable<TTranslationContext['document']>\n config: NonNullable<TTranslationContext['config']>\n}\n\nexport const useTranslationContext = (): ValidatedTranslationContext => {\n const context = useContext(TranslationContext)\n\n if (!context.document || !context.config) {\n throw new Error(\n 'useTranslationContext must be used within a TranslationContextProvider with document and config props',\n )\n }\n\n return context as ValidatedTranslationContext\n}\n","import type { PluginConfig, TranslationMetadataDocument } from '../../types'\n\nexport const getExistingTranslationId = (\n translationMetadata: TranslationMetadataDocument,\n targetLanguage: PluginConfig['supportedLanguages'][number]['id'],\n): string | null => {\n if (!translationMetadata?.translations?.length) {\n return null\n }\n\n const existingTranslation = translationMetadata.translations.find(\n (translation) => translation._key === targetLanguage,\n )\n\n return existingTranslation?.value._ref || null\n}\n","/**\n * Schema type name for translation metadata documents\n */\nexport const TRANSLATION_METADATA_TYPE = 'translation.metadata'\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument, TranslationMetadataDocument } from '../../types'\nimport { undraftId } from '../document/undraftId'\nimport { TRANSLATION_METADATA_TYPE } from './constants'\n\ninterface PrepareTranslationMetadataParams {\n client: SanityClient\n sourceDocument: TranslatableDocument\n sourceLanguage: string\n}\n\nexport const getTranslationMetadata = async ({\n client,\n sourceDocument,\n sourceLanguage,\n}: PrepareTranslationMetadataParams): Promise<TranslationMetadataDocument> => {\n try {\n const existingDoc = await client.fetch<TranslationMetadataDocument | null>(\n '*[_type == $tmType && references($sourceDocumentId)][0]',\n {\n tmType: TRANSLATION_METADATA_TYPE,\n sourceDocumentId: undraftId(sourceDocument._id),\n },\n )\n\n if (existingDoc) {\n return existingDoc\n }\n\n const newDoc = (await client.create({\n _type: TRANSLATION_METADATA_TYPE,\n translations: [\n {\n _key: sourceLanguage,\n _type: 'internationalizedArrayReferenceValue',\n value: {\n _type: 'reference',\n _ref: undraftId(sourceDocument._id),\n _weak: true,\n _strengthenOnPublish: {\n _type: sourceDocument._type,\n _weak: false,\n },\n },\n },\n ],\n translationHistory: [],\n })) as TranslationMetadataDocument\n\n return newDoc\n } catch (error) {\n console.error(sourceDocument._id, 'Error creating translation metadata:', error)\n throw error\n }\n}\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { getExistingTranslationId } from '../metadata/getExistingTranslationId'\nimport { getTranslationMetadata } from '../metadata/getTranslationMetadata'\nimport type { AssembleReferencesResult } from '../references/assembleReferences'\n\nexport interface ReleaseConflict {\n sourceDocumentId: string\n sourceDocumentTitle?: string\n sourceDocumentType?: string\n targetLanguage: string\n existingVersionId: string\n}\n\nexport interface CheckReleaseConflictsParams {\n client: SanityClient\n releaseId: string\n sourceDocument: TranslatableDocument\n targetLanguages: string[]\n languageFieldName: string\n /** Referenced documents that will also be translated */\n references?: AssembleReferencesResult\n}\n\n/**\n * Checks a single document for conflicts in the specified release.\n */\nconst checkDocumentConflicts = async ({\n client,\n releaseId,\n documentId,\n documentTitle,\n documentType,\n targetLanguages,\n languageFieldName,\n}: {\n client: SanityClient\n releaseId: string\n documentId: string\n documentTitle?: string\n documentType?: string\n targetLanguages: string[]\n languageFieldName: string\n}): Promise<ReleaseConflict[]> => {\n const conflicts: ReleaseConflict[] = []\n\n // Fetch the document to get translation metadata\n const document = await client.withConfig({ perspective: 'drafts' }).getDocument<TranslatableDocument>(documentId)\n\n if (!document) {\n return conflicts\n }\n\n const sourceLanguage = document[languageFieldName] as string\n\n // Get translation metadata to find existing translation IDs\n const translationMetadata = await getTranslationMetadata({\n client,\n sourceDocument: document,\n sourceLanguage,\n })\n\n // Check each target language for existing versions in the release\n for (const targetLanguage of targetLanguages) {\n const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage)\n\n if (existingTranslationId) {\n // Check if a version of this document exists in the release\n const versionedId = `versions.${releaseId}.${existingTranslationId}`\n\n const existingVersion = await client.getDocument(versionedId)\n\n if (existingVersion) {\n conflicts.push({\n sourceDocumentId: documentId,\n sourceDocumentTitle: documentTitle || (document.title as string) || (document.name as string),\n sourceDocumentType: documentType || document._type,\n targetLanguage,\n existingVersionId: versionedId,\n })\n\n devLog.debug('Found existing version in release', {\n releaseId,\n sourceDocumentId: documentId,\n targetLanguage,\n versionedId,\n })\n }\n }\n }\n\n return conflicts\n}\n\n/**\n * Checks if any target translation documents already exist in the specified release.\n * Returns a list of conflicts that would occur if the translation proceeds.\n * Also checks referenced documents if they are included in the translation.\n */\nexport const checkReleaseConflicts = async ({\n client,\n releaseId,\n sourceDocument,\n targetLanguages,\n languageFieldName,\n references = [],\n}: CheckReleaseConflictsParams): Promise<ReleaseConflict[]> => {\n const allConflicts: ReleaseConflict[] = []\n\n try {\n // Build list of all documents to check (source + references)\n const documentsToCheck = [\n {\n _id: sourceDocument._id,\n _type: sourceDocument._type,\n title: (sourceDocument.title || sourceDocument.name) as string | undefined,\n },\n ...references.map((ref) => ({\n _id: ref._id,\n _type: ref._type,\n title: ref.title,\n })),\n ]\n\n // Check all documents in parallel\n const conflictChecks = await Promise.all(\n documentsToCheck.map((doc) =>\n checkDocumentConflicts({\n client,\n releaseId,\n documentId: doc._id,\n documentTitle: doc.title,\n documentType: doc._type,\n targetLanguages,\n languageFieldName,\n }),\n ),\n )\n\n // Flatten all conflicts\n for (const conflicts of conflictChecks) {\n allConflicts.push(...conflicts)\n }\n\n devLog.debug('Release conflict check complete', {\n releaseId,\n documentsChecked: documentsToCheck.length,\n conflictsFound: allConflicts.length,\n })\n } catch (error) {\n devLog.error('Error checking release conflicts', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n sourceDocumentId: sourceDocument._id,\n targetLanguages,\n referencesCount: references.length,\n })\n // Don't throw - return empty conflicts and let the translation proceed\n // The actual error will be caught during translation\n }\n\n return allConflicts\n}\n","import { devLog } from './devLog'\n\n/**\n * Error classification utilities for determining retry strategies\n */\n\n/**\n * Checks if an error is a network error that should be retried immediately\n */\nexport function isNetworkError(error: unknown): boolean {\n if (!error) return false\n\n // Check for fetch TypeError (network failures)\n if (error instanceof TypeError) {\n const message = error.message.toLowerCase()\n return (\n message.includes('fetch') ||\n message.includes('network') ||\n message.includes('connection') ||\n message.includes('failed to fetch') ||\n message.includes('networkerror')\n )\n }\n\n // Check for Node.js network error codes\n if (error && typeof error === 'object' && 'code' in error) {\n const code = (error as { code: string }).code\n return ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED', 'EHOSTUNREACH'].includes(code)\n }\n\n // Check for HTTP error responses\n if (error && typeof error === 'object' && 'response' in error) {\n const response = (error as { response: { status?: number } }).response\n if (response?.status) {\n // Retry on 5xx server errors and 429 rate limiting\n return response.status >= 500 || response.status === 429\n }\n }\n\n // Check for HTTP status in error message\n if (error instanceof Error) {\n const message = error.message.toLowerCase()\n // Check for 5xx or 429 in error message\n if (/\\b(50[0-9]|429)\\b/.test(message)) {\n return true\n }\n // Check for rate limit error messages\n if (message.includes('rate limit')) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Checks if an error is a Sanity API mutation error\n * These errors typically indicate invalid document structure or schema violations\n */\nexport function isSanityMutationError(error: unknown): boolean {\n if (!error) return false\n\n if (error instanceof Error) {\n const message = error.message.toLowerCase()\n\n // Common Sanity error patterns\n const sanityErrorPatterns = [\n 'validation',\n 'schema',\n 'invalid document',\n 'invalid field',\n 'type mismatch',\n 'reference',\n 'patch',\n 'mutation',\n 'document structure',\n 'required field',\n 'field type',\n 'sanity',\n ]\n\n return sanityErrorPatterns.some((pattern) => message.includes(pattern))\n }\n\n // Check for Sanity-specific error structure\n if (error && typeof error === 'object') {\n const errorObj = error as Record<string, unknown>\n // Sanity errors often have these properties\n if ('statusCode' in errorObj || 'response' in errorObj || 'details' in errorObj) {\n // Check if it's a client error (4xx) that's not a network issue\n if ('statusCode' in errorObj) {\n const statusCode = errorObj.statusCode as number\n if (statusCode >= 400 && statusCode < 500 && statusCode !== 429) {\n return true\n }\n }\n }\n }\n\n return false\n}\n\n/**\n * Checks if an error is an OpenAI API error\n */\nexport function isOpenAIError(error: unknown): boolean {\n if (!error) return false\n\n if (error instanceof Error) {\n const message = error.message.toLowerCase()\n return message.includes('openai') || message.includes('gpt-5 api error') || message.includes('api error')\n }\n\n return false\n}\n\n/**\n * Configuration options for retry logic\n */\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 2) */\n maxAttempts?: number\n /** Initial delay in milliseconds before first retry (default: 0 for immediate) */\n initialDelayMs?: number\n /** Maximum delay in milliseconds (default: 5000) */\n maxDelayMs?: number\n /** Backoff multiplier (default: 2) */\n backoffMultiplier?: number\n /** Custom function to determine if error is retryable (default: isNetworkError) */\n retryableErrors?: (error: unknown) => boolean\n /** Context for logging */\n context?: Record<string, unknown>\n /** Whether to log retry attempts */\n logRetries?: boolean\n}\n\n/**\n * Default retry options\n */\nconst DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, 'context' | 'retryableErrors'>> & {\n retryableErrors: (error: unknown) => boolean\n} = {\n maxAttempts: 2,\n initialDelayMs: 0,\n maxDelayMs: 5000,\n backoffMultiplier: 2,\n retryableErrors: isNetworkError,\n logRetries: true,\n}\n\n/**\n * Calculates delay for exponential backoff\n */\nfunction calculateDelay(\n attempt: number,\n options: Required<Omit<RetryOptions, 'context' | 'retryableErrors'>> & {\n retryableErrors: (error: unknown) => boolean\n },\n): number {\n const delay = options.initialDelayMs * options.backoffMultiplier ** attempt\n return Math.min(delay, options.maxDelayMs)\n}\n\n/**\n * Waits for the specified number of milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Retries an operation with exponential backoff\n *\n * @param operation - The async operation to retry\n * @param options - Retry configuration options\n * @returns The result of the operation\n * @throws The last error if all retries are exhausted\n *\n * @example\n * ```typescript\n * const result = await retryWithBackoff(\n * () => fetch('https://api.example.com/data'),\n * {\n * maxAttempts: 3,\n * initialDelayMs: 100,\n * context: { documentId: 'doc123' }\n * }\n * )\n * ```\n */\nexport async function retryWithBackoff<T>(operation: () => Promise<T>, options: RetryOptions = {}): Promise<T> {\n const opts = {\n ...DEFAULT_RETRY_OPTIONS,\n ...options,\n retryableErrors: options.retryableErrors || DEFAULT_RETRY_OPTIONS.retryableErrors,\n }\n\n let lastError: unknown\n const maxAttempts = opts.maxAttempts + 1 // +1 for initial attempt\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n const result = await operation()\n if (attempt > 1 && opts.logRetries) {\n devLog.info(`Operation succeeded after ${attempt} attempts`, {\n ...opts.context,\n attempt,\n totalAttempts: attempt,\n })\n }\n return result\n } catch (error) {\n lastError = error\n\n // Check if error is retryable\n if (!opts.retryableErrors(error)) {\n if (opts.logRetries) {\n devLog.warn('Error is not retryable, aborting retries', {\n ...opts.context,\n attempt,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n throw error\n }\n\n // If this was the last attempt, throw the error\n if (attempt >= maxAttempts) {\n if (opts.logRetries) {\n devLog.error(\n `Operation failed after ${maxAttempts} attempts`,\n error instanceof Error ? error : new Error(String(error)),\n {\n ...opts.context,\n totalAttempts: maxAttempts,\n },\n )\n }\n throw error\n }\n\n // Calculate delay and wait before retrying\n const delay = calculateDelay(attempt - 1, opts)\n if (opts.logRetries) {\n devLog.warn(`Operation failed, retrying in ${delay}ms (attempt ${attempt}/${maxAttempts})`, {\n ...opts.context,\n attempt,\n totalAttempts: maxAttempts,\n delayMs: delay,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n if (delay > 0) {\n await sleep(delay)\n }\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError\n}\n","/**\n * Semaphore class for rate limiting concurrent operations\n * Ensures that only a specified number of operations can run simultaneously\n */\nexport class Semaphore {\n private permits: number\n private queue: Array<() => void> = []\n\n constructor(permits: number) {\n this.permits = permits\n }\n\n /**\n * Acquire a permit from the semaphore\n * If no permits are available, the caller will wait until one becomes available\n */\n async acquire(): Promise<void> {\n if (this.permits > 0) {\n this.permits--\n return Promise.resolve()\n }\n\n // Wait for a permit to become available\n return new Promise<void>((resolve) => {\n this.queue.push(resolve)\n })\n }\n\n /**\n * Release a permit back to the semaphore\n * If there are waiting callers, one will be woken up\n */\n release(): void {\n const next = this.queue.shift()\n if (next) {\n // Give the permit to the next waiting caller\n next()\n } else {\n // No one is waiting, increment the available permits\n this.permits++\n }\n }\n\n /**\n * Execute a function with semaphore protection\n * Automatically acquires and releases the permit\n */\n async withPermit<T>(fn: () => Promise<T>): Promise<T> {\n await this.acquire()\n try {\n return await fn()\n } finally {\n this.release()\n }\n }\n\n /**\n * Get the number of available permits\n */\n availablePermits(): number {\n return this.permits\n }\n\n /**\n * Get the number of waiting callers\n */\n queueLength(): number {\n return this.queue.length\n }\n\n /**\n * Update the number of permits\n * If increasing permits, waiting callers will be notified\n * If decreasing permits below current available, available permits will be reduced\n */\n updatePermits(newPermits: number): void {\n const currentTotal = this.permits + this.queue.length\n const difference = newPermits - currentTotal\n\n if (difference > 0) {\n // If permits increased, resolve waiting promises or add to available permits\n for (let i = 0; i < difference && this.queue.length > 0; i++) {\n const next = this.queue.shift()\n if (next) next()\n }\n // Add any remaining permits to available count\n const remainingIncrease = difference - (currentTotal - this.permits - this.queue.length)\n if (remainingIncrease > 0) {\n this.permits += remainingIncrease\n }\n } else if (difference < 0) {\n // Decreasing permits - reduce available permits (but don't interrupt running operations)\n this.permits = Math.max(0, this.permits + difference)\n }\n }\n}\n","import { type PortableTextBlock, toPlainText } from '@portabletext/react'\nimport type { TranslatableDocument } from '../../types'\n\n/**\n * Function to convert a sanity document to plain text.\n * Returns an object following these rules:\n * - All top-level fields are included.\n * - If a field is an array AND contains at least one object with `_type === 'block'`, it is converted to plain text with `toPlainText`.\n */\nexport function documentToPlainText(document?: TranslatableDocument): string {\n if (!document) return ''\n\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(document)) {\n // Check if the value is an array\n if (Array.isArray(value)) {\n // Check if the array contains at least one Portable Text block\n const hasBlockType = value.some(\n (item) => typeof item === 'object' && item !== null && '_type' in item && item._type === 'block',\n )\n\n if (hasBlockType) {\n // Convert Portable Text blocks to plain text\n result[key] = toPlainText(value as PortableTextBlock[])\n } else {\n // Keep the array as-is\n result[key] = value\n }\n } else {\n // Keep non-array values as-is\n result[key] = value\n }\n }\n\n return JSON.stringify(result)\n}\n","/**\n * Fuzzy glossary-term matching against a single source document.\n *\n * Design goals:\n * - Tokenize the source ONCE, then test many glossary terms cheaply.\n * - Order-agnostic “bag of words” matching inside a sliding window.\n * - Tolerates filler words and a small number of extra words (“window slack”).\n * - Case-insensitive, punctuation-insensitive, and diacritic-insensitive.\n * - Prefers recall (false positives are more acceptable than false negatives).\n */\n\nexport type Match =\n | { matched: true; term: string; windowStart: number; windowEnd: number; matchedRatio: number }\n | { matched: false; term: string; reason: string }\n\nexport interface MatchOptions {\n /**\n * Extra tokens allowed on top of the term length for the sliding window.\n * e.g., term has 3 tokens → default window size = 3 + windowSlack.\n * Larger slack → more forgiving (higher recall, lower precision).\n */\n windowSlack?: number\n\n /**\n * Ratio [0..1] of term tokens that must be satisfied inside a window.\n * 1.0 == all term tokens present (default).\n * 0.8 == at least 80% of term tokens present (useful for partials/variants).\n */\n minTokenMatchRatio?: number\n\n /**\n * Stop/filler words to ignore in term tokenization (e.g., “a”, “the”, “to”).\n * These are removed from the term ONLY (not from the source tokens),\n * so that “navigate using a map” still matches “navigate using the digital map”.\n */\n stopwords?: string[]\n\n /**\n * If true, we’ll also drop very short tokens from the term (length <= 2),\n * after stopword removal. Helps avoid spurious matches on tiny tokens.\n */\n dropVeryShortTermTokens?: boolean\n\n /**\n * If provided, only these characters are considered word chars in tokenization.\n * By default we use a broad “letters/digits/underscore” definition.\n */\n allowedWordCharsRegex?: RegExp\n}\n\nexport interface SourceContext {\n /** Tokenized, normalized source text. */\n tokens: string[]\n}\n\n/** Reasonable defaults tuned for translation glossary recall. */\nconst DEFAULTS: Required<MatchOptions> = {\n windowSlack: 2,\n minTokenMatchRatio: 1.0,\n stopwords: [\n // common English fillers — extend per your locales\n 'a',\n 'an',\n 'the',\n 'and',\n 'or',\n 'to',\n 'of',\n 'for',\n 'in',\n 'on',\n 'at',\n 'by',\n 'with',\n 'using',\n 'from',\n 'into',\n 'over',\n 'under',\n 'as',\n 'is',\n 'are',\n 'be',\n ],\n dropVeryShortTermTokens: true,\n allowedWordCharsRegex: /[\\p{L}\\p{N}_]+/gu, // letters/numbers/underscore (Unicode)\n}\n\n/** Normalize to improve matching across punctuation/case/diacritics. */\nfunction normalizeForMatch(s: string): string {\n // Convert to lowercase, normalize Unicode, strip diacritics, and trim\n return s\n .toLowerCase()\n .normalize('NFKD')\n .replace(/\\p{M}+/gu, '') // remove diacritic marks\n .trim()\n}\n\n/** Tokenize a string into normalized word tokens. */\nexport function tokenize(text: string, allowedWordChars = DEFAULTS.allowedWordCharsRegex): string[] {\n const norm = normalizeForMatch(text)\n // Extract “words” per allowedWordChars; fallback to split on whitespace\n const matches = norm.match(allowedWordChars)\n if (!matches) return []\n return matches\n}\n\n/** Build a SourceContext once for a given document. */\nexport function preprocessSource(\n sourceText: string,\n opts?: Pick<MatchOptions, 'allowedWordCharsRegex'>,\n): SourceContext {\n const tokens = tokenize(sourceText, opts?.allowedWordCharsRegex ?? DEFAULTS.allowedWordCharsRegex)\n return { tokens }\n}\n\n/** Prepare a term: tokenize, remove stopwords, optionally drop very short tokens. */\nfunction prepareTermTokens(term: string, opts: Required<MatchOptions>): string[] {\n const raw = tokenize(term, opts.allowedWordCharsRegex)\n const filtered = raw.filter((t) => !opts.stopwords.includes(t))\n if (!opts.dropVeryShortTermTokens) return filtered\n return filtered.filter((t) => t.length > 2)\n}\n\n/** Build a frequency map for tokens. */\nfunction freqMap(tokens: string[]): Map<string, number> {\n const m = new Map<string, number>()\n for (const t of tokens) m.set(t, (m.get(t) ?? 0) + 1)\n return m\n}\n\n/**\n * Core matcher: bag-of-words inside a sliding window.\n * We only count tokens that appear in the (prepared) term.\n *\n * This function can be used for matching glossary terms, protected phrases,\n * and other text patterns within documents.\n */\nexport function matchTermInDocument(term: string, source: SourceContext, options?: MatchOptions): Match {\n const opts: Required<MatchOptions> = { ...DEFAULTS, ...(options ?? {}) }\n\n const termTokens = prepareTermTokens(term, opts)\n if (termTokens.length === 0) {\n return { matched: false, term, reason: 'No substantive tokens after filtering.' }\n }\n\n const termFreq = freqMap(termTokens)\n const requiredCount = Math.ceil(termTokens.length * opts.minTokenMatchRatio)\n const windowSize = Math.max(1, termTokens.length + opts.windowSlack)\n\n // Cache matching tokens to minimize window calculations\n const interesting = new Set(termFreq.keys())\n\n // Sliding window counts ONLY for tokens that are in termFreq.\n const windowCounts = new Map<string, number>()\n let satisfiedCount = 0 // sum over tokens: min(windowCounts[token], termFreq[token])\n\n // Helper to add a token to the window (if interesting), updating satisfiedCount.\n const addToken = (tok: string) => {\n if (!interesting.has(tok)) return\n const before = windowCounts.get(tok) ?? 0\n const after = before + 1\n windowCounts.set(tok, after)\n // biome-ignore lint/style/noNonNullAssertion: this is safe\n const cap = termFreq.get(tok)!\n // Count token matches up to term's frequency requirement only\n if (before < cap) satisfiedCount += 1\n }\n\n // Helper to remove a token from the window, updating satisfiedCount.\n const removeToken = (tok: string) => {\n if (!interesting.has(tok)) return\n const before = windowCounts.get(tok) ?? 0\n if (before === 0) return\n const after = before - 1\n windowCounts.set(tok, after)\n\n // biome-ignore lint/style/noNonNullAssertion: this is safe\n const cap = termFreq.get(tok)!\n // Decrement satisfied count when token falls below frequency cap\n if (before <= cap) satisfiedCount -= 1\n }\n\n const { tokens } = source\n // Prime the first window\n for (let i = 0; i < Math.min(windowSize, tokens.length); i++) addToken(tokens[i])\n\n if (satisfiedCount >= requiredCount) {\n return {\n matched: true,\n term,\n windowStart: 0,\n windowEnd: Math.min(windowSize, tokens.length) - 1,\n matchedRatio: satisfiedCount / termTokens.length,\n }\n }\n\n // Slide the window across the source\n for (let start = 1; start + windowSize <= tokens.length; start++) {\n const prev = tokens[start - 1]\n const next = tokens[start + windowSize - 1]\n removeToken(prev)\n addToken(next)\n\n if (satisfiedCount >= requiredCount) {\n return {\n matched: true,\n term,\n windowStart: start,\n windowEnd: start + windowSize - 1,\n matchedRatio: satisfiedCount / termTokens.length,\n }\n }\n }\n\n return { matched: false, term, reason: 'No window satisfied the token threshold.' }\n}\n\n/**\n * Batch check: evaluate many terms against the same preprocessed source.\n * Returns match results in the same order as input terms.\n * Can be used for glossary terms, protected phrases, or other text patterns.\n */\nexport function matchAllTermsInDocument(terms: string[], source: SourceContext, options?: MatchOptions): Match[] {\n return terms.map((t) => matchTermInDocument(t, source, options))\n}\n\n/**\n * @deprecated Use matchTermInDocument instead\n */\nexport const matchGlossaryTerm = matchTermInDocument\n\n/**\n * @deprecated Use matchAllTermsInDocument instead\n */\nexport const matchAllGlossaryTerms = matchAllTermsInDocument\n\n/* =========================\n Example usage (pseudo):\n =========================\n\nconst sourceText = `\n You can navigate using the digital map, or rely on landmarks.\n Turn-by-turn directions are available.\n`;\n\nconst source = preprocessSource(sourceText);\n\nconst terms = [\n \"navigate using a map\",\n \"turn by turn directions\",\n \"offline routing\",\n];\n\nconst results = matchAllTermsInDocument(terms, source, {\n windowSlack: 3,\n minTokenMatchRatio: 1.0, // require all term tokens present (after filtering)\n stopwords: [...DEFAULTS.stopwords, \"a\"], // extend if desired\n});\n\nconsole.log(results);\n\n*/\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument, TranslationGlossaryTermDocument, TranslationSettingsDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { documentToPlainText } from '../document/documentToPlainText'\nimport { matchTermInDocument, preprocessSource } from './matchTermInDocument'\n\ninterface FetchTranslationPromptsOptions {\n client: SanityClient\n sourceLanguage?: string\n sourceDocument?: TranslatableDocument\n targetLanguages: Array<string>\n}\n\nexport interface FetchTranslationPromptsResult {\n globalPrompt?: string\n protectedPhrases?: Array<string>\n localeInstructions: Record<\n string,\n {\n prompt?: string\n glossaryTerms?: string\n }\n >\n}\n\nexport const fetchTranslationPrompts = async ({\n client,\n sourceLanguage,\n sourceDocument,\n targetLanguages,\n}: FetchTranslationPromptsOptions): Promise<FetchTranslationPromptsResult> => {\n const sourceDocumentString = documentToPlainText(sourceDocument)\n\n const translationSettings = await client.fetch<TranslationSettingsDocument | null>(\n `*[_id == \"translation-settings\"][0] {\n globalPrompt,\n localePrompts[_key in $targetLanguages],\n protectedPhrases\n }`,\n { targetLanguages },\n )\n\n devLog.debug('Fetched translation settings', {\n hasSettings: !!translationSettings,\n hasGlobalPrompt: !!translationSettings?.globalPrompt,\n globalPromptLength: translationSettings?.globalPrompt?.length,\n localePromptsCount: translationSettings?.localePrompts?.length,\n localePrompts: translationSettings?.localePrompts,\n protectedPhrasesCount: translationSettings?.protectedPhrases?.length,\n targetLanguages,\n })\n\n // Fetch all translation glossary terms that have a translation in both the source and target languages\n const translationGlossaryTerms =\n (await client.fetch<Array<NonNullable<TranslationGlossaryTermDocument['translations']>> | null>(\n `*[\n _type == \"translationGlossaryTerm\"\n && count(translations[_key in $targetLanguages]) > 0\n && count(translations[_key == $sourceLanguage]) > 0\n ].translations`,\n { targetLanguages, sourceLanguage },\n )) || []\n\n const glossaryTermsByLocale: Record<string, Array<{ from: string; to: string }>> = {}\n\n for (const targetLanguage of targetLanguages) {\n const relevantTerms = translationGlossaryTerms.filter((termTranslations) =>\n termTranslations.some((translation) => translation._key === targetLanguage),\n )\n\n interface GlossaryTermMapping {\n from: string\n to: string\n }\n\n let relevantTranslations = relevantTerms\n .map((termTranslations): GlossaryTermMapping | null => {\n const from = termTranslations.find((translation) => translation._key === sourceLanguage)?.value\n const to = termTranslations.find((translation) => translation._key === targetLanguage)?.value\n if (!from || !to) {\n return null\n }\n return { from, to }\n })\n .filter((item): item is GlossaryTermMapping => item !== null)\n\n // Filter to only include terms that appear in the source document\n if (sourceDocument) {\n relevantTranslations = relevantTranslations.filter(({ from }) =>\n glossaryTermAppearsInDocument(from, sourceDocumentString),\n )\n }\n\n glossaryTermsByLocale[targetLanguage] = relevantTranslations\n }\n\n // Filter protected phrases to only include those that appear in the source document\n let filteredProtectedPhrases = translationSettings?.protectedPhrases\n if (sourceDocument && filteredProtectedPhrases) {\n devLog.debug('Filtering protected phrases', {\n totalPhrases: filteredProtectedPhrases.length,\n phrases: filteredProtectedPhrases,\n sourceDocumentStringLength: sourceDocumentString.length,\n sourceDocumentPreview: sourceDocumentString.substring(0, 500),\n })\n\n filteredProtectedPhrases = filteredProtectedPhrases.filter((phrase) => {\n const appears = protectedPhraseAppearsInDocument(phrase, sourceDocumentString)\n devLog.debug('Protected phrase match result', {\n phrase,\n appears,\n })\n return appears\n })\n\n devLog.debug('Filtered protected phrases result', {\n remainingCount: filteredProtectedPhrases.length,\n remainingPhrases: filteredProtectedPhrases,\n })\n }\n\n return {\n globalPrompt: translationSettings?.globalPrompt,\n protectedPhrases: filteredProtectedPhrases,\n localeInstructions: Object.fromEntries(\n targetLanguages.map((locale) => [\n locale,\n {\n prompt: translationSettings?.localePrompts?.find((lp) => lp._key === locale)?.value,\n glossaryTerms: formatGlossaryTermInstruction(glossaryTermsByLocale[locale]),\n },\n ]),\n ),\n }\n}\n\n/**\n * Checks if a glossary term appears anywhere in the document content using fuzzy matching.\n * Uses order-agnostic bag-of-words matching.\n */\nconst glossaryTermAppearsInDocument = (term: string, documentString: string): boolean => {\n // Handle colon-delimited terms by checking the base term (before the colon)\n const baseTerm = term.split(':')[0].trim()\n\n // Preprocess the document string once for efficient matching\n const source = preprocessSource(documentString)\n\n // Match the term against the document with default options\n // (windowSlack: 10, minTokenMatchRatio: 1.0, stopwords included)\n const result = matchTermInDocument(baseTerm, source, {\n windowSlack: 10,\n })\n\n return result.matched\n}\n\n/**\n * Checks if a protected phrase appears anywhere in the document content using fuzzy matching.\n * Uses order-agnostic bag-of-words matching.\n */\nconst protectedPhraseAppearsInDocument = (phrase: string, documentString: string): boolean => {\n // Preprocess the document string once for efficient matching\n const source = preprocessSource(documentString)\n\n // Match the phrase against the document with the same options as glossary terms\n const result = matchTermInDocument(phrase, source, {\n windowSlack: 10,\n })\n\n return result.matched\n}\n\nconst formatGlossaryTermInstruction = (terms: Array<{ from: string; to: string }> | undefined) => {\n if (!terms || terms.length === 0) {\n return undefined\n }\n\n const instructions = [\n '## Glossary Terms',\n 'Use the translations below whenever the matching source term appears. Treat these mappings as required terminology.',\n 'Guidelines:',\n '- Apply glossary terms when the meaning matches, even if there are slight variations in wording, punctuation, or word order.',\n '- Preserve surrounding punctuation and keep the sentence natural.',\n '- Match the source casing when possible; only adjust when required for correct grammar.',\n '- If the source term meaning is absent, do not introduce a translation from this list.',\n '- When a term includes a colon, translate the text before the colon for general use; use the text after the colon when the feminine form is required.',\n '- Do not remove important words or phrases from the source text when they are essential to the meaning.',\n '- Never add punctuation or symbols that are not present in the source text.',\n '- Never apply glossary terms in code examples.',\n ]\n\n terms.forEach(({ from, to }) => {\n instructions.push(\n `When text conveys the same meaning as \"${from}\", translate it as \"${to}\" unless the term is used in a code example.`,\n )\n })\n\n return instructions.join('\\n')\n}\n","export type RecursiveSearchable =\n | null\n | undefined\n | string\n | number\n | boolean\n | RecursiveSearchable[]\n | { [key: string]: RecursiveSearchable }\n\nexport type FindKeyValueResult<T = unknown> = {\n value: T\n path: Array<string | number>\n}\n\n/**\n * Recursively searches through an object/array structure to find all values\n * associated with a specific key name, along with their paths.\n *\n * @param obj - The object or array to search through\n * @param targetKey - The key name to search for\n * @returns An array of objects containing the value and path for each occurrence of the target key\n */\nexport const findKeyValues = <T = unknown>(obj: RecursiveSearchable, targetKey: string): FindKeyValueResult<T>[] => {\n const results: FindKeyValueResult<T>[] = []\n\n const recursiveSearch = (current: RecursiveSearchable, currentPath: Array<string | number>): void => {\n // Handle null/undefined\n if (current === null || current === undefined) {\n return\n }\n\n // Recursively search each array element for the target key\n if (Array.isArray(current)) {\n current.forEach((item, index) => {\n recursiveSearch(item, [...currentPath, index])\n })\n return\n }\n\n // For objects, check properties for the target key\n if (typeof current === 'object') {\n for (const [key, value] of Object.entries(current)) {\n const newPath = [...currentPath, key]\n\n // Record matching value and its path for caller\n if (key === targetKey) {\n results.push({\n value: value as T,\n path: newPath,\n })\n }\n\n // Continue searching recursively\n recursiveSearch(value, newPath)\n }\n }\n }\n\n recursiveSearch(obj, [])\n return results\n}\n","import { getPublishedId, pathToString, type SanityClient } from 'sanity'\nimport type { PluginConfig } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { type FindKeyValueResult, findKeyValues } from '../common/findKeyValues'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\nimport { TRANSLATION_METADATA_TYPE } from '../metadata/constants'\n\ninterface LocalizeReferenceParams {\n client: SanityClient\n documentId: string\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n}\n\n/**\n * Localizes all references in a translated document to point to the appropriate\n * localized versions of referenced documents.\n *\n * This function:\n * 1. Fetches the document and extracts its target language\n * 2. Finds all `_ref` fields within the document\n * 3. Batch fetches translation IDs for all references\n * 4. Updates references to point to localized versions\n * 5. Sets weak references with strengthenOnPublish behavior\n *\n * @param params - Configuration for localizing references\n * @param params.client - Sanity client instance\n * @param params.documentId - ID of the document whose references should be localized\n * @param params.languageFieldName - Name of the field containing the language identifier\n *\n * @example\n * ```typescript\n * await localizeReferences({\n * client,\n * documentId: 'post-es',\n * languageFieldName: 'language'\n * })\n * // Updates all references in post-es to point to Spanish versions\n * ```\n */\nexport const localizeReferences = async ({\n client,\n documentId,\n sourceLanguage,\n languageFieldName,\n}: LocalizeReferenceParams) => {\n try {\n const doc = await retryWithBackoff(\n () => client.fetch('*[_id == $documentId][0]', { documentId }),\n {\n maxAttempts: 3,\n initialDelayMs: 1000,\n context: { operation: 'localizeReferences.fetchDocument', documentId },\n },\n )\n\n if (!doc) {\n devLog.error('Document not found', new Error('Document not found'), {\n documentId,\n component: 'localizeReferences',\n })\n return\n }\n\n const targetLanguage = doc[languageFieldName]\n\n if (!targetLanguage) {\n devLog.error('No value found for language field', new Error('Missing language field'), {\n documentId,\n languageFieldName,\n component: 'localizeReferences',\n })\n return\n }\n\n const refs = findKeyValues<string>(doc, '_ref')\n\n if (!refs?.length) {\n return\n }\n\n await updateReferences(documentId, client, refs, sourceLanguage, targetLanguage)\n } catch (error) {\n devLog.error('Error localizing references', error instanceof Error ? error : new Error(String(error)), {\n documentId,\n component: 'localizeReferences',\n })\n }\n}\n\n/**\n * Updates document references to their localized versions in a single batch operation.\n *\n * This function performs efficient batch processing:\n * 1. Batch fetches all translation IDs in one query (avoids N+1 problem)\n * 2. Builds a translation map for quick lookup\n * 3. Applies all patches in a single commit\n *\n * References are set as weak with `_strengthenOnPublish` to ensure they become\n * strong references when the document is published.\n *\n * @param documentId - ID of the document to update\n * @param client - Sanity client instance\n * @param refs - Array of reference results from findKeyValues\n * @param targetLanguage - Target language to find localized references for\n *\n * @example\n * ```typescript\n * const refs = findKeyValues(document, '_ref')\n * await updateReferences('doc-es', client, refs, 'es')\n * // All references in doc-es now point to Spanish versions\n * ```\n */\nexport const updateReferences = async (\n documentId: string,\n client: SanityClient,\n refs: Array<FindKeyValueResult<string>>,\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id'],\n targetLanguage: PluginConfig['supportedLanguages'][number]['id'],\n) => {\n if (!refs?.length) {\n return []\n }\n\n // Step 1: Batch fetch all reference translations\n const refIds = refs.map((r) => getPublishedId(r.value))\n const translations = await retryWithBackoff(\n () =>\n client.fetch<Array<{ sourceId: string; targetId: string; targetType: string }>>(\n `*[_type == $translationMetadataSchemaType && array::intersects(translations[].value._ref, $refIds)] {\n \"sourceId\": translations[_key == $sourceLanguage][0].value._ref,\n \"targetId\": translations[_key == $targetLanguage][0].value._ref,\n \"targetType\": translations[_key == $sourceLanguage][0].value -> _type\n }`,\n {\n translationMetadataSchemaType: TRANSLATION_METADATA_TYPE,\n refIds,\n sourceLanguage,\n targetLanguage,\n },\n ),\n {\n maxAttempts: 3,\n initialDelayMs: 1000,\n context: { operation: 'localizeReferences.fetchTranslations', refCount: refIds.length },\n },\n )\n\n // Step 2: Build translation map\n const translationMap = new Map<string, string>()\n for (const t of translations) {\n if (t.sourceId && t.targetId) {\n translationMap.set(t.sourceId, t.targetId)\n }\n }\n\n const patch = client.patch(documentId)\n let hasChanges = false\n\n for (const ref of refs) {\n const sourceId = getPublishedId(ref.value)\n const newId = translationMap.get(sourceId)\n\n if (!newId) {\n // Skip references without translations—document may not be translatable or translated yet\n continue\n }\n\n patch.set({ [pathToString(ref.path)]: getPublishedId(newId) })\n // Versions handle weak reference conversion; explicit _weak setting not needed\n patch.set({\n [`${pathToString(ref.path.slice(0, -1))}._strengthenOnPublish`]: {\n type: translations.find((t) => t.targetId === getPublishedId(newId))?.targetType || undefined,\n },\n })\n hasChanges = true\n }\n\n if (hasChanges) {\n await retryWithBackoff(\n async () => {\n await patch.commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'localizeReferences',\n documentId,\n referenceCount: refs.length,\n },\n },\n )\n devLog.info('Updated references in batch', {\n documentId,\n count: refs.length,\n translatedCount: translationMap.size,\n component: 'updateReferences',\n })\n }\n}\n","import type { SanityClient } from 'sanity'\nimport type { PluginConfig } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { localizeReferences } from './localizeReferences'\n\ninterface UpdateBackwardReferencesParams {\n client: SanityClient\n releaseId: string\n /** Target languages that were translated to */\n targetLanguages: string[]\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n /** Newly translated versioned document IDs (already processed by localizeReferences) */\n excludeDocumentIds: string[]\n}\n\n/**\n * Updates references in existing documents within a release.\n *\n * This handles the \"backward reference\" problem where:\n * 1. Doc A (which references Doc B) was translated earlier\n * 2. Doc B is translated later to the same release\n * 3. Doc A's reference to B should now point to the translated B\n *\n * The function iterates over all documents in the release for the target languages\n * (excluding newly translated docs) and runs localizeReferences on each.\n * This is idempotent - documents without stale refs are unaffected.\n *\n * @example\n * ```typescript\n * // After translating Doc B to Spanish in a release:\n * await updateBackwardReferences({\n * client,\n * releaseId: 'r123',\n * targetLanguages: ['es'],\n * sourceLanguage: 'en',\n * languageFieldName: 'language',\n * excludeDocumentIds: ['versions.r123.docB-es'], // Already processed\n * })\n * // This updates all Spanish docs in the release to point to translated references\n * ```\n */\nexport const updateBackwardReferences = async ({\n client,\n releaseId,\n targetLanguages,\n sourceLanguage,\n languageFieldName,\n excludeDocumentIds,\n}: UpdateBackwardReferencesParams): Promise<void> => {\n if (!targetLanguages.length) {\n return\n }\n\n devLog.debug('Starting backward reference update', {\n releaseId,\n targetLanguages,\n excludeCount: excludeDocumentIds.length,\n component: 'updateBackwardReferences',\n })\n\n try {\n // Fetch all documents in the release for the target languages, excluding newly translated ones\n const existingDocs = await client.fetch<Array<{ _id: string; language: string }>>(\n `*[\n sanity::partOfRelease($releaseId) &&\n @[$languageFieldName] in $targetLanguages &&\n !(_id in $excludeIds)\n ]{\n _id,\n \"language\": @[$languageFieldName]\n }`,\n {\n releaseId,\n targetLanguages,\n languageFieldName,\n excludeIds: excludeDocumentIds,\n },\n )\n\n if (existingDocs.length === 0) {\n devLog.debug('No existing documents found in release for target languages', {\n releaseId,\n targetLanguages,\n component: 'updateBackwardReferences',\n })\n return\n }\n\n devLog.info('Processing existing documents for backward reference updates', {\n releaseId,\n targetLanguages,\n count: existingDocs.length,\n component: 'updateBackwardReferences',\n })\n\n // Process documents sequentially to avoid rate limits\n // Each localizeReferences call makes multiple API requests\n let updated = 0\n let errors = 0\n\n for (const { _id } of existingDocs) {\n try {\n await localizeReferences({\n client,\n documentId: _id,\n sourceLanguage,\n languageFieldName,\n })\n updated++\n } catch (error) {\n errors++\n devLog.warn('Failed to update references for document', {\n documentId: _id,\n error: error instanceof Error ? error.message : String(error),\n component: 'updateBackwardReferences',\n })\n }\n }\n\n devLog.info('Backward reference update complete', {\n releaseId,\n targetLanguages,\n processed: existingDocs.length,\n updated,\n errors,\n component: 'updateBackwardReferences',\n })\n } catch (error) {\n devLog.error('Error updating backward references', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n targetLanguages,\n component: 'updateBackwardReferences',\n })\n // Don't throw - backward reference updates are best-effort\n // The manual reconcile action can be used to fix any issues\n }\n}\n","import type { Dispatch } from 'react'\nimport type { SetTranslationJobStatusAction, TranslationAction } from '../../types'\nimport { updateActiveTranslationJob } from './activeTranslations'\n\nexport const setupTranslationJobStatuses = (\n dispatch: Dispatch<TranslationAction>,\n sourceDocuments: Array<{\n _id: string\n _type: string\n title?: string\n }>,\n targetLanguages: Array<string>,\n /** Main source document ID for updating active translations store */\n mainSourceDocumentId?: string,\n) => {\n for (const sourceDocument of sourceDocuments) {\n for (const lang of targetLanguages) {\n const payload: SetTranslationJobStatusAction['payload'] = {\n _id: sourceDocument._id,\n _type: sourceDocument._type,\n language: lang,\n status: 'queued',\n title: sourceDocument.title,\n }\n\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload,\n })\n\n // Also update the module-level active translations store\n if (mainSourceDocumentId) {\n updateActiveTranslationJob(mainSourceDocumentId, payload)\n }\n }\n }\n}\n","import type { SanityDocument } from 'sanity'\nimport type {\n TelemetryConfig,\n TelemetryEvent,\n TranslationErrorBundle,\n TranslationErrorType,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\n\n/**\n * Emits a telemetry event if the onEvent callback is configured.\n * Safe to call even if telemetry is not configured.\n *\n * @param config - Telemetry configuration (may be undefined)\n * @param event - The telemetry event to emit\n */\nexport const emitTelemetryEvent = (config: TelemetryConfig | undefined, event: TelemetryEvent): void => {\n if (!config?.onEvent) {\n return\n }\n\n try {\n config.onEvent(event)\n devLog.debug('Telemetry event emitted', { eventName: event.name })\n } catch (error) {\n // Don't let telemetry errors break the translation flow\n devLog.warn('Failed to emit telemetry event', {\n eventName: event.name,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n}\n\n/**\n * Emits a telemetry error bundle if the onError callback is configured.\n * Safe to call even if telemetry is not configured.\n *\n * @param config - Telemetry configuration (may be undefined)\n * @param bundle - The error bundle to emit\n */\nexport const emitTelemetryError = (config: TelemetryConfig | undefined, bundle: TranslationErrorBundle): void => {\n if (!config?.onError) {\n return\n }\n\n try {\n config.onError(bundle)\n devLog.debug('Telemetry error emitted', {\n documentId: bundle.documentId,\n errorType: bundle.error.type,\n })\n } catch (error) {\n // Don't let telemetry errors break the translation flow\n devLog.warn('Failed to emit telemetry error', {\n documentId: bundle.documentId,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n}\n\n/**\n * Parameters for building an error bundle\n */\nexport interface BuildErrorBundleParams {\n /** Document ID being translated */\n documentId: string\n /** Document schema type */\n documentType: string\n /** Target language for translation */\n targetLanguage: string\n /** Source language of the document */\n sourceLanguage: string\n /** Translation mode used */\n translationMode: TranslationMode\n /** The error that occurred */\n error: Error\n /** Categorized error type */\n errorType: TranslationErrorType\n /** Start time of the translation attempt (for duration calculation) */\n startTime: number\n /** Full source document */\n originalDocument: SanityDocument\n /** XML-serialized document sent to OpenAI (if OpenAI mode) */\n serializedDocument?: string\n /** Raw API response from OpenAI (if OpenAI mode) */\n openaiResponse?: string\n /** Translation prompts used */\n translationPrompts?: TranslationErrorBundle['debug']['translationPrompts']\n /** Validation errors if structure validation failed */\n validationErrors?: TranslationErrorBundle['debug']['validationErrors']\n}\n\n/**\n * Builds a TranslationErrorBundle from the given parameters.\n * Centralizes the construction of error bundles for consistency.\n *\n * @param params - Parameters for building the error bundle\n * @returns A complete TranslationErrorBundle\n */\nexport const buildErrorBundle = (params: BuildErrorBundleParams): TranslationErrorBundle => {\n const durationMs = Date.now() - params.startTime\n\n return {\n documentId: params.documentId,\n documentType: params.documentType,\n targetLanguage: params.targetLanguage,\n sourceLanguage: params.sourceLanguage,\n translationMode: params.translationMode,\n error: {\n message: params.error.message,\n stack: params.error.stack,\n type: params.errorType,\n },\n debug: {\n originalDocument: params.originalDocument,\n serializedDocument: params.serializedDocument,\n openaiResponse: params.openaiResponse,\n translationPrompts: params.translationPrompts,\n validationErrors: params.validationErrors,\n },\n timestamp: new Date().toISOString(),\n durationMs,\n }\n}\n\n/**\n * Helper to create a timestamp string for telemetry events\n */\nexport const createTimestamp = (): string => new Date().toISOString()\n\n/**\n * Classifies an error into a TranslationErrorType based on error properties and context.\n *\n * @param error - The error to classify\n * @param context - Optional context about where the error occurred\n * @returns The classified error type\n */\nexport const classifyError = (\n error: Error,\n context?: 'serialization' | 'deserialization' | 'openai_api' | 'sanity_agent' | 'validation',\n): TranslationErrorType => {\n // If context is provided, use it\n if (context) {\n return context\n }\n\n const message = error.message.toLowerCase()\n\n // Check for network errors\n if (\n message.includes('network') ||\n message.includes('fetch') ||\n message.includes('timeout') ||\n message.includes('econnrefused') ||\n message.includes('enotfound')\n ) {\n return 'network'\n }\n\n // Check for OpenAI API errors\n if (message.includes('openai') || message.includes('gpt') || message.includes('api error')) {\n return 'openai_api'\n }\n\n // Check for validation errors\n if (message.includes('validation') || message.includes('structure') || message.includes('invalid')) {\n return 'validation'\n }\n\n // Check for deserialization errors (check before serialization since \"deserialize\" contains \"serialize\")\n if (message.includes('deserialize') || message.includes('parse')) {\n return 'deserialization'\n }\n\n // Check for serialization errors\n if (message.includes('serialize') || message.includes('xml')) {\n return 'serialization'\n }\n\n // Check for Sanity agent errors\n if (message.includes('agent') || message.includes('sanity')) {\n return 'sanity_agent'\n }\n\n return 'unknown'\n}\n","import { getPublishedId, type SanityClient, type SanityDocument } from 'sanity'\nimport type { TranslationMetadataDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\ninterface UpdateTranslationMetadataParams {\n client: SanityClient\n sourceDocument: SanityDocument\n sourceLanguage: string\n newDocument: SanityDocument\n targetLanguage: string\n translationMetadata: TranslationMetadataDocument\n isNewTranslation: boolean\n}\n\nexport const updateTranslationMetadata = async ({\n client,\n targetLanguage,\n sourceDocument,\n sourceLanguage,\n newDocument,\n translationMetadata,\n isNewTranslation,\n}: UpdateTranslationMetadataParams) => {\n try {\n const refToNewTranslation: TranslationMetadataDocument['translations'][number] = {\n _key: targetLanguage,\n _type: 'internationalizedArrayReferenceValue',\n value: {\n _type: 'reference',\n _ref: getPublishedId(newDocument._id),\n _weak: true,\n _strengthenOnPublish: {\n _type: 'reference',\n _weak: false,\n },\n },\n }\n\n const newTranslationHistory: TranslationMetadataDocument['translationHistory'][number] = {\n _key: targetLanguage,\n language: targetLanguage,\n sourceId: getPublishedId(sourceDocument._id),\n sourceRev: sourceDocument._rev,\n sourceLanguage,\n sourceSnapshot: JSON.stringify(sourceDocument),\n targetId: newDocument._id,\n targetRev: newDocument._rev,\n targetSnapshot: JSON.stringify(newDocument),\n }\n\n const translationPatch = client\n .patch(translationMetadata._id)\n // Initialize translations array if missing\n .setIfMissing({ translations: [] })\n\n if (isNewTranslation) {\n await retryWithBackoff(\n async () => {\n await translationPatch.insert('after', 'translations[-1]', [refToNewTranslation]).commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.translations',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewTranslation: true,\n },\n },\n )\n } else {\n await retryWithBackoff(\n async () => {\n await translationPatch\n .insert('replace', `translations[_key == \"${targetLanguage}\"]`, [refToNewTranslation])\n .commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.translations',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewTranslation: false,\n },\n },\n )\n }\n\n const historyPatch = client.patch(translationMetadata._id).setIfMissing({ translationHistory: [] })\n const translationHistoryExists = !!translationMetadata.translationHistory?.some(\n (history) => history.language === targetLanguage,\n )\n\n if (!translationHistoryExists) {\n await retryWithBackoff(\n async () => {\n await historyPatch.insert('after', 'translationHistory[-1]', [newTranslationHistory]).commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.history',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewHistory: true,\n },\n },\n )\n } else {\n await retryWithBackoff(\n async () => {\n await historyPatch\n .insert('replace', `translationHistory[_key == \"${targetLanguage}\"]`, [newTranslationHistory])\n .commit()\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'updateTranslationMetadata.history',\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n isNewHistory: false,\n },\n },\n )\n }\n } catch (error) {\n devLog.error('Error updating translation metadata', error instanceof Error ? error : new Error(String(error)), {\n sourceDocumentId: sourceDocument._id,\n targetLanguage,\n newDocumentId: newDocument._id,\n component: 'updateTranslationMetadata',\n })\n throw error\n }\n}\n","import defaults from 'lodash.defaults'\nimport type { Dispatch } from 'react'\nimport type { SetTranslationJobStatusAction, TranslationAction, TranslationJob } from '../../types'\nimport { updateActiveTranslationJob } from './activeTranslations'\n\nexport const setJobStatus = (\n dispatch: Dispatch<TranslationAction>,\n _id: string,\n language: string,\n status: TranslationJob['status'],\n payload?: Partial<SetTranslationJobStatusAction['payload']>,\n /** Source document ID for updating active translations store */\n sourceDocumentId?: string,\n) => {\n const job = defaults(payload, {\n _id,\n language,\n status,\n }) as TranslationJob\n\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload: job,\n })\n\n // Also update the module-level active translations store\n if (sourceDocumentId) {\n updateActiveTranslationJob(sourceDocumentId, job)\n }\n}\n","import { devLog } from '../common/devLog'\n\n/**\n * Result of filtering excluded fields from a document\n * @internal\n */\nexport interface FilteredDocument<T = any> {\n /** Document with excluded fields removed */\n filteredDocument: T\n /** Excluded field values that were removed */\n excludedValues: Record<string, any>\n}\n\n/**\n * Checks if a field path matches any of the exclusion patterns\n * @param fieldPath - The field path to check (e.g., 'status', 'meta.category', 'body[0].imageType')\n * @param excludeFields - Array of field patterns to exclude\n * @returns True if the field should be excluded\n */\nfunction shouldExcludeField(fieldPath: string, excludeFields: string[]): boolean {\n for (const pattern of excludeFields) {\n // Exact match for full path (including array notation)\n if (fieldPath === pattern) {\n return true\n }\n\n // Check if pattern starts with '**.' - this means match field name anywhere in hierarchy\n if (pattern.startsWith('**.')) {\n const fieldName = pattern.substring(3) // Remove '**.'\n const segments = fieldPath.split('.')\n const lastSegment = segments[segments.length - 1]\n // Remove array notation if present: \"items[0]\" -> \"items\"\n const actualFieldName = lastSegment?.split('[')[0]\n if (actualFieldName === fieldName) {\n return true\n }\n }\n\n // Check if pattern is just a field name (no dots) - match anywhere in hierarchy\n else if (!pattern.includes('.') && !pattern.includes('[')) {\n const segments = fieldPath.split('.')\n const lastSegment = segments[segments.length - 1]\n // Remove array notation if present: \"items[0]\" -> \"items\"\n const actualFieldName = lastSegment?.split('[')[0]\n if (actualFieldName === pattern) {\n return true\n }\n }\n\n // Pattern with dots or array notation - check for pattern match\n else {\n // Support wildcard array indexing: \"body[*].imageType\" matches \"body[0].imageType\", \"body[1].imageType\", etc.\n if (pattern.includes('[*]')) {\n const regexPattern = pattern\n .replace(/\\[/g, '\\\\[')\n .replace(/\\]/g, '\\\\]')\n .replace(/\\\\\\[\\*\\\\\\]/g, '\\\\[\\\\d+\\\\]')\n const regex = new RegExp(`^${regexPattern}$`)\n if (regex.test(fieldPath)) {\n return true\n }\n }\n\n // Exact path match (already handled above, but included for clarity)\n }\n }\n\n return false\n}\n\n/**\n * Recursively filters out excluded fields from a document\n * @param document - The document to filter\n * @param excludeFields - Array of field patterns to exclude. Supports:\n * - Field names: 'status' (matches any field named 'status' anywhere)\n * - Exact paths: 'meta.category' (matches only 'category' under 'meta')\n * - Array paths: 'body[0].imageType' (matches 'imageType' in first array item)\n * - Wildcard arrays: 'body[*].imageType' (matches 'imageType' in any array item)\n * - Hierarchical: '**.status' (explicitly matches 'status' anywhere - same as just 'status')\n * @param currentPath - Current path in the document (used internally)\n * @returns Filtered document and excluded values\n * @internal\n */\nexport function filterExcludedFields<T extends Record<string, any>>(\n document: T,\n excludeFields: string[] = [],\n currentPath = '',\n): FilteredDocument<T> {\n if (!excludeFields.length) {\n return {\n filteredDocument: document,\n excludedValues: {},\n }\n }\n\n const filteredDocument = {} as T\n const excludedValues: Record<string, any> = {}\n let hasExclusions = false\n\n // Process each field in the document\n for (const [key, value] of Object.entries(document)) {\n const fieldPath = currentPath ? `${currentPath}.${key}` : key\n\n if (shouldExcludeField(fieldPath, excludeFields)) {\n // This field should be excluded\n excludedValues[fieldPath] = value\n hasExclusions = true\n\n devLog.debug('Excluding field from translation', {\n fieldPath,\n fieldType: typeof value,\n value: typeof value === 'string' ? value : '[object]',\n })\n\n continue\n }\n\n // If it's an object (but not an array), recursively process it\n if (value && typeof value === 'object' && !Array.isArray(value) && value._type !== 'reference') {\n const nested = filterExcludedFields(value, excludeFields, fieldPath)\n filteredDocument[key as keyof T] = nested.filteredDocument\n\n // Merge nested exclusions\n Object.assign(excludedValues, nested.excludedValues)\n if (Object.keys(nested.excludedValues).length > 0) {\n hasExclusions = true\n }\n }\n // If it's an array, recursively process each element to handle custom blocks in portable text\n else if (Array.isArray(value)) {\n const filteredArray: any[] = []\n let arrayHasExclusions = false\n\n for (let i = 0; i < value.length; i++) {\n const arrayItem = value[i]\n const itemPath = `${fieldPath}[${i}]`\n\n // If the array item is an object, recursively process it\n if (\n arrayItem &&\n typeof arrayItem === 'object' &&\n !Array.isArray(arrayItem) &&\n arrayItem._type !== 'reference'\n ) {\n const nested = filterExcludedFields(arrayItem, excludeFields, itemPath)\n filteredArray[i] = nested.filteredDocument\n\n // Merge nested exclusions\n Object.assign(excludedValues, nested.excludedValues)\n if (Object.keys(nested.excludedValues).length > 0) {\n arrayHasExclusions = true\n }\n } else {\n // For primitives, references, or other arrays, keep as-is\n filteredArray[i] = arrayItem\n }\n }\n\n filteredDocument[key as keyof T] = filteredArray as any\n if (arrayHasExclusions) {\n hasExclusions = true\n }\n } else {\n // Keep the field as-is (primitives, references, etc.)\n filteredDocument[key as keyof T] = value\n }\n }\n\n if (hasExclusions) {\n devLog.info('Filtered excluded fields from document', {\n documentId: document._id || 'unknown',\n excludedFieldCount: Object.keys(excludedValues).length,\n excludedFields: Object.keys(excludedValues),\n })\n }\n\n return {\n filteredDocument,\n excludedValues,\n }\n}\n\n/**\n * Restores previously excluded fields to a translated document\n * @param translatedDocument - The translated document\n * @param excludedValues - The excluded field values to restore\n * @returns Document with excluded fields restored\n * @internal\n */\nexport function restoreExcludedFields<T extends Record<string, any>>(\n translatedDocument: T,\n excludedValues: Record<string, any>,\n): T {\n if (!Object.keys(excludedValues).length) {\n return translatedDocument\n }\n\n // Clone the translated document to avoid mutations\n const result = { ...translatedDocument }\n\n // Restore each excluded field\n for (const [fieldPath, value] of Object.entries(excludedValues)) {\n setNestedValue(result, fieldPath, value)\n }\n\n devLog.debug('Restored excluded fields to translated document', {\n documentId: result._id || 'unknown',\n restoredFieldCount: Object.keys(excludedValues).length,\n restoredFields: Object.keys(excludedValues),\n })\n\n return result\n}\n\n/**\n * Sets a nested value in an object using a dot-separated path with array support\n * @param obj - The object to modify\n * @param path - Path with dots and array notation (e.g., 'meta.category', 'body[0].imageType')\n * @param value - Value to set\n */\nfunction setNestedValue(obj: any, path: string, value: any): void {\n const segments = path.split('.')\n let current = obj\n\n // Navigate to the parent object/array\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i]\n\n // Handle array notation: \"body[0]\" -> field \"body\", index 0\n const arrayMatch = segment.match(/^(.+)\\[(\\d+)\\]$/)\n if (arrayMatch) {\n const [, fieldName, indexStr] = arrayMatch\n const index = Number.parseInt(indexStr, 10)\n\n if (!(fieldName in current) || !Array.isArray(current[fieldName])) {\n current[fieldName] = []\n }\n\n // Ensure array is long enough\n while (current[fieldName].length <= index) {\n current[fieldName].push({})\n }\n\n current = current[fieldName][index]\n } else {\n // Regular object property\n if (!(segment in current) || typeof current[segment] !== 'object') {\n current[segment] = {}\n }\n current = current[segment]\n }\n }\n\n // Set the final value\n const finalSegment = segments[segments.length - 1]\n const arrayMatch = finalSegment.match(/^(.+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, fieldName, indexStr] = arrayMatch\n const index = Number.parseInt(indexStr, 10)\n\n if (!(fieldName in current) || !Array.isArray(current[fieldName])) {\n current[fieldName] = []\n }\n\n // Ensure array is long enough\n while (current[fieldName].length <= index) {\n current[fieldName].push(null)\n }\n\n current[fieldName][index] = value\n } else {\n current[finalSegment] = value\n }\n}\n\n/**\n * Common enum field names that are typically excluded from translation\n * @internal\n */\nexport const COMMON_ENUM_FIELDS = [\n 'status',\n 'state',\n 'type',\n 'category',\n 'priority',\n 'visibility',\n 'role',\n 'permission',\n 'level',\n 'mode',\n 'format',\n 'variant',\n 'size',\n 'theme',\n 'kind',\n 'phase',\n 'stage',\n] as const\n\n/**\n * Predefined exclusion lists for common use cases\n * @internal\n */\nexport const PREDEFINED_EXCLUSIONS = {\n /** Common enum and status fields */\n enums: [...COMMON_ENUM_FIELDS],\n\n /** All metadata and system fields (starting with _) */\n metadata: ['_id', '_type', '_rev', '_createdAt', '_updatedAt'],\n\n /** Common technical and configuration fields */\n technical: [\n 'slug',\n 'handle',\n 'key',\n 'code',\n 'id',\n 'uuid',\n 'token',\n 'hash',\n 'checksum',\n 'version',\n 'config',\n 'settings',\n ],\n\n /** SEO and URL-related fields that often shouldn't be translated */\n seo: ['slug', 'handle', 'permalink', 'canonical', 'redirect', 'robots', 'sitemap'],\n} as const\n","import type { TransformContext, TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { TransformRegistry } from './transformRegistry'\n\n/**\n * Stores original field values and their transforms for later restoration.\n * Keys are field paths (e.g., \"content\", \"metadata.description\").\n */\nexport interface OriginalStructure {\n [path: string]: {\n value: any\n transform: any\n }\n}\n\ninterface ApplyTransformsResult {\n transformedDocument: any\n originalStructure: OriginalStructure\n}\n\n/**\n * Recursively traverses a document and applies transforms to matching fields.\n * @param document - The document to transform\n * @param registry - The transform registry to use\n * @param schemaType - The document schema type\n * @param parentPath - Parent path (used for recursion)\n * @returns The transformed document and original structure\n */\nexport function applyTransforms({\n document,\n registry,\n schemaType,\n parentPath = '',\n}: {\n document: any\n registry: TransformRegistry\n schemaType: string\n parentPath?: string\n}): ApplyTransformsResult {\n if (!document) {\n return { transformedDocument: document, originalStructure: {} }\n }\n\n // Handle primitive values (no transforms needed)\n if (typeof document !== 'object') {\n return { transformedDocument: document, originalStructure: {} }\n }\n\n // Handle arrays\n if (Array.isArray(document)) {\n const originalStructure: OriginalStructure = {}\n const transformedArray = document.map((item, index) => {\n const itemPath = parentPath ? `${parentPath}[${index}]` : `[${index}]`\n const { transformedDocument: transformedItem, originalStructure: itemOriginalStructure } = applyTransforms({\n document: item,\n registry,\n schemaType,\n parentPath: itemPath,\n })\n\n // Merge original structure from items\n Object.assign(originalStructure, itemOriginalStructure)\n\n return transformedItem\n })\n\n return { transformedDocument: transformedArray, originalStructure }\n }\n\n // Handle objects\n const transformedObj: any = {}\n const originalStructure: OriginalStructure = {}\n\n // Process each property\n for (const [key, value] of Object.entries(document)) {\n const fieldPath = parentPath ? `${parentPath}.${key}` : key\n\n // Skip internal fields (starting with _) from transformation\n if (key.startsWith('_') && key !== '_type') {\n transformedObj[key] = value\n continue\n }\n\n // Check if there's a transform for this field\n // Pass value and document for value-based matching\n const transform = registry.findTransform(schemaType, fieldPath, value, document)\n\n if (transform) {\n devLog.debug('Applying transform', { fieldPath, schemaType })\n\n const context: TransformContext = {\n fieldPath,\n schemaType,\n document: document as TranslatableDocument,\n }\n\n // Apply transform\n const transformedValue = transform.toTranslatable(value, context)\n transformedObj[key] = transformedValue\n\n // Store original value for later restoration\n originalStructure[fieldPath] = {\n value,\n transform,\n }\n } else {\n // No transform found, process recursively\n const { transformedDocument: transformedValue, originalStructure: valueOriginalStructure } = applyTransforms({\n document: value,\n registry,\n schemaType,\n parentPath: fieldPath,\n })\n\n // Merge original structure from nested fields\n Object.assign(originalStructure, valueOriginalStructure)\n\n transformedObj[key] = transformedValue\n }\n }\n\n return { transformedDocument: transformedObj, originalStructure }\n}\n","import type { TransformContext, TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { OriginalStructure } from './applyTransforms'\nimport type { TransformRegistry } from './transformRegistry'\n\n// Type for values that can be translated and restored\ntype TranslatableValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | TranslatableValue[]\n | { [key: string]: TranslatableValue }\n | TranslatableDocument\n\n/**\n * Restores original structure with translated content.\n * @param translatedDocument - The translated document\n * @param originalStructure - Original structure with transform information\n * @param registry - The transform registry\n * @param schemaType - The document schema type\n * @param parentPath - Parent path (used for recursion)\n * @returns The restored document\n */\nexport function restoreTransforms({\n translatedDocument,\n originalStructure,\n registry,\n schemaType,\n parentPath = '',\n}: {\n translatedDocument: TranslatableValue\n originalStructure: OriginalStructure\n registry: TransformRegistry\n schemaType: string\n parentPath?: string\n}): TranslatableValue {\n if (translatedDocument === null || translatedDocument === undefined) {\n return translatedDocument\n }\n\n // Handle primitive values\n if (typeof translatedDocument !== 'object') {\n return translatedDocument\n }\n\n // Handle arrays\n if (Array.isArray(translatedDocument)) {\n return translatedDocument.map((item, index) => {\n const itemPath = parentPath ? `${parentPath}[${index}]` : `[${index}]`\n return restoreTransforms({\n translatedDocument: item,\n originalStructure,\n registry,\n schemaType,\n parentPath: itemPath,\n })\n })\n }\n\n // Check if current path has stored original structure\n const originalData = originalStructure[parentPath]\n if (originalData) {\n const { value: originalValue, transform } = originalData\n const context: TransformContext = {\n fieldPath: parentPath,\n schemaType,\n document: translatedDocument as TranslatableDocument,\n }\n\n devLog.debug('Restoring transform', { fieldPath: parentPath, schemaType })\n\n // Apply reverse transform\n return transform.fromTranslatable(translatedDocument, originalValue, context)\n }\n\n // Handle objects - at this point we know translatedDocument is a non-array object\n const restoredObj: Record<string, TranslatableValue> = {}\n const translatedObj = translatedDocument as Record<string, TranslatableValue>\n\n for (const [key, value] of Object.entries(translatedObj)) {\n const fieldPath = parentPath ? `${parentPath}.${key}` : key\n\n // Check if this specific field path has an original structure\n const fieldOriginalData = originalStructure[fieldPath]\n\n if (fieldOriginalData) {\n const { value: originalValue, transform } = fieldOriginalData\n const context: TransformContext = {\n fieldPath,\n schemaType,\n document: translatedDocument as TranslatableDocument,\n }\n\n devLog.debug('Restoring transform for field', { fieldPath, schemaType })\n\n // Apply reverse transform\n const restoredValue = transform.fromTranslatable(value, originalValue, context)\n restoredObj[key] = restoredValue\n } else {\n // Process recursively\n restoredObj[key] = restoreTransforms({\n translatedDocument: value,\n originalStructure,\n registry,\n schemaType,\n parentPath: fieldPath,\n })\n }\n }\n\n return restoredObj\n}\n","import type { AnyFieldTransform } from '../../types'\nimport { devLog } from '../common/devLog'\n\n/**\n * Registry for managing and applying field transforms.\n * @public\n */\nexport class TransformRegistry {\n private transforms: AnyFieldTransform[] = []\n\n /**\n * Register a transform with the registry.\n * @param transform - Field transform to register\n */\n register(transform: AnyFieldTransform): void {\n devLog.debug('Registering field transform', {\n matcher: transform.matcher,\n })\n this.transforms.push(transform)\n }\n\n /**\n * Find a matching transform for the given field.\n * If multiple transforms match, returns the first one (order matters!).\n * @param schemaType - Document schema type\n * @param fieldPath - Path to the field\n * @param value - Optional field value for value-based matching\n * @param document - Optional full document for context\n * @returns Matching transform or undefined if none found\n */\n findTransform(\n schemaType: string,\n fieldPath: string,\n // biome-ignore lint/suspicious/noExplicitAny: Field values can be any type\n value?: any,\n // biome-ignore lint/suspicious/noExplicitAny: Documents can be any shape\n document?: any,\n ): AnyFieldTransform | undefined {\n const matches: AnyFieldTransform[] = []\n\n for (const transform of this.transforms) {\n const { matcher } = transform\n\n // Check schema type if specified\n if (matcher.schemaType && matcher.schemaType !== schemaType) {\n continue\n }\n\n // Check field path if specified\n if (matcher.fieldPath) {\n if (typeof matcher.fieldPath === 'string') {\n if (matcher.fieldPath !== fieldPath) {\n continue\n }\n } else if (matcher.fieldPath instanceof RegExp) {\n if (!matcher.fieldPath.test(fieldPath)) {\n continue\n }\n }\n }\n\n // Check field name if specified\n if (matcher.fieldName) {\n // Extract field name from path (last segment, handling nested paths and array notation)\n // Examples: \"captions\" -> \"captions\", \"video.captions\" -> \"captions\", \"items[0].name\" -> \"name\"\n const segments = fieldPath.split('.')\n const lastSegment = segments[segments.length - 1]\n // Remove array notation if present: \"items[0]\" -> \"items\"\n const fieldName = lastSegment?.split('[')[0]\n\n if (fieldName !== matcher.fieldName) {\n continue\n }\n }\n\n // Check value-based matcher if specified\n if (matcher.valueMatcher) {\n // Value matcher requires the actual value to be provided\n if (value === undefined) {\n continue\n }\n\n const context = {\n fieldPath,\n schemaType,\n document: document || {},\n }\n\n if (!matcher.valueMatcher(value, context)) {\n continue\n }\n }\n\n // Transform matches all criteria\n matches.push(transform)\n }\n\n // Warn if multiple transforms match the same field\n if (matches.length > 1) {\n devLog.warn('Multiple transforms match the same field - using first match', {\n schemaType,\n fieldPath,\n matchCount: matches.length,\n firstMatcher: matches[0].matcher,\n })\n }\n\n return matches[0]\n }\n}\n\n/**\n * Global transform registry instance.\n * @public\n */\nexport const globalTransformRegistry = new TransformRegistry()\n","import { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\nexport interface GPT5Request {\n messages: Array<{ role: 'developer' | 'user' | 'assistant'; content: string }>\n model?: string\n temperature?: number\n reasoningEffort?: 'low' | 'medium' | 'high'\n}\n\nexport interface GPT5Response {\n content: string\n tokensUsed: {\n prompt: number\n completion: number\n reasoning: number\n total: number\n cached: number\n }\n cost: number\n}\n\n/**\n * Calls the OpenAI API with the given request parameters.\n * All GPT-5 variants (gpt-5, gpt-5.1, gpt-5-mini, gpt-5-nano, gpt-5-pro) use the new /v1/responses endpoint.\n * GPT-4 models use the standard /v1/chat/completions endpoint.\n */\nexport const callGPT5 = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n const isGPT5Family = model.startsWith('gpt-5')\n\n // All GPT-5 variants require /v1/responses endpoint; GPT-4 uses /v1/chat/completions\n if (isGPT5Family) {\n return await callGPT5ResponsesAPI(apiKey, request)\n }\n\n // GPT-4 and other models use the standard chat completions endpoint\n return await callChatCompletionsAPI(apiKey, request)\n}\n\n/**\n * Calls all GPT-5 variants using the new /v1/responses endpoint.\n * This endpoint supports reasoning configuration for applicable models.\n */\nconst callGPT5ResponsesAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n\n // Format request for OpenAI Responses API (different from chat completions API)\n const requestBody: Record<string, unknown> = {\n model: model === 'gpt-5' ? 'gpt-5.1' : model, // Map gpt-5 to gpt-5.1, use others as-is\n input: request.messages, // Uses 'input' instead of 'messages'\n }\n\n // Include reasoning effort setting if provided by caller\n if (request.reasoningEffort) {\n requestBody.reasoning = {\n effort: request.reasoningEffort,\n summary: 'auto', // Enable automatic reasoning summaries\n }\n }\n\n // Log the prompt being sent to OpenAI\n devLog.debug('OpenAI GPT-5 Request', {\n model: requestBody.model,\n reasoningEffort: request.reasoningEffort,\n messages: request.messages.map((m) => ({\n role: m.role,\n contentLength: m.content.length,\n contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? '...' : ''),\n })),\n })\n devLog.debug('OpenAI GPT-5 Full Prompt', {\n messages: request.messages,\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/responses', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `GPT-5 API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model: requestBody.model, endpoint: 'responses' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`GPT-5 API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n // Parse token usage from Responses API response structure\n const tokensUsed = {\n prompt: result.usage?.input_tokens || 0,\n completion: result.usage?.output_tokens || 0,\n reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,\n total: result.usage?.total_tokens || 0,\n cached: result.usage?.input_tokens_details?.cached_tokens || 0,\n }\n\n const cost = calculateCost(tokensUsed, model)\n\n // Responses API wraps output in array with reasoning and message objects\n let content = ''\n\n if (result.output && Array.isArray(result.output)) {\n // Find the message item in the output array\n const messageItem = result.output.find((item: { type: string }) => item.type === 'message') as\n | { content?: Array<{ type: string; text?: string }> }\n | undefined\n\n if (messageItem?.content && Array.isArray(messageItem.content)) {\n // Find the output_text item in the content array\n const outputTextItem = messageItem.content.find((item) => item.type === 'output_text')\n content = outputTextItem?.text || ''\n }\n }\n\n // Fallback to other possible field names if output array approach didn't work\n if (!content) {\n content = result.output_text || result.text || ''\n }\n\n return {\n content,\n tokensUsed,\n cost,\n }\n}\n\n/**\n * Calls GPT-4 and other models using the standard /v1/chat/completions endpoint.\n */\nconst callChatCompletionsAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-4o'\n\n const requestBody: Record<string, unknown> = {\n model,\n messages: request.messages,\n temperature: request.temperature ?? 0.3,\n }\n\n // Log the prompt being sent to OpenAI\n devLog.debug('OpenAI Chat Completions Request', {\n model,\n temperature: request.temperature ?? 0.3,\n messages: request.messages.map((m) => ({\n role: m.role,\n contentLength: m.content.length,\n contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? '...' : ''),\n })),\n })\n devLog.debug('OpenAI Chat Completions Full Prompt', {\n messages: request.messages,\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `OpenAI API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model, endpoint: 'chat/completions' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`OpenAI API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n const usage = result.usage || {}\n const tokensUsed = {\n prompt: usage.prompt_tokens || 0,\n completion: usage.completion_tokens || 0,\n reasoning: usage.reasoning_tokens || 0,\n total: usage.total_tokens || 0,\n cached: usage.prompt_tokens_details?.cached_tokens || 0,\n }\n\n const cost = calculateCost(tokensUsed, model)\n const message = result.choices[0].message\n const messageContent = message.content || ''\n\n return {\n content: messageContent,\n tokensUsed,\n cost,\n }\n}\n\n/**\n * Calculates the cost of an OpenAI API call based on token usage.\n * Supports pricing for all GPT-5 variants, GPT-4o, GPT-4 Turbo, and GPT-4.\n */\nconst calculateCost = (\n tokens: { prompt: number; completion: number; reasoning: number; cached: number },\n model: string,\n): number => {\n const pricing: Record<string, { input: number; output: number; reasoning: number }> = {\n 'gpt-5': {\n input: 1.25 / 1000000,\n output: 10.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-5.1': {\n input: 1.25 / 1000000,\n output: 10.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-5-mini': {\n input: 0.25 / 1000000,\n output: 2.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-5-nano': {\n input: 0.05 / 1000000,\n output: 0.4 / 1000000,\n reasoning: 0,\n },\n 'gpt-5-pro': {\n input: 15.0 / 1000000,\n output: 120.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-4o': {\n input: 2.5 / 1000000,\n output: 10.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-4-turbo': {\n input: 10.0 / 1000000,\n output: 30.0 / 1000000,\n reasoning: 0,\n },\n 'gpt-4': {\n input: 30.0 / 1000000,\n output: 60.0 / 1000000,\n reasoning: 0,\n },\n }\n\n const modelPricing = pricing[model] || pricing['gpt-5']\n\n // For GPT-5.1, cached input tokens are 10x cheaper (0.125 vs 1.25 per 1M tokens)\n const cachedInputRate = model === 'gpt-5.1' ? 0.125 / 1000000 : modelPricing.input\n const regularInputTokens = Math.max(0, tokens.prompt - tokens.cached)\n\n return (\n regularInputTokens * modelPricing.input +\n tokens.cached * cachedInputRate +\n tokens.completion * modelPricing.output +\n tokens.reasoning * modelPricing.reasoning\n )\n}\n","import cloneDeep from 'lodash.clonedeep'\nimport get from 'lodash.get'\nimport set from 'lodash.set'\nimport type { SanityDocument } from 'sanity'\nimport { devLog } from '../common/devLog'\n\ninterface ParsedNode {\n type: 'field' | 'block' | 'span'\n name?: string\n text?: string\n attributes: Record<string, string>\n children: ParsedNode[]\n}\n\n/**\n * Deserializes a reviewed text format back into a Sanity document.\n * Only applies text changes - all structure and metadata preserved.\n */\nexport const deserializeDocument = (originalDoc: SanityDocument, reviewedText: string): SanityDocument => {\n // Log raw response for debugging (first 1000 chars to avoid log spam)\n devLog.debug('Raw GPT-5 response received', {\n length: reviewedText.length,\n preview: reviewedText.substring(0, 1000),\n hasReasoning: reviewedText.includes('<reasoning>'),\n hasCodeBlocks: reviewedText.includes('```'),\n })\n\n // Preprocess: Remove reasoning tags and code block markers that GPT-5 may include\n const cleanedText = preprocessResponse(reviewedText)\n\n // Log cleaned response for debugging\n if (cleanedText.length !== reviewedText.length) {\n devLog.debug('Response after preprocessing', {\n originalLength: reviewedText.length,\n cleanedLength: cleanedText.length,\n preview: cleanedText.substring(0, 500),\n })\n }\n\n // Parse OpenAI's XML-structured response back into node tree\n const parsed = parseReviewFormat(cleanedText, originalDoc)\n\n devLog.debug('Parsed response structure', {\n rootNodeCount: parsed.length,\n rootNodeNames: parsed.map((n) => (n.type === 'field' && n.name ? n.name : 'unknown')),\n })\n\n // Deep clone original\n const result = cloneDeep(originalDoc)\n\n // Apply text changes from parsed structure\n applyTextChanges(result, parsed)\n\n return result\n}\n\n/**\n * Preprocesses GPT-5 response to remove reasoning tags and code block markers.\n * GPT-5 may wrap responses in <reasoning> tags and code blocks (```xml ... ```).\n */\nconst preprocessResponse = (text: string): string => {\n const originalLength = text.length\n let cleaned = text\n\n // Remove reasoning tags and their content\n // Matches <reasoning>...</reasoning> including multiline content\n const beforeReasoning = cleaned.length\n cleaned = cleaned.replace(/<reasoning>[\\s\\S]*?<\\/reasoning>/gi, '')\n const reasoningRemoved = beforeReasoning !== cleaned.length\n\n // Remove code block markers (```xml, ```, etc.)\n // Matches opening markers like ```xml, ```json, etc.\n const beforeCodeBlocks = cleaned.length\n cleaned = cleaned.replace(/^```[a-z]*\\s*$/gim, '')\n // Matches closing markers ```\n cleaned = cleaned.replace(/^```\\s*$/gim, '')\n const codeBlocksRemoved = beforeCodeBlocks !== cleaned.length\n\n const finalLength = cleaned.trim().length\n\n if (reasoningRemoved || codeBlocksRemoved) {\n devLog.debug('Preprocessed GPT-5 response', {\n originalLength,\n finalLength,\n reasoningRemoved,\n codeBlocksRemoved,\n preview: cleaned.trim().substring(0, 200),\n })\n }\n\n return cleaned.trim()\n}\n\nconst parseReviewFormat = (text: string, originalDoc: SanityDocument): ParsedNode[] => {\n const root: ParsedNode[] = []\n const stack: ParsedNode[] = []\n\n // Parse XML-like format\n const lines = text.split('\\n')\n\n devLog.debug('Parsing response text', {\n lineCount: lines.length,\n firstLines: lines.slice(0, 10).map((l, idx) => ({ line: idx, content: l.substring(0, 100) })),\n hasFieldTags: text.includes('<field'),\n hasBlockTags: text.includes('<block'),\n hasSpanTags: text.includes('<span'),\n textLength: text.length,\n textPreview: text.substring(0, 500),\n })\n\n let processedLineCount = 0\n let skippedEmptyLineCount = 0\n let matchedPatternCount = 0\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const trimmed = line.trim()\n if (!trimmed) {\n skippedEmptyLineCount++\n continue\n }\n processedLineCount++\n\n // Check for compact string format (fieldName: value) - legacy support\n const compactStringMatch = trimmed.match(/^(\\w+):\\s*(.*)$/)\n if (compactStringMatch && !trimmed.includes('<')) {\n matchedPatternCount++\n const [, fieldName, text] = compactStringMatch\n\n const node: ParsedNode = {\n type: 'field',\n name: fieldName,\n text: unescapeXml(text),\n attributes: { type: 'string' },\n children: [],\n }\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n continue\n }\n\n // Check for compact format markers (--- fieldName ---)\n const compactStartMatch = trimmed.match(/^---\\s+(\\w+)\\s+---$/)\n if (compactStartMatch) {\n matchedPatternCount++\n const fieldName = compactStartMatch[1]\n const contentLines: string[] = []\n i++ // Move to next line\n\n // Capture content until closing marker\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.trim().match(new RegExp(`^---\\\\s+/${fieldName}\\\\s+---$`))) {\n break\n }\n contentLines.push(currentLine)\n i++\n }\n\n const content = contentLines.join('\\n')\n\n // Try to get the actual type from the original document field\n // This ensures custom transforms work without hardcoding types\n let contentType: string | undefined\n try {\n const fieldValue = get(originalDoc, [fieldName])\n if (fieldValue && typeof fieldValue === 'object' && fieldValue !== null) {\n const fieldValueObj = fieldValue as Record<string, unknown>\n const fieldType = fieldValueObj._type\n if (typeof fieldType === 'string' && fieldType.startsWith('translatable.')) {\n contentType = fieldType\n }\n }\n } catch {\n // Field doesn't exist or path is invalid, fall through to content-based detection\n }\n\n // Fallback to content-based detection if we couldn't get type from original document\n if (!contentType) {\n if (content.includes('<block') || content.includes('<span')) {\n contentType = 'translatable.richtext'\n } else if (content.match(/^\\[\\d+\\]/m)) {\n // Content with [index] format - default to VTT for backward compatibility\n contentType = 'translatable.vtt.captions'\n } else {\n // Unknown format, default to VTT\n contentType = 'translatable.vtt.captions'\n }\n }\n\n const node: ParsedNode = {\n type: 'field',\n name: fieldName,\n text: content,\n attributes: { type: contentType },\n children: [],\n }\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n continue\n }\n\n // Opening tag\n const openMatch = trimmed.match(/^<(field|block|span)([^>]*)>(.*)$/)\n if (openMatch) {\n matchedPatternCount++\n devLog.debug('Matched opening tag', {\n line: i,\n tagName: openMatch[1],\n attributes: openMatch[2],\n contentPreview: openMatch[3]?.substring(0, 50),\n })\n const [, tagName, attrsStr, content] = openMatch\n const attributes = parseAttributes(attrsStr)\n\n const node: ParsedNode = {\n type: tagName as 'field' | 'block' | 'span',\n name: attributes.name,\n text: content ? unescapeXml(content) : undefined,\n attributes,\n children: [],\n }\n\n // Check if self-closing (has content on same line)\n const closingTag = `</${tagName}>`\n if (content.includes(closingTag)) {\n // Self-closing: extract text up to first closing tag; push back remainder (handles siblings on same line)\n const closeIdx = content.indexOf(closingTag)\n node.text = unescapeXml(content.substring(0, closeIdx))\n const remainder = content.substring(closeIdx + closingTag.length).trim()\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n if (remainder) {\n lines[i] = remainder\n i--\n }\n } else {\n // Special handling for transformed fields - capture raw content\n // Generic check for any translatable.* type\n if (attributes.type && typeof attributes.type === 'string' && attributes.type.startsWith('translatable.')) {\n // Capture all content until closing tag (break when line contains </field>, push back remainder)\n const contentLines: string[] = []\n i++ // Move to next line\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.includes('</field>')) {\n const idx = currentLine.indexOf('</field>')\n const before = currentLine.substring(0, idx).trimEnd()\n if (before.trim()) contentLines.push(before)\n const after = currentLine.substring(idx + '</field>'.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n node.text = contentLines.join('\\n')\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n } else if (attributes.type === 'string' && content && !content.includes(closingTag)) {\n // Multi-line string field with content starting on opening line\n // This handles GPT-5 responses where the content spans multiple lines\n // e.g., <field name=\"text\" type=\"string\">First line content\n //\n // More content on subsequent lines </field>\n const contentLines: string[] = [content]\n i++ // Move to next line\n while (i < lines.length) {\n const currentLine = lines[i]\n // Check if this line contains the closing tag (could be at end of content line)\n if (currentLine.includes(closingTag)) {\n const closingIndex = currentLine.indexOf(closingTag)\n const remainingContent = currentLine.substring(0, closingIndex).trimEnd()\n if (remainingContent) {\n contentLines.push(remainingContent)\n }\n const after = currentLine.substring(closingIndex + closingTag.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n node.text = unescapeXml(contentLines.join('\\n'))\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n } else if (attributes.type === 'string' && !content) {\n // Multi-line string field - capture content until closing tag (break when line contains </field>, push back remainder)\n const contentLines: string[] = []\n i++ // Move to next line\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.includes('</field>')) {\n const idx = currentLine.indexOf('</field>')\n const before = currentLine.substring(0, idx).trimEnd()\n if (before.trim()) contentLines.push(before)\n const after = currentLine.substring(idx + '</field>'.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n node.text = contentLines.join('\\n').trim()\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n } else {\n // Opening tag for container\n stack.push(node)\n }\n }\n continue\n }\n\n // Closing tag\n // Match closing tags even when there's extra content on the same line (comments, whitespace, etc.)\n // Consume all closing tags on this line in a loop to avoid infinite loop when multiple </field> on one line\n const closeMatch = trimmed.match(/^<\\/(field|block|span)>/)\n if (closeMatch) {\n let afterClosingTag = trimmed\n // Consume one or more closing tags from the start of the line\n for (;;) {\n const match = afterClosingTag.match(/^<\\/(field|block|span)>/)\n if (!match) break\n if (stack.length === 0) {\n devLog.warn('Closing tag found but stack is empty', {\n line: i,\n tagName: match[1],\n trimmed: afterClosingTag,\n })\n afterClosingTag = afterClosingTag.substring(match[0].length).trim()\n afterClosingTag = afterClosingTag.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n continue\n }\n matchedPatternCount++\n devLog.debug('Matched closing tag', {\n line: i,\n tagName: match[1],\n trimmed: afterClosingTag,\n stackLength: stack.length,\n stackTopName: stack[stack.length - 1]?.name,\n })\n const node = stack.pop()\n if (node) {\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(node)\n } else {\n root.push(node)\n }\n }\n afterClosingTag = afterClosingTag.substring(match[0].length).trim()\n afterClosingTag = afterClosingTag.replace(/<!--[\\s\\S]*?-->/g, '').trim()\n }\n // If there's content after the closing tag(s), process opening tag or push back remainder\n if (afterClosingTag) {\n const remainingOpenMatch = afterClosingTag.match(/^<(field|block|span)([^>]*)>(.*)$/)\n if (remainingOpenMatch) {\n const [, tagName, attrsStr, content] = remainingOpenMatch\n const attributes = parseAttributes(attrsStr)\n\n const remainingNode: ParsedNode = {\n type: tagName as 'field' | 'block' | 'span',\n name: attributes.name,\n text: content ? unescapeXml(content) : undefined,\n attributes,\n children: [],\n }\n\n const remainingClosingTag = `</${tagName}>`\n if (content.includes(remainingClosingTag)) {\n // Self-closing on same line: extract text up to first closing tag, push back remainder\n const closeIdx = content.indexOf(remainingClosingTag)\n remainingNode.text = unescapeXml(content.substring(0, closeIdx))\n const remainder = content.substring(closeIdx + remainingClosingTag.length).trim()\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(remainingNode)\n } else {\n root.push(remainingNode)\n }\n if (remainder) {\n lines[i] = remainder\n i--\n }\n } else if (attributes.type === 'string' && content) {\n remainingNode.text = unescapeXml(content)\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(remainingNode)\n } else {\n root.push(remainingNode)\n }\n } else if (attributes.type === 'string' && !content) {\n const contentLines: string[] = []\n i++\n while (i < lines.length) {\n const currentLine = lines[i]\n if (currentLine.includes(`</${tagName}>`)) {\n const idx = currentLine.indexOf(`</${tagName}>`)\n const before = currentLine.substring(0, idx).trimEnd()\n if (before.trim()) contentLines.push(before)\n const after = currentLine.substring(idx + `</${tagName}>`.length).trim()\n if (after) {\n lines[i] = after\n i--\n }\n break\n }\n contentLines.push(currentLine)\n i++\n }\n remainingNode.text = contentLines.join('\\n').trim()\n\n if (stack.length > 0) {\n const parent = stack[stack.length - 1]\n if (parent) parent.children.push(remainingNode)\n } else {\n root.push(remainingNode)\n }\n } else {\n stack.push(remainingNode)\n }\n } else {\n // Remainder is not an opening tag (e.g. more closing tags with different spacing); push back to reprocess\n lines[i] = afterClosingTag\n i--\n }\n }\n continue\n }\n\n // If we get here, the line didn't match any pattern\n // Check if it looks like a closing tag that didn't match\n if (trimmed.startsWith('</')) {\n const attemptedCloseMatch = trimmed.match(/^<\\/(field|block|span)>/)\n if (!attemptedCloseMatch) {\n devLog.warn('Line starts with </ but did not match closing tag pattern', {\n line: i,\n trimmed,\n stackLength: stack.length,\n stackTopName: stack[stack.length - 1]?.name,\n stackTopType: stack[stack.length - 1]?.type,\n })\n } else if (stack.length === 0) {\n devLog.warn('Closing tag matched but stack is empty (unmatched closing tag)', {\n line: i,\n tagName: attemptedCloseMatch[1],\n trimmed,\n })\n }\n }\n if (processedLineCount <= 20) {\n // Log first 20 non-empty lines that don't match\n devLog.debug('Line did not match any pattern', {\n line: i,\n trimmed,\n startsWithLessThan: trimmed.startsWith('<'),\n startsWithField: trimmed.startsWith('<field'),\n startsWithBacktick: trimmed.startsWith('`'),\n startsWithClosingTag: trimmed.startsWith('</'),\n })\n }\n }\n\n // If there are nodes remaining in the stack, add them to root\n // This handles cases where closing tags are missing or malformed\n if (stack.length > 0) {\n devLog.warn('Nodes remaining in stack after parsing complete', {\n stackLength: stack.length,\n remainingNodes: stack.map((n) => ({\n type: n.type,\n name: n.name,\n attributes: n.attributes,\n })),\n })\n // Add all remaining nodes to root (they should have been closed but weren't)\n while (stack.length > 0) {\n const node = stack.pop()\n if (node) {\n root.push(node)\n }\n }\n }\n\n devLog.debug('Parse complete', {\n totalLines: lines.length,\n processedLines: processedLineCount,\n skippedEmptyLines: skippedEmptyLineCount,\n rootNodeCount: root.length,\n stackLength: stack.length,\n matchedPatterns: matchedPatternCount,\n })\n\n return root\n}\n\nconst parseAttributes = (attrStr: string): Record<string, string> => {\n const attrs: Record<string, string> = {}\n const attrRegex = /(\\w+)=\"([^\"]*)\"/g\n let match: RegExpExecArray | null = attrRegex.exec(attrStr)\n\n while (match !== null) {\n attrs[match[1]] = unescapeXml(match[2])\n match = attrRegex.exec(attrStr)\n }\n\n return attrs\n}\n\nconst unescapeXml = (str: string): string => {\n return str\n .replace(/&apos;/g, \"'\")\n .replace(/&quot;/g, '\"')\n .replace(/&gt;/g, '>')\n .replace(/&lt;/g, '<')\n .replace(/&amp;/g, '&')\n}\n\nconst applyTextChanges = (doc: Record<string, unknown>, parsedNodes: ParsedNode[]): void => {\n devLog.debug('Applying text changes from parsed nodes', {\n nodeCount: parsedNodes.length,\n nodeNames: parsedNodes.map((n) => (n.type === 'field' && n.name ? n.name : 'unknown')),\n })\n\n for (const node of parsedNodes) {\n if (node.type === 'field' && node.name) {\n const path = [node.name]\n const beforeValue = get(doc, path)\n applyFieldChanges(doc, path, node)\n const afterValue = get(doc, path)\n if (beforeValue !== afterValue) {\n devLog.debug('Field updated during deserialization', {\n path: path.join('.'),\n before: typeof beforeValue === 'string' ? beforeValue.substring(0, 50) : beforeValue,\n after: typeof afterValue === 'string' ? afterValue.substring(0, 50) : afterValue,\n })\n }\n }\n }\n}\n\nconst applyFieldChanges = (doc: Record<string, unknown>, path: (string | number)[], node: ParsedNode): void => {\n // Generic handling for any transformed field with translatable.* type\n // This allows custom transforms to work without modifying deserialization code\n const nodeType = node.attributes.type\n if (typeof nodeType === 'string' && nodeType.startsWith('translatable.')) {\n const fieldValue = get(doc, path)\n if (fieldValue && typeof fieldValue === 'object' && fieldValue !== null) {\n const fieldValueObj = fieldValue as Record<string, unknown>\n const fieldType = fieldValueObj._type\n // Verify the field type matches what we expect (safety check)\n if (typeof fieldType === 'string' && fieldType === nodeType) {\n // The translated content is captured in node.text during parsing\n if (node.text) {\n // Update the translationText field with the translated content\n set(doc, [...path, 'translationText'], node.text)\n }\n }\n }\n return\n }\n\n if (node.attributes.type === 'array') {\n // Handle array fields\n node.children.forEach((child, idx) => {\n applyFieldChanges(doc, [...path, idx], child)\n })\n return\n }\n\n if (node.attributes.type === 'object') {\n // Handle object fields\n const currentValue = get(doc, path)\n\n // Verify that the current value is actually an object (safety check)\n if (currentValue && typeof currentValue === 'object' && !Array.isArray(currentValue)) {\n const currentValueObj = currentValue as Record<string, unknown>\n\n for (const child of node.children) {\n if (child.name) {\n // Check if the child name exists as a field in the actual object\n const childValue = currentValueObj[child.name]\n\n // If child name is numeric and the value doesn't exist, this is likely a parsing error\n // Skip it to avoid creating incorrect paths like \"questions.2.3.stem\"\n const numericName = /^\\d+$/.test(child.name)\n if (numericName && childValue === undefined) {\n devLog.warn('Skipping numeric child name in object field - field does not exist (likely parsing error)', {\n path: path.join('.'),\n childName: child.name,\n availableKeys: Object.keys(currentValueObj),\n childType: child.attributes.type,\n })\n continue\n }\n\n applyFieldChanges(doc, [...path, child.name], child)\n }\n }\n } else {\n devLog.warn('Object field type but value is not an object', {\n path: path.join('.'),\n actualType: Array.isArray(currentValue) ? 'array' : typeof currentValue,\n })\n }\n return\n }\n\n // Check for blocks (Portable Text)\n if (node.children.some((c) => c.type === 'block')) {\n const fieldValue = get(doc, path)\n if (Array.isArray(fieldValue)) {\n node.children.forEach((blockNode, idx) => {\n if (blockNode.type === 'block') {\n applyBlockChanges(fieldValue[idx], blockNode)\n }\n })\n }\n return\n }\n\n // Simple text field (including explicit string type)\n // Only update fields that exist in the original document - don't create new fields\n // This prevents OpenAI from adding fields that were intentionally excluded\n // CRITICAL: Only update if existing value is a string - never overwrite objects/arrays with strings\n // This prevents LLM malformed output (empty object tags, wrong types) from corrupting document structure\n if (node.text !== undefined && !node.attributes.readonly) {\n const existingValue = get(doc, path)\n if (existingValue !== undefined && typeof existingValue === 'string') {\n set(doc, path, node.text)\n } else if (existingValue !== undefined) {\n devLog.debug('Skipping field - existing value is not a string (would corrupt structure)', {\n path: path.join('.'),\n existingType: typeof existingValue,\n attemptedValue: node.text?.substring(0, 50),\n })\n } else {\n devLog.debug('Skipping field not present in original document', {\n path: path.join('.'),\n attemptedValue: node.text,\n })\n }\n }\n}\n\nconst applyBlockChanges = (block: Record<string, unknown>, blockNode: ParsedNode): void => {\n // Verify metadata matches (safety check)\n if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {\n devLog.warn('Block key mismatch, skipping', {\n expected: block._key,\n found: blockNode.attributes._key,\n component: 'deserializeDocument',\n })\n return\n }\n\n // Handle standard Portable Text blocks with span children\n const children = block.children\n if (Array.isArray(children) && blockNode.children.some((c) => c.type === 'span')) {\n blockNode.children.forEach((spanNode, idx) => {\n const childItem = (children as Record<string, unknown>[])[idx]\n if (spanNode.type === 'span' && childItem) {\n applySpanChanges(childItem, spanNode)\n }\n })\n } else if (blockNode.children.some((c) => c.type === 'field')) {\n // Handle custom block types with nested fields\n blockNode.children.forEach((fieldNode) => {\n if (fieldNode.type === 'field' && fieldNode.name) {\n applyBlockFieldChanges(block, fieldNode.name, fieldNode)\n }\n })\n }\n}\n\nconst applyBlockFieldChanges = (block: Record<string, unknown>, fieldName: string, fieldNode: ParsedNode): void => {\n const currentValue = block[fieldName]\n\n // Handle arrays\n if (fieldNode.attributes.type === 'array') {\n if (Array.isArray(currentValue)) {\n fieldNode.children.forEach((childNode, idx) => {\n if (childNode.type === 'block' && currentValue[idx]) {\n // Nested block in array\n applyBlockChanges(currentValue[idx] as Record<string, unknown>, childNode)\n } else if (childNode.type === 'field') {\n // Nested field in array\n applyBlockFieldChanges(currentValue[idx] as Record<string, unknown>, childNode.name || String(idx), childNode)\n }\n })\n }\n return\n }\n\n // Handle objects\n if (fieldNode.attributes.type === 'object') {\n if (currentValue && typeof currentValue === 'object') {\n fieldNode.children.forEach((childNode) => {\n if (childNode.name) {\n applyBlockFieldChanges(currentValue as Record<string, unknown>, childNode.name, childNode)\n }\n })\n }\n return\n }\n\n // Handle simple text fields\n // Only update fields that exist in the original block - don't create new fields\n // CRITICAL: Only update if existing value is a string - never overwrite objects/arrays with strings\n // This prevents LLM malformed output from corrupting block structure (e.g. figureDetails, children)\n if (fieldNode.text !== undefined && !fieldNode.attributes.readonly) {\n if (fieldName in block && typeof block[fieldName] === 'string') {\n block[fieldName] = fieldNode.text\n } else if (fieldName in block) {\n devLog.debug('Skipping block field - existing value is not a string (would corrupt structure)', {\n fieldName,\n existingType: typeof block[fieldName],\n attemptedValue: fieldNode.text?.substring(0, 50),\n })\n } else {\n devLog.debug('Skipping block field not present in original', {\n fieldName,\n attemptedValue: fieldNode.text,\n })\n }\n }\n}\n\nconst applySpanChanges = (span: Record<string, unknown>, spanNode: ParsedNode): void => {\n // Verify metadata matches\n if (spanNode.attributes._key && span._key !== spanNode.attributes._key) {\n devLog.warn('Span key mismatch, skipping', {\n expected: span._key,\n found: spanNode.attributes._key,\n component: 'deserializeDocument',\n })\n return\n }\n\n // ONLY update the text property\n if (spanNode.text !== undefined) {\n span.text = spanNode.text\n }\n}\n","import type { SanityDocument } from 'sanity'\n\n/**\n * Converts Sanity documents to a structured text format for OpenAI review.\n * The format is deterministic and preserves all structure and metadata.\n * Only text content can be modified during review.\n */\nexport const serializeDocument = (doc: SanityDocument) => {\n const lines: string[] = []\n\n const serialize = (value: unknown, path: (string | number)[] = [], indent = 0): void => {\n const indentStr = ' '.repeat(indent)\n\n // Skip Sanity metadata at root\n if (path.length === 0) {\n if (isPlainObject(value)) {\n for (const [key, val] of Object.entries(value)) {\n if (!key.startsWith('_') && key !== 'language') {\n serialize(val, [key], 0) // Always use indent 0 to reduce whitespace\n }\n }\n }\n return\n }\n\n const fieldName = path[path.length - 1]\n\n // Handle arrays\n if (Array.isArray(value)) {\n // Check if it's Portable Text\n const isPortableText = value.some((item) => item?._type === 'block')\n\n if (isPortableText) {\n lines.push(`${indentStr}<field name=\"${fieldName}\">`)\n value.forEach((block, idx) => {\n serializeBlock(block, [...path, idx], indent + 1, lines)\n })\n lines.push(`${indentStr}</field>`)\n } else {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"array\">`)\n value.forEach((item, idx) => {\n serialize(item, [...path, idx], indent + 1)\n })\n lines.push(`${indentStr}</field>`)\n }\n return\n }\n\n // Handle objects\n if (isPlainObject(value)) {\n // Generic handling for any transformed field with translatable.* type\n // This allows custom transforms to work without modifying serialization code\n const valueObj = value as Record<string, unknown>\n const valueType = valueObj._type\n const translationText = valueObj.translationText\n if (\n typeof valueType === 'string' &&\n valueType.startsWith('translatable.') &&\n typeof translationText === 'string'\n ) {\n // Output the translationText directly in compact format\n // Use a simple marker format without extra XML overhead\n lines.push(`${indentStr}--- ${fieldName} ---`)\n lines.push(translationText)\n lines.push(`${indentStr}--- /${fieldName} ---`)\n return\n }\n\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"object\">`)\n for (const [key, val] of Object.entries(value)) {\n if (!key.startsWith('_')) {\n serialize(val, [...path, key], indent + 1)\n }\n }\n lines.push(`${indentStr}</field>`)\n return\n }\n\n // Handle strings (actual translatable content)\n if (typeof value === 'string') {\n if (value.length > 0) {\n // Use explicit field markers for consistency with GPT-5\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"string\">${escapeXml(value)}</field>`)\n }\n return\n }\n\n // Other primitives (numbers, booleans) - include but mark readonly\n if (typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"${typeof value}\" readonly=\"true\">${value}</field>`)\n }\n }\n\n serialize(doc)\n return lines.join('\\n')\n}\n\nconst serializeBlock = (\n block: Record<string, unknown>,\n path: (string | number)[],\n indent: number,\n lines: string[],\n): void => {\n const indentStr = ' '.repeat(indent)\n\n // Store block metadata\n const metadata: Record<string, unknown> = {}\n if (block._type) metadata._type = block._type\n if (block._key) metadata._key = block._key\n if (block.style) metadata.style = block.style\n if (block.listItem) metadata.listItem = block.listItem\n if (block.level) metadata.level = block.level\n if (block.markDefs) metadata.markDefs = block.markDefs\n\n const metaStr = Object.entries(metadata)\n .map(([k, v]) => `${k}=\"${typeof v === 'string' ? escapeXml(v) : escapeXml(JSON.stringify(v))}\"`)\n .join(' ')\n\n lines.push(`${indentStr}<block ${metaStr}>`)\n\n // Handle standard Portable Text blocks with children (spans)\n if (Array.isArray(block.children) && block._type === 'block') {\n block.children.forEach((child: unknown, idx: number) => {\n serializeSpan(child as Record<string, unknown>, [...path, 'children', idx], indent + 1, lines)\n })\n } else {\n // Handle custom block types by recursively serializing all non-metadata fields\n for (const [key, value] of Object.entries(block)) {\n // Skip metadata fields already serialized in the block header\n if (key.startsWith('_') || key === 'style' || key === 'listItem' || key === 'level' || key === 'markDefs') {\n continue\n }\n\n // Recursively serialize the field content\n serializeBlockField(value, key, [...path, key], indent + 1, lines)\n }\n }\n\n lines.push(`${indentStr}</block>`)\n}\n\nconst serializeBlockField = (\n value: unknown,\n fieldName: string,\n path: (string | number)[],\n indent: number,\n lines: string[],\n): void => {\n const indentStr = ' '.repeat(indent)\n\n // Handle arrays\n if (Array.isArray(value)) {\n // Check if array contains blocks (nested Portable Text or custom blocks)\n const hasBlocks = value.some((item) => item?._type)\n\n if (hasBlocks) {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"array\">`)\n value.forEach((item, idx) => {\n if (item?._type === 'block' && item?.children) {\n // Standard Portable Text block\n serializeBlock(item, [...path, idx], indent + 1, lines)\n } else if (item?._type) {\n // Custom block type - treat as block\n serializeBlock(item, [...path, idx], indent + 1, lines)\n } else {\n // Non-block array item\n serializeBlockField(item, String(idx), [...path, idx], indent + 1, lines)\n }\n })\n lines.push(`${indentStr}</field>`)\n } else {\n // Simple array without blocks\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"array\">`)\n value.forEach((item, idx) => {\n serializeBlockField(item, String(idx), [...path, idx], indent + 1, lines)\n })\n lines.push(`${indentStr}</field>`)\n }\n return\n }\n\n // Handle objects\n if (isPlainObject(value)) {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"object\">`)\n for (const [key, val] of Object.entries(value)) {\n if (!key.startsWith('_')) {\n serializeBlockField(val, key, [...path, key], indent + 1, lines)\n }\n }\n lines.push(`${indentStr}</field>`)\n return\n }\n\n // Handle strings\n if (typeof value === 'string' && value.length > 0) {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"string\">${escapeXml(value)}</field>`)\n return\n }\n\n // Other primitives\n if (typeof value === 'number' || typeof value === 'boolean') {\n lines.push(`${indentStr}<field name=\"${fieldName}\" type=\"${typeof value}\" readonly=\"true\">${value}</field>`)\n }\n}\n\nconst serializeSpan = (\n span: Record<string, unknown>,\n _path: (string | number)[],\n indent: number,\n lines: string[],\n): void => {\n const indentStr = ' '.repeat(indent)\n\n if (!span || typeof span !== 'object') return\n\n // Store span metadata\n const metadata: Record<string, unknown> = {}\n if (span._type) metadata._type = span._type\n if (span._key) metadata._key = span._key\n if (span.marks && Array.isArray(span.marks)) {\n metadata.marks = span.marks.join(',')\n }\n\n const metaStr = Object.entries(metadata)\n .map(([k, v]) => `${k}=\"${escapeXml(String(v))}\"`)\n .join(' ')\n\n const text = typeof span.text === 'string' ? span.text : ''\n\n if (metaStr) {\n lines.push(`${indentStr}<span ${metaStr}>${escapeXml(text)}</span>`)\n } else {\n lines.push(`${indentStr}<span>${escapeXml(text)}</span>`)\n }\n}\n\nconst escapeXml = (str: string): string => {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\nconst isPlainObject = (val: unknown): val is Record<string, unknown> => {\n return typeof val === 'object' && val !== null && !Array.isArray(val)\n}\n","import type { SanityDocument } from 'sanity'\n\nexport interface ValidationError {\n path: string[]\n expected: unknown\n actual: unknown\n type: 'missing' | 'type_mismatch' | 'extra' | 'metadata_changed'\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: ValidationError[]\n}\n\n/**\n * Validates that document structure has not been changed.\n * Extremely strict validation - any structural change is an error.\n * Only text content in 'text' fields is allowed to change.\n */\nexport const validateStructureIntegrity = (original: SanityDocument, modified: SanityDocument): ValidationResult => {\n const errors: ValidationError[] = []\n\n // Phase 1: Check critical metadata\n const criticalFields = ['_id', '_type', '_rev', '_createdAt', '_updatedAt']\n for (const field of criticalFields) {\n if (original[field] !== modified[field]) {\n errors.push({\n path: [field],\n expected: original[field],\n actual: modified[field],\n type: 'metadata_changed',\n })\n }\n }\n\n // Phase 2: Deep structure validation\n validateStructureRecursive(original, modified, [], errors)\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\nconst validateStructureRecursive = (\n original: unknown,\n modified: unknown,\n path: string[],\n errors: ValidationError[],\n): void => {\n // Type check\n if (typeof original !== typeof modified) {\n errors.push({\n path,\n expected: typeof original,\n actual: typeof modified,\n type: 'type_mismatch',\n })\n return\n }\n\n // Array validation\n if (Array.isArray(original)) {\n if (!Array.isArray(modified)) {\n errors.push({\n path,\n expected: 'array',\n actual: typeof modified,\n type: 'type_mismatch',\n })\n return\n }\n\n if (original.length !== modified.length) {\n errors.push({\n path,\n expected: `length ${original.length}`,\n actual: `length ${modified.length}`,\n type: 'type_mismatch',\n })\n return\n }\n\n original.forEach((item, idx) => {\n validateStructureRecursive(item, modified[idx], [...path, String(idx)], errors)\n })\n return\n }\n\n // Object validation\n if (typeof original === 'object' && original !== null && typeof modified === 'object' && modified !== null) {\n const origKeys = Object.keys(original as object).sort()\n const modKeys = Object.keys(modified as object).sort()\n\n // Check for missing keys\n for (const key of origKeys) {\n if (!(key in modified)) {\n errors.push({\n path: [...path, key],\n expected: 'present',\n actual: 'missing',\n type: 'missing',\n })\n }\n }\n\n // Check for extra keys\n for (const key of modKeys) {\n if (!(key in original)) {\n errors.push({\n path: [...path, key],\n expected: 'absent',\n actual: 'present',\n type: 'extra',\n })\n }\n }\n\n // Validate nested structure\n const origObj = original as Record<string, unknown>\n const modObj = modified as Record<string, unknown>\n for (const key of origKeys) {\n if (key in modObj) {\n // For metadata fields, require exact match\n if (key.startsWith('_') || key === 'marks' || key === 'style' || key === 'markDefs') {\n if (JSON.stringify(origObj[key]) !== JSON.stringify(modObj[key])) {\n errors.push({\n path: [...path, key],\n expected: origObj[key],\n actual: modObj[key],\n type: 'metadata_changed',\n })\n }\n } else if (key !== 'text') {\n // For non-text fields, validate structure recursively\n validateStructureRecursive(origObj[key], modObj[key], [...path, key], errors)\n }\n // Only 'text' fields can be modified; all other structure is immutable\n }\n }\n }\n}\n","import type { Dispatch } from 'react'\nimport type { PluginConfig, TelemetryConfig, TranslatableDocument, TranslationAction } from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { callGPT5 } from '../openai/callGPT5'\nimport { deserializeDocument } from '../openai/deserializeDocument'\nimport { serializeDocument } from '../openai/serializeDocument'\nimport { validateStructureIntegrity } from '../openai/validateStructure'\nimport { setJobStatus } from '../state/setJobStatus'\nimport { buildErrorBundle, classifyError, emitTelemetryError } from '../telemetry'\n\n/**\n * Result from OpenAI translation including document and metrics\n */\nexport interface OpenAITranslationResult {\n document: TranslatableDocument\n tokensUsed: number\n cost: number\n}\n\n/**\n * Translates or reviews a document using OpenAI.\n * Works in-place - receives a document, returns translated/reviewed document with metrics.\n */\nexport const getOpenAITranslation = async ({\n dispatch,\n sourceDocument,\n languageFieldName,\n translationPrompts,\n targetLanguage,\n openaiConfig,\n previousError: _previousError,\n errorContext,\n telemetryConfig,\n}: {\n dispatch: Dispatch<TranslationAction>\n sourceDocument: TranslatableDocument\n targetLanguage: string\n languageFieldName: string\n translationPrompts: FetchTranslationPromptsResult\n openaiConfig: NonNullable<PluginConfig['openai']>\n previousError?: Error\n errorContext?: string\n telemetryConfig?: TelemetryConfig\n}): Promise<OpenAITranslationResult | null> => {\n // Note: _previousError is available for future use in error recovery strategies\n const startTime = Date.now()\n const sourceLanguage = sourceDocument[languageFieldName] as string\n const logContext = {\n sourceId: sourceDocument._id,\n sourceLanguage,\n }\n\n // Variables for telemetry error bundle\n let serializedDocument: string | undefined\n let openaiResponseContent: string | undefined\n\n try {\n // Serialize documents\n const sourceText = serializeDocument(sourceDocument)\n serializedDocument = sourceText // Capture for error bundle\n\n devLog.info('Serialized document for OpenAI', {\n ...logContext,\n characterCount: sourceText.length,\n estimatedTokens: Math.ceil(sourceText.length / 4),\n preview: sourceText.substring(0, 500),\n })\n\n // Build prompt\n const prompt = buildTranslationPrompt({\n sourceText,\n sourceLanguage,\n targetLanguage,\n translationPrompts,\n errorContext,\n })\n\n const response = await callGPT5(openaiConfig.apiKey, {\n messages: [\n {\n role: 'developer',\n content: 'You are a translation quality reviewer. Review and refine translations while preserving structure.',\n },\n { role: 'user', content: prompt },\n ],\n model: openaiConfig.model,\n reasoningEffort: openaiConfig.reasoningEffort,\n })\n\n // Capture response content for error bundle\n openaiResponseContent = response.content\n\n devLog.info('OpenAI translation response received', {\n ...logContext,\n tokensUsed: response.tokensUsed,\n cost: response.cost,\n responseLength: response.content.length,\n responsePreview: response.content.substring(0, 500),\n })\n\n // Deserialize response - use the transformed/filtered document that was sent to GPT-5\n // This ensures the structure matches what GPT-5 translated\n const result = deserializeDocument(sourceDocument, response.content) as TranslatableDocument\n\n // Set the language field to the target language\n result[languageFieldName] = targetLanguage\n\n // Validate structure - compare against the source document we used for deserialization\n const validation = validateStructureIntegrity(sourceDocument, result)\n if (!validation.valid) {\n devLog.error('Structure validation failed', {\n ...logContext,\n errors: validation.errors,\n })\n\n const unexpectedErrors = validation.errors.filter(\n (err) => !(err.path.length === 1 && err.path[0] === languageFieldName),\n )\n\n if (unexpectedErrors.length > 0) {\n throw new Error(\n `Structure validation failed: ${unexpectedErrors.length} errors. First: ${unexpectedErrors[0].type} at ${unexpectedErrors[0].path.join('.')}`,\n )\n }\n }\n\n // Validate that important fields were translated - use filtered document for this check\n const translationValidation = validateTranslationCompleteness(sourceDocument, result, languageFieldName)\n if (!translationValidation.valid) {\n devLog.warn('Translation completeness warning', {\n ...logContext,\n skippedFields: translationValidation.skippedFields,\n warning: 'GPT-5 may have skipped some important fields',\n })\n\n // Log details but don't throw - this is a warning, not a failure\n devLog.info('Skipped field details', {\n ...logContext,\n skippedFields: translationValidation.skippedFields.map((field) => ({\n path: field.path,\n originalValue: field.originalValue,\n translatedValue: field.translatedValue,\n })),\n })\n }\n\n setJobStatus(dispatch, sourceDocument._id, targetLanguage, 'translating_with_openai', {\n tokensUsed: response.tokensUsed.total,\n estimatedCost: response.cost,\n })\n\n return {\n document: result,\n tokensUsed: response.tokensUsed.total,\n cost: response.cost,\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n devLog.error('OpenAI translation failed', err, logContext)\n\n // Emit telemetry error bundle with full debug context\n emitTelemetryError(\n telemetryConfig,\n buildErrorBundle({\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n targetLanguage,\n sourceLanguage,\n translationMode: 'openai-only',\n error: err,\n errorType: classifyError(err, 'openai_api'),\n startTime,\n originalDocument: sourceDocument,\n serializedDocument,\n openaiResponse: openaiResponseContent,\n translationPrompts: {\n globalPrompt: translationPrompts.globalPrompt,\n localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,\n protectedPhrases: translationPrompts.protectedPhrases,\n glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms\n ? [translationPrompts.localeInstructions[targetLanguage].glossaryTerms]\n : undefined,\n },\n }),\n )\n\n throw error\n }\n}\n\nconst buildTranslationPrompt = ({\n sourceText,\n sourceLanguage,\n targetLanguage,\n translationPrompts,\n errorContext,\n}: {\n sourceText: string\n sourceLanguage: string\n targetLanguage: string\n translationPrompts: FetchTranslationPromptsResult\n errorContext?: string\n}): string => {\n const { globalPrompt, localeInstructions, protectedPhrases } = translationPrompts\n const localePrompt = localeInstructions[targetLanguage]?.prompt\n const glossaryTerms = localeInstructions[targetLanguage]?.glossaryTerms\n\n return `# TRANSLATION TASK\n\nTranslate this ${sourceLanguage} document into ${targetLanguage}.\n\n${errorContext ? `## ⚠️ IMPORTANT: Previous Attempt Error\\n\\n${errorContext}\\n\\nPlease carefully review the error above and ensure your translation fixes the issue.\\n\\n` : ''}## Guidelines\n\n${globalPrompt ? `### General Guidelines\\n${globalPrompt}\\n` : ''}\n${localePrompt ? `### ${targetLanguage} Guidelines\\n${localePrompt}\\n` : ''}\n${glossaryTerms ? `### Required Terminology\\n${glossaryTerms}\\n` : ''}\n${protectedPhrases?.length ? `### Protected Phrases (do not translate)\\n${protectedPhrases.join(', ')}\\n` : ''}\n\n## Source Document (${sourceLanguage})\n\n\\`\\`\\`\n${sourceText}\n\\`\\`\\`\n\n## Instructions\n\n1. **TRANSLATE ALL TEXT FIELDS**: Every field marked with type=\"string\" or containing text must be translated\n2. **DO NOT SKIP SIMPLE FIELDS**: Pay special attention to simple fields like \"title\", \"name\", \"description\" - these are often the most important\n3. Follow the guidelines and terminology requirements above\n4. Preserve protected phrases exactly as they appear\n5. Return the document in THE EXACT SAME FORMAT\n6. **CRITICAL**: Only modify text content between tags\n7. Never add, remove, or change any tags, attributes, or structure\n\n**Common fields that must always be translated:**\n- title, name, heading, label\n- description, caption, alt text\n- All text within <field> tags\n- All text within <span> tags\n\nIf you used significant reasoning to make translation decisions, you may include a brief summary:\n<reasoning>\nBrief summary of key translation decisions and reasoning\n</reasoning>\n\nThen return the translated document:`\n}\n\n/**\n * Validates that important translatable fields were actually translated by GPT-5.\n * This helps detect when GPT-5 inconsistently skips simple fields like \"title\".\n */\nconst validateTranslationCompleteness = (\n sourceDoc: TranslatableDocument,\n translatedDoc: TranslatableDocument,\n languageFieldName: string,\n): { valid: boolean; skippedFields: Array<{ path: string; originalValue: string; translatedValue: string }> } => {\n const skippedFields: Array<{ path: string; originalValue: string; translatedValue: string }> = []\n\n const checkField = (source: unknown, translated: unknown, path: string[] = []): void => {\n // Skip metadata fields and language field\n if (path.length > 0) {\n const fieldName = path[path.length - 1]\n if (fieldName.startsWith('_') || fieldName === languageFieldName) {\n return\n }\n }\n\n // Check string fields that should be translated\n if (typeof source === 'string' && typeof translated === 'string') {\n if (source.length > 0 && source === translated) {\n // Important fields that commonly get skipped\n const fieldName = path[path.length - 1] || 'unknown'\n const isImportantField = ['title', 'name', 'heading', 'label', 'description', 'caption', 'alt'].includes(\n fieldName,\n )\n\n if (isImportantField || source.length > 10) {\n // Flag important fields or longer text\n skippedFields.push({\n path: path.join('.'),\n originalValue: source,\n translatedValue: translated,\n })\n }\n }\n return\n }\n\n // Recursively check objects\n if (source && translated && typeof source === 'object' && typeof translated === 'object') {\n if (Array.isArray(source) && Array.isArray(translated)) {\n const maxLength = Math.min(source.length, translated.length)\n for (let i = 0; i < maxLength; i++) {\n checkField(source[i], translated[i], [...path, String(i)])\n }\n } else if (!Array.isArray(source) && !Array.isArray(translated)) {\n for (const key of Object.keys(source as Record<string, unknown>)) {\n if (!key.startsWith('_') && key !== languageFieldName) {\n checkField((source as Record<string, unknown>)[key], (translated as Record<string, unknown>)[key], [\n ...path,\n key,\n ])\n }\n }\n }\n }\n }\n\n checkField(sourceDoc, translatedDoc)\n\n return {\n valid: skippedFields.length === 0,\n skippedFields,\n }\n}\n","import type { TranslatableDocument } from '../../types'\n\ninterface AssembleLanguageSpecsParams {\n sourceDocument: TranslatableDocument\n languageFieldName: string\n targetLanguage: string\n}\n\n/**\n * Assembles language specification objects for translation operations.\n * Extracts the source language from the document and formats both source and target\n * languages into objects suitable for Sanity Agent Action translation calls.\n *\n * @param params - Configuration for assembling language specs\n * @param params.sourceDocument - The document being translated (contains source language ID)\n * @param params.languageFieldName - Name of the field containing the language identifier\n * @param params.targetLanguage - Target language ID to translate to\n * @returns Object containing fromLanguage and toLanguage specifications\n * @throws Error if source language ID is missing from the document\n *\n * @example\n * ```typescript\n * const specs = assembleLanguageSpecs({\n * sourceDocument: { language: 'en', ... },\n * languageFieldName: 'language',\n * targetLanguage: 'es'\n * })\n * // Returns: { fromLanguage: { id: 'en', title: 'en' }, toLanguage: { id: 'es', title: 'es' } }\n * ```\n */\nexport const assembleLanguageSpecs = ({\n sourceDocument,\n languageFieldName,\n targetLanguage,\n}: AssembleLanguageSpecsParams): {\n toLanguage: { id: string; title: string }\n fromLanguage: { id: string; title: string }\n} => {\n const sourceLanugageId = sourceDocument[languageFieldName] as string | undefined\n\n if (!sourceLanugageId) {\n throw new Error('[assembleLanguages] Source language ID is missing')\n }\n\n return {\n fromLanguage: {\n id: sourceLanugageId,\n title: sourceLanugageId,\n },\n toLanguage: {\n id: targetLanguage,\n title: targetLanguage,\n },\n }\n}\n","export interface PromptComponents {\n globalPrompt?: string\n localePrompt?: string\n glossaryTerms?: string\n protectedPhrases?: string[]\n}\n\nexport interface BuildTranslationPromptParams {\n sourceLanguage: string\n targetLanguage: string\n components: PromptComponents\n isUpdate?: boolean\n}\n\n/**\n * Builds a unified translation prompt for both Sanity Agent and OpenAI.\n * No character limits - includes all provided content.\n */\nexport const buildTranslationPrompt = ({\n sourceLanguage,\n targetLanguage,\n components,\n isUpdate = false,\n}: BuildTranslationPromptParams): string => {\n const sections: string[] = []\n\n // Section 1: Agent workflow directives\n sections.push(buildAgentDirectivesSection(sourceLanguage, targetLanguage))\n\n // Section 2: Critical rules\n sections.push(buildCriticalRulesSection(sourceLanguage, targetLanguage))\n\n // Section 3: Update instructions (if applicable)\n if (isUpdate) {\n sections.push(buildUpdateInstructionsSection())\n }\n\n // Section 4: Glossary terms (if provided)\n if (components.glossaryTerms?.trim()) {\n sections.push(buildGlossarySection(components.glossaryTerms))\n }\n\n // Section 5: Protected phrases (if provided)\n if (components.protectedPhrases?.length) {\n sections.push(buildProtectedPhrasesSection(components.protectedPhrases))\n }\n\n // Section 6: Style guide (if provided)\n const styleSection = buildStyleGuideSection(components.globalPrompt, components.localePrompt)\n if (styleSection) {\n sections.push(styleSection)\n }\n\n return sections.join('\\n\\n---\\n\\n')\n}\n\n/**\n * SECTION 1: Agent workflow directives to align with best practices\n */\nconst buildAgentDirectivesSection = (sourceLanguage: string, targetLanguage: string): string => {\n const fromLanguage = sourceLanguage || 'the source language'\n const toLanguage = targetLanguage || 'the target language'\n\n return `# AGENT WORKFLOW DIRECTIVES\n\n- Use only the provided ${fromLanguage} source; never fetch outside context.\n- Scan each field once, then move directly into translation work.\n- Plan updates before editing so document structure remains intact.\n- Default to medium reasoning effort and escalate only for ambiguous passages.\n- Output just the ${toLanguage} translation and mirror the source formatting exactly.`\n}\n\n/**\n * SECTION 2: Critical rules that must never be violated\n */\nconst buildCriticalRulesSection = (sourceLanguage: string, targetLanguage: string): string => {\n const fromLanguage = sourceLanguage || 'the source language'\n const toLanguage = targetLanguage || 'the target language'\n\n return `# CRITICAL TRANSLATION RULES\n\nFollow these without exception:\n\n1. Translate from ${fromLanguage} into ${toLanguage}; every output token must be ${toLanguage} unless flagged as protected.\n2. Preserve meaning exactly—no additions, deletions, or tone shifts.\n3. Maintain all layout, markdown, rich-text marks, and data structure.\n4. Never invent metadata or field values that are absent in the source.\n5. Leave technical tokens (code, IDs, filenames) untouched.\n6. Keep punctuation, casing, and spacing unless ${toLanguage} grammar forces a change.`\n}\n\n/**\n * SECTION 3: Instructions for updating existing translations\n */\nconst buildUpdateInstructionsSection = (): string => {\n return `# UPDATING EXISTING TRANSLATION\n\nThis document already has a translation; only adjust fields that changed.\n\n- Compare the latest source to the existing target text.\n- Keep prior terminology and style unless the source demands a change.\n- Edit minimally while guaranteeing accuracy and completeness.`\n}\n\n/**\n * SECTION 4: Required glossary term translations\n */\nconst buildGlossarySection = (glossaryTerms: string): string => {\n return `# TERMINOLOGY RULES\n\nApply these mappings immediately after protected phrases and before style preferences.\n\n${glossaryTerms}`\n}\n\n/**\n * SECTION 5: Protected phrases that must never be translated\n */\nconst buildProtectedPhrasesSection = (protectedPhrases: string[]): string => {\n return `# PROTECTED PHRASES\n\nThese phrases must NEVER be translated. Keep them exactly as they appear:\n\n${protectedPhrases.join(', ')}`\n}\n\n/**\n * SECTION 6: Style guide and user preferences\n */\nconst buildStyleGuideSection = (globalPrompt?: string, localePrompt?: string): string | null => {\n const components: string[] = []\n\n if (globalPrompt || localePrompt) {\n components.push('# TRANSLATION STYLE GUIDE (LOWEST PRIORITY)')\n components.push(\n 'Apply these directions only after honoring critical rules, protected phrases, and glossary terminology.',\n )\n components.push('')\n }\n\n if (globalPrompt) {\n components.push('## General Guidelines')\n components.push(globalPrompt)\n components.push('')\n }\n\n if (localePrompt) {\n components.push('## Language-Specific Guidelines')\n components.push(localePrompt)\n }\n\n return components.length > 0 ? components.join('\\n') : null\n}\n","import type { SanityClient } from 'sanity'\nimport type { TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\nimport { assembleLanguageSpecs } from '../document/assembleLanguageSpecs'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { buildTranslationPrompt } from './assembleTranslationPrompt'\n\ninterface GetSanityAgentTranslationParams {\n sourceDocument: TranslatableDocument\n targetLanguage: string\n languageFieldName: string\n schemaId: string\n client: SanityClient\n translationPrompts: FetchTranslationPromptsResult\n}\n\nexport const getSanityAgentTranslation = async ({\n sourceDocument,\n targetLanguage,\n languageFieldName,\n schemaId,\n client,\n translationPrompts,\n}: GetSanityAgentTranslationParams) => {\n // Build unified translation prompt (no character limit)\n const styleGuide = buildTranslationPrompt({\n sourceLanguage: sourceDocument[languageFieldName] as string,\n targetLanguage,\n isUpdate: false, // Always fresh translation in a version\n components: {\n globalPrompt: translationPrompts.globalPrompt,\n localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,\n glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms,\n protectedPhrases: translationPrompts.protectedPhrases,\n },\n })\n\n const { fromLanguage, toLanguage } = assembleLanguageSpecs({\n sourceDocument,\n languageFieldName,\n targetLanguage,\n })\n\n return (await retryWithBackoff(\n async () => {\n return await client.agent.action.translate({\n schemaId,\n toLanguage,\n fromLanguage,\n documentId: sourceDocument._id,\n noWrite: true, // Do not write to the document, just return the translated content\n languageFieldPath: [languageFieldName],\n styleGuide,\n protectedPhrases: translationPrompts.protectedPhrases,\n conditionalPaths: {\n defaultHidden: false,\n defaultReadOnly: false,\n },\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'agent.action.translate',\n documentId: sourceDocument._id,\n targetLanguage,\n schemaId,\n },\n },\n )) as TranslatableDocument | null\n}\n","import { uuid } from '@sanity/uuid'\nimport type { Dispatch } from 'react'\nimport type { SanityClient } from 'sanity'\nimport type {\n PluginConfig,\n TelemetryConfig,\n TranslatableDocument,\n TranslationAction,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, isSanityMutationError, retryWithBackoff } from '../common/retry'\nimport { filterExcludedFields, restoreExcludedFields } from '../document/excludeFields'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { setJobStatus } from '../state/setJobStatus'\nimport { buildErrorBundle, classifyError, emitTelemetryError } from '../telemetry'\nimport { applyTransforms } from '../transforms/applyTransforms'\nimport { restoreTransforms } from '../transforms/restoreTransforms'\nimport { globalTransformRegistry } from '../transforms/transformRegistry'\nimport { getOpenAITranslation } from './getOpenAITranslation'\nimport { getSanityAgentTranslation } from './getSanityAgentTranslation'\n\n/**\n * Result from handleTranslation including document and optional metrics\n */\nexport interface HandleTranslationResult {\n document: TranslatableDocument\n tokensUsed?: number\n cost?: number\n}\n\ninterface TranslateEntireDocumentOptions {\n dispatch: Dispatch<TranslationAction>\n client: SanityClient\n schemaId: string\n sourceDocument: TranslatableDocument\n targetLanguage: string\n existingTranslationId: string | null\n languageFieldName: string\n translationPrompts: FetchTranslationPromptsResult\n translationMode: TranslationMode\n openaiConfig?: PluginConfig['openai']\n releaseId: string\n excludeFields?: string[]\n /** Whether to replace an existing version in the release */\n replaceExisting?: boolean\n /** Main source document ID for tracking in active translations store */\n mainSourceDocumentId?: string\n /** Optional telemetry configuration */\n telemetryConfig?: TelemetryConfig\n}\n\n/**\n * Main translation orchestrator.\n * Creates a version in the release, then routes to the appropriate translation method.\n */\nexport const handleTranslation = async ({\n dispatch,\n client,\n schemaId,\n sourceDocument,\n targetLanguage,\n existingTranslationId,\n languageFieldName,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields = [],\n replaceExisting = false,\n mainSourceDocumentId,\n telemetryConfig,\n}: TranslateEntireDocumentOptions): Promise<HandleTranslationResult | null> => {\n const startTime = Date.now()\n const sourceLanguage = sourceDocument[languageFieldName] as string\n // 1. Filter out excluded fields before translation\n // CRITICAL: Never exclude the language field - it's required for translation\n const safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName)\n\n if (excludeFields.length !== safeExcludeFields.length) {\n devLog.warn('Language field was in excludeFields - removing it to prevent translation errors', {\n documentId: sourceDocument._id,\n languageFieldName,\n originalExcludeFields: excludeFields,\n safeExcludeFields,\n })\n }\n\n devLog.info('Filtering excluded fields before translation', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n excludeFields: safeExcludeFields,\n excludeFieldsLength: safeExcludeFields?.length ?? 0,\n sourceHasTitle: 'title' in sourceDocument,\n sourceTitleValue: (sourceDocument as any).title,\n })\n\n const { filteredDocument, excludedValues } = filterExcludedFields(sourceDocument, safeExcludeFields)\n\n devLog.info('After filtering excluded fields', {\n documentId: sourceDocument._id,\n filteredHasTitle: 'title' in filteredDocument,\n filteredTitleValue: (filteredDocument as any).title,\n excludedValuesKeys: Object.keys(excludedValues),\n excludedValues,\n })\n\n // 2. Apply transforms to document before translation (OpenAI mode only)\n // Note: Sanity Agent fetches the document directly from the database by documentId,\n // so transforms are not needed and would cause issues with portable text fields.\n let documentToTranslate: TranslatableDocument\n let originalStructure: Record<string, { value: unknown; transform: unknown }> = {}\n\n if (translationMode === 'openai-only') {\n devLog.debug('Applying field transforms before translation (OpenAI mode)', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n })\n\n const transformResult = applyTransforms({\n document: filteredDocument as TranslatableDocument,\n registry: globalTransformRegistry,\n schemaType: sourceDocument._type,\n })\n documentToTranslate = transformResult.transformedDocument\n originalStructure = transformResult.originalStructure\n } else {\n // For Sanity Agent mode, use filtered document directly (no transforms)\n devLog.debug('Skipping field transforms (Sanity Agent mode)', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n })\n documentToTranslate = filteredDocument as TranslatableDocument\n }\n\n // 3. Create translation in memory via Sanity Agent or ChatGPT\n // For OpenAI mode, we'll retry the entire translation if mutation fails\n let translatedDoc: TranslatableDocument | null\n let translationMetrics: { tokensUsed?: number; cost?: number } = {}\n let previousError: Error | null = null\n\n interface TranslationAttemptResult {\n document: TranslatableDocument | null\n tokensUsed?: number\n cost?: number\n }\n\n const performTranslation = async (errorContext?: string): Promise<TranslationAttemptResult> => {\n switch (translationMode) {\n case 'sanity-only': {\n setJobStatus(dispatch, sourceDocument._id, targetLanguage, 'translating', undefined, mainSourceDocumentId)\n try {\n const doc = await getSanityAgentTranslation({\n sourceDocument: documentToTranslate,\n targetLanguage,\n languageFieldName,\n schemaId,\n client,\n translationPrompts,\n })\n return { document: doc }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n\n // Emit telemetry error for Sanity Agent failures\n emitTelemetryError(\n telemetryConfig,\n buildErrorBundle({\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n targetLanguage,\n sourceLanguage,\n translationMode: 'sanity-only',\n error: err,\n errorType: classifyError(err, 'sanity_agent'),\n startTime,\n originalDocument: sourceDocument,\n translationPrompts: {\n globalPrompt: translationPrompts.globalPrompt,\n localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,\n protectedPhrases: translationPrompts.protectedPhrases,\n },\n }),\n )\n\n throw error\n }\n }\n case 'openai-only': {\n if (!openaiConfig?.enabled || !openaiConfig?.apiKey) {\n throw new Error('OpenAI is not configured but openai-only mode was selected')\n }\n\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'translating_with_openai',\n undefined,\n mainSourceDocumentId,\n )\n const result = await getOpenAITranslation({\n dispatch,\n sourceDocument: documentToTranslate,\n targetLanguage,\n languageFieldName,\n translationPrompts,\n openaiConfig,\n previousError: errorContext && previousError ? previousError : undefined,\n errorContext,\n telemetryConfig,\n })\n\n if (!result) {\n return { document: null }\n }\n\n return {\n document: result.document,\n tokensUsed: result.tokensUsed,\n cost: result.cost,\n }\n }\n default: {\n throw new Error(`Invalid translation mode: ${translationMode}`)\n }\n }\n }\n\n // Initial translation attempt\n const translationResult = await performTranslation()\n translatedDoc = translationResult.document\n translationMetrics = {\n tokensUsed: translationResult.tokensUsed,\n cost: translationResult.cost,\n }\n\n if (!translatedDoc) {\n return null\n }\n\n // 4. Restore original structure with translated content (only if transforms were applied)\n let transformedResult: TranslatableDocument\n\n if (Object.keys(originalStructure).length > 0) {\n devLog.debug('Restoring field transforms after translation', {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n })\n\n transformedResult = restoreTransforms({\n translatedDocument: translatedDoc,\n originalStructure,\n registry: globalTransformRegistry,\n schemaType: sourceDocument._type,\n }) as TranslatableDocument\n } else {\n // No transforms were applied, use translated document directly\n transformedResult = translatedDoc\n }\n\n // 5. Restore excluded fields to final document\n // NOTE: The language field should never be in excludedValues (filtered out above),\n // but we double-check here as a safety measure to ensure it's not restored\n const excludedValuesToRestore = { ...excludedValues }\n delete excludedValuesToRestore[languageFieldName]\n\n devLog.info('Before restoring excluded fields', {\n documentId: sourceDocument._id,\n excludedFieldCount: Object.keys(excludedValues).length,\n excludedValuesKeys: Object.keys(excludedValues),\n languageFieldExcluded: languageFieldName in excludedValues,\n transformedResultHasTitle: 'title' in transformedResult,\n transformedResultTitle: (transformedResult as any).title,\n })\n\n const finalDoc = restoreExcludedFields(transformedResult, excludedValuesToRestore)\n\n devLog.info('After restoring excluded fields', {\n documentId: sourceDocument._id,\n finalDocHasTitle: 'title' in finalDoc,\n finalDocTitle: (finalDoc as any).title,\n })\n\n // 6. Create new version of the target document with the translated content\n // Overwrite the existing translation as base if it exists, otherwise create a new id\n const targetDocumentPublishedId = existingTranslationId || uuid()\n const targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`\n const newDoc = {\n ...finalDoc,\n _id: targetDocumentVersionId,\n }\n\n // If replaceExisting is true, delete the existing version first\n if (replaceExisting) {\n const existingVersion = await client.getDocument(targetDocumentVersionId)\n if (existingVersion) {\n devLog.info('Replacing existing version in release', {\n documentId: sourceDocument._id,\n targetDocumentVersionId,\n releaseId,\n })\n await client.delete(targetDocumentVersionId)\n }\n }\n\n // 6. Create version with retry logic\n // If mutation fails after OpenAI translation, retry the entire translation\n try {\n await retryWithBackoff(\n async () => {\n await client.action({\n actionType: 'sanity.action.document.version.create',\n publishedId: targetDocumentPublishedId,\n document: newDoc,\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n documentId: sourceDocument._id,\n targetLanguage,\n operation: 'version.create',\n },\n },\n )\n } catch (error) {\n // If this is a mutation error after OpenAI translation, retry the entire translation\n if (translationMode === 'openai-only' && isSanityMutationError(error)) {\n devLog.warn('Mutation error after OpenAI translation, retrying full translation', {\n documentId: sourceDocument._id,\n targetLanguage,\n error: error instanceof Error ? error.message : String(error),\n })\n\n previousError = error instanceof Error ? error : new Error(String(error))\n const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`\n\n // Retry the entire translation with error context\n const retryResult = await performTranslation(errorContext)\n translatedDoc = retryResult.document\n\n if (!translatedDoc) {\n throw new Error('Translation retry failed: no document returned')\n }\n\n // Update metrics from retry\n translationMetrics = {\n tokensUsed: retryResult.tokensUsed,\n cost: retryResult.cost,\n }\n\n // Rebuild the document with the new translation\n let retryTransformedResult: TranslatableDocument\n\n if (Object.keys(originalStructure).length > 0) {\n retryTransformedResult = restoreTransforms({\n translatedDocument: translatedDoc,\n originalStructure,\n registry: globalTransformRegistry,\n schemaType: sourceDocument._type,\n }) as TranslatableDocument\n } else {\n retryTransformedResult = translatedDoc\n }\n\n const retryFinalDoc = restoreExcludedFields(retryTransformedResult, excludedValuesToRestore)\n const retryNewDoc = {\n ...retryFinalDoc,\n _id: targetDocumentVersionId,\n }\n\n // Try creating the version again with the retried translation\n await retryWithBackoff(\n async () => {\n await client.action({\n actionType: 'sanity.action.document.version.create',\n publishedId: targetDocumentPublishedId,\n document: retryNewDoc,\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n documentId: sourceDocument._id,\n targetLanguage,\n operation: 'version.create',\n isRetry: true,\n },\n },\n )\n\n return {\n document: retryNewDoc as TranslatableDocument,\n tokensUsed: translationMetrics.tokensUsed,\n cost: translationMetrics.cost,\n }\n }\n\n // For other errors or Sanity Agent mode, just throw\n throw error\n }\n\n return {\n document: newDoc as TranslatableDocument,\n tokensUsed: translationMetrics.tokensUsed,\n cost: translationMetrics.cost,\n }\n}\n","import type { Dispatch } from 'react'\nimport type { SanityClient } from 'sanity'\nimport type {\n PluginConfig,\n TelemetryConfig,\n TranslatableDocument,\n TranslationAction,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\nimport type { FetchTranslationPromptsResult } from '../matching/fetchTranslationPrompts'\nimport { getExistingTranslationId } from '../metadata/getExistingTranslationId'\nimport { getTranslationMetadata } from '../metadata/getTranslationMetadata'\nimport { updateTranslationMetadata } from '../metadata/updateTranslationMetadata'\nimport { setJobStatus } from '../state/setJobStatus'\nimport { classifyError, createTimestamp, emitTelemetryEvent } from '../telemetry'\nimport { handleTranslation } from './handleTranslation'\n\ninterface TranslateDocumentParams {\n dispatch: Dispatch<TranslationAction>\n client: SanityClient\n sourceDocument: TranslatableDocument\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n targetLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n schemaId: string\n translationPrompts: FetchTranslationPromptsResult\n translationMode: TranslationMode\n openaiConfig?: PluginConfig['openai']\n releaseId: string\n excludeFields?: string[]\n /** Whether to replace an existing version in the release */\n replaceExisting?: boolean\n /** Main source document ID for tracking in active translations store */\n mainSourceDocumentId?: string\n /** Optional telemetry configuration */\n telemetryConfig?: TelemetryConfig\n}\n\nexport const translateDocument = async ({\n dispatch,\n client,\n sourceDocument,\n sourceLanguage,\n targetLanguage,\n languageFieldName,\n schemaId,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields,\n replaceExisting,\n mainSourceDocumentId,\n telemetryConfig,\n}: TranslateDocumentParams) => {\n const jobStartTime = Date.now()\n const logContext = {\n documentId: sourceDocument._id,\n targetLanguage,\n schemaId,\n translationMode,\n }\n\n devLog.group('translateDocument', logContext)\n devLog.time('translateDocument')\n\n setJobStatus(dispatch, sourceDocument._id, targetLanguage, 'preparing_translation', undefined, mainSourceDocumentId)\n\n try {\n const translationMetadata = await getTranslationMetadata({\n client,\n sourceDocument,\n sourceLanguage,\n })\n\n // Get the existing translation id if it exists\n const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage)\n\n // Delegate to handleTranslation which orchestrates the translation\n const translationResult = await handleTranslation({\n dispatch,\n client,\n schemaId,\n sourceDocument,\n targetLanguage,\n existingTranslationId,\n languageFieldName,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields,\n replaceExisting,\n mainSourceDocumentId,\n telemetryConfig,\n })\n\n const newDocument = translationResult?.document\n\n if (!newDocument) {\n const failureError = new Error('Translation returned null')\n devLog.error('Translation failed: no document returned', failureError, logContext)\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'error',\n {\n errorMessage: 'Translation failed',\n },\n mainSourceDocumentId,\n )\n\n // Emit job failed telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.job_failed',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguage,\n translationMode,\n durationMs: Date.now() - jobStartTime,\n errorType: classifyError(failureError),\n errorMessage: 'Translation failed',\n },\n })\n\n devLog.timeEnd('translateDocument')\n devLog.groupEnd()\n return undefined\n }\n\n // Store translation history and update metadata\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'updating_metadata',\n {\n targetId: newDocument._id,\n },\n mainSourceDocumentId,\n )\n\n await updateTranslationMetadata({\n client,\n sourceDocument,\n sourceLanguage,\n newDocument,\n targetLanguage,\n translationMetadata,\n isNewTranslation: !existingTranslationId,\n })\n\n // Mark translation complete with target document reference for UI\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'completed',\n {\n targetId: newDocument._id,\n tokensUsed: translationResult?.tokensUsed,\n estimatedCost: translationResult?.cost,\n },\n mainSourceDocumentId,\n )\n devLog.info('Translation completed successfully', {\n ...logContext,\n newDocumentId: newDocument._id,\n })\n\n // Emit job completed telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.job_completed',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguage,\n translationMode,\n durationMs: Date.now() - jobStartTime,\n tokensUsed: translationResult?.tokensUsed,\n estimatedCost: translationResult?.cost,\n },\n })\n\n devLog.timeEnd('translateDocument')\n devLog.groupEnd()\n return newDocument._id\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n const errorObj = error instanceof Error ? error : new Error(String(error))\n devLog.error('Translation failed', errorObj, logContext)\n setJobStatus(\n dispatch,\n sourceDocument._id,\n targetLanguage,\n 'error',\n {\n errorMessage,\n },\n mainSourceDocumentId,\n )\n\n // Emit job failed telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.job_failed',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguage,\n translationMode,\n durationMs: Date.now() - jobStartTime,\n errorType: classifyError(errorObj),\n errorMessage,\n },\n })\n\n devLog.timeEnd('translateDocument')\n devLog.groupEnd()\n // Don't re-throw - let the batch continue with other translations\n return undefined\n }\n}\n","import type { Dispatch } from 'react'\nimport type { SanityClient } from 'sanity'\nimport type {\n PluginConfig,\n ReleaseConfig,\n TelemetryConfig,\n TranslatableDocument,\n TranslationAction,\n TranslationMode,\n} from '../../types'\nimport { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\nimport { Semaphore } from '../common/Semaphore'\nimport { fetchTranslationPrompts } from '../matching/fetchTranslationPrompts'\nimport type { AssembleReferencesResult } from '../references/assembleReferences'\nimport { localizeReferences } from '../references/localizeReferences'\nimport { updateBackwardReferences } from '../references/updateBackwardReferences'\nimport {\n cleanupActiveTranslation,\n registerActiveTranslation,\n updateActiveTranslationStatus,\n} from '../state/activeTranslations'\nimport { setupTranslationJobStatuses } from '../state/setupTranslationJobStatuses'\nimport { createTimestamp, emitTelemetryEvent } from '../telemetry'\nimport { translateDocument } from './translateDocument'\n\ninterface TranslateDocumentsParams {\n sourceDocument: TranslatableDocument\n targetLanguages: Array<string>\n client: SanityClient\n dispatch: Dispatch<TranslationAction>\n languageFieldName: string\n schemaId: string\n references: AssembleReferencesResult\n maxConcurrentTranslationJobs?: number\n maxReferenceDepth?: number\n translationMode: TranslationMode\n openaiConfig?: PluginConfig['openai']\n excludeFields?: string[]\n releaseConfig: ReleaseConfig\n telemetryConfig?: TelemetryConfig\n}\n\nconst translationSemaphore = new Semaphore(2)\n\nexport const translateDocumentSet = async ({\n client,\n sourceDocument,\n targetLanguages,\n dispatch,\n languageFieldName,\n schemaId,\n references = [],\n maxConcurrentTranslationJobs,\n translationMode,\n openaiConfig,\n excludeFields,\n releaseConfig,\n telemetryConfig,\n}: TranslateDocumentsParams) => {\n const batchStartTime = Date.now()\n const logContext = {\n documentId: sourceDocument._id,\n documentRev: sourceDocument._rev,\n targetLanguages,\n referencesCount: references.length,\n maxConcurrentJobs: maxConcurrentTranslationJobs,\n translationMode,\n releaseMode: releaseConfig.mode,\n }\n\n let releaseId: string | undefined\n let createdNewRelease = false\n\n try {\n const sourceLanguage = sourceDocument[languageFieldName] as string\n\n if (!sourceLanguage) {\n devLog.error('Source language not found')\n throw new Error('Source language not found')\n }\n\n // Emit batch started telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.batch_started',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguages,\n translationMode,\n referenceCount: references.length,\n releaseMode: releaseConfig.mode,\n },\n })\n\n // Configure semaphore with max concurrent jobs if specified\n if (maxConcurrentTranslationJobs) {\n translationSemaphore.updatePermits(maxConcurrentTranslationJobs)\n }\n\n dispatch({\n type: 'SET_OVERALL_STATUS',\n payload: 'translating',\n })\n\n // Handle release creation based on releaseConfig\n if (releaseConfig.mode === 'existing' && releaseConfig.existingReleaseId) {\n // Use existing release\n releaseId = releaseConfig.existingReleaseId\n devLog.info('Using existing Content Release', {\n ...logContext,\n releaseId,\n })\n } else {\n // Create a new release with the user-specified or default title\n const releaseTitle = releaseConfig.newReleaseTitle\n const releaseDescription = `AI translation batch created on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}`\n\n const createResult = await retryWithBackoff(\n async () => {\n return await client.releases.create({\n metadata: {\n title: releaseTitle,\n description: releaseDescription,\n releaseType: 'undecided',\n },\n })\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: {\n operation: 'releases.create',\n releaseTitle,\n ...logContext,\n },\n },\n )\n releaseId = createResult.releaseId\n createdNewRelease = true\n\n devLog.info('Content Release created', {\n ...logContext,\n releaseId,\n releaseTitle,\n })\n }\n\n // Register this translation in the module-level store so dialog can reconnect\n // Must happen after releaseId is determined above\n registerActiveTranslation(sourceDocument._id, releaseId)\n updateActiveTranslationStatus(sourceDocument._id, 'translating')\n\n const sourceDocuments = [\n {\n _id: sourceDocument._id,\n _type: sourceDocument._type,\n // TODO: Extract document title more robustly across schema types\n title: (sourceDocument.name || sourceDocument.title) as string | undefined,\n },\n ...references,\n ]\n\n const sourceDocumentIds = sourceDocuments.map((doc) => doc._id)\n // Array of versioned _ids for target documents. These follow the format `versions.<version_id>.<id_after_published>`\n const versionedTargetDocumentIds: Array<string> = []\n\n // Assemble prompts for translation task style guides\n const translationPrompts = await fetchTranslationPrompts({\n client,\n sourceLanguage,\n targetLanguages,\n sourceDocument,\n })\n\n // Dispatch new job statuses for each document\n setupTranslationJobStatuses(dispatch, sourceDocuments, targetLanguages, sourceDocument._id)\n\n // Helper to check if a document/language pair should be skipped\n const shouldSkipConflict = (documentId: string, targetLanguage: string): boolean => {\n if (!releaseConfig.skipConflicts || releaseConfig.skipConflicts.length === 0) {\n return false\n }\n return releaseConfig.skipConflicts.some(\n (conflict) => conflict.sourceDocumentId === documentId && conflict.targetLanguage === targetLanguage,\n )\n }\n\n // Calculate skip count upfront (avoids mutable counter in async callbacks)\n const skippedCount = sourceDocumentIds.reduce((count, documentId) => {\n return count + targetLanguages.filter((lang) => shouldSkipConflict(documentId, lang)).length\n }, 0)\n\n // Create translation tasks for all documents and all languages\n const translationTasks = sourceDocumentIds.flatMap((documentId) =>\n targetLanguages.map(async (targetLanguage) => {\n // Check if this document/language pair should be skipped due to existing conflict\n if (shouldSkipConflict(documentId, targetLanguage)) {\n const docInfo = sourceDocuments.find((d) => d._id === documentId)\n\n devLog.info('Skipping conflicting translation', {\n documentId,\n targetLanguage,\n reason: 'User chose to skip conflicts',\n })\n\n // Mark this job as skipped\n dispatch({\n type: 'SET_TRANSLATION_JOB_STATUS',\n payload: {\n _id: documentId,\n _type: docInfo?._type,\n language: targetLanguage,\n status: 'skipped',\n title: docInfo?.title,\n },\n })\n return\n }\n\n try {\n await translationSemaphore.withPermit(async () => {\n const document = await client\n .withConfig({\n perspective: 'drafts',\n })\n .getDocument<TranslatableDocument>(documentId)\n\n if (!document) {\n devLog.error('❌ SOURCE DOCUMENT NOT FOUND', { documentId })\n }\n\n if (document && releaseId) {\n const versionedTargetDocumentId = await translateDocument({\n dispatch,\n client,\n sourceDocument: document,\n sourceLanguage,\n targetLanguage,\n languageFieldName,\n schemaId,\n translationPrompts,\n translationMode,\n openaiConfig,\n releaseId,\n excludeFields,\n replaceExisting: releaseConfig.replaceExisting,\n // Pass main source document ID for active translations tracking\n mainSourceDocumentId: sourceDocument._id,\n telemetryConfig,\n })\n\n if (versionedTargetDocumentId) {\n versionedTargetDocumentIds.push(versionedTargetDocumentId)\n }\n }\n })\n } catch (error) {\n // Log the error but don't fail the entire batch\n devLog.error(\n 'Individual translation task failed',\n error instanceof Error ? error : new Error(String(error)),\n {\n ...logContext,\n failedDocumentId: documentId,\n failedTargetLanguage: targetLanguage,\n },\n )\n }\n }),\n )\n\n const results = await Promise.allSettled(translationTasks)\n\n // Log summary of results (exclude skipped from succeeded/failed counts)\n const succeeded = results.filter((r) => r.status === 'fulfilled').length - skippedCount\n const failed = results.filter((r) => r.status === 'rejected').length\n\n devLog.info('Translation tasks completed', {\n ...logContext,\n total: results.length,\n succeeded,\n failed,\n skipped: skippedCount,\n targetDocuments: versionedTargetDocumentIds.length,\n })\n\n // Emit batch completed telemetry event\n emitTelemetryEvent(telemetryConfig, {\n name: 'translation.batch_completed',\n timestamp: createTimestamp(),\n data: {\n documentId: sourceDocument._id,\n documentType: sourceDocument._type,\n sourceLanguage,\n targetLanguages,\n translationMode,\n succeeded,\n failed,\n skipped: skippedCount,\n durationMs: Date.now() - batchStartTime,\n },\n })\n\n dispatch({\n type: 'SET_OVERALL_STATUS',\n payload: 'linking_references',\n })\n updateActiveTranslationStatus(sourceDocument._id, 'linking_references')\n\n // Localize references in newly translated documents\n await Promise.all(\n versionedTargetDocumentIds.map(async (documentId) => {\n await localizeReferences({\n client,\n documentId,\n sourceLanguage,\n languageFieldName,\n })\n }),\n )\n\n // Update backward references in existing release documents\n // This handles the case where Doc A (translated earlier) references Doc B (translated now)\n // Only needed for existing releases - new releases have no pre-existing documents\n devLog.debug('Checking if backward reference update is needed', {\n releaseMode: releaseConfig.mode,\n releaseId,\n willUpdate: releaseConfig.mode === 'existing' && !!releaseId,\n component: 'translateDocumentSet',\n })\n if (releaseConfig.mode === 'existing' && releaseId) {\n await updateBackwardReferences({\n client,\n releaseId,\n targetLanguages,\n sourceLanguage,\n languageFieldName,\n excludeDocumentIds: versionedTargetDocumentIds,\n })\n }\n\n // Determine overall status based on individual job statuses\n const hasAnyFailures = failed > 0\n\n if (hasAnyFailures && succeeded === 0) {\n // All translations failed\n devLog.error('All translation tasks failed', undefined, logContext)\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'error' })\n updateActiveTranslationStatus(sourceDocument._id, 'error')\n cleanupActiveTranslation(sourceDocument._id)\n } else if (hasAnyFailures) {\n // Partial success - some translations failed but others succeeded\n devLog.warn('Translation completed with some failures', {\n ...logContext,\n succeeded,\n failed,\n })\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'completed' })\n updateActiveTranslationStatus(sourceDocument._id, 'completed')\n cleanupActiveTranslation(sourceDocument._id)\n } else {\n // All translations succeeded\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'completed' })\n updateActiveTranslationStatus(sourceDocument._id, 'completed')\n cleanupActiveTranslation(sourceDocument._id)\n }\n\n devLog.timeEnd('translateDocumentSet')\n devLog.groupEnd()\n } catch (error) {\n // This catch block should only catch unexpected errors that aren't handled above\n devLog.error(\n 'translateDocumentSet failed with unexpected error',\n error instanceof Error ? error : new Error(String(error)),\n logContext,\n )\n\n // Clean up release if fatal error occurred (only if we created a new release)\n if (releaseId && createdNewRelease) {\n try {\n await client.releases.delete({ releaseId })\n devLog.warn('Deleted release due to fatal error', {\n ...logContext,\n releaseId,\n })\n } catch (cleanupError) {\n devLog.error(\n 'Failed to cleanup release',\n cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError)),\n logContext,\n )\n }\n }\n\n devLog.timeEnd('translateDocumentSet')\n devLog.groupEnd()\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'error' })\n updateActiveTranslationStatus(sourceDocument._id, 'error')\n cleanupActiveTranslation(sourceDocument._id)\n throw error\n }\n}\n","import { WarningOutlineIcon } from '@sanity/icons'\nimport { Box, Button, Card, Flex, Heading, Stack, Text } from '@sanity/ui'\nimport { type FC, useMemo } from 'react'\nimport type { ReleaseConflictInfo } from '../types'\n\ninterface ReleaseConflictWarningProps {\n conflicts: ReleaseConflictInfo[]\n /** Total number of document/language translation pairs requested */\n totalTranslations: number\n onCancel: () => void\n onReplace: () => void\n onSkipConflicts: () => void\n}\n\nexport const ReleaseConflictWarning: FC<ReleaseConflictWarningProps> = ({\n conflicts,\n totalTranslations,\n onCancel,\n onReplace,\n onSkipConflicts,\n}) => {\n // Count unique documents with conflicts\n const uniqueDocuments = useMemo(() => {\n const docIds = new Set(conflicts.map((c) => c.sourceDocumentId))\n return docIds.size\n }, [conflicts])\n\n const nonConflictingCount = totalTranslations - conflicts.length\n const hasNonConflicting = nonConflictingCount > 0\n\n const summaryText = useMemo(() => {\n if (conflicts.length === 1) {\n return '1 translation'\n }\n if (uniqueDocuments === 1) {\n return `${conflicts.length} translations`\n }\n return `${conflicts.length} translations across ${uniqueDocuments} documents`\n }, [conflicts.length, uniqueDocuments])\n\n return (\n <Card tone=\"caution\" padding={4} border radius={2}>\n <Stack space={4}>\n <Flex align=\"center\" gap={2}>\n <Text size={2}>\n <WarningOutlineIcon />\n </Text>\n <Text weight=\"semibold\">\n This release already contains translations for this content and target language(s).\n </Text>\n </Flex>\n\n <Text size={1}>The selected release already has {summaryText} that will be overwritten if you continue:</Text>\n\n <Card padding={3} border radius={2}>\n <Box style={{ maxHeight: '500px', overflowY: 'auto' }}>\n <Stack space={2}>\n {conflicts.map((conflict) => (\n <Text key={conflict.existingVersionId} size={1} muted>\n {conflict.sourceDocumentTitle || conflict.sourceDocumentId}\n {conflict.sourceDocumentType && ` (${conflict.sourceDocumentType})`} → {conflict.targetLanguage}\n </Text>\n ))}\n </Stack>\n </Box>\n </Card>\n\n <Stack space={4}>\n <Heading size={0}>Choose an action:</Heading>\n <Text size={1}>\n <strong>Cancel</strong> — Go back and select a different release, or create a new one.\n </Text>\n <Text size={1}>\n <strong>Replace All</strong> — Delete the existing translations in this release and create new ones.\n </Text>\n <Text size={1}>\n <strong>Skip Conflicts</strong> — Translate only the {nonConflictingCount} non-conflicting translation\n {nonConflictingCount !== 1 ? 's' : ''}, leaving existing ones unchanged.\n {!hasNonConflicting && ' (All translations have conflicts)'}\n </Text>\n </Stack>\n\n <Flex gap={2} justify=\"space-between\">\n <Button mode=\"ghost\" text=\"Cancel\" onClick={onCancel} />\n <Flex gap={2} justify=\"flex-end\">\n <Button mode=\"bleed\" tone=\"caution\" text=\"Replace All\" onClick={onReplace} />\n <Button\n mode=\"ghost\"\n tone=\"caution\"\n text=\"Skip Conflicts\"\n onClick={onSkipConflicts}\n disabled={!hasNonConflicting}\n />\n </Flex>\n </Flex>\n </Stack>\n </Card>\n )\n}\n","import { Button, Flex, Stack } from '@sanity/ui'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { ReleaseConflictInfo } from '../types'\nimport { checkReleaseConflicts } from '../utils/releases/checkReleaseConflicts'\nimport { translateDocumentSet } from '../utils/translation/translateDocumentSet'\nimport { ReleaseConflictWarning } from './ReleaseConflictWarning'\n\ninterface ActionButtonBarProps {\n setIsDialogOpen: (isOpen: boolean) => void\n}\n\n/**\n * Stores the release ID alongside conflicts to prevent race conditions.\n * When the user changes release selection during async conflict checking,\n * we need to use the originally checked release ID, not the current one.\n */\ninterface ConflictContext {\n releaseId: string\n conflicts: ReleaseConflictInfo[]\n}\n\nexport const ActionButtonBar = ({ setIsDialogOpen }: ActionButtonBarProps) => {\n const {\n document,\n config,\n overallStatus,\n dispatch,\n references,\n selectedReferenceIds,\n selectedTargetLanguages,\n translationMode,\n releaseConfig,\n } = useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n\n const [conflictContext, setConflictContext] = useState<ConflictContext | null>(null)\n const [isCheckingConflicts, setIsCheckingConflicts] = useState(false)\n\n // Filter references to only include selected ones\n const filteredReferences = references.filter((ref) => selectedReferenceIds.has(ref._id))\n\n // Calculate total number of document/language translation pairs\n // This is (1 source + selected references) * target languages\n const totalTranslations = useMemo(() => {\n const documentCount = 1 + filteredReferences.length\n return documentCount * selectedTargetLanguages.length\n }, [filteredReferences.length, selectedTargetLanguages.length])\n\n // Dismiss conflict warning if user switches to \"create new release\" mode\n // since new releases can't have conflicts\n useEffect(() => {\n if (releaseConfig.mode === 'new' && conflictContext) {\n setConflictContext(null)\n }\n }, [releaseConfig.mode, conflictContext])\n\n const startTranslation = useCallback(\n (replaceExisting: boolean, overrideReleaseId?: string, skipConflicts?: ReleaseConflictInfo[]) => {\n // Clear conflict context and start translation\n setConflictContext(null)\n\n translateDocumentSet({\n sourceDocument: document,\n targetLanguages: selectedTargetLanguages,\n client,\n dispatch,\n languageFieldName: config.languageFieldName,\n schemaId: config.schemaId,\n references: filteredReferences,\n maxConcurrentTranslationJobs: config.maxConcurrentTranslationJobs,\n maxReferenceDepth: config.maxReferenceDepth,\n translationMode,\n openaiConfig: config.openai,\n excludeFields: config.excludeFields,\n releaseConfig: {\n ...releaseConfig,\n replaceExisting,\n skipConflicts,\n // Use the override release ID if provided (from conflict context),\n // otherwise use the current releaseConfig value\n existingReleaseId: overrideReleaseId ?? releaseConfig.existingReleaseId,\n },\n telemetryConfig: config.telemetry,\n })\n },\n [\n document,\n selectedTargetLanguages,\n client,\n dispatch,\n config.languageFieldName,\n config.schemaId,\n config.maxConcurrentTranslationJobs,\n config.maxReferenceDepth,\n config.openai,\n config.excludeFields,\n config.telemetry,\n filteredReferences,\n translationMode,\n releaseConfig,\n ],\n )\n\n const handleTranslate = useCallback(async () => {\n // If using a new release, no conflicts possible - start translation directly\n if (releaseConfig.mode === 'new') {\n startTranslation(false)\n return\n }\n\n // Check for conflicts when using an existing release\n if (releaseConfig.mode === 'existing' && releaseConfig.existingReleaseId) {\n // Capture the release ID at the start of the async operation\n const checkedReleaseId = releaseConfig.existingReleaseId\n\n setIsCheckingConflicts(true)\n\n const foundConflicts = await checkReleaseConflicts({\n client,\n releaseId: checkedReleaseId,\n sourceDocument: document,\n targetLanguages: selectedTargetLanguages,\n languageFieldName: config.languageFieldName,\n references: filteredReferences,\n })\n\n setIsCheckingConflicts(false)\n\n if (foundConflicts.length > 0) {\n // Store both conflicts AND the release ID they were checked against\n // This prevents race conditions if user changes release selection during check\n setConflictContext({\n releaseId: checkedReleaseId,\n conflicts: foundConflicts,\n })\n return\n }\n }\n\n // No conflicts - start translation\n startTranslation(false)\n }, [\n releaseConfig,\n client,\n document,\n selectedTargetLanguages,\n config.languageFieldName,\n startTranslation,\n filteredReferences,\n ])\n\n const handleCancelConflict = useCallback(() => {\n setConflictContext(null)\n }, [])\n\n const handleReplaceConflict = useCallback(() => {\n // Use the stored release ID from when conflicts were checked,\n // not the current releaseConfig (which may have changed)\n if (conflictContext) {\n startTranslation(true, conflictContext.releaseId)\n }\n }, [startTranslation, conflictContext])\n\n const handleSkipConflicts = useCallback(() => {\n // Skip the conflicting document/language pairs and translate only non-conflicting ones\n if (conflictContext) {\n startTranslation(false, conflictContext.releaseId, conflictContext.conflicts)\n }\n }, [startTranslation, conflictContext])\n\n const handleSelectAll = useCallback(() => {\n dispatch({\n type: 'SET_SELECTED_TARGET_LANGUAGES',\n payload: config.supportedLanguages.map((lang) => lang.id).filter((id) => id !== document.language),\n })\n }, [dispatch, config.supportedLanguages, document.language])\n\n const handleSelectNone = useCallback(() => {\n dispatch({\n type: 'SET_SELECTED_TARGET_LANGUAGES',\n payload: [],\n })\n }, [dispatch])\n\n // Show conflict warning if conflicts were detected\n if (conflictContext) {\n return (\n <Stack space={4}>\n <ReleaseConflictWarning\n conflicts={conflictContext.conflicts}\n totalTranslations={totalTranslations}\n onCancel={handleCancelConflict}\n onReplace={handleReplaceConflict}\n onSkipConflicts={handleSkipConflicts}\n />\n </Stack>\n )\n }\n\n return (\n <Flex justify=\"space-between\" gap={2}>\n <Flex justify=\"flex-start\" gap={2}>\n <Button mode=\"ghost\" size={2} onClick={handleSelectAll}>\n Select All\n </Button>\n <Button mode=\"ghost\" size={2} onClick={handleSelectNone}>\n Select None\n </Button>\n </Flex>\n <Flex justify=\"flex-end\" gap={2}>\n <Button onClick={() => setIsDialogOpen(false)} mode=\"ghost\" size={2}>\n Cancel\n </Button>\n <Button\n mode=\"ghost\"\n tone=\"positive\"\n size={2}\n onClick={handleTranslate}\n disabled={\n selectedTargetLanguages.length === 0 ||\n !['idle', 'completed'].includes(overallStatus) ||\n (releaseConfig.mode === 'existing' && !releaseConfig.existingReleaseId) ||\n isCheckingConflicts\n }\n >\n {isCheckingConflicts ? 'Checking...' : 'Translate'}\n </Button>\n </Flex>\n </Flex>\n )\n}\n","import { useEffect } from 'react'\nimport { useListeningQuery } from 'sanity-plugin-utils'\nimport type { TranslationMetadataDocument } from '../types'\nimport { undraftId } from '../utils/document/undraftId'\nimport { TRANSLATION_METADATA_TYPE } from '../utils/metadata/constants'\nimport { useTranslationContext } from './useTranslationContext'\n\nconst TRANSLATION_METADATA_QUERY = '*[_type == $translationMetadataType && references($documentId)][0]'\n\ninterface UseTranslationMetadataProps {\n documentId: string\n}\n\nexport const useTranslationMetadata = ({ documentId }: UseTranslationMetadataProps) => {\n const { dispatch } = useTranslationContext()\n\n const { data, loading, error } = useListeningQuery<TranslationMetadataDocument | null>(TRANSLATION_METADATA_QUERY, {\n params: {\n documentId: undraftId(documentId),\n translationMetadataType: TRANSLATION_METADATA_TYPE,\n },\n initialValue: null,\n })\n\n useEffect(() => {\n if (loading) {\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'fetching_metadata' })\n return\n }\n\n if (error) {\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'error' })\n return\n }\n\n if (data) {\n dispatch({ type: 'SET_TRANSLATION_METADATA', payload: data as TranslationMetadataDocument })\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'idle' })\n return\n }\n\n dispatch({ type: 'SET_OVERALL_STATUS', payload: 'idle' })\n }, [data, loading, error, dispatch])\n\n return {\n data,\n loading,\n error,\n }\n}\n","import { Button, Checkbox, Flex, Inline, Spinner, Text } from '@sanity/ui'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { PluginConfig } from '../types'\n\ninterface LanguageCheckboxProps {\n lang: PluginConfig['supportedLanguages'][number]\n isLoading: boolean\n}\n\nexport const LanguageCheckbox = ({ lang, isLoading }: LanguageCheckboxProps) => {\n const { document, dispatch, selectedTargetLanguages } = useTranslationContext()\n const isDisabled = lang.id === document.language\n const isChecked = selectedTargetLanguages.includes(lang.id)\n\n const handleChange = () => {\n if (isDisabled) return\n\n const updatedLanguages = isChecked\n ? selectedTargetLanguages.filter((langId) => langId !== lang.id)\n : [...selectedTargetLanguages, lang.id]\n\n dispatch({\n type: 'SET_SELECTED_TARGET_LANGUAGES',\n payload: updatedLanguages,\n })\n }\n\n if (isLoading) {\n return (\n <Button type=\"button\" mode=\"bleed\" size={2} onClick={handleChange} disabled muted>\n <Flex align=\"center\" justify=\"center\" paddingY={3}>\n <Spinner size={1} />\n </Flex>\n </Button>\n )\n }\n\n return (\n <Button\n type=\"button\"\n mode={isChecked ? 'ghost' : 'bleed'}\n size={2}\n onClick={handleChange}\n disabled={isDisabled}\n tone={isChecked ? 'primary' : 'default'}\n muted\n >\n <Flex align=\"center\" gap={2} style={{ cursor: 'pointer' }}>\n <Inline space={3}>\n <Checkbox checked={isChecked} onChange={handleChange} label={lang.title} disabled={isDisabled} />\n <Flex direction=\"column\" gap={3}>\n <Text>{lang.title}</Text>\n {isDisabled && (\n <Text size={1} muted>\n The current language\n </Text>\n )}\n </Flex>\n </Inline>\n </Flex>\n </Button>\n )\n}\n","import { Card, Grid } from '@sanity/ui'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport { useTranslationMetadata } from '../hooks/useTranslationMetadata'\nimport { LanguageCheckbox } from './LanguageCheckbox'\n\nexport const LanguageSelectionPanel = () => {\n // Get the configuration from the context\n const { document, config } = useTranslationContext()\n\n const { loading } = useTranslationMetadata({\n documentId: document._id,\n })\n\n return (\n <Card padding={4} muted border>\n <Grid columns={[1, 2]} gap={3}>\n {config?.supportedLanguages.map((lang) => (\n <LanguageCheckbox key={lang.id} lang={lang} isLoading={loading} />\n ))}\n </Grid>\n </Card>\n )\n}\n","import type { SanityClient } from 'sanity'\nimport type { PluginConfig } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { localizeReferences } from './localizeReferences'\n\ninterface ReconcileReleaseReferencesParams {\n client: SanityClient\n releaseId: string\n sourceLanguage: PluginConfig['supportedLanguages'][number]['id']\n languageFieldName: PluginConfig['languageFieldName']\n /** Optional callback for progress updates */\n onProgress?: (current: number, total: number) => void\n}\n\ninterface ReconcileReleaseReferencesResult {\n /** Total documents processed */\n total: number\n /** Number of documents that had references updated */\n updated: number\n /** Number of documents that failed to process */\n errors: number\n}\n\n/**\n * Reconciles all references in a Content Release.\n *\n * This function runs `localizeReferences` on every document in the release,\n * ensuring all references point to translated versions when available.\n *\n * Use this for:\n * - Manual cleanup of stale references\n * - Edge cases where automatic backward reference updates missed something\n * - Releases with documents added through other means (manual, API, etc.)\n *\n * @example\n * ```typescript\n * const result = await reconcileReleaseReferences({\n * client,\n * releaseId: 'r123',\n * sourceLanguage: 'en',\n * languageFieldName: 'language',\n * onProgress: (current, total) => {\n * console.log(`Processing ${current}/${total}`)\n * },\n * })\n * console.log(`Updated ${result.updated} of ${result.total} documents`)\n * ```\n */\nexport const reconcileReleaseReferences = async ({\n client,\n releaseId,\n sourceLanguage,\n languageFieldName,\n onProgress,\n}: ReconcileReleaseReferencesParams): Promise<ReconcileReleaseReferencesResult> => {\n devLog.info('Starting release reference reconciliation', {\n releaseId,\n sourceLanguage,\n component: 'reconcileReleaseReferences',\n })\n\n try {\n // Fetch all versioned documents in the release\n const docsInRelease = await client.fetch<Array<{ _id: string; language: string }>>(\n `*[_id match \"versions.\" + $releaseId + \".*\"] {\n _id,\n \"language\": @[$languageFieldName]\n }`,\n { releaseId, languageFieldName },\n )\n\n if (docsInRelease.length === 0) {\n devLog.info('No documents found in release', {\n releaseId,\n component: 'reconcileReleaseReferences',\n })\n return { total: 0, updated: 0, errors: 0 }\n }\n\n devLog.info('Found documents in release', {\n releaseId,\n count: docsInRelease.length,\n component: 'reconcileReleaseReferences',\n })\n\n let updated = 0\n let errors = 0\n\n // Process each document\n for (let i = 0; i < docsInRelease.length; i++) {\n const doc = docsInRelease[i]\n\n // Report progress\n if (onProgress) {\n onProgress(i + 1, docsInRelease.length)\n }\n\n try {\n // Skip documents that don't have a language (shouldn't happen, but be safe)\n if (!doc.language) {\n devLog.warn('Document missing language field, skipping', {\n documentId: doc._id,\n component: 'reconcileReleaseReferences',\n })\n continue\n }\n\n // Run localizeReferences on the document\n await localizeReferences({\n client,\n documentId: doc._id,\n sourceLanguage,\n languageFieldName,\n })\n\n updated++\n } catch (error) {\n errors++\n devLog.error(\n 'Failed to reconcile references for document',\n error instanceof Error ? error : new Error(String(error)),\n {\n documentId: doc._id,\n component: 'reconcileReleaseReferences',\n },\n )\n }\n }\n\n const result = {\n total: docsInRelease.length,\n updated,\n errors,\n }\n\n devLog.info('Release reference reconciliation complete', {\n releaseId,\n ...result,\n component: 'reconcileReleaseReferences',\n })\n\n return result\n } catch (error) {\n devLog.error('Failed to reconcile release references', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n component: 'reconcileReleaseReferences',\n })\n throw error\n }\n}\n","import { SyncIcon } from '@sanity/icons'\nimport { Button, Card, Flex, Spinner, Stack, Text } from '@sanity/ui'\nimport { type FC, useCallback, useEffect, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { ReconciliationStatus } from '../types'\nimport { devLog } from '../utils/common/devLog'\nimport { reconcileReleaseReferences } from '../utils/references/reconcileReleaseReferences'\n\ninterface ReconcileReferencesButtonProps {\n /** Source language for reference localization */\n sourceLanguage: string\n}\n\n/**\n * Button component that triggers reference reconciliation for an existing release.\n *\n * This component:\n * - Only appears when an existing release is selected\n * - Shows the count of documents in the release\n * - Displays progress during reconciliation\n * - Is disabled during active translation\n */\nexport const ReconcileReferencesButton: FC<ReconcileReferencesButtonProps> = ({ sourceLanguage }) => {\n const { releaseConfig, overallStatus, config } = useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n\n const [documentCount, setDocumentCount] = useState<number | null>(null)\n const [isLoadingCount, setIsLoadingCount] = useState(false)\n\n // Local state for reconciliation (not shared across releases)\n const [reconcilingStatus, setReconcilingStatus] = useState<ReconciliationStatus>('idle')\n const [reconciliationProgress, setReconciliationProgress] = useState<{ current: number; total: number } | undefined>(\n undefined,\n )\n\n const releaseId = releaseConfig.existingReleaseId\n const languageFieldName = config?.languageFieldName ?? 'language'\n\n // Fetch document count when release is selected\n useEffect(() => {\n if (!releaseId || releaseConfig.mode !== 'existing') {\n setDocumentCount(null)\n return\n }\n\n const fetchCount = async () => {\n setIsLoadingCount(true)\n try {\n const count = await client.fetch<number>(`count(*[_id match \"versions.\" + $releaseId + \".*\"])`, { releaseId })\n setDocumentCount(count)\n devLog.debug('Fetched release document count', {\n releaseId,\n count,\n component: 'ReconcileReferencesButton',\n })\n } catch (error) {\n devLog.error(\n 'Failed to fetch release document count',\n error instanceof Error ? error : new Error(String(error)),\n {\n releaseId,\n component: 'ReconcileReferencesButton',\n },\n )\n setDocumentCount(null)\n } finally {\n setIsLoadingCount(false)\n }\n }\n\n fetchCount()\n }, [releaseId, releaseConfig.mode, client])\n\n const handleReconcile = useCallback(async () => {\n if (!releaseId) return\n\n devLog.info('Starting reference reconciliation', {\n releaseId,\n sourceLanguage,\n component: 'ReconcileReferencesButton',\n })\n\n setReconcilingStatus('reconciling')\n\n try {\n const result = await reconcileReleaseReferences({\n client,\n releaseId,\n sourceLanguage,\n languageFieldName,\n onProgress: (current, total) => {\n setReconciliationProgress({ current, total })\n },\n })\n\n devLog.info('Reference reconciliation completed', {\n releaseId,\n ...result,\n component: 'ReconcileReferencesButton',\n })\n\n setReconcilingStatus('completed')\n\n // Reset to idle after a short delay to show completion\n setTimeout(() => {\n setReconcilingStatus('idle')\n setReconciliationProgress(undefined)\n }, 2000)\n } catch (error) {\n devLog.error('Reference reconciliation failed', error instanceof Error ? error : new Error(String(error)), {\n releaseId,\n component: 'ReconcileReferencesButton',\n })\n setReconcilingStatus('error')\n setReconciliationProgress(undefined)\n }\n }, [releaseId, sourceLanguage, languageFieldName, client])\n\n // Only show when an existing release is selected\n if (releaseConfig.mode !== 'existing' || !releaseId) {\n return null\n }\n\n const isTranslating = overallStatus === 'translating' || overallStatus === 'linking_references'\n const isReconciling = reconcilingStatus === 'reconciling'\n const isDisabled = isTranslating || isReconciling || isLoadingCount || documentCount === 0\n\n return (\n <Card padding={3} border radius={2} tone={reconcilingStatus === 'error' ? 'critical' : 'default'}>\n <Stack space={3}>\n <Flex align=\"center\" justify=\"space-between\" gap={3}>\n <Stack space={2}>\n <Text size={1} weight=\"medium\">\n Reconcile References\n </Text>\n <Text size={1} muted>\n {isLoadingCount ? (\n 'Loading...'\n ) : documentCount === null ? (\n 'Unable to load document count'\n ) : documentCount === 0 ? (\n 'No documents in release'\n ) : (\n <>\n {documentCount} document{documentCount !== 1 ? 's' : ''} in release\n </>\n )}\n </Text>\n </Stack>\n\n <Button\n icon={isReconciling ? Spinner : SyncIcon}\n text={\n reconcilingStatus === 'completed'\n ? 'Done!'\n : reconcilingStatus === 'error'\n ? 'Retry'\n : isReconciling\n ? 'Reconciling...'\n : 'Reconcile'\n }\n tone={\n reconcilingStatus === 'completed' ? 'positive' : reconcilingStatus === 'error' ? 'critical' : 'default'\n }\n mode=\"ghost\"\n onClick={handleReconcile}\n disabled={isDisabled && reconcilingStatus !== 'error'}\n />\n </Flex>\n\n {/* Progress indicator */}\n {isReconciling && reconciliationProgress && (\n <Flex align=\"center\" gap={2}>\n <Text size={1} muted>\n Processing {reconciliationProgress.current} of {reconciliationProgress.total}...\n </Text>\n </Flex>\n )}\n\n {/* Error state */}\n {reconcilingStatus === 'error' && (\n <Text size={1} muted>\n Reconciliation failed. Click Retry to try again.\n </Text>\n )}\n\n {/* Help text */}\n {reconcilingStatus === 'idle' && documentCount !== null && documentCount > 0 && (\n <Text size={1} muted>\n Updates all document references to point to their translated versions in this release.\n </Text>\n )}\n </Stack>\n </Card>\n )\n}\n","import { SearchIcon, SyncIcon } from '@sanity/icons'\nimport { Box, Button, Card, Flex, Radio, Spinner, Stack, Text, TextInput } from '@sanity/ui'\nimport { type FC, useCallback, useEffect, useMemo, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { ReleaseSelectionMode } from '../types'\nimport { devLog } from '../utils/common/devLog'\nimport { ReconcileReferencesButton } from './ReconcileReferencesButton'\n\ninterface SanityRelease {\n _id: string\n _createdAt: string\n metadata: {\n title: string\n description?: string\n releaseType: 'asap' | 'scheduled' | 'undecided'\n }\n state: 'active' | 'scheduled' | 'publishing' | 'published' | 'archived'\n}\n\nexport const ReleaseSelectionPanel: FC = () => {\n const { dispatch, releaseConfig, document, config } = useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n\n const languageFieldName = config?.languageFieldName ?? 'language'\n const sourceLanguage = document?.[languageFieldName] as string | undefined\n\n const [releases, setReleases] = useState<SanityRelease[]>([])\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [searchQuery, setSearchQuery] = useState('')\n const [showReconcileUI, setShowReconcileUI] = useState(false)\n\n // Fetch active releases\n useEffect(() => {\n const fetchReleases = async () => {\n setIsLoading(true)\n setError(null)\n\n try {\n const result = await client.fetch<SanityRelease[]>(\n `releases::all()[state == \"active\"] | order(_createdAt desc)`,\n )\n setReleases(result)\n devLog.debug('Fetched active releases', {\n count: result.length,\n component: 'ReleaseSelectionPanel',\n })\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Failed to fetch releases'\n setError(errorMessage)\n devLog.error('Failed to fetch releases', err instanceof Error ? err : new Error(errorMessage), {\n component: 'ReleaseSelectionPanel',\n })\n } finally {\n setIsLoading(false)\n }\n }\n\n fetchReleases()\n }, [client])\n\n const handleModeChange = useCallback(\n (mode: ReleaseSelectionMode) => {\n dispatch({\n type: 'SET_RELEASE_CONFIG',\n payload: { mode },\n })\n },\n [dispatch],\n )\n\n const handleTitleChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n dispatch({\n type: 'SET_RELEASE_CONFIG',\n payload: { newReleaseTitle: event.target.value },\n })\n },\n [dispatch],\n )\n\n const handleSelectRelease = useCallback(\n (releaseId: string) => {\n dispatch({\n type: 'SET_RELEASE_CONFIG',\n payload: { existingReleaseId: releaseId },\n })\n },\n [dispatch],\n )\n\n const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(event.target.value)\n }, [])\n\n const filteredReleases = useMemo(() => {\n if (!searchQuery.trim()) {\n return releases\n }\n const query = searchQuery.toLowerCase()\n return releases.filter((release) => release.metadata.title.toLowerCase().includes(query))\n }, [releases, searchQuery])\n\n const formatDate = (dateString: string) => {\n const date = new Date(dateString)\n return date.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n }\n\n return (\n <Card padding={4} border>\n <Stack space={4}>\n {/* Radio Options */}\n <Stack space={3}>\n <Flex align=\"center\" gap={3} as=\"label\" style={{ cursor: 'pointer' }} onClick={() => handleModeChange('new')}>\n <Radio checked={releaseConfig.mode === 'new'} readOnly />\n <Text weight=\"medium\">Create new release</Text>\n </Flex>\n\n <Flex\n align=\"center\"\n gap={3}\n as=\"label\"\n style={{ cursor: 'pointer' }}\n onClick={() => handleModeChange('existing')}\n >\n <Radio checked={releaseConfig.mode === 'existing'} readOnly />\n <Text weight=\"medium\">Add to existing release</Text>\n </Flex>\n </Stack>\n\n {/* New Release Title Input */}\n {releaseConfig.mode === 'new' && (\n <TextInput\n value={releaseConfig.newReleaseTitle}\n onChange={handleTitleChange}\n placeholder=\"Release title...\"\n />\n )}\n\n {/* Existing Release Selector */}\n {releaseConfig.mode === 'existing' && (\n <Stack space={3}>\n {isLoading && (\n <Flex align=\"center\" gap={2}>\n <Spinner muted />\n <Text size={1} muted>\n Loading...\n </Text>\n </Flex>\n )}\n\n {error && (\n <Text size={1} muted>\n {error}\n </Text>\n )}\n\n {!isLoading && !error && releases.length === 0 && (\n <Text size={1} muted>\n No active releases found.\n </Text>\n )}\n\n {!isLoading && !error && releases.length > 0 && (\n <>\n <TextInput\n icon={SearchIcon}\n value={searchQuery}\n onChange={handleSearchChange}\n placeholder=\"Search releases...\"\n />\n <Box style={{ maxHeight: '200px', overflowY: 'auto' }}>\n <Stack space={1}>\n {filteredReleases.length === 0 ? (\n <Text size={1} muted>\n No releases match your search.\n </Text>\n ) : (\n filteredReleases.map((release) => {\n const releaseName = release._id.replace('_.releases.', '')\n const isSelected = releaseConfig.existingReleaseId === releaseName\n\n return (\n <Card\n key={release._id}\n padding={3}\n radius={2}\n tone={isSelected ? 'primary' : 'default'}\n style={{ cursor: 'pointer' }}\n onClick={() => {\n handleSelectRelease(releaseName)\n // Reset reconcile UI when selecting a different release\n if (!isSelected) {\n setShowReconcileUI(false)\n }\n }}\n border\n muted={isSelected}\n >\n <Flex direction=\"column\" gap={3}>\n <Flex justify=\"space-between\" align=\"center\">\n <Flex direction=\"column\" gap={2}>\n <Text size={1}>{release.metadata.title}</Text>\n <Text size={1} muted>\n {formatDate(release._createdAt)}\n </Text>\n </Flex>\n {isSelected && sourceLanguage && (\n <Button\n icon={SyncIcon}\n text={showReconcileUI ? 'Hide' : 'Reconcile References'}\n mode=\"bleed\"\n tone=\"primary\"\n fontSize={0}\n padding={2}\n onClick={(e) => {\n e.stopPropagation()\n setShowReconcileUI(!showReconcileUI)\n }}\n />\n )}\n </Flex>\n {/* Reconcile References UI - shown when user clicks the Reconcile button */}\n {isSelected && sourceLanguage && showReconcileUI && (\n <ReconcileReferencesButton sourceLanguage={sourceLanguage} />\n )}\n </Flex>\n </Card>\n )\n })\n )}\n </Stack>\n </Box>\n </>\n )}\n </Stack>\n )}\n </Stack>\n </Card>\n )\n}\n","import { Badge, Button, Card, Inline, Radio, Stack, Text } from '@sanity/ui'\nimport type { FC } from 'react'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { TranslationMode } from '../types'\n\nexport const TranslationModePanel: FC = () => {\n const { config, translationMode, openaiValidated, dispatch } = useTranslationContext()\n\n // Panel only relevant when OpenAI integration is configured\n if (!config?.openai?.enabled || !config?.openai?.apiKey) {\n return null\n }\n\n // Show validation status\n if (!openaiValidated) {\n return (\n <Card tone=\"caution\" padding={4} border>\n <Stack space={2}>\n <Text weight=\"semibold\">OpenAI Configuration</Text>\n <Text size={1} muted>\n Validating API key...\n </Text>\n </Stack>\n </Card>\n )\n }\n\n const handleModeChange = (mode: TranslationMode) => {\n dispatch({\n type: 'SET_TRANSLATION_MODE',\n payload: mode,\n })\n }\n\n return (\n <Card padding={4} border>\n <Stack space={3}>\n <Inline space={2}>\n <Text weight=\"semibold\">Translation Mode</Text>\n <Badge tone=\"positive\">OpenAI Available</Badge>\n </Inline>\n\n <Stack space={2}>\n {/* Sanity Only */}\n <Button\n type=\"button\"\n mode={translationMode === 'sanity-only' ? 'ghost' : 'bleed'}\n tone={translationMode === 'sanity-only' ? 'primary' : 'default'}\n onClick={() => handleModeChange('sanity-only')}\n >\n <Inline space={2}>\n <Radio checked={translationMode === 'sanity-only'} readOnly />\n <Stack space={2}>\n <Text>Sanity Agent Only</Text>\n <Text size={1} muted>\n Fast, reliable, and included with Sanity.\n </Text>\n </Stack>\n </Inline>\n </Button>\n\n {/* OpenAI Only */}\n <Button\n type=\"button\"\n mode={translationMode === 'openai-only' ? 'ghost' : 'bleed'}\n tone={translationMode === 'openai-only' ? 'primary' : 'default'}\n onClick={() => handleModeChange('openai-only')}\n >\n <Inline space={2}>\n <Radio checked={translationMode === 'openai-only'} readOnly />\n <Stack space={2}>\n <Inline space={2}>\n <Text>OpenAI Only</Text>\n <Badge tone=\"caution\">Beta</Badge>\n </Inline>\n <Text size={1} muted>\n Full style guide capacity with 1M token context window.\n </Text>\n </Stack>\n </Inline>\n </Button>\n </Stack>\n\n {/* Cost Warning */}\n {translationMode !== 'sanity-only' && (\n <Card tone=\"caution\" padding={3} border>\n <Text size={1}>\n 💰 <strong>OpenAI costs apply:</strong> You'll be charged for API usage based on tokens processed.\n </Text>\n </Card>\n )}\n </Stack>\n </Card>\n )\n}\n","import type { SanityClient, SanityDocument } from 'sanity'\nimport type { TranslatableDocument } from '../../types'\nimport { devLog } from '../common/devLog'\nimport { findKeyValues, type RecursiveSearchable } from '../common/findKeyValues/findKeyValues'\n\ninterface AssembleReferencesParams {\n client: SanityClient\n sourceDocument: SanityDocument\n maxReferenceDepth: number\n translatableSchemaTypes?: Array<string>\n}\n\nexport type AssembleReferencesResult = Array<{\n _id: SanityDocument['_id']\n _type: SanityDocument['_type']\n title?: string\n}>\n\n// Sanity documents are objects of arbitrary shape, which can\n// contain references to other documents. References are always\n// represented as a `_ref` key with a value of the referenced document's\n// `_id`. This function takes a Sanity document, appends all `_ref` keys\n// within it to an array, then fetches the references documents and repeats\n// the process until the maximum reference depth is reached.\n\nexport const assembleReferences = async ({\n client,\n sourceDocument,\n maxReferenceDepth,\n translatableSchemaTypes = [],\n}: AssembleReferencesParams): Promise<AssembleReferencesResult> => {\n // Validate required parameters before processing\n if (!client) {\n throw new Error('assembleReferences: client is required')\n }\n\n if (!sourceDocument || !sourceDocument._id) {\n throw new Error('assembleReferences: sourceDocument with _id is required')\n }\n\n if (typeof maxReferenceDepth !== 'number' || maxReferenceDepth < 0) {\n throw new Error('assembleReferences: maxReferenceDepth must be a non-negative number')\n }\n\n const references = new Set<string>()\n const documentCache = new Map<string, TranslatableDocument | null>()\n const errors: Array<{ refId: string; error: Error }> = []\n\n let currentDepthDocuments = [sourceDocument]\n\n for (let depth = 0; depth < maxReferenceDepth && currentDepthDocuments.length > 0; depth++) {\n // Extract all _ref IDs from current depth to process recursively\n const allRefIds = new Set<string>()\n\n for (const document of currentDepthDocuments) {\n try {\n const refResults = findKeyValues<string>(document as RecursiveSearchable, '_ref')\n for (const result of refResults) {\n const refId = result.value\n if (typeof refId === 'string' && refId.length > 0 && !references.has(refId) && !documentCache.has(refId)) {\n allRefIds.add(refId)\n }\n }\n } catch (error) {\n devLog.error(\n 'Error finding references in document',\n error instanceof Error ? error : new Error(String(error)),\n {\n documentId: document._id,\n component: 'assembleReferences',\n },\n )\n }\n }\n\n if (allRefIds.size === 0) {\n break\n }\n\n // Batch fetch all references for this depth level\n const refIdArray = Array.from(allRefIds)\n try {\n const batchResults = await client.fetch<TranslatableDocument[]>('*[_id in $ids]', { ids: refIdArray })\n\n // Index documents by ID for O(1) reference lookup\n const fetchedDocuments = new Map<string, TranslatableDocument>()\n for (const doc of batchResults) {\n fetchedDocuments.set(doc._id, doc)\n }\n\n // Check each reference: if translatable and not cached, include it\n const nextDepthDocuments: TranslatableDocument[] = []\n\n for (const refId of refIdArray) {\n const fetchedDoc = fetchedDocuments.get(refId)\n\n if (fetchedDoc) {\n // Skip non-translatable documents\n if (translatableSchemaTypes.length > 0 && !translatableSchemaTypes.includes(fetchedDoc._type)) {\n documentCache.set(refId, null)\n continue\n }\n\n documentCache.set(refId, fetchedDoc)\n references.add(refId)\n nextDepthDocuments.push(fetchedDoc)\n } else {\n // Document not found\n documentCache.set(refId, null)\n devLog.warn(`Document with _id \"${refId}\" not found`, {\n refId,\n component: 'assembleReferences',\n })\n }\n }\n\n currentDepthDocuments = nextDepthDocuments\n } catch (fetchError) {\n // Log the error but continue processing\n const error = fetchError instanceof Error ? fetchError : new Error(String(fetchError))\n errors.push({ refId: `batch(${refIdArray.join(',')})`, error })\n devLog.error('Failed to batch fetch documents', error, {\n refIdCount: refIdArray.length,\n depth,\n component: 'assembleReferences',\n })\n break\n }\n }\n\n // Log summary of errors if any occurred\n if (errors.length > 0) {\n devLog.warn(`Encountered ${errors.length} error(s) while fetching references`, {\n errorCount: errors.length,\n errors: errors.map((e) => ({ refId: e.refId, message: e.error.message })),\n component: 'assembleReferences',\n })\n }\n\n // Convert the cached documents to the expected result format\n const result: AssembleReferencesResult = []\n for (const refId of references) {\n const cachedDoc = documentCache.get(refId)\n if (cachedDoc) {\n result.push({\n _id: cachedDoc._id,\n _type: cachedDoc._type,\n title: (cachedDoc.name || cachedDoc.title) as string | undefined,\n })\n }\n }\n\n return result\n}\n","import { Badge, Box, Button, Card, Checkbox, Flex, Select, Stack, Text } from '@sanity/ui'\nimport { useMemo, useState } from 'react'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\n\nexport const ReferenceSelectionPanel = () => {\n const { references, selectedReferenceIds, dispatch } = useTranslationContext()\n const [typeFilter, setTypeFilter] = useState<string>('all')\n\n // Get unique document types from references\n const documentTypes = useMemo(() => {\n const types = new Set(references.map((ref) => ref._type))\n return Array.from(types).sort()\n }, [references])\n\n // Filter references by selected type\n const filteredReferences = useMemo(() => {\n if (typeFilter === 'all') {\n return references\n }\n return references.filter((ref) => ref._type === typeFilter)\n }, [references, typeFilter])\n\n // Count selected references in filtered list\n const selectedCount = useMemo(() => {\n return filteredReferences.filter((ref) => selectedReferenceIds.has(ref._id)).length\n }, [filteredReferences, selectedReferenceIds])\n\n const handleToggleReference = (referenceId: string) => {\n const newSelection = new Set(selectedReferenceIds)\n if (newSelection.has(referenceId)) {\n newSelection.delete(referenceId)\n } else {\n newSelection.add(referenceId)\n }\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n\n const handleSelectAll = () => {\n // Add all filtered references to the existing selection\n const newSelection = new Set(selectedReferenceIds)\n filteredReferences.forEach((ref) => {\n newSelection.add(ref._id)\n })\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n\n const handleSelectNone = () => {\n // Remove only filtered references from the selection\n const newSelection = new Set(selectedReferenceIds)\n filteredReferences.forEach((ref) => {\n newSelection.delete(ref._id)\n })\n dispatch({\n type: 'SET_SELECTED_REFERENCE_IDS',\n payload: newSelection,\n })\n }\n\n if (references.length === 0) {\n return null\n }\n\n return (\n <Card padding={4} border muted>\n <Stack space={3}>\n {/* Header with filter and selection controls */}\n <Flex justify=\"space-between\" align=\"center\" gap={2}>\n <Flex gap={2} align=\"center\">\n <Text size={1} weight=\"semibold\">\n Select References\n </Text>\n <Text size={1} muted>\n ({selectedCount} of {filteredReferences.length} selected)\n </Text>\n </Flex>\n <Flex gap={2}>\n <Button mode=\"ghost\" size={1} onClick={handleSelectAll} text=\"Select All\" />\n <Button mode=\"ghost\" size={1} onClick={handleSelectNone} text=\"Select None\" />\n </Flex>\n </Flex>\n\n {/* Document type filter */}\n {documentTypes.length > 1 && (\n <Flex align=\"center\" gap={2}>\n <Text size={1} muted>\n Filter by type:\n </Text>\n <Select\n value={typeFilter}\n onChange={(event: React.ChangeEvent<HTMLSelectElement>) => setTypeFilter(event.target.value)}\n style={{ minWidth: '150px' }}\n >\n <option value=\"all\">All types ({references.length})</option>\n {documentTypes.map((type) => {\n const count = references.filter((ref) => ref._type === type).length\n return (\n <option key={type} value={type}>\n {type} ({count})\n </option>\n )\n })}\n </Select>\n </Flex>\n )}\n\n {/* Reference list */}\n <Box style={{ maxHeight: '400px', overflowY: 'auto' }}>\n <Stack space={2}>\n {filteredReferences.length === 0 ? (\n <Text size={1} muted>\n No references match the selected filter.\n </Text>\n ) : (\n filteredReferences.map((reference) => {\n const isSelected = selectedReferenceIds.has(reference._id)\n const displayTitle = reference.title || reference._id\n\n return (\n <Card key={reference._id} padding={3} border radius={2}>\n <Flex align=\"center\" gap={3}>\n <Checkbox checked={isSelected} onChange={() => handleToggleReference(reference._id)} />\n <Flex flex={1} align=\"center\" gap={2}>\n <Text size={1}>{displayTitle}</Text>\n <Badge tone=\"primary\" fontSize={0}>\n {reference._type}\n </Badge>\n </Flex>\n </Flex>\n </Card>\n )\n })\n )}\n </Stack>\n </Box>\n </Stack>\n </Card>\n )\n}\n","import { Button, Checkbox, Flex, Inline, Select, Stack, Text } from '@sanity/ui'\nimport { useEffect, useState } from 'react'\nimport { useClient } from 'sanity'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport { assembleReferences } from '../utils/references/assembleReferences'\nimport { ReferenceSelectionPanel } from './ReferenceSelectionPanel'\n\nexport const ReferenceConfigPanel = () => {\n const { dispatch, document, shouldTranslateReferences, maxReferenceDepth, config, references, selectedReferenceIds } =\n useTranslationContext()\n const client = useClient({ apiVersion: 'vX' })\n const [isLoadingReferences, setIsLoadingReferences] = useState(false)\n const [showSelectionPanel, setShowSelectionPanel] = useState(false)\n const maxDepth = config.maxReferenceDepth || 3\n\n useEffect(() => {\n const updateReferences = async () => {\n setIsLoadingReferences(true)\n\n if (!shouldTranslateReferences) {\n dispatch({\n type: 'SET_REFERENCES',\n payload: [],\n })\n return\n }\n\n const newReferences = await assembleReferences({\n client,\n sourceDocument: document,\n maxReferenceDepth,\n translatableSchemaTypes: config.translatableSchemaTypes,\n })\n dispatch({\n type: 'SET_REFERENCES',\n payload: newReferences,\n })\n\n setIsLoadingReferences(false)\n }\n\n updateReferences()\n }, [document, client, dispatch, shouldTranslateReferences, maxReferenceDepth, config.translatableSchemaTypes])\n\n const handleMaxReferenceDepthChange = async (event: React.ChangeEvent<HTMLSelectElement>) => {\n dispatch({\n type: 'SET_MAX_REFERENCE_DEPTH',\n payload: Number.parseInt(event.target.value),\n })\n }\n\n const handleShouldTranslateChange = () => {\n dispatch({\n type: 'SET_SHOULD_TRANSLATE_REFERENCES',\n payload: !shouldTranslateReferences,\n })\n }\n\n return (\n <Stack space={2}>\n <Flex justify=\"space-between\" align=\"center\">\n <Button\n type=\"button\"\n mode={shouldTranslateReferences ? 'ghost' : 'bleed'}\n size={2}\n onClick={handleShouldTranslateChange}\n tone={shouldTranslateReferences ? 'primary' : 'neutral'}\n >\n <Inline space={2}>\n <Checkbox\n checked={shouldTranslateReferences}\n onChange={handleShouldTranslateChange}\n label={shouldTranslateReferences ? 'Disable' : 'Enable'}\n />\n <Text>Translate References</Text>\n </Inline>\n </Button>\n {shouldTranslateReferences ? (\n <Inline space={2}>\n <Text>Max Depth:</Text>\n <Select\n disabled={!shouldTranslateReferences}\n placeholder=\"Depth\"\n onChange={handleMaxReferenceDepthChange}\n value={maxReferenceDepth.toString()}\n >\n {Array.from({ length: maxDepth }, (_, i) => i + 1).map((depth) => (\n <option key={depth} value={depth.toString()}>\n {depth}\n </option>\n ))}\n </Select>\n </Inline>\n ) : null}\n </Flex>\n {shouldTranslateReferences ? (\n <>\n <Flex padding={2} justify=\"space-between\" align=\"center\">\n <Text size={1} muted>\n {isLoadingReferences\n ? 'Loading references...'\n : references.length === 0\n ? 'No translatable references found.'\n : (() => {\n const selectedCount = references.filter((ref) => selectedReferenceIds.has(ref._id)).length\n const totalCount = references.length\n if (selectedCount === totalCount) {\n return `${totalCount} reference${totalCount === 1 ? '' : 's'} will be translated along with this document.`\n }\n return `${selectedCount} of ${totalCount} reference${totalCount === 1 ? '' : 's'} will be translated along with this document.`\n })()}\n </Text>\n {!isLoadingReferences && references.length > 0 && (\n <Button\n mode=\"ghost\"\n size={1}\n text={showSelectionPanel ? 'Hide selection' : 'Select specific references...'}\n onClick={() => setShowSelectionPanel(!showSelectionPanel)}\n />\n )}\n </Flex>\n {!isLoadingReferences && references.length > 0 && showSelectionPanel && <ReferenceSelectionPanel />}\n </>\n ) : null}\n </Stack>\n )\n}\n","import { ReferenceConfigPanel } from './ReferenceConfigPanel'\n\nexport const WriteSettingsPanel = () => {\n return <ReferenceConfigPanel />\n}\n","import { Card, Label, Stack } from '@sanity/ui'\nimport type { FC } from 'react'\nimport { ActionButtonBar } from './ActionButtonBar'\nimport { LanguageSelectionPanel } from './LanguageSelectionPanel'\nimport { ReleaseSelectionPanel } from './ReleaseSelectionPanel'\nimport { TranslationModePanel } from './TranslationModePanel'\nimport { WriteSettingsPanel } from './WriteSettingsPanel'\n\ninterface TranslationConfigViewProps {\n setIsDialogOpen: (isOpen: boolean) => void\n}\n\nexport const TranslationConfigView: FC<TranslationConfigViewProps> = ({ setIsDialogOpen }) => {\n return (\n <Stack space={4}>\n <Stack space={2}>\n <Label>Languages</Label>\n <LanguageSelectionPanel />\n </Stack>\n\n <Stack space={2}>\n <Label>Translation Method</Label>\n <TranslationModePanel />\n </Stack>\n\n <Stack space={2}>\n <Label>Content Release</Label>\n <ReleaseSelectionPanel />\n </Stack>\n\n <Stack space={2}>\n <Label>Settings</Label>\n <Card padding={4} border muted>\n <WriteSettingsPanel />\n </Card>\n </Stack>\n <ActionButtonBar setIsDialogOpen={setIsDialogOpen} />\n </Stack>\n )\n}\n","import { ArrowRightIcon, CheckmarkIcon, CircleIcon, ErrorOutlineIcon, PanelRightIcon, ResetIcon } from '@sanity/icons'\nimport { Badge, Box, Button, Card, Code, Flex, Heading, Inline, Label, Spinner, Stack, Text, Tooltip } from '@sanity/ui'\nimport { useCallback, useMemo } from 'react'\nimport { useOpenInNewPane } from 'sanity-plugin-utils'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport { forceCleanupActiveTranslation } from '../utils/state/activeTranslations'\n\nconst TERMINAL_STATUSES = ['completed', 'skipped', 'error']\n\nexport const TranslationStatusPanel = () => {\n const { overallStatus, translationJobs, translationMode, dispatch, document } = useTranslationContext()\n\n const headline = useMemo(() => {\n if (overallStatus === 'completed') return 'Translation completed'\n if (overallStatus === 'error') return 'Translation failed'\n return 'Translation in progress'\n }, [overallStatus])\n\n const jobs = useMemo(() => Array.from(translationJobs.values()), [translationJobs])\n\n const isTerminalState = overallStatus === 'completed' || overallStatus === 'error'\n\n const handleReset = useCallback(() => {\n // Clean up the active translation from module-level store\n forceCleanupActiveTranslation(document._id)\n // Reset the context state to allow a new translation\n dispatch({ type: 'RESET_TRANSLATION_STATE' })\n }, [dispatch, document._id])\n\n // Aggregate token usage and costs across completed OpenAI translation jobs\n const openaiStats = useMemo(() => {\n if (translationMode === 'sanity-only') return null\n\n const completedJobs = jobs.filter((j) => j.status === 'completed')\n const totalTokens = completedJobs.reduce((sum, job) => sum + (job.tokensUsed || 0), 0)\n const totalCost = completedJobs.reduce((sum, job) => sum + (job.estimatedCost || 0), 0)\n\n return { totalTokens, totalCost, jobsWithUsage: completedJobs.filter((j) => j.tokensUsed).length }\n }, [jobs, translationMode])\n\n return (\n <Stack space={4}>\n <Heading size={2}>{headline}</Heading>\n <ProgressBar progress={jobs.filter((job) => TERMINAL_STATUSES.includes(job.status)).length} max={jobs.length} />\n <Stack space={3}>\n <Label>Translations</Label>\n <Card padding={2} tone=\"transparent\">\n <Stack space={2}>\n <ul style={{ listStyleType: 'none', paddingInlineStart: '0' }}>\n {jobs.map((job) => (\n <li key={job._id}>\n <Box padding={3}>\n <Flex justify=\"space-between\" align=\"center\">\n <Inline space={2}>\n <Flex align=\"center\" justify=\"center\" style={{ width: '24px', height: '24px' }}>\n {['completed', 'skipped'].includes(job.status) ? (\n <CheckmarkIcon fontSize={24} />\n ) : job.status === 'error' ? (\n <Box style={{ color: 'var(--card-critical-fg-color)' }}>\n <ErrorOutlineIcon fontSize={24} />\n </Box>\n ) : job.status === 'queued' ? (\n <CircleIcon fontSize={24} />\n ) : (\n <Spinner />\n )}\n </Flex>\n <Stack space={3}>\n <Inline space={2}>\n <Text size={1} weight=\"semibold\">\n {job.title || 'Untitled Document'}\n </Text>\n <ArrowRightIcon fontSize={18} />\n <Code size={1}>{job.language}</Code>\n </Inline>\n <Code size={1}>\n {job._type} — {job._id?.slice(-6) || 'unknown'}\n </Code>\n {!['completed', 'skipped', 'error'].includes(job.status) && (\n <Text size={1} weight=\"semibold\">\n {getStatusLabel(job.status)}\n </Text>\n )}\n {job.status === 'completed' && job.tokensUsed ? (\n <Text size={1} muted>\n {job.tokensUsed.toLocaleString()} tokens (~${job.estimatedCost?.toFixed(4)})\n </Text>\n ) : null}\n {job.status === 'error' && job.errorMessage ? (\n <Card tone=\"critical\" padding={2} radius={2}>\n <Text size={1}>Error: {job.errorMessage}</Text>\n </Card>\n ) : null}\n </Stack>\n </Inline>\n <Flex gap={2} align=\"center\">\n {job.status === 'skipped' ? <SkippedBadge /> : null}\n {job.status === 'error' ? <ErrorBadge /> : null}\n {job.targetId ? (\n <TranslationOpenerButton\n target={{\n _id: job.targetId,\n _type: job._type as string,\n }}\n />\n ) : null}\n </Flex>\n </Flex>\n </Box>\n </li>\n ))}\n </ul>\n </Stack>\n </Card>\n </Stack>\n\n {/* OpenAI Usage Summary */}\n {openaiStats && openaiStats.jobsWithUsage > 0 && (\n <Card tone=\"primary\" padding={4} border>\n <Stack space={2}>\n <Text weight=\"semibold\">OpenAI Usage Summary</Text>\n <Inline space={4}>\n <Text size={1}>\n Total Tokens: <strong>{openaiStats.totalTokens.toLocaleString()}</strong>\n </Text>\n <Text size={1}>\n Total Cost: <strong>${openaiStats.totalCost.toFixed(4)}</strong>\n </Text>\n </Inline>\n </Stack>\n </Card>\n )}\n\n {/* Reset button for starting a new translation */}\n {isTerminalState && (\n <Flex justify=\"flex-end\">\n <Button mode=\"ghost\" tone=\"primary\" text=\"Translate More\" icon={ResetIcon} onClick={handleReset} />\n </Flex>\n )}\n </Stack>\n )\n}\n\nconst ProgressBar = ({ progress, max = 100 }: { progress: number; max?: number }) => {\n const percentage = Math.min((progress / max) * 100, 100)\n\n return (\n <Stack space={2}>\n <div\n style={{\n width: '100%',\n height: '8px',\n backgroundColor: 'var(--card-border-color)',\n borderRadius: '4px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n width: `${percentage}%`,\n height: '100%',\n backgroundColor: 'var(--card-accent-fg-color)',\n borderRadius: '4px',\n transition: 'width 0.3s ease-in-out',\n }}\n />\n </div>\n <Flex justify=\"space-between\" align=\"center\">\n <Text size={1} muted>\n {percentage.toFixed(0)}%\n </Text>\n <Text size={1} muted>\n {progress} / {max} jobs completed.\n </Text>\n </Flex>\n </Stack>\n )\n}\n\nconst TranslationOpenerButton = ({ target }: { target: { _id: string; _type: string } }) => {\n const openInNewPane = useOpenInNewPane(target._id, target._type)\n\n return <Button mode=\"bleed\" text=\"View\" iconRight={PanelRightIcon} onClick={() => openInNewPane()} />\n}\n\nconst SkippedBadge = () => (\n <Tooltip\n portal\n content={\n <Box padding={1}>\n <Text size={1} muted>\n There was already a fresh translation for this document and language.\n </Text>\n </Box>\n }\n >\n <Badge tone=\"neutral\">Skipped</Badge>\n </Tooltip>\n)\n\nconst ErrorBadge = () => (\n <Tooltip\n portal\n content={\n <Box padding={1}>\n <Text size={1} muted>\n This translation failed. Check the error message for details.\n </Text>\n </Box>\n }\n >\n <Badge tone=\"critical\">Failed</Badge>\n </Tooltip>\n)\n\nconst getStatusLabel = (status: string): string => {\n const labels: Record<string, string> = {\n idle: 'Idle',\n queued: 'Queued...',\n preparing_translation: 'Preparing translation...',\n translating: 'Translating with Sanity Agent...',\n translating_with_openai: 'Translating with OpenAI...',\n updating_metadata: 'Saving metadata...',\n error: 'Error',\n skipped: 'Skipped',\n completed: 'Completed',\n }\n return labels[status] || status\n}\n","import { type FC, useEffect } from 'react'\nimport { useTranslationContext } from '../hooks/useTranslationContext'\nimport type { OverallTranslationStatus } from '../types'\nimport { devLog } from '../utils/common/devLog'\nimport { TranslationConfigView } from './TranslationConfigPanel'\nimport { TranslationStatusPanel } from './TranslationStatusPanel'\n\ninterface TranslationDialogProps {\n setIsDialogOpen: (isOpen: boolean) => void\n /** Callback to notify parent of status changes (for close confirmation) */\n onStatusChange?: (status: OverallTranslationStatus) => void\n}\n\nconst STATES_IN_WHICH_TO_SHOW_CONFIG: Array<OverallTranslationStatus> = ['fetching_metadata', 'idle']\n\nconst STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS: Array<OverallTranslationStatus> = [\n 'translating',\n 'linking_references',\n 'completed',\n]\n\nconst STATES_IN_WHICH_TO_SHOW_ERROR: Array<OverallTranslationStatus> = ['error']\n\nexport const TranslationDialog: FC<TranslationDialogProps> = ({ setIsDialogOpen, onStatusChange }) => {\n const { overallStatus, document } = useTranslationContext()\n\n // Notify parent of status changes so it can show confirmation on close\n useEffect(() => {\n onStatusChange?.(overallStatus)\n }, [overallStatus, onStatusChange])\n\n if (STATES_IN_WHICH_TO_SHOW_CONFIG.includes(overallStatus)) {\n return <TranslationConfigView setIsDialogOpen={setIsDialogOpen} />\n }\n\n if (STATES_IN_WHICH_TO_SHOW_TRANSLATION_PROGRESS.includes(overallStatus)) {\n return <TranslationStatusPanel />\n }\n\n if (STATES_IN_WHICH_TO_SHOW_ERROR.includes(overallStatus)) {\n return <div>Error occurred</div>\n }\n\n devLog.warn('Unhandled translation dialog state', {\n documentId: document._id,\n overallStatus,\n })\n\n return <div>Unhandled overall state</div>\n}\n","import { TranslateIcon } from '@sanity/icons'\nimport { useCallback, useState } from 'react'\nimport type { DocumentActionDescription, DocumentActionProps } from 'sanity'\nimport { TranslationContextProvider } from '../../hooks/useTranslationContext/TranslationContextProvider'\nimport type { OverallTranslationStatus, PluginConfig, TranslatableDocument } from '../../types'\nimport { devLog } from '../../utils/common/devLog'\nimport { TranslationDialog } from '../TranslationDialog'\n\ninterface TranslateActionProps extends DocumentActionProps {\n config: PluginConfig\n}\n\n/** Statuses that indicate translation is actively in progress */\nconst IN_PROGRESS_STATUSES: OverallTranslationStatus[] = ['translating', 'linking_references']\n\n/** Get the button label based on current translation status */\nconst getButtonLabel = (status: OverallTranslationStatus | null): string => {\n if (status === 'completed') return 'Translation Complete'\n if (status && IN_PROGRESS_STATUSES.includes(status)) return 'Translating...'\n return 'Translate'\n}\n\nexport const getTranslateAction = (config: PluginConfig) => {\n return ({ draft, published }: TranslateActionProps): DocumentActionDescription | null => {\n const document = draft ?? published\n const [isDialogOpen, setIsDialogOpen] = useState(false)\n const [currentStatus, setCurrentStatus] = useState<OverallTranslationStatus | null>(null)\n\n const isTranslating = currentStatus !== null && IN_PROGRESS_STATUSES.includes(currentStatus)\n\n // Callback for child to report translation status changes\n const handleStatusChange = useCallback((status: OverallTranslationStatus) => {\n setCurrentStatus(status)\n }, [])\n\n // Handle close with confirmation when translation is in progress\n const handleClose = useCallback(() => {\n if (isTranslating) {\n const confirmed = window.confirm(\n 'Translation is in progress. Closing this dialog will not stop the translation, ' +\n 'but refreshing the page will cancel the translation. You can reopen the ' +\n 'Translate dialog to see updated progress.',\n )\n if (!confirmed) {\n return\n }\n devLog.warn('User closed dialog during active translation', {\n documentId: document?._id,\n component: 'TranslateAction',\n })\n }\n setIsDialogOpen(false)\n }, [isTranslating, document?._id])\n\n devLog.debug('TranslateAction initialized', {\n documentId: document?._id,\n documentType: document?._type,\n })\n\n if (!document || !config.translatableSchemaTypes?.includes(document._type)) {\n devLog.warn('Document not in translatable schema types', {\n documentType: document?._type,\n translatableTypes: config.translatableSchemaTypes,\n })\n return null\n }\n\n if (!config) {\n devLog.error('Missing config in TranslateAction')\n return null\n }\n\n return {\n label: getButtonLabel(currentStatus),\n icon: TranslateIcon,\n onHandle: () => {\n devLog.info('Translation dialog opened', {\n documentId: document._id,\n documentType: document._type,\n component: 'TranslateAction',\n })\n setIsDialogOpen(true)\n },\n dialog: isDialogOpen && {\n type: 'dialog',\n onClose: handleClose,\n header: 'Translate Document',\n content: (\n <TranslationContextProvider document={document as TranslatableDocument} config={config}>\n <TranslationDialog setIsDialogOpen={setIsDialogOpen} onStatusChange={handleStatusChange} />\n </TranslationContextProvider>\n ),\n },\n }\n }\n}\n","import { TranslateIcon } from '@sanity/icons'\nimport { defineField, defineType } from 'sanity'\n\nexport const translationGlossaryTerm = defineType({\n name: 'translationGlossaryTerm',\n title: 'Translation Glossary Term',\n icon: TranslateIcon,\n type: 'document',\n fields: [\n defineField({\n name: 'translations',\n title: 'Translations',\n type: 'internationalizedArrayString',\n }),\n ],\n preview: {\n select: {\n translations: 'translations',\n },\n prepare({ translations }: { translations: { locale: string; value: string }[] }) {\n const enTranslation = translations?.find((t) => t.locale === 'en-us')\n const firstTranslation = translations?.[0]\n return {\n title: enTranslation?.value || firstTranslation?.value || 'No translation available',\n }\n },\n },\n})\n","import { defineField } from 'sanity'\n\nexport const translationHistory = defineField({\n name: 'translationHistory',\n type: 'array',\n of: [\n {\n name: 'targetLanguageHistory',\n type: 'object',\n fields: [\n defineField({\n name: 'language',\n title: 'Language',\n description: 'The language of the target document',\n type: 'string',\n }),\n defineField({\n name: 'sourceId',\n title: 'Source ID',\n description: 'The ID of the source document',\n type: 'string',\n }),\n defineField({\n name: 'sourceRev',\n title: 'Source Revision',\n description: 'The revision ID of the source document at the time of translation',\n type: 'string',\n }),\n defineField({\n name: 'sourceLanguage',\n title: 'Source Language',\n description: 'The language of the source document',\n type: 'string',\n }),\n defineField({\n name: 'sourceSnapshot',\n title: 'Source Snapshot',\n description: 'The snapshot of the source document at the time of translation',\n type: 'text',\n }),\n defineField({\n name: 'targetId',\n title: 'Target ID',\n description: 'The ID of the target document',\n type: 'string',\n }),\n ],\n },\n ],\n})\n","import { TranslateIcon } from '@sanity/icons'\nimport { defineField, defineType } from 'sanity'\n\nexport const translationSettings = defineType({\n name: 'translationSettings',\n title: 'Translation Settings',\n icon: TranslateIcon,\n type: 'document',\n groups: [\n {\n name: 'general',\n title: 'General',\n default: true,\n },\n {\n name: 'history',\n title: 'History',\n },\n ],\n fields: [\n defineField({\n name: 'globalPrompt',\n title: 'Global Translation Prompt',\n description: 'A prompt that will given to the Sanity AI Agent for all translation tasks.',\n type: 'text',\n rows: 5,\n group: 'general',\n }),\n defineField({\n name: 'localePrompts',\n title: 'Locale Prompts',\n description: 'Prompts that will be given to the Sanity AI Agent for each locale.',\n type: 'internationalizedArrayText',\n group: 'general',\n }),\n defineField({\n name: 'protectedPhrases',\n title: 'Protected Phrases',\n description: 'Phrases that will never be translated.',\n type: 'array',\n of: [{ type: 'string' }],\n group: 'general',\n }),\n defineField({\n name: 'changeHistory',\n title: 'Change History',\n description: 'History of changes made to translation settings.',\n type: 'array',\n readOnly: true,\n group: 'history',\n of: [\n {\n type: 'object',\n fields: [\n defineField({\n name: 'timestamp',\n title: 'Timestamp',\n type: 'datetime',\n }),\n defineField({\n name: 'changeType',\n title: 'Change Type',\n type: 'string',\n options: {\n list: [\n { title: 'Global Prompt', value: 'globalPrompt' },\n { title: 'Locale Prompt', value: 'localePrompt' },\n { title: 'Protected Phrase', value: 'protectedPhrase' },\n { title: 'Glossary Term', value: 'glossaryTerm' },\n ],\n },\n }),\n defineField({\n name: 'description',\n title: 'Description',\n type: 'text',\n rows: 3,\n }),\n defineField({\n name: 'locale',\n title: 'Locale',\n type: 'string',\n description: 'Target locale for locale-specific changes',\n }),\n ],\n preview: {\n select: {\n timestamp: 'timestamp',\n changeType: 'changeType',\n description: 'description',\n },\n prepare({ timestamp, changeType, description }) {\n return {\n title: `${changeType} - ${new Date(timestamp).toLocaleDateString()}`,\n subtitle: description,\n }\n },\n },\n },\n ],\n }),\n ],\n preview: {\n prepare: () => ({\n title: 'Translation Settings',\n }),\n },\n})\n","import type { SanityDocument } from 'sanity'\nimport type { FieldTransform } from '../../../../types'\nimport { devLog } from '../../../common/devLog'\nimport { deserializeDocument } from '../../../openai/deserializeDocument'\nimport { serializeDocument } from '../../../openai/serializeDocument'\n\n/**\n * Special type to track transformed rich text content.\n * This separates the serialized XML content from regular strings\n * so the main serialization can handle it correctly.\n */\ninterface TranslatableRichText {\n _type: 'translatable.richtext'\n // XML-formatted text content for translation\n translationText: string\n // Original value stored for fallback\n _originalValue: unknown[]\n}\n\n/**\n * Identifies if an array is a Portable Text field (block content)\n */\nfunction isPortableText(value: unknown[]): boolean {\n // biome-ignore lint/suspicious/noExplicitAny: Items in array can be any shape\n return Array.isArray(value) && value.some((item: any) => item?._type === 'block')\n}\n\n/**\n * Transform for handling Portable Text (rich text) content using XML-like serialization.\n * This leverages the existing serialization utilities to convert complex rich text\n * to a format that's easier for translation models to process.\n *\n * Uses value-based detection to identify Portable Text arrays (checking for _type === 'block').\n * The fieldPath pattern is used as a performance optimization to avoid checking every field,\n * but the valueMatcher is the definitive check.\n *\n * @public\n */\nexport const richTextTransform: FieldTransform<unknown[], TranslatableRichText> = {\n // Matcher combines field name pattern with value-based detection\n // fieldPath pattern acts as a pre-filter for performance\n // valueMatcher definitively checks if the value is actually Portable Text\n matcher: {\n // Pre-filter: Match all fields to enable detection based solely on value\n fieldPath: /.*/,\n\n // Definitive check: Verify the value is actually Portable Text\n valueMatcher: (value) => {\n return Array.isArray(value) && isPortableText(value)\n },\n },\n\n toTranslatable: (value, context) => {\n if (!Array.isArray(value) || !isPortableText(value)) {\n // Return a translatable wrapper even for non-portable text\n return {\n _type: 'translatable.richtext' as const,\n translationText: JSON.stringify(value),\n _originalValue: value,\n }\n }\n\n devLog.info('Applying rich text transform with XML serialization', {\n fieldPath: context.fieldPath,\n blockCount: value.length,\n })\n\n try {\n // Create a temporary document wrapper for the rich text\n const tempDoc = {\n _id: 'temp',\n _type: 'richTextWrapper',\n _createdAt: new Date().toISOString(),\n _updatedAt: new Date().toISOString(),\n _rev: 'temp',\n content: value,\n } as SanityDocument\n\n // Use the existing serialization\n const serialized = serializeDocument(tempDoc)\n devLog.debug('Serialized rich text document', {\n fieldPath: context.fieldPath,\n serializedLength: serialized.length,\n preview: serialized.substring(0, 200),\n })\n\n // Extract just the serialized rich text part\n // Use greedy match to capture everything until the LAST </field> tag (the outer one)\n // The content field is the only top-level field, so the last </field> is the right one\n const contentRegex = /<field name=\"content\"[^>]*>([\\s\\S]*)<\\/field>/s\n const match = serialized.match(contentRegex)\n\n if (match?.[1]) {\n // Return a special object type that the serializer recognizes\n // This prevents the XML content from being escaped as a regular string\n const result: TranslatableRichText = {\n _type: 'translatable.richtext',\n translationText: match[1].trim(),\n _originalValue: value,\n }\n devLog.debug('Successfully extracted and wrapped rich text', {\n fieldPath: context.fieldPath,\n translationTextLength: result.translationText.length,\n })\n return result\n }\n\n devLog.warn('Failed to serialize rich text content - regex did not match', {\n fieldPath: context.fieldPath,\n serializedPreview: serialized.substring(0, 500),\n })\n // Return a fallback translatable object\n return {\n _type: 'translatable.richtext' as const,\n translationText: JSON.stringify(value),\n _originalValue: value,\n }\n } catch (error) {\n devLog.error('Error serializing rich text', error, {\n fieldPath: context.fieldPath,\n })\n // Return a fallback translatable object\n return {\n _type: 'translatable.richtext' as const,\n translationText: JSON.stringify(value),\n _originalValue: value,\n }\n }\n },\n\n fromTranslatable: (translatedValue, originalValue, context) => {\n // Check if the value is our translatable format\n if (!translatedValue || translatedValue._type !== 'translatable.richtext' || !translatedValue.translationText) {\n devLog.debug('Value is not a translatable rich text object, returning original', {\n fieldPath: context.fieldPath,\n hasCorrectType: translatedValue._type === 'translatable.richtext',\n })\n return originalValue // Return original value as we're expecting unknown[]\n }\n\n devLog.info('Deserializing rich text content', {\n fieldPath: context.fieldPath,\n translationTextLength: translatedValue.translationText.length,\n })\n\n try {\n const { translationText, _originalValue } = translatedValue as TranslatableRichText\n\n // Create a wrapper document for deserialization\n const wrappedSerialized = `<field name=\"content\">\\n${translationText}\\n</field>`\n\n devLog.debug('Prepared wrapped serialized content', {\n fieldPath: context.fieldPath,\n wrappedLength: wrappedSerialized.length,\n preview: wrappedSerialized.substring(0, 300),\n })\n\n // Create original temp doc for structure\n const tempOriginalDoc = {\n _id: 'temp',\n _type: 'richTextWrapper',\n _createdAt: new Date().toISOString(),\n _updatedAt: new Date().toISOString(),\n _rev: 'temp',\n content: originalValue,\n } as SanityDocument\n\n // Use existing deserialization\n const result = deserializeDocument(tempOriginalDoc, wrappedSerialized)\n\n devLog.debug('Deserialization result', {\n fieldPath: context.fieldPath,\n hasContent: !!result.content,\n contentType: Array.isArray(result.content) ? 'array' : typeof result.content,\n contentLength: Array.isArray(result.content) ? result.content.length : undefined,\n })\n\n if (!result.content) {\n devLog.warn('Deserialization returned no content, returning original', {\n fieldPath: context.fieldPath,\n resultKeys: Object.keys(result),\n })\n return originalValue\n }\n\n return result.content as unknown[]\n } catch (error) {\n devLog.error('Failed to deserialize rich text', error, {\n fieldPath: context.fieldPath,\n })\n return originalValue\n }\n },\n}\n","import type { FieldTransform } from '../../../../types'\nimport { devLog } from '../../../common/devLog'\nimport { escapeXml, unescapeXml } from '../../xmlUtils'\n\n/**\n * Represents a VTT caption cue with timing and text content.\n */\ninterface VTTCaption {\n _key?: string\n _type?: string\n text?: string\n startTime?: number\n endTime?: number\n [key: string]: string | number | undefined\n}\n\n/**\n * VTT caption metadata without text content, used for preservation during translation.\n */\ninterface VTTCaptionMetadata {\n _key?: string\n _type?: string\n startTime?: number\n endTime?: number\n _originalIndex: number\n [key: string]: string | number | undefined\n}\n\n/**\n * Special type to track transformed VTT captions.\n * This combines metadata separation for token efficiency with XML format for translation.\n */\ninterface TranslatableVTTCaptions {\n _type: 'translatable.vtt.captions'\n // XML-formatted text content for translation\n translationText: string\n // Metadata stored separately to avoid sending to translation model\n _originalMetadata: VTTCaptionMetadata[]\n}\n\n/**\n * Transform for handling VTT captions in video documents using a hybrid approach.\n * This stores metadata separately for token efficiency while formatting\n * text content as XML for better translation results.\n *\n * @public\n */\nexport const vttCaptionTransform: FieldTransform<VTTCaption[], TranslatableVTTCaptions> = {\n matcher: {\n fieldName: 'captions',\n schemaType: 'video',\n },\n\n toTranslatable: (captionsArray, context) => {\n devLog.info('VTT captions toTranslatable - input:', {\n fieldPath: context.fieldPath,\n isArray: Array.isArray(captionsArray),\n count: Array.isArray(captionsArray) ? captionsArray.length : 0,\n firstCaption: Array.isArray(captionsArray) && captionsArray.length > 0 ? captionsArray[0] : null,\n })\n\n if (!Array.isArray(captionsArray)) {\n devLog.debug('VTT caption field is not an array, returning empty transform', {\n fieldPath: context.fieldPath,\n value: captionsArray,\n })\n // Return empty translatable format for non-array values\n return {\n _type: 'translatable.vtt.captions' as const,\n translationText: '',\n _originalMetadata: [],\n }\n }\n\n devLog.info('Applying VTT caption hybrid transform', {\n fieldPath: context.fieldPath,\n count: captionsArray.length,\n })\n\n // Extract text for XML format and metadata for separate storage\n const translationText = serializeVTTCaptionText(captionsArray)\n const originalMetadata = captionsArray.map((caption, index) => {\n // Remove text content but preserve all other properties\n const { text: _text, ...metadata } = caption\n return {\n ...metadata,\n _originalIndex: index,\n }\n })\n\n devLog.info('VTT captions serialized:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n metadataCount: originalMetadata.length,\n metadataExample: originalMetadata.length > 0 ? originalMetadata[0] : null,\n })\n\n // Return object with separated text and metadata\n const result: TranslatableVTTCaptions = {\n _type: 'translatable.vtt.captions',\n translationText,\n _originalMetadata: originalMetadata,\n }\n\n return result\n },\n\n fromTranslatable: (translatedValue, _originalValue, context) => {\n devLog.info('VTT captions fromTranslatable - input:', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n isCorrectType: translatedValue._type === 'translatable.vtt.captions',\n hasText: !!translatedValue.translationText,\n hasMetadata: Array.isArray(translatedValue._originalMetadata),\n })\n\n // Check if the value is our hybrid format\n if (\n !translatedValue ||\n translatedValue._type !== 'translatable.vtt.captions' ||\n !translatedValue.translationText ||\n !Array.isArray(translatedValue._originalMetadata)\n ) {\n devLog.debug('Value is not a translatable VTT caption object, skipping transform', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n translatedValue: translatedValue,\n })\n return [] // Return empty array as we're expecting VTTCaption[]\n }\n\n devLog.info('Processing translated VTT captions', {\n fieldPath: context.fieldPath,\n metadataCount: translatedValue._originalMetadata.length,\n })\n\n // Get translated text and metadata\n const { translationText, _originalMetadata } = translatedValue\n\n devLog.info('VTT captions translationText:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n })\n\n // Merge translated text with preserved metadata\n const result = deserializeVTTCaptionText(translationText, _originalMetadata)\n\n devLog.info('VTT captions final result:', {\n fieldPath: context.fieldPath,\n resultCount: result.length,\n resultExample: result.length > 0 ? result[0] : null,\n })\n\n return result\n },\n}\n\n/**\n * Serialize VTT captions to compact text format for translation\n * @param captions - Array of VTT caption objects\n * @returns Compact string with indexed text content\n */\nexport function serializeVTTCaptionText(captions: VTTCaption[]): string {\n devLog.debug('serializeVTTCaptionText - input:', {\n captionsCount: captions.length,\n firstCaption: captions.length > 0 ? captions[0] : null,\n })\n\n if (!Array.isArray(captions) || captions.length === 0) {\n return ''\n }\n\n // Use a compact format: [index] text\n // Escape newlines in text to prevent them from breaking the line-by-line parsing\n const lines: string[] = captions.map((caption, index) => {\n devLog.debug(`Processing caption ${index}:`, {\n text: caption.text,\n hasText: typeof caption.text === 'string',\n })\n // Replace actual newlines with escaped newlines to preserve multi-line cues\n const escapedText = (caption.text || '').replace(/\\n/g, '\\\\n')\n return `[${index}] ${escapedText}`\n })\n\n const result = lines.join('\\n')\n\n devLog.debug('serializeVTTCaptionText - output:', {\n resultLength: result.length,\n resultPreview: result.substring(0, 100) + (result.length > 100 ? '...' : ''),\n })\n\n return result\n}\n\n/**\n * Deserialize compact text format and merge with preserved metadata\n * @param translationText - Compact string with translated text\n * @param originalMetadata - Array of original caption metadata\n * @returns Reconstructed captions array with translated text\n */\nexport function deserializeVTTCaptionText(\n translationText: string,\n originalMetadata: VTTCaptionMetadata[],\n): VTTCaption[] {\n devLog.debug('deserializeVTTCaptionText - input:', {\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 100) + (translationText.length > 100 ? '...' : ''),\n metadataCount: originalMetadata.length,\n metadataExample: originalMetadata.length > 0 ? originalMetadata[0] : null,\n })\n\n if (!translationText || translationText.trim() === '') {\n return []\n }\n\n // Parse compact format: [index] text\n // Handle multi-line cues by restoring escaped newlines and continuation lines\n const result: VTTCaption[] = []\n const lines = translationText.split('\\n')\n const lineRegex = /^\\[(\\d+)\\]\\s*(.*)$/\n\n let currentIndex: number | null = null\n let currentTextParts: string[] = []\n\n for (const line of lines) {\n const match = line.match(lineRegex)\n\n if (match) {\n // Found a new caption - process the previous one if it exists\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = originalMetadata.find((m) => m._originalIndex === currentIndex)\n\n if (metadata) {\n const { _originalIndex, ...metadataWithoutIndex } = metadata\n result.push({\n ...metadataWithoutIndex,\n text,\n })\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n result.push({\n _type: 'vtt.cue',\n _key: `caption-${currentIndex}`,\n text,\n startTime: 0,\n endTime: 0,\n })\n }\n }\n\n // Start new caption\n currentIndex = Number.parseInt(match[1], 10)\n currentTextParts = [match[2]]\n } else if (currentIndex !== null) {\n // Continuation line for current caption (handles cases where translation service converted \\n to actual newlines)\n currentTextParts.push(line)\n } else {\n // Line doesn't match format and we're not in a caption - skip it\n devLog.warn('Failed to parse caption line', { line })\n }\n }\n\n // Process the last caption\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = originalMetadata.find((m) => m._originalIndex === currentIndex)\n\n if (metadata) {\n const { _originalIndex, ...metadataWithoutIndex } = metadata\n result.push({\n ...metadataWithoutIndex,\n text,\n })\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n result.push({\n _type: 'vtt.cue',\n _key: `caption-${currentIndex}`,\n text,\n startTime: 0,\n endTime: 0,\n })\n }\n }\n\n // Verify all captions were processed\n if (result.length < originalMetadata.length) {\n devLog.warn(`Missing captions in translation: found ${result.length} out of ${originalMetadata.length}`)\n }\n\n devLog.debug('deserializeVTTCaptionText - output:', {\n resultCount: result.length,\n resultExample: result.length > 0 ? result[0] : null,\n })\n\n return result\n}\n\n// Export the full serialization functions for testing and standalone usage\n/**\n * Legacy function for full XML serialization - used in tests\n */\nexport function serializeVTTCaptions(captions: VTTCaption[]): string {\n if (!Array.isArray(captions) || captions.length === 0) {\n return '<captions></captions>'\n }\n\n const lines: string[] = ['<captions>']\n\n captions.forEach((caption, index) => {\n const { text, _key, startTime, endTime, _type, ...otherProps } = caption\n\n // Build attributes string\n const attributes: string[] = [`start=\"${startTime}\"`, `end=\"${endTime}\"`]\n\n if (_key) {\n attributes.push(`key=\"${escapeXml(_key)}\"`)\n }\n\n if (_type) {\n attributes.push(`type=\"${escapeXml(_type)}\"`)\n }\n\n // Add index for matching during deserialization\n attributes.push(`index=\"${index}\"`)\n\n // Add any other properties as attributes (excluding objects)\n Object.entries(otherProps).forEach(([key, value]) => {\n if (!key.startsWith('_') && typeof value !== 'object') {\n attributes.push(`${key}=\"${escapeXml(String(value))}\"`)\n }\n })\n\n // Add the cue with its text content\n lines.push(` <cue ${attributes.join(' ')}>`)\n lines.push(` ${escapeXml(text ?? '')}`)\n lines.push(' </cue>')\n })\n\n lines.push('</captions>')\n return lines.join('\\n')\n}\n\n/**\n * Legacy function for full XML deserialization - used in tests\n */\nexport function deserializeVTTCaptions(\n serialized: string,\n originalCaptions: Array<Record<string, unknown>>,\n): Array<Record<string, unknown>> {\n // Create maps for looking up original captions\n const originalByKey = new Map<string, { caption: Record<string, unknown>; index: number }>()\n const originalByIndex = [...originalCaptions]\n\n originalCaptions.forEach((caption, index) => {\n if (caption._key && typeof caption._key === 'string') {\n originalByKey.set(caption._key, { caption, index })\n }\n })\n\n // Extract captions from XML\n const result: Array<Record<string, unknown>> = []\n const captionsRegex = /<captions>([\\s\\S]*?)<\\/captions>/\n const cueRegex = /<cue\\s+([^>]*)>([\\s\\S]*?)<\\/cue>/g\n const attrRegex = /([a-zA-Z0-9_-]+)=\"([^\"]*)\"/g\n\n const captionsMatch = serialized.match(captionsRegex)\n\n if (!captionsMatch) {\n // Return original if no match\n devLog.warn('Failed to parse serialized VTT captions', { serialized })\n return originalCaptions\n }\n\n const captionsContent = captionsMatch[1]\n let processedCount = 0\n\n // Use Array.from to iterate over regex matches without assignment in condition\n const cueMatches = Array.from(captionsContent.matchAll(cueRegex))\n\n for (const cueMatch of cueMatches) {\n const attributesStr = cueMatch[1]\n const text = unescapeXml(cueMatch[2].trim())\n const attributes: Record<string, string> = {}\n\n // Parse attributes using matchAll to avoid assignment in condition\n const attrMatches = Array.from(attributesStr.matchAll(new RegExp(attrRegex.source, 'g')))\n for (const attrMatch of attrMatches) {\n attributes[attrMatch[1]] = unescapeXml(attrMatch[2])\n }\n\n // Find the original caption (by key, index, or position)\n let original: Record<string, unknown> | null = null\n const index = Number.parseInt(attributes.index || '0')\n\n if (attributes.key && originalByKey.has(attributes.key)) {\n const originalData = originalByKey.get(attributes.key)\n if (originalData) {\n original = originalData.caption\n }\n } else if (index < originalByIndex.length) {\n original = originalByIndex[index]\n } else if (processedCount < originalByIndex.length) {\n original = originalByIndex[processedCount]\n }\n\n if (original) {\n // Merge translated text with preserved original metadata\n result.push({\n ...original,\n text,\n })\n } else {\n // Fall back to reconstructing original caption from metadata if not cached\n devLog.warn('Creating caption from attributes as original not found', {\n key: attributes.key,\n index,\n })\n\n result.push({\n _type: attributes.type || 'vtt.cue',\n _key: attributes.key || `caption-${processedCount}`,\n text,\n startTime: Number.parseFloat(attributes.start) || 0,\n endTime: Number.parseFloat(attributes.end) || 0,\n })\n }\n\n processedCount++\n }\n\n return result\n}\n","/**\n * ISO-639-2 language code utilities for converting locale codes to ISO language codes.\n * @public\n */\n\nexport const SUPPORTED_ISO_LANGUAGES = ['eng', 'spa', 'fra', 'ger', 'ita', 'por', 'rus', 'jpn', 'kor', 'chi'] as const\n\nexport type IsoLanguageCode = (typeof SUPPORTED_ISO_LANGUAGES)[number]\n\n// ISO-639-2 language codes\nexport const ISO_LANGUAGE_NAMES: Record<IsoLanguageCode, string> = {\n eng: 'English',\n spa: 'Spanish',\n fra: 'French',\n ger: 'German',\n ita: 'Italian',\n por: 'Portuguese',\n rus: 'Russian',\n jpn: 'Japanese',\n kor: 'Korean',\n chi: 'Chinese',\n}\n\n// Map from locale codes to ISO-639-2\nconst LOCALE_TO_ISO: Record<string, IsoLanguageCode> = {\n // English variants\n 'en-us': 'eng',\n 'en-US': 'eng',\n 'en-gb': 'eng',\n 'en-GB': 'eng',\n 'en-ca': 'eng',\n 'en-CA': 'eng',\n 'en-au': 'eng',\n 'en-AU': 'eng',\n\n // Spanish variants\n es: 'spa',\n 'es-es': 'spa',\n 'es-ES': 'spa',\n 'es-mx': 'spa',\n 'es-MX': 'spa',\n\n // French variants\n fr: 'fra',\n 'fr-fr': 'fra',\n 'fr-FR': 'fra',\n 'fr-ca': 'fra',\n 'fr-CA': 'fra',\n\n // German variants\n de: 'ger',\n 'de-de': 'ger',\n 'de-DE': 'ger',\n 'de-at': 'ger',\n 'de-AT': 'ger',\n\n // Italian variants\n it: 'ita',\n 'it-it': 'ita',\n 'it-IT': 'ita',\n\n // Portuguese variants\n pt: 'por',\n 'pt-br': 'por',\n 'pt-BR': 'por',\n 'pt-pt': 'por',\n 'pt-PT': 'por',\n\n // Russian variants\n ru: 'rus',\n 'ru-ru': 'rus',\n 'ru-RU': 'rus',\n\n // Japanese variants\n ja: 'jpn',\n 'ja-jp': 'jpn',\n 'ja-JP': 'jpn',\n\n // Korean variants\n ko: 'kor',\n 'ko-kr': 'kor',\n 'ko-KR': 'kor',\n\n // Chinese variants\n zh: 'chi',\n 'zh-cn': 'chi',\n 'zh-CN': 'chi',\n 'zh-tw': 'chi',\n 'zh-TW': 'chi',\n 'zh-hk': 'chi',\n 'zh-HK': 'chi',\n}\n\n/**\n * Converts a locale code (e.g., 'en-US') to its ISO-639-2 language code (e.g., 'eng')\n * @param code The locale code to convert\n * @returns The ISO-639-2 language code, or undefined if the locale is not supported\n * @public\n */\nexport const normalizeToIsoLanguageCode = (code: string): IsoLanguageCode | undefined => {\n const normalizedCode = code.toLowerCase()\n return LOCALE_TO_ISO[normalizedCode]\n}\n\n/**\n * Gets the display name for a locale code in English\n * @param code The locale code to get the display name for\n * @returns The display name in English, or the original code if not found\n * @public\n */\nexport const getIsoLanguageName = (code: string): string => {\n const normalizedCode = normalizeToIsoLanguageCode(code)\n return normalizedCode ? ISO_LANGUAGE_NAMES[normalizedCode] : code\n}\n","import type { FieldTransform } from '../../../../types'\nimport { devLog } from '../../../common/devLog'\nimport { normalizeToIsoLanguageCode } from '../../../common/isoLanguage'\n\n/**\n * Represents a Wistia captions object with SRT content and metadata.\n */\nexport interface WistiaCaptions {\n captionContent?: string\n captionFileName?: string\n isoLanguage?: string\n captionUploadStatus?: string\n captionUploadMessage?: string\n [key: string]: string | undefined\n}\n\n/**\n * Represents a parsed SRT cue with timing and text content.\n */\ninterface SRTCue {\n index: number\n startTime: string\n endTime: string\n text: string\n}\n\n/**\n * Wistia captions metadata without captionContent, used for preservation during translation.\n */\ninterface WistiaCaptionsMetadata {\n captionFileName?: string\n isoLanguage?: string\n captionUploadStatus?: string\n captionUploadMessage?: string\n _originalIndex: number\n [key: string]: string | number | undefined\n}\n\n/**\n * Special type to track transformed Wistia captions.\n * This combines metadata separation for token efficiency with compact text format for translation.\n */\ninterface TranslatableWistiaCaptions {\n _type: 'translatable.wistia.captions'\n // Compact text content for translation\n translationText: string\n // Metadata stored separately to avoid sending to translation model\n _originalMetadata: WistiaCaptionsMetadata\n // Store parsed SRT cues for reconstruction\n _srtCues: Array<{ index: number; startTime: string; endTime: string }>\n}\n\n/**\n * Transform for handling Wistia captions in documents using SRT format.\n * This stores metadata separately for token efficiency while formatting\n * text content in a compact indexed format for better translation results.\n *\n * @public\n */\nexport const wistiaCaptionTransform: FieldTransform<WistiaCaptions, TranslatableWistiaCaptions> = {\n matcher: {\n fieldName: 'wistiaCaptions',\n },\n\n toTranslatable: (wistiaCaptions, context) => {\n devLog.info('Wistia captions toTranslatable - input:', {\n fieldPath: context.fieldPath,\n hasCaptionContent: !!wistiaCaptions?.captionContent,\n captionContentLength: wistiaCaptions?.captionContent?.length ?? 0,\n isoLanguage: wistiaCaptions?.isoLanguage,\n })\n\n if (!wistiaCaptions || !wistiaCaptions.captionContent) {\n devLog.debug('Wistia caption field is missing or has no content, returning empty transform', {\n fieldPath: context.fieldPath,\n value: wistiaCaptions,\n })\n // Return empty translatable format for missing/invalid values\n return {\n _type: 'translatable.wistia.captions' as const,\n translationText: '',\n _originalMetadata: {\n captionFileName: wistiaCaptions?.captionFileName,\n isoLanguage: wistiaCaptions?.isoLanguage,\n captionUploadStatus: wistiaCaptions?.captionUploadStatus,\n captionUploadMessage: wistiaCaptions?.captionUploadMessage,\n _originalIndex: 0,\n },\n _srtCues: [],\n }\n }\n\n devLog.info('Applying Wistia caption transform', {\n fieldPath: context.fieldPath,\n captionContentPreview: wistiaCaptions.captionContent.substring(0, 200),\n })\n\n // Parse SRT content into structured cues\n const srtCues = parseSRTContent(wistiaCaptions.captionContent)\n\n devLog.debug('Parsed SRT cues:', {\n fieldPath: context.fieldPath,\n cueCount: srtCues.length,\n firstCue: srtCues.length > 0 ? srtCues[0] : null,\n })\n\n // Extract text for compact format and metadata for separate storage\n const translationText = serializeWistiaCaptionText(srtCues)\n const originalMetadata: WistiaCaptionsMetadata = {\n captionFileName: wistiaCaptions.captionFileName,\n isoLanguage: wistiaCaptions.isoLanguage,\n captionUploadStatus: wistiaCaptions.captionUploadStatus,\n captionUploadMessage: wistiaCaptions.captionUploadMessage,\n _originalIndex: 0,\n }\n\n // Store timing information for each cue\n const srtCuesMetadata = srtCues.map((cue) => ({\n index: cue.index,\n startTime: cue.startTime,\n endTime: cue.endTime,\n }))\n\n devLog.info('Wistia captions serialized:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n cueCount: srtCues.length,\n })\n\n // Return object with separated text and metadata\n const result: TranslatableWistiaCaptions = {\n _type: 'translatable.wistia.captions',\n translationText,\n _originalMetadata: originalMetadata,\n _srtCues: srtCuesMetadata,\n }\n\n return result\n },\n\n fromTranslatable: (translatedValue, originalValue, context) => {\n devLog.info('Wistia captions fromTranslatable - input:', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n isCorrectType: translatedValue._type === 'translatable.wistia.captions',\n hasText: !!translatedValue.translationText,\n hasMetadata: !!translatedValue._originalMetadata,\n hasCues: Array.isArray(translatedValue._srtCues),\n })\n\n // Check if the value is our transformed format\n if (\n !translatedValue ||\n translatedValue._type !== 'translatable.wistia.captions' ||\n !translatedValue._originalMetadata ||\n !Array.isArray(translatedValue._srtCues)\n ) {\n devLog.debug('Value is not a translatable Wistia caption object, returning original', {\n fieldPath: context.fieldPath,\n valueType: typeof translatedValue,\n translatedValue: translatedValue,\n })\n return originalValue || {}\n }\n\n devLog.info('Processing translated Wistia captions', {\n fieldPath: context.fieldPath,\n cueCount: translatedValue._srtCues.length,\n })\n\n // Get translated text and metadata\n const { translationText, _originalMetadata, _srtCues } = translatedValue\n\n devLog.info('Wistia captions translationText:', {\n fieldPath: context.fieldPath,\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 200) + (translationText.length > 200 ? '...' : ''),\n })\n\n // Reconstruct SRT format from translated text and original timing\n const reconstructedCaptionContent = deserializeWistiaCaptionText(translationText, _srtCues)\n\n // Get the document's language field to update isoLanguage\n const documentLanguage = getDocumentLanguage(context.document)\n let updatedIsoLanguage = _originalMetadata.isoLanguage\n\n if (documentLanguage) {\n const isoCode = normalizeToIsoLanguageCode(documentLanguage)\n if (isoCode) {\n updatedIsoLanguage = isoCode\n devLog.debug('Updated isoLanguage based on document language', {\n fieldPath: context.fieldPath,\n documentLanguage,\n isoCode,\n })\n } else {\n devLog.debug('Could not convert document language to ISO code, preserving original', {\n fieldPath: context.fieldPath,\n documentLanguage,\n originalIsoLanguage: _originalMetadata.isoLanguage,\n })\n }\n } else {\n devLog.debug('Document language field not found, preserving original isoLanguage', {\n fieldPath: context.fieldPath,\n originalIsoLanguage: _originalMetadata.isoLanguage,\n })\n }\n\n // Merge translated content with preserved metadata\n const result: WistiaCaptions = {\n captionContent: reconstructedCaptionContent,\n captionFileName: _originalMetadata.captionFileName,\n isoLanguage: updatedIsoLanguage,\n captionUploadStatus: _originalMetadata.captionUploadStatus,\n captionUploadMessage: _originalMetadata.captionUploadMessage,\n }\n\n devLog.info('Wistia captions final result:', {\n fieldPath: context.fieldPath,\n hasCaptionContent: !!result.captionContent,\n captionContentLength: result.captionContent?.length ?? 0,\n isoLanguage: result.isoLanguage,\n })\n\n return result\n },\n}\n\n/**\n * Parses SRT format content into structured cues.\n * SRT format:\n * ```\n * 1\n * 00:00:00,160 --> 00:00:03,920\n * Text line 1\n * Text line 2\n *\n * 2\n * 00:00:03,920 --> 00:00:07,080\n * More text\n * ```\n * @param content - SRT format string\n * @returns Array of parsed SRT cues\n */\nexport function parseSRTContent(content: string): SRTCue[] {\n devLog.debug('parseSRTContent - input:', {\n contentLength: content.length,\n contentPreview: content.substring(0, 100) + (content.length > 100 ? '...' : ''),\n })\n\n if (!content || content.trim() === '') {\n return []\n }\n\n // Handle escaped newlines - unescape \\n sequences to actual newlines\n // This handles cases where SRT content is stored with escaped newlines (e.g., from JSON)\n let normalizedContent = content\n if (content.includes('\\\\n') && !content.includes('\\n')) {\n // Only unescape if we see escaped newlines but no actual newlines\n normalizedContent = content.replace(/\\\\n/g, '\\n')\n devLog.debug('Unescaped newlines in SRT content', {\n originalLength: content.length,\n normalizedLength: normalizedContent.length,\n })\n }\n\n const cues: SRTCue[] = []\n // Split by double newlines to get individual cues\n const cueBlocks = normalizedContent.split(/\\n\\s*\\n/)\n\n for (const block of cueBlocks) {\n const lines = block.trim().split('\\n')\n if (lines.length < 3) {\n // Need at least: number, timestamp, and one text line\n continue\n }\n\n // First line is the cue number\n const cueNumber = Number.parseInt(lines[0].trim(), 10)\n if (Number.isNaN(cueNumber)) {\n devLog.warn('Invalid SRT cue number', { line: lines[0] })\n continue\n }\n\n // Second line is the timestamp\n const timestampLine = lines[1].trim()\n const timestampMatch = timestampLine.match(/^(\\d{2}:\\d{2}:\\d{2}[,.]\\d{3})\\s*-->\\s*(\\d{2}:\\d{2}:\\d{2}[,.]\\d{3})$/)\n if (!timestampMatch) {\n devLog.warn('Invalid SRT timestamp format', { line: timestampLine })\n continue\n }\n\n // Normalize timestamps (convert periods to commas for consistency)\n const startTime = timestampMatch[1].replace('.', ',')\n const endTime = timestampMatch[2].replace('.', ',')\n\n // Remaining lines are the text content\n const text = lines.slice(2).join('\\n').trim()\n\n if (text) {\n cues.push({\n index: cueNumber,\n startTime,\n endTime,\n text,\n })\n }\n }\n\n devLog.debug('parseSRTContent - output:', {\n cueCount: cues.length,\n firstCue: cues.length > 0 ? cues[0] : null,\n })\n\n return cues\n}\n\n/**\n * Serialize Wistia captions to compact text format for translation\n * @param cues - Array of parsed SRT cues\n * @returns Compact string with indexed text content\n */\nexport function serializeWistiaCaptionText(cues: SRTCue[]): string {\n devLog.debug('serializeWistiaCaptionText - input:', {\n cuesCount: cues.length,\n firstCue: cues.length > 0 ? cues[0] : null,\n })\n\n if (!Array.isArray(cues) || cues.length === 0) {\n return ''\n }\n\n // Use a compact format: [index] text\n // Note: We use the cue's index (from SRT numbering) for matching\n // Escape newlines in text to prevent them from breaking the line-by-line parsing\n const lines: string[] = cues.map((cue) => {\n devLog.debug(`Processing cue ${cue.index}:`, {\n text: cue.text,\n hasText: typeof cue.text === 'string',\n })\n // Replace actual newlines with escaped newlines to preserve multi-line cues\n const escapedText = cue.text.replace(/\\n/g, '\\\\n')\n return `[${cue.index}] ${escapedText}`\n })\n\n const result = lines.join('\\n')\n\n devLog.debug('serializeWistiaCaptionText - output:', {\n resultLength: result.length,\n resultPreview: result.substring(0, 100) + (result.length > 100 ? '...' : ''),\n })\n\n return result\n}\n\n/**\n * Deserialize compact text format and merge with preserved timing metadata\n * @param translationText - Compact string with translated text\n * @param srtCuesMetadata - Array of original SRT cue timing metadata\n * @returns Reconstructed SRT format string\n */\nexport function deserializeWistiaCaptionText(\n translationText: string,\n srtCuesMetadata: Array<{ index: number; startTime: string; endTime: string }>,\n): string {\n devLog.debug('deserializeWistiaCaptionText - input:', {\n translationTextLength: translationText.length,\n translationTextPreview: translationText.substring(0, 100) + (translationText.length > 100 ? '...' : ''),\n metadataCount: srtCuesMetadata.length,\n metadataExample: srtCuesMetadata.length > 0 ? srtCuesMetadata[0] : null,\n })\n\n if (!translationText || translationText.trim() === '') {\n return ''\n }\n\n // Parse compact format: [index] text\n // Handle multi-line cues by restoring escaped newlines and continuation lines\n const result: string[] = []\n const lines = translationText.split('\\n')\n const lineRegex = /^\\[(\\d+)\\]\\s*(.*)$/\n\n // Create a map of metadata by index for quick lookup\n const metadataByIndex = new Map<number, { startTime: string; endTime: string }>()\n for (const metadata of srtCuesMetadata) {\n metadataByIndex.set(metadata.index, { startTime: metadata.startTime, endTime: metadata.endTime })\n }\n\n let currentIndex: number | null = null\n let currentTextParts: string[] = []\n\n for (const line of lines) {\n const match = line.match(lineRegex)\n\n if (match) {\n // Found a new caption - process the previous one if it exists\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = metadataByIndex.get(currentIndex)\n\n if (metadata) {\n result.push(`${currentIndex}`)\n result.push(`${metadata.startTime} --> ${metadata.endTime}`)\n result.push(text)\n result.push('') // Empty line between cues\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n }\n }\n\n // Start new caption\n currentIndex = Number.parseInt(match[1], 10)\n currentTextParts = [match[2]]\n } else if (currentIndex !== null) {\n // Continuation line for current caption (handles cases where translation service converted \\n to actual newlines)\n currentTextParts.push(line)\n } else {\n // Line doesn't match format and we're not in a caption - skip it\n devLog.warn('Failed to parse caption line', { line })\n }\n }\n\n // Process the last caption\n if (currentIndex !== null) {\n const text = currentTextParts.join('\\n').replace(/\\\\n/g, '\\n')\n const metadata = metadataByIndex.get(currentIndex)\n\n if (metadata) {\n result.push(`${currentIndex}`)\n result.push(`${metadata.startTime} --> ${metadata.endTime}`)\n result.push(text)\n result.push('') // Empty line between cues\n } else {\n devLog.warn('Missing metadata for caption', { index: currentIndex })\n }\n }\n\n // Remove trailing empty line\n if (result.length > 0 && result[result.length - 1] === '') {\n result.pop()\n }\n\n const reconstructed = result.join('\\n')\n\n devLog.debug('deserializeWistiaCaptionText - output:', {\n resultLength: reconstructed.length,\n resultPreview: reconstructed.substring(0, 200) + (reconstructed.length > 200 ? '...' : ''),\n })\n\n return reconstructed\n}\n\n/**\n * Attempts to find the language field in a document.\n * Tries common field names: 'language', 'lang', 'locale'\n * @param document - The document to search\n * @returns The language value if found, undefined otherwise\n */\nfunction getDocumentLanguage(document: Record<string, unknown>): string | undefined {\n if (!document || typeof document !== 'object') {\n return undefined\n }\n\n // Try common language field names\n const languageFieldNames = ['language', 'lang', 'locale']\n for (const fieldName of languageFieldNames) {\n if (fieldName in document && typeof document[fieldName] === 'string') {\n return document[fieldName]\n }\n }\n\n return undefined\n}\n","import { definePlugin } from 'sanity'\nimport { getTranslateAction } from './components/actions'\nimport { translationGlossaryTerm } from './schemas/translationGlossaryTerm.schema'\nimport { translationHistory } from './schemas/translationHistory.schema'\nimport { translationSettings } from './schemas/translationSettings.schema'\nimport type { PluginConfig } from './types'\nimport { devLog } from './utils/common/devLog'\nimport { globalTransformRegistry } from './utils/transforms/transformRegistry'\n\nexport type {\n AnyFieldTransform,\n FieldMatcher,\n FieldTransform,\n InferOriginalType,\n InferTranslatableType,\n PluginConfig,\n TransformContext,\n} from './types'\n\n/**\n * Field exclusion utilities for preventing specific fields from being translated\n *\n * @public\n */\nexport {\n COMMON_ENUM_FIELDS,\n type FilteredDocument,\n filterExcludedFields,\n PREDEFINED_EXCLUSIONS,\n restoreExcludedFields,\n} from './utils/document/excludeFields'\n\n/**\n * AI translation plugin for Sanity Studio\n * See the PluginConfig export for configuration options\n *\n * @public\n */\nexport const aiTranslationPlugin = definePlugin<PluginConfig>((config) => {\n // Register custom transforms provided by plugin consumer\n if (config.fieldTransforms?.length) {\n devLog.info(`Registering ${config.fieldTransforms.length} custom field transforms`)\n config.fieldTransforms.forEach((transform) => {\n globalTransformRegistry.register(transform)\n })\n }\n\n return {\n name: 'sanity-plugin-ai-translation',\n document: {\n actions: [getTranslateAction(config)],\n },\n schema: {\n types: [translationSettings, translationGlossaryTerm],\n },\n }\n})\n\n/**\n * Schema for translation history\n *\n * @public\n */\nexport const translationHistorySchema = translationHistory\n\n/**\n * Export types for custom transform development\n *\n * @public\n */\n// Types are exported at the top of the file with additional helper types\n\n/**\n * Built-in field transform for Portable Text (rich text) content\n * Converts complex rich text structures to XML-like format for translation\n *\n * @public\n */\nexport { richTextTransform } from './utils/transforms/builtIn/richTextTransform'\n\n/**\n * Built-in field transform for VTT video captions\n * Converts VTT caption arrays to a compact format for translation\n * while preserving timing metadata\n *\n * @public\n */\nexport { vttCaptionTransform } from './utils/transforms/builtIn/vttCaptionTransform'\n\n/**\n * Built-in field transform for Wistia captions\n * Converts Wistia caption objects with SRT format content to a compact format for translation\n * while preserving timing metadata and updating isoLanguage based on document language\n *\n * @public\n */\nexport { wistiaCaptionTransform } from './utils/transforms/builtIn/wistiaCaptionTransform'\n"],"names":["translationSettings","arrayMatch","originalStructure","text","buildTranslationPrompt","result"],"mappings":"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"}