@braze/sanity-plugin-ai-translation 0.2.69 → 0.2.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1039,6 +1039,57 @@ const fetchTranslationPrompts = async ({
1039
1039
  payload
1040
1040
  }), mainSourceDocumentId && updateActiveTranslationJob(mainSourceDocumentId, payload);
1041
1041
  }
1042
+ }, emitTelemetryEvent = (config, event) => {
1043
+ if (config?.onEvent)
1044
+ try {
1045
+ config.onEvent(event), devLog.debug("Telemetry event emitted", { eventName: event.name });
1046
+ } catch (error) {
1047
+ devLog.warn("Failed to emit telemetry event", {
1048
+ eventName: event.name,
1049
+ error: error instanceof Error ? error.message : String(error)
1050
+ });
1051
+ }
1052
+ }, emitTelemetryError = (config, bundle) => {
1053
+ if (config?.onError)
1054
+ try {
1055
+ config.onError(bundle), devLog.debug("Telemetry error emitted", {
1056
+ documentId: bundle.documentId,
1057
+ errorType: bundle.error.type
1058
+ });
1059
+ } catch (error) {
1060
+ devLog.warn("Failed to emit telemetry error", {
1061
+ documentId: bundle.documentId,
1062
+ error: error instanceof Error ? error.message : String(error)
1063
+ });
1064
+ }
1065
+ }, buildErrorBundle = (params) => {
1066
+ const durationMs = Date.now() - params.startTime;
1067
+ return {
1068
+ documentId: params.documentId,
1069
+ documentType: params.documentType,
1070
+ targetLanguage: params.targetLanguage,
1071
+ sourceLanguage: params.sourceLanguage,
1072
+ translationMode: params.translationMode,
1073
+ error: {
1074
+ message: params.error.message,
1075
+ stack: params.error.stack,
1076
+ type: params.errorType
1077
+ },
1078
+ debug: {
1079
+ originalDocument: params.originalDocument,
1080
+ serializedDocument: params.serializedDocument,
1081
+ openaiResponse: params.openaiResponse,
1082
+ translationPrompts: params.translationPrompts,
1083
+ validationErrors: params.validationErrors
1084
+ },
1085
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1086
+ durationMs
1087
+ };
1088
+ }, createTimestamp = () => (/* @__PURE__ */ new Date()).toISOString(), classifyError = (error, context) => {
1089
+ if (context)
1090
+ return context;
1091
+ const message = error.message.toLowerCase();
1092
+ return message.includes("network") || message.includes("fetch") || message.includes("timeout") || message.includes("econnrefused") || message.includes("enotfound") ? "network" : message.includes("openai") || message.includes("gpt") || message.includes("api error") ? "openai_api" : message.includes("validation") || message.includes("structure") || message.includes("invalid") ? "validation" : message.includes("deserialize") || message.includes("parse") ? "deserialization" : message.includes("serialize") || message.includes("xml") ? "serialization" : message.includes("agent") || message.includes("sanity") ? "sanity_agent" : "unknown";
1042
1093
  }, updateTranslationMetadata = async ({
1043
1094
  client,
1044
1095
  targetLanguage,
@@ -2231,16 +2282,18 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2231
2282
  translationPrompts,
2232
2283
  targetLanguage,
2233
2284
  openaiConfig,
2234
- previousError,
2235
- errorContext
2285
+ previousError: _previousError,
2286
+ errorContext,
2287
+ telemetryConfig
2236
2288
  }) => {
2237
- const sourceLanguage = sourceDocument[languageFieldName], logContext = {
2289
+ const startTime = Date.now(), sourceLanguage = sourceDocument[languageFieldName], logContext = {
2238
2290
  sourceId: sourceDocument._id,
2239
2291
  sourceLanguage
2240
2292
  };
2293
+ let serializedDocument, openaiResponseContent;
2241
2294
  try {
2242
2295
  const sourceText = serializeDocument(sourceDocument);
2243
- devLog.info("Serialized document for OpenAI", {
2296
+ serializedDocument = sourceText, devLog.info("Serialized document for OpenAI", {
2244
2297
  ...logContext,
2245
2298
  characterCount: sourceText.length,
2246
2299
  estimatedTokens: Math.ceil(sourceText.length / 4),
@@ -2263,7 +2316,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2263
2316
  model: openaiConfig.model,
2264
2317
  reasoningEffort: openaiConfig.reasoningEffort
2265
2318
  });
2266
- devLog.info("OpenAI translation response received", {
2319
+ openaiResponseContent = response.content, devLog.info("OpenAI translation response received", {
2267
2320
  ...logContext,
2268
2321
  tokensUsed: response.tokensUsed,
2269
2322
  cost: response.cost,
@@ -2301,9 +2354,35 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2301
2354
  })), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai", {
2302
2355
  tokensUsed: response.tokensUsed.total,
2303
2356
  estimatedCost: response.cost
2304
- }), result;
2357
+ }), {
2358
+ document: result,
2359
+ tokensUsed: response.tokensUsed.total,
2360
+ cost: response.cost
2361
+ };
2305
2362
  } catch (error) {
2306
- throw devLog.error("OpenAI translation failed", error instanceof Error ? error : new Error(String(error)), logContext), error;
2363
+ const err = error instanceof Error ? error : new Error(String(error));
2364
+ throw devLog.error("OpenAI translation failed", err, logContext), emitTelemetryError(
2365
+ telemetryConfig,
2366
+ buildErrorBundle({
2367
+ documentId: sourceDocument._id,
2368
+ documentType: sourceDocument._type,
2369
+ targetLanguage,
2370
+ sourceLanguage,
2371
+ translationMode: "openai-only",
2372
+ error: err,
2373
+ errorType: classifyError(err, "openai_api"),
2374
+ startTime,
2375
+ originalDocument: sourceDocument,
2376
+ serializedDocument,
2377
+ openaiResponse: openaiResponseContent,
2378
+ translationPrompts: {
2379
+ globalPrompt: translationPrompts.globalPrompt,
2380
+ localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,
2381
+ protectedPhrases: translationPrompts.protectedPhrases,
2382
+ glossaryTerms: translationPrompts.localeInstructions[targetLanguage]?.glossaryTerms ? [translationPrompts.localeInstructions[targetLanguage].glossaryTerms] : void 0
2383
+ }
2384
+ })
2385
+ ), error;
2307
2386
  }
2308
2387
  }, buildTranslationPrompt$1 = ({
2309
2388
  sourceText,
@@ -2539,9 +2618,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2539
2618
  releaseId,
2540
2619
  excludeFields = [],
2541
2620
  replaceExisting = !1,
2542
- mainSourceDocumentId
2621
+ mainSourceDocumentId,
2622
+ telemetryConfig
2543
2623
  }) => {
2544
- const safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName);
2624
+ const startTime = Date.now(), sourceLanguage = sourceDocument[languageFieldName], safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName);
2545
2625
  excludeFields.length !== safeExcludeFields.length && devLog.warn("Language field was in excludeFields - removing it to prevent translation errors", {
2546
2626
  documentId: sourceDocument._id,
2547
2627
  languageFieldName,
@@ -2580,29 +2660,55 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2580
2660
  documentId: sourceDocument._id,
2581
2661
  documentType: sourceDocument._type
2582
2662
  }), documentToTranslate = filteredDocument;
