@braze/sanity-plugin-ai-translation 0.2.33 → 0.2.37

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.d.ts CHANGED
@@ -38,6 +38,30 @@ export declare const aiTranslationPlugin: Plugin_2<PluginConfig>;
38
38
  */
39
39
  export declare type AnyFieldTransform = FieldTransform<any, any>;
40
40
 
41
+ /**
42
+ * Common enum field names that are typically excluded from translation
43
+ * @internal
44
+ */
45
+ export declare const COMMON_ENUM_FIELDS: readonly [
46
+ "status",
47
+ "state",
48
+ "type",
49
+ "category",
50
+ "priority",
51
+ "visibility",
52
+ "role",
53
+ "permission",
54
+ "level",
55
+ "mode",
56
+ "format",
57
+ "variant",
58
+ "size",
59
+ "theme",
60
+ "kind",
61
+ "phase",
62
+ "stage",
63
+ ];
64
+
41
65
  /**
42
66
  * Criteria for matching fields that should be transformed.
43
67
  * @public
@@ -169,6 +193,36 @@ export declare interface FieldTransform<
169
193
  ) => TOriginal;
170
194
  }
171
195
 
196
+ /**
197
+ * Result of filtering excluded fields from a document
198
+ * @internal
199
+ */
200
+ export declare interface FilteredDocument<T = any> {
201
+ /** Document with excluded fields removed */
202
+ filteredDocument: T;
203
+ /** Excluded field values that were removed */
204
+ excludedValues: Record<string, any>;
205
+ }
206
+
207
+ /**
208
+ * Recursively filters out excluded fields from a document
209
+ * @param document - The document to filter
210
+ * @param excludeFields - Array of field patterns to exclude. Supports:
211
+ * - Field names: 'status' (matches any field named 'status' anywhere)
212
+ * - Exact paths: 'meta.category' (matches only 'category' under 'meta')
213
+ * - Array paths: 'body[0].imageType' (matches 'imageType' in first array item)
214
+ * - Wildcard arrays: 'body[*].imageType' (matches 'imageType' in any array item)
215
+ * - Hierarchical: '**.status' (explicitly matches 'status' anywhere - same as just 'status')
216
+ * @param currentPath - Current path in the document (used internally)
217
+ * @returns Filtered document and excluded values
218
+ * @internal
219
+ */
220
+ export declare function filterExcludedFields<T extends Record<string, any>>(
221
+ document: T,
222
+ excludeFields?: string[],
223
+ currentPath?: string,
224
+ ): FilteredDocument<T>;
225
+
172
226
  /**
173
227
  * Helper type to infer the original type from a FieldTransform.
174
228
  * Useful for type checking and creating wrapper functions.
@@ -247,8 +301,91 @@ export declare interface PluginConfig {
247
301
  defaultMode?: TranslationMode;
248
302
  };
249
303
  fieldTransforms?: AnyFieldTransform[];
304
+ /**
305
+ * Fields to exclude from translation. These fields will be preserved as-is in the translated document.
306
+ * Supports field names (e.g., 'status') and nested field paths (e.g., 'meta.category').
307
+ * Useful for enum fields, IDs, technical values, and other non-translatable content.
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * excludeFields: ['status', 'category', 'type', 'meta.priority', 'config.mode']
312
+ * ```
313
+ */
314
+ excludeFields?: string[];
250
315
  }
251
316
 