2583
- let translatedDoc, previousError = null;
2663
+ let translatedDoc, translationMetrics = {}, previousError = null;
2584
2664
  const performTranslation = async (errorContext) => {
2585
2665
  switch (translationMode) {
2586
- case "sanity-only":
2587
- return setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating", void 0, mainSourceDocumentId), await getSanityAgentTranslation({
2588
- sourceDocument: documentToTranslate,
2589
- targetLanguage,
2590
- languageFieldName,
2591
- schemaId,
2592
- client,
2593
- translationPrompts
2594
- });
2666
+ case "sanity-only": {
2667
+ setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating", void 0, mainSourceDocumentId);
2668
+ try {
2669
+ return { document: await getSanityAgentTranslation({
2670
+ sourceDocument: documentToTranslate,
2671
+ targetLanguage,
2672
+ languageFieldName,
2673
+ schemaId,
2674
+ client,
2675
+ translationPrompts
2676
+ }) };
2677
+ } catch (error) {
2678
+ const err = error instanceof Error ? error : new Error(String(error));
2679
+ throw emitTelemetryError(
2680
+ telemetryConfig,
2681
+ buildErrorBundle({
2682
+ documentId: sourceDocument._id,
2683
+ documentType: sourceDocument._type,
2684
+ targetLanguage,
2685
+ sourceLanguage,
2686
+ translationMode: "sanity-only",
2687
+ error: err,
2688
+ errorType: classifyError(err, "sanity_agent"),
2689
+ startTime,
2690
+ originalDocument: sourceDocument,
2691
+ translationPrompts: {
2692
+ globalPrompt: translationPrompts.globalPrompt,
2693
+ localePrompt: translationPrompts.localeInstructions[targetLanguage]?.prompt,
2694
+ protectedPhrases: translationPrompts.protectedPhrases
2695
+ }
2696
+ })
2697
+ ), error;
2698
+ }
2699
+ }
2595
2700
  case "openai-only": {
2596
2701
  if (!openaiConfig?.enabled || !openaiConfig?.apiKey)
2597
2702
  throw new Error("OpenAI is not configured but openai-only mode was selected");
2598
- return setJobStatus(
2703
+ setJobStatus(
2599
2704
  dispatch,
2600
2705
  sourceDocument._id,
2601
2706
  targetLanguage,
2602
2707
  "translating_with_openai",
2603
2708
  void 0,
2604
2709
  mainSourceDocumentId
2605
- ), await getOpenAITranslation({
2710
+ );
2711
+ const result = await getOpenAITranslation({
2606
2712
  dispatch,
2607
2713
  sourceDocument: documentToTranslate,
2608
2714
  targetLanguage,
@@ -2610,14 +2716,23 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2610
2716
  translationPrompts,
2611
2717
  openaiConfig,
2612
2718
  previousError: errorContext && previousError ? previousError : void 0,
2613
- errorContext
2719
+ errorContext,
2720
+ telemetryConfig
2614
2721
  });
2722
+ return result ? {
2723
+ document: result.document,
2724
+ tokensUsed: result.tokensUsed,
2725
+ cost: result.cost
2726
+ } : { document: null };
2615
2727
  }
2616
2728
  default:
2617
2729
  throw new Error(`Invalid translation mode: ${translationMode}`);
2618
2730
  }
2619
- };
2620
- if (translatedDoc = await performTranslation(), !translatedDoc)
2731
+ }, translationResult = await performTranslation();
2732
+ if (translatedDoc = translationResult.document, translationMetrics = {
2733
+ tokensUsed: translationResult.tokensUsed,
2734
+ cost: translationResult.cost
2735
+ }, !translatedDoc)
2621
2736
  return null;