317
+ /**
318
+ * Predefined exclusion lists for common use cases
319
+ * @internal
320
+ */
321
+ export declare const PREDEFINED_EXCLUSIONS: {
322
+ /** Common enum and status fields */
323
+ readonly enums: readonly [
324
+ "status",
325
+ "state",
326
+ "type",
327
+ "category",
328
+ "priority",
329
+ "visibility",
330
+ "role",
331
+ "permission",
332
+ "level",
333
+ "mode",
334
+ "format",
335
+ "variant",
336
+ "size",
337
+ "theme",
338
+ "kind",
339
+ "phase",
340
+ "stage",
341
+ ];
342
+ /** All metadata and system fields (starting with _) */
343
+ readonly metadata: readonly [
344
+ "_id",
345
+ "_type",
346
+ "_rev",
347
+ "_createdAt",
348
+ "_updatedAt",
349
+ ];
350
+ /** Common technical and configuration fields */
351
+ readonly technical: readonly [
352
+ "slug",
353
+ "handle",
354
+ "key",
355
+ "code",
356
+ "id",
357
+ "uuid",
358
+ "token",
359
+ "hash",
360
+ "checksum",
361
+ "version",
362
+ "config",
363
+ "settings",
364
+ ];
365
+ /** SEO and URL-related fields that often shouldn't be translated */
366
+ readonly seo: readonly [
367
+ "slug",
368
+ "handle",
369
+ "permalink",
370
+ "canonical",
371
+ "redirect",
372
+ "robots",
373
+ "sitemap",
374
+ ];
375
+ };
376
+
377
+ /**
378
+ * Restores previously excluded fields to a translated document
379
+ * @param translatedDocument - The translated document
380
+ * @param excludedValues - The excluded field values to restore
381
+ * @returns Document with excluded fields restored
382
+ * @internal
383
+ */
384
+ export declare function restoreExcludedFields<T extends Record<string, any>>(
385
+ translatedDocument: T,
386
+ excludedValues: Record<string, any>,
387
+ ): T;
388
+
252
389
  /**
253
390
  * Transform for handling Portable Text (rich text) content using XML-like serialization.
254
391
  * This leverages the existing serialization utilities to convert complex rich text
package/dist/index.js CHANGED
@@ -1799,6 +1799,146 @@ const getExistingTranslationId = (translationMetadata, targetLanguage) => transl
1799
1799
  })
1800
1800
  });
1801
1801
  };
1802
+ function shouldExcludeField(fieldPath, excludeFields) {
1803
+ for (const pattern of excludeFields) {
1804
+ if (fieldPath === pattern)
1805
+ return !0;
1806
+ if (pattern.startsWith("**.")) {
1807
+ const fieldName = pattern.substring(3), segments = fieldPath.split(".");
1808
+ if (segments[segments.length - 1]?.split("[")[0] === fieldName)
1809
+ return !0;
1810
+ } else if (!pattern.includes(".") && !pattern.includes("[")) {
1811
+ const segments = fieldPath.split(".");
1812
+ if (segments[segments.length - 1]?.split("[")[0] === pattern)
1813
+ return !0;
1814
+ } else if (pattern.includes("[*]")) {
1815
+ const regexPattern = pattern.replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/\\\[\*\\\]/g, "\\[\\d+\\]");
1816
+ if (new RegExp(`^${regexPattern}$`).test(fieldPath))
1817
+ return !0;
1818
+ }
1819
+ }
1820
+ return !1;
1821
+ }
1822
+ function filterExcludedFields(document, excludeFields = [], currentPath = "") {
1823
+ if (!excludeFields.length)
1824
+ return {
1825
+ filteredDocument: document,
1826
+ excludedValues: {}
1827
+ };
1828
+ const filteredDocument = {}, excludedValues = {};
1829
+ let hasExclusions = !1;
1830
+ for (const [key, value] of Object.entries(document)) {
1831
+ const fieldPath = currentPath ? `${currentPath}.${key}` : key;
1832
+ if (shouldExcludeField(fieldPath, excludeFields)) {
1833
+ excludedValues[fieldPath] = value, hasExclusions = !0, devLog.debug("Excluding field from translation", {
1834
+ fieldPath,
1835
+ fieldType: typeof value,
1836
+ value: typeof value == "string" ? value : "[object]"
1837
+ });
1838
+ continue;
1839
+ }
1840
+ if (value && typeof value == "object" && !Array.isArray(value) && value._type !== "reference") {
1841
+ const nested = filterExcludedFields(value, excludeFields, fieldPath);
1842
+ filteredDocument[key] = nested.filteredDocument, Object.assign(excludedValues, nested.excludedValues), Object.keys(nested.excludedValues).length > 0 && (hasExclusions = !0);
1843
+ } else if (Array.isArray(value)) {
1844
+ const filteredArray = [];
1845
+ let arrayHasExclusions = !1;
1846
+ for (let i = 0; i < value.length; i++) {
1847
+ const arrayItem = value[i], itemPath = `${fieldPath}[${i}]`;
1848
+ if (arrayItem && typeof arrayItem == "object" && !Array.isArray(arrayItem) && arrayItem._type !== "reference") {
1849
+ const nested = filterExcludedFields(arrayItem, excludeFields, itemPath);
1850
+ filteredArray[i] = nested.filteredDocument, Object.assign(excludedValues, nested.excludedValues), Object.keys(nested.excludedValues).length > 0 && (arrayHasExclusions = !0);
1851
+ } else
1852
+ filteredArray[i] = arrayItem;
1853
+ }
1854
+ filteredDocument[key] = filteredArray, arrayHasExclusions && (hasExclusions = !0);
1855
+ } else
1856
+ filteredDocument[key] = value;
1857
+ }
1858
+ return hasExclusions && devLog.info("Filtered excluded fields from document", {
1859
+ documentId: document._id || "unknown",
1860
+ excludedFieldCount: Object.keys(excludedValues).length,
1861
+ excludedFields: Object.keys(excludedValues)
1862
+ }), {
1863
+ filteredDocument,
1864
+ excludedValues
1865
+ };
1866
+ }
1867
+ function restoreExcludedFields(translatedDocument, excludedValues) {
1868
+ if (!Object.keys(excludedValues).length)
1869
+ return translatedDocument;
1870
+ const result = { ...translatedDocument };
1871
+ for (const [fieldPath, value] of Object.entries(excludedValues))
1872
+ setNestedValue(result, fieldPath, value);
1873
+ return devLog.debug("Restored excluded fields to translated document", {
1874
+ documentId: result._id || "unknown",
1875
+ restoredFieldCount: Object.keys(excludedValues).length,
1876
+ restoredFields: Object.keys(excludedValues)
1877
+ }), result;
1878
+ }
1879
+ function setNestedValue(obj, path, value) {
1880
+ const segments = path.split(".");
1881
+ let current = obj;
1882
+ for (let i = 0; i < segments.length - 1; i++) {
1883
+ const segment = segments[i], arrayMatch2 = segment.match(/^(.+)\[(\d+)\]$/);
1884
+ if (arrayMatch2) {
1885
+ const [, fieldName, indexStr] = arrayMatch2, index = Number.parseInt(indexStr, 10);
1886
+ for ((!(fieldName in current) || !Array.isArray(current[fieldName])) && (current[fieldName] = []); current[fieldName].length <= index; )
1887
+ current[fieldName].push({});
1888
+ current = current[fieldName][index];
1889
+ } else
1890
+ (!(segment in current) || typeof current[segment] != "object") && (current[segment] = {}), current = current[segment];
1891
+ }
1892
+ const finalSegment = segments[segments.length - 1], arrayMatch = finalSegment.match(/^(.+)\[(\d+)\]$/);
1893
+ if (arrayMatch) {
1894
+ const [, fieldName, indexStr] = arrayMatch, index = Number.parseInt(indexStr, 10);
1895
+ for ((!(fieldName in current) || !Array.isArray(current[fieldName])) && (current[fieldName] = []); current[fieldName].length <= index; )
1896
+ current[fieldName].push(null);
1897
+ current[fieldName][index] = value;
1898
+ } else
1899
+ current[finalSegment] = value;
1900
+ }
1901
+ const COMMON_ENUM_FIELDS = [
1902
+ "status",
1903
+ "state",
1904
+ "type",
1905
+ "category",
1906
+ "priority",
1907
+ "visibility",
1908
+ "role",
1909
+ "permission",
1910
+ "level",
1911
+ "mode",
1912
+ "format",
1913
+ "variant",
1914
+ "size",
1915
+ "theme",
1916
+ "kind",
1917
+ "phase",
1918
+ "stage"
1919
+ ], PREDEFINED_EXCLUSIONS = {
1920
+ /** Common enum and status fields */
1921
+ enums: [...COMMON_ENUM_FIELDS],
1922
+ /** All metadata and system fields (starting with _) */
1923
+ metadata: ["_id", "_type", "_rev", "_createdAt", "_updatedAt"],
1924
+ /** Common technical and configuration fields */
1925
+ technical: [
1926
+ "slug",
1927
+ "handle",
1928
+ "key",
1929
+ "code",
1930
+ "id",
1931
+ "uuid",
1932
+ "token",
1933
+ "hash",
1934
+ "checksum",
1935
+ "version",
1936
+ "config",
1937
+ "settings"
1938
+ ],
1939
+ /** SEO and URL-related fields that often shouldn't be translated */
1940
+ seo: ["slug", "handle", "permalink", "canonical", "redirect", "robots", "sitemap"]
1941
+ };
1802
1942
  function applyTransforms({
1803
1943
  document,
1804
1944
  registry,
@@ -1999,7 +2139,8 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
1999
2139
  prompt: result.usage?.input_tokens || 0,
2000
2140
  completion: result.usage?.output_tokens || 0,
2001
2141
  reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
2002
- total: result.usage?.total_tokens || 0
2142
+ total: result.usage?.total_tokens || 0,
2143
+ cached: result.usage?.input_tokens_details?.cached_tokens || 0
2003
2144
  }, cost = calculateCost(tokensUsed, model);
2004
2145
  let content = "";
2005
2146
  if (result.output && Array.isArray(result.output)) {
@@ -2044,7 +2185,8 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2044
2185
  prompt: usage.prompt_tokens || 0,
2045
2186
  completion: usage.completion_tokens || 0,
2046
2187
  reasoning: usage.reasoning_tokens || 0,
2047
- total: usage.total_tokens || 0
2188
+ total: usage.total_tokens || 0,
2189
+ cached: usage.prompt_tokens_details?.cached_tokens || 0
2048
2190
  }, cost = calculateCost(tokensUsed, model);
2049
2191
  return {
2050
2192
  content: result.choices[0].message.content || "",
@@ -2054,47 +2196,47 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2054
2196
  }, calculateCost = (tokens, model) => {
2055
2197
  const pricing = {
2056
2198
  "gpt-5": {
2057
- input: 125e-5,
2058
- output: 0.01,
2199
+ input: 125e-8,
2200
+ output: 1e-5,
2059
2201
  reasoning: 0
2060
2202
  },
2061
2203
  "gpt-5.1": {
2062
- input: 125e-5,
2063
- output: 0.01,
2204
+ input: 125e-8,
2205
+ output: 1e-5,
2064
2206
  reasoning: 0
2065
2207
  },
2066
2208
  "gpt-5-mini": {
2067
- input: 25e-5,
2068
- output: 2e-3,
2209
+ input: 25e-8,
2210
+ output: 2e-6,
2069
2211
  reasoning: 0
2070
2212
  },
2071
2213
  "gpt-5-nano": {
2072
- input: 5e-5,
2073
- output: 4e-4,
2214
+ input: 50000000000000004e-24,
2215
+ output: 40000000000000003e-23,
2074
2216
  reasoning: 0
2075
2217
  },
2076
2218
  "gpt-5-pro": {
2077
- input: 0.015,
2078
- output: 0.12,
2219
+ input: 15e-6,
2220
+ output: 12e-5,
2079
2221
  reasoning: 0
2080
2222
  },
2081
2223
  "gpt-4o": {
2082
- input: 5e-6,
2083
- output: 14999999999999999e-21,
2224
+ input: 25e-7,
2225
+ output: 1e-5,
2084
2226
  reasoning: 0
2085
2227
  },
2086
2228
  "gpt-4-turbo": {
2087
2229
  input: 1e-5,
2088
- output: 29999999999999997e-21,
2230
+ output: 3e-5,
2089
2231
  reasoning: 0
2090
2232
  },
2091
2233
  "gpt-4": {
2092
- input: 29999999999999997e-21,
2093
- output: 59999999999999995e-21,
2234
+ input: 3e-5,
2235
+ output: 6e-5,
2094
2236
  reasoning: 0
2095
2237
  }
2096
- }, modelPricing = pricing[model] || pricing["gpt-5"];
2097
- return tokens.prompt * modelPricing.input + tokens.completion * modelPricing.output + tokens.reasoning * modelPricing.reasoning;
2238
+ }, modelPricing = pricing[model] || pricing["gpt-5"], cachedInputRate = model === "gpt-5.1" ? 0.125 / 1e6 : modelPricing.input;
2239
+ return Math.max(0, tokens.prompt - tokens.cached) * modelPricing.input + tokens.cached * cachedInputRate + tokens.completion * modelPricing.output + tokens.reasoning * modelPricing.reasoning;
2098
2240
  }, deserializeDocument = (originalDoc, reviewedText) => {
2099
2241
  const parsed = parseReviewFormat(reviewedText), result = cloneDeep(originalDoc);
2100
2242
  return applyTextChanges(result, parsed), result;
@@ -2245,7 +2387,10 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2245
2387
  });