2622
2737
  let transformedResult;
2623
2738
  Object.keys(originalStructure).length > 0 ? (devLog.debug("Restoring field transforms after translation", {
@@ -2680,9 +2795,13 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2680
2795
  targetLanguage,
2681
2796
  error: error instanceof Error ? error.message : String(error)
2682
2797
  }), previousError = error instanceof Error ? error : new Error(String(error));
2683
- const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`;
2684
- if (translatedDoc = await performTranslation(errorContext), !translatedDoc)
2798
+ const errorContext = `Previous translation attempt failed with Sanity validation error: ${previousError.message}. Please ensure the translated document structure exactly matches the source document structure.`, retryResult = await performTranslation(errorContext);
2799
+ if (translatedDoc = retryResult.document, !translatedDoc)
2685
2800
  throw new Error("Translation retry failed: no document returned");
2801
+ translationMetrics = {
2802
+ tokensUsed: retryResult.tokensUsed,
2803
+ cost: retryResult.cost
2804
+ };
2686
2805
  let retryTransformedResult;
2687
2806
  Object.keys(originalStructure).length > 0 ? retryTransformedResult = restoreTransforms({
2688
2807
  translatedDocument: translatedDoc,
@@ -2713,11 +2832,19 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2713
2832
  isRetry: !0
2714
2833
  }
2715
2834
  }
2716
- ), retryNewDoc;
2835
+ ), {
2836
+ document: retryNewDoc,
2837
+ tokensUsed: translationMetrics.tokensUsed,
2838
+ cost: translationMetrics.cost
2839
+ };
2717
2840
  }
2718
2841
  throw error;
2719
2842
  }
2720
- return newDoc;
2843
+ return {
2844
+ document: newDoc,
2845
+ tokensUsed: translationMetrics.tokensUsed,
2846
+ cost: translationMetrics.cost
2847
+ };
2721
2848
  }, translateDocument = async ({
2722
2849
  dispatch,
2723
2850
  client,
@@ -2732,9 +2859,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2732
2859
  releaseId,
2733
2860
  excludeFields,
2734
2861
  replaceExisting,
2735
- mainSourceDocumentId
2862
+ mainSourceDocumentId,
2863
+ telemetryConfig
2736
2864
  }) => {
2737
- const logContext = {
2865
+ const jobStartTime = Date.now(), logContext = {
2738
2866
  documentId: sourceDocument._id,
2739
2867
  targetLanguage,
2740
2868
  schemaId,
@@ -2746,7 +2874,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2746
2874
  client,
2747
2875
  sourceDocument,
2748
2876
  sourceLanguage
2749
- }), existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage), newDocument = await handleTranslation({
2877
+ }), existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage), translationResult = await handleTranslation({
2750
2878
  dispatch,
2751
2879
  client,
2752
2880
  schemaId,
@@ -2760,10 +2888,12 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2760
2888
  releaseId,
2761
2889
  excludeFields,
2762
2890
  replaceExisting,
2763
- mainSourceDocumentId
2764
- });
2891
+ mainSourceDocumentId,
2892
+ telemetryConfig
2893
+ }), newDocument = translationResult?.document;
2765
2894
  if (!newDocument) {
2766
- devLog.error("Translation failed: no document returned", new Error("Translation returned null"), logContext), setJobStatus(
2895
+ const failureError = new Error("Translation returned null");
2896
+ devLog.error("Translation failed: no document returned", failureError, logContext), setJobStatus(
2767
2897
  dispatch,
2768
2898
  sourceDocument._id,
2769
2899
  targetLanguage,
@@ -2772,7 +2902,20 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2772
2902
  errorMessage: "Translation failed"
2773
2903
  },
2774
2904
  mainSourceDocumentId
2775
- ), devLog.timeEnd("translateDocument"), devLog.groupEnd();
2905
+ ), emitTelemetryEvent(telemetryConfig, {
2906
+ name: "translation.job_failed",
2907
+ timestamp: createTimestamp(),
2908
+ data: {
2909
+ documentId: sourceDocument._id,
2910
+ documentType: sourceDocument._type,
2911
+ sourceLanguage,
2912
+ targetLanguage,
2913
+ translationMode,
2914
+ durationMs: Date.now() - jobStartTime,
2915
+ errorType: classifyError(failureError),
2916
+ errorMessage: "Translation failed"
2917
+ }
2918
+ }), devLog.timeEnd("translateDocument"), devLog.groupEnd();
2776
2919
  return;
2777
2920
  }
2778
2921
  return setJobStatus(
@@ -2798,16 +2941,31 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2798
2941
  targetLanguage,
2799
2942
  "completed",
2800
2943
  {
2801
- targetId: newDocument._id
2944
+ targetId: newDocument._id,
2945
+ tokensUsed: translationResult?.tokensUsed,
2946
+ estimatedCost: translationResult?.cost
2802
2947
  },
2803
2948
  mainSourceDocumentId
2804
2949
  ), devLog.info("Translation completed successfully", {
2805
2950
  ...logContext,
2806
2951
  newDocumentId: newDocument._id
2952
+ }), emitTelemetryEvent(telemetryConfig, {
2953
+ name: "translation.job_completed",
2954
+ timestamp: createTimestamp(),
2955
+ data: {
2956
+ documentId: sourceDocument._id,
2957
+ documentType: sourceDocument._type,
2958
+ sourceLanguage,
2959
+ targetLanguage,
2960
+ translationMode,
2961
+ durationMs: Date.now() - jobStartTime,
2962
+ tokensUsed: translationResult?.tokensUsed,
2963
+ estimatedCost: translationResult?.cost
2964
+ }
2807
2965
  }), devLog.timeEnd("translateDocument"), devLog.groupEnd(), newDocument._id;
2808
2966
  } catch (error) {
2809
- const errorMessage = error instanceof Error ? error.message : String(error);
2810
- devLog.error("Translation failed", error instanceof Error ? error : new Error(String(error)), logContext), setJobStatus(
2967
+ const errorMessage = error instanceof Error ? error.message : String(error), errorObj = error instanceof Error ? error : new Error(String(error));
2968
+ devLog.error("Translation failed", errorObj, logContext), setJobStatus(
2811
2969
  dispatch,
2812
2970
  sourceDocument._id,
2813
2971
  targetLanguage,
@@ -2816,7 +2974,20 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2816
2974
  errorMessage
2817
2975
  },
2818
2976
  mainSourceDocumentId
2819
- ), devLog.timeEnd("translateDocument"), devLog.groupEnd();
2977
+ ), emitTelemetryEvent(telemetryConfig, {
2978
+ name: "translation.job_failed",
2979
+ timestamp: createTimestamp(),
2980
+ data: {
2981
+ documentId: sourceDocument._id,
2982
+ documentType: sourceDocument._type,
2983
+ sourceLanguage,
2984
+ targetLanguage,
2985
+ translationMode,
2986
+ durationMs: Date.now() - jobStartTime,
2987
+ errorType: classifyError(errorObj),
2988
+ errorMessage
2989
+ }
2990
+ }), devLog.timeEnd("translateDocument"), devLog.groupEnd();
2820
2991
  return;
2821
2992
  }
2822
2993
  }, translationSemaphore = new Semaphore(2), translateDocumentSet = async ({
@@ -2831,9 +3002,10 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2831
3002
  translationMode,
2832
3003
  openaiConfig,
2833
3004
  excludeFields,
2834
- releaseConfig
3005
+ releaseConfig,
3006
+ telemetryConfig
2835
3007
  }) => {
2836
- const logContext = {
3008
+ const batchStartTime = Date.now(), logContext = {
2837
3009
  documentId: sourceDocument._id,
2838
3010
  documentRev: sourceDocument._rev,
2839
3011
  targetLanguages,
@@ -2847,7 +3019,19 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2847
3019
  const sourceLanguage = sourceDocument[languageFieldName];
2848
3020
  if (!sourceLanguage)
2849
3021
  throw devLog.error("Source language not found"), new Error("Source language not found");
2850
- if (maxConcurrentTranslationJobs && translationSemaphore.updatePermits(maxConcurrentTranslationJobs), dispatch({
3022
+ if (emitTelemetryEvent(telemetryConfig, {
3023
+ name: "translation.batch_started",
3024
+ timestamp: createTimestamp(),
3025
+ data: {
3026
+ documentId: sourceDocument._id,
3027
+ documentType: sourceDocument._type,
3028
+ sourceLanguage,
3029
+ targetLanguages,
3030
+ translationMode,
3031
+ referenceCount: references.length,
3032
+ releaseMode: releaseConfig.mode
3033
+ }
3034
+ }), maxConcurrentTranslationJobs && translationSemaphore.updatePermits(maxConcurrentTranslationJobs), dispatch({
2851
3035
  type: "SET_OVERALL_STATUS",
2852
3036
  payload: "translating"
2853
3037
  }), releaseConfig.mode === "existing" && releaseConfig.existingReleaseId)
@@ -2940,7 +3124,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2940
3124
  excludeFields,
2941
3125
  replaceExisting: releaseConfig.replaceExisting,
2942
3126
  // Pass main source document ID for active translations tracking
2943
- mainSourceDocumentId: sourceDocument._id
3127
+ mainSourceDocumentId: sourceDocument._id,
3128
+ telemetryConfig
2944
3129
  });
2945
3130
  versionedTargetDocumentId && versionedTargetDocumentIds.push(versionedTargetDocumentId);
2946
3131
  }
@@ -2965,6 +3150,20 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2965
3150
  failed,
2966
3151
  skipped: skippedCount,
2967
3152
  targetDocuments: versionedTargetDocumentIds.length
3153
+ }), emitTelemetryEvent(telemetryConfig, {
3154
+ name: "translation.batch_completed",
3155
+ timestamp: createTimestamp(),
3156
+ data: {
3157
+ documentId: sourceDocument._id,
3158
+ documentType: sourceDocument._type,
3159
+ sourceLanguage,
3160
+ targetLanguages,
3161
+ translationMode,
3162
+ succeeded,
3163
+ failed,
3164
+ skipped: skippedCount,
3165
+ durationMs: Date.now() - batchStartTime
3166
+ }
2968
3167
  }), dispatch({
2969
3168
  type: "SET_OVERALL_STATUS",
2970
3169
  payload: "linking_references"
@@ -3110,7 +3309,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3110
3309
  // Use the override release ID if provided (from conflict context),
3111
3310
  // otherwise use the current releaseConfig value
3112
3311
  existingReleaseId: overrideReleaseId ?? releaseConfig.existingReleaseId
3113
- }
3312
+ },
3313
+ telemetryConfig: config.telemetry
3114
3314
  });
3115
3315
  },
3116
3316
  [
@@ -3124,6 +3324,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3124
3324
  config.maxReferenceDepth,
3125
3325
  config.openai,
3126
3326
  config.excludeFields,
3327
+ config.telemetry,
3127
3328
  filteredReferences,
3128
3329
  translationMode,
3129
3330
  releaseConfig
@@ -3343,7 +3544,9 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3343
3544
  }), error;
3344
3545
  }
3345
3546
  }, ReconcileReferencesButton = ({ sourceLanguage }) => {
3346
- const { dispatch, releaseConfig, overallStatus, reconcilingStatus, reconciliationProgress, config } = useTranslationContext(), client = useClient({ apiVersion: "vX" }), [documentCount, setDocumentCount] = useState(null), [isLoadingCount, setIsLoadingCount] = useState(!1), releaseId = releaseConfig.existingReleaseId, languageFieldName = config?.languageFieldName ?? "language";
3547
+ const { releaseConfig, overallStatus, config } = useTranslationContext(), client = useClient({ apiVersion: "vX" }), [documentCount, setDocumentCount] = useState(null), [isLoadingCount, setIsLoadingCount] = useState(!1), [reconcilingStatus, setReconcilingStatus] = useState("idle"), [reconciliationProgress, setReconciliationProgress] = useState(
3548
+ void 0
3549
+ ), releaseId = releaseConfig.existingReleaseId, languageFieldName = config?.languageFieldName ?? "language";
3347
3550
  useEffect(() => {
3348
3551
  if (!releaseId || releaseConfig.mode !== "existing") {
3349
3552
  setDocumentCount(null);
@@ -3378,7 +3581,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3378
3581
  releaseId,
3379
3582
  sourceLanguage,
3380
3583
  component: "ReconcileReferencesButton"
3381
- }), dispatch({ type: "SET_RECONCILING_STATUS", payload: "reconciling" });
3584
+ }), setReconcilingStatus("reconciling");
3382
3585
  try {
3383
3586
  const result = await reconcileReleaseReferences({
3384
3587
  client,
@@ -3386,27 +3589,24 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3386
3589
  sourceLanguage,
3387
3590
  languageFieldName,
3388
3591
  onProgress: (current, total) => {
3389
- dispatch({
3390
- type: "SET_RECONCILIATION_PROGRESS",
3391
- payload: { current, total }
3392
- });
3592
+ setReconciliationProgress({ current, total });
3393
3593
  }
3394
3594
  });
3395
3595
  devLog.info("Reference reconciliation completed", {
3396
3596
  releaseId,
3397
3597
  ...result,
3398
3598
  component: "ReconcileReferencesButton"
3399
- }), dispatch({ type: "SET_RECONCILING_STATUS", payload: "completed" }), setTimeout(() => {
3400
- dispatch({ type: "SET_RECONCILING_STATUS", payload: "idle" });
3599
+ }), setReconcilingStatus("completed"), setTimeout(() => {
3600
+ setReconcilingStatus("idle"), setReconciliationProgress(void 0);
3401
3601
  }, 2e3);
3402
3602
  } catch (error) {
3403
3603
  devLog.error("Reference reconciliation failed", error instanceof Error ? error : new Error(String(error)), {
3404
3604
  releaseId,
3405
3605
  component: "ReconcileReferencesButton"
3406
- }), dispatch({ type: "SET_RECONCILING_STATUS", payload: "error" });
3606
+ }), setReconcilingStatus("error"), setReconciliationProgress(void 0);
3407
3607
  }
3408
3608
  }
3409
- }, [releaseId, sourceLanguage, languageFieldName, client, dispatch]);
3609
+ }, [releaseId, sourceLanguage, languageFieldName, client]);
3410
3610
  if (releaseConfig.mode !== "existing" || !releaseId)
3411
3611
  return null;
3412
3612
  const isTranslating = overallStatus === "translating" || overallStatus === "linking_references", isReconciling = reconcilingStatus === "reconciling";
@@ -3583,7 +3783,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3583
3783
  }
3584
3784
  )
3585
3785
  ] }),
3586
- releaseConfig.existingReleaseId && sourceLanguage && showReconcileUI && /* @__PURE__ */ jsx(ReconcileReferencesButton, { sourceLanguage })
3786
+ isSelected && sourceLanguage && showReconcileUI && /* @__PURE__ */ jsx(ReconcileReferencesButton, { sourceLanguage })
3587
3787
  ] })
3588
3788
  },
3589
3789
  release._id