2246
2388
  return;
2247
2389
  }
2248
- node.text !== void 0 && !node.attributes.readonly && set(doc, path, node.text);
2390
+ node.text !== void 0 && !node.attributes.readonly && (get(doc, path) !== void 0 ? set(doc, path, node.text) : devLog.debug("Skipping field not present in original document", {
2391
+ path: path.join("."),
2392
+ attemptedValue: node.text
2393
+ }));
2249
2394
  }, applyBlockChanges = (block, blockNode) => {
2250
2395
  if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {
2251
2396
  devLog.warn("Block key mismatch, skipping", {
@@ -2276,7 +2421,10 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2276
2421
  });
2277
2422
  return;
2278
2423
  }
2279
- fieldNode.text !== void 0 && !fieldNode.attributes.readonly && (block[fieldName] = fieldNode.text);
2424
+ fieldNode.text !== void 0 && !fieldNode.attributes.readonly && (fieldName in block ? block[fieldName] = fieldNode.text : devLog.debug("Skipping block field not present in original", {
2425
+ fieldName,
2426
+ attemptedValue: fieldNode.text
2427
+ }));
2280
2428
  }, applySpanChanges = (span, spanNode) => {
2281
2429
  if (spanNode.attributes._key && span._key !== spanNode.attributes._key) {
2282
2430
  devLog.warn("Span key mismatch, skipping", {
@@ -2444,6 +2592,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2444
2592
  }, getOpenAITranslation = async ({
2445
2593
  dispatch,
2446
2594
  sourceDocument,
2595
+ originalSourceDocument,
2447
2596
  languageFieldName,
2448
2597
  translationPrompts,
2449
2598
  targetLanguage,
@@ -2484,9 +2633,9 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2484
2633
  responseLength: response.content.length,
2485
2634
  responsePreview: response.content.substring(0, 500)
2486
2635
  });
2487
- const result = deserializeDocument(sourceDocument, response.content);
2636
+ const baseDocument = originalSourceDocument || sourceDocument, result = deserializeDocument(baseDocument, response.content);
2488
2637
  result[languageFieldName] = targetLanguage;
2489
- const validation = validateStructureIntegrity(sourceDocument, result);
2638
+ const validation = validateStructureIntegrity(baseDocument, result);
2490
2639
  if (!validation.valid) {
2491
2640
  devLog.error("Structure validation failed", {
2492
2641
  ...logContext,
@@ -2730,14 +2879,30 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2730
2879
  translationPrompts,
2731
2880
  translationMode,
2732
2881
  openaiConfig,
2733
- releaseId
2882
+ releaseId,
2883
+ excludeFields = []
2734
2884
  }) => {
2735
- devLog.debug("Applying field transforms before translation", {
2885
+ devLog.info("Filtering excluded fields before translation", {
2886
+ documentId: sourceDocument._id,
2887
+ documentType: sourceDocument._type,
2888
+ excludeFields,
2889
+ excludeFieldsLength: excludeFields?.length ?? 0,
2890
+ sourceHasTitle: "title" in sourceDocument,
2891
+ sourceTitleValue: sourceDocument.title
2892
+ });
2893
+ const { filteredDocument, excludedValues } = filterExcludedFields(sourceDocument, excludeFields);
2894
+ devLog.info("After filtering excluded fields", {
2895
+ documentId: sourceDocument._id,
2896
+ filteredHasTitle: "title" in filteredDocument,
2897
+ filteredTitleValue: filteredDocument.title,
2898
+ excludedValuesKeys: Object.keys(excludedValues),
2899
+ excludedValues
2900
+ }), devLog.debug("Applying field transforms before translation", {
2736
2901
  documentId: sourceDocument._id,
2737
2902
  documentType: sourceDocument._type
2738
2903
  });
2739
2904
  const { transformedDocument, originalStructure } = applyTransforms({
2740
- document: sourceDocument,
2905
+ document: filteredDocument,
2741
2906
  registry: globalTransformRegistry,
2742
2907
  schemaType: sourceDocument._type
2743
2908
  });
@@ -2760,6 +2925,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2760
2925
  setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai"), translatedDoc = await getOpenAITranslation({
2761
2926
  dispatch,
2762
2927
  sourceDocument: transformedDocument,
2928
+ originalSourceDocument: sourceDocument,
2763
2929
  targetLanguage,
2764
2930
  languageFieldName,
2765
2931
  translationPrompts,
@@ -2778,12 +2944,26 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2778
2944
  documentId: sourceDocument._id,
2779
2945
  documentType: sourceDocument._type
2780
2946
  });
2781
- const finalDoc = restoreTransforms({
2947
+ const transformedResult = restoreTransforms({
2782
2948
  translatedDocument: translatedDoc,
2783
2949
  originalStructure,
2784
2950
  registry: globalTransformRegistry,
2785
2951
  schemaType: sourceDocument._type
2786
- }), targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
2952
+ });
2953
+ devLog.info("Before restoring excluded fields", {
2954
+ documentId: sourceDocument._id,
2955
+ excludedFieldCount: Object.keys(excludedValues).length,
2956
+ excludedValuesKeys: Object.keys(excludedValues),
2957
+ transformedResultHasTitle: "title" in transformedResult,
2958
+ transformedResultTitle: transformedResult.title
2959
+ });
2960
+ const finalDoc = restoreExcludedFields(transformedResult, excludedValues);
2961
+ devLog.info("After restoring excluded fields", {
2962
+ documentId: sourceDocument._id,
2963
+ finalDocHasTitle: "title" in finalDoc,
2964
+ finalDocTitle: finalDoc.title
2965
+ });
2966
+ const targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
2787
2967
  ...finalDoc,
2788
2968
  _id: targetDocumentVersionId
2789
2969
  };
@@ -2804,7 +2984,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2804
2984
  translationPrompts,
2805
2985
  translationMode,
2806
2986
  openaiConfig,
2807
- releaseId
2987
+ releaseId,
2988
+ excludeFields
2808
2989
  }) => {
2809
2990
  const logContext = {
2810
2991
  documentId: sourceDocument._id,
@@ -2840,7 +3021,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2840
3021
  translationPrompts,
2841
3022
  translationMode,
2842
3023
  openaiConfig,
2843
- releaseId
3024
+ releaseId,
3025
+ excludeFields
2844
3026
  }),
2845
3027
  {
2846
3028
  maxRetries: 3,
@@ -2906,7 +3088,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2906
3088
  references = [],
2907
3089
  maxConcurrentTranslationJobs,
2908
3090
  translationMode,
2909
- openaiConfig
3091
+ openaiConfig,
3092
+ excludeFields
2910
3093
  }) => {
2911
3094
  const logContext = {
2912
3095
  documentId: sourceDocument._id,
@@ -2972,7 +3155,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
2972
3155
  translationPrompts,
2973
3156
  translationMode,
2974
3157
  openaiConfig,
2975
- releaseId: releaseId2
3158
+ releaseId: releaseId2,
3159
+ excludeFields
2976
3160
  });
2977
3161
  versionedTargetDocumentId && versionedTargetDocumentIds.push(versionedTargetDocumentId);
2978
3162
  }
@@ -3045,7 +3229,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3045
3229
  maxConcurrentTranslationJobs: config.maxConcurrentTranslationJobs,
3046
3230
  maxReferenceDepth: config.maxReferenceDepth,
3047
3231
  translationMode,
3048
- openaiConfig: config.openai
3232
+ openaiConfig: config.openai,
3233
+ excludeFields: config.excludeFields
3049
3234
  });
3050
3235
  }, [
3051
3236
  document,
@@ -3057,6 +3242,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3057
3242
  config.maxConcurrentTranslationJobs,
3058
3243
  config.maxReferenceDepth,
3059
3244
  config.openai,
3245
+ config.excludeFields,
3060
3246
  references,
3061
3247
  shouldOverwriteFreshTranslations,
3062
3248
  translationMode
@@ -3439,7 +3625,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
3439
3625
  /* @__PURE__ */ jsxs(Code, { size: 1, children: [
3440
3626
  job._type,
3441
3627
  " \u2014 ",
3442
- job._id.slice(-6)
3628
+ job._id?.slice(-6) || "unknown"
3443
3629
  ] }),
3444
3630
  !["completed", "skipped", "error"].includes(job.status) && /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: getStatusLabel(job.status) }),
3445
3631
  job.status === "completed" && job.tokensUsed ? /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
@@ -4038,7 +4224,11 @@ const aiTranslationPlugin = definePlugin((config) => (config.fieldTransforms?.le
4038
4224
  }
4039
4225
  })), translationHistorySchema = translationHistory;
4040
4226
  export {
4227
+ COMMON_ENUM_FIELDS,
4228
+ PREDEFINED_EXCLUSIONS,
4041
4229
  aiTranslationPlugin,
4230
+ filterExcludedFields,
4231
+ restoreExcludedFields,
4042
4232
  richTextTransform,
4043
4233
  translationHistorySchema,
4044
4234
  vttCaptionTransform