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

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
@@ -233,7 +233,15 @@ export declare interface PluginConfig {
233
233
  maxReferenceDepth?: number;
234
234
  openai?: {
235
235
  apiKey: string;
236
- model?: "gpt-5.1" | "gpt-5" | "gpt-4o" | "gpt-4-turbo" | "gpt-4";
236
+ model?:
237
+ | "gpt-5.1"
238
+ | "gpt-5"
239
+ | "gpt-5-mini"
240
+ | "gpt-5-nano"
241
+ | "gpt-5-pro"
242
+ | "gpt-4o"
243
+ | "gpt-4-turbo"
244
+ | "gpt-4";
237
245
  reasoningEffort?: "low" | "medium" | "high";
238
246
  enabled?: boolean;
239
247
  defaultMode?: TranslationMode;
package/dist/index.js CHANGED
@@ -1961,9 +1961,10 @@ class TransformRegistry {
1961
1961
  }), matches[0];
1962
1962
  }
1963
1963
  }
1964
- const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKey, request) => (request.model || "gpt-5") === "gpt-5" ? await callGPT5ResponsesAPI(apiKey, request) : await callChatCompletionsAPI(apiKey, request), callGPT5ResponsesAPI = async (apiKey, request) => {
1964
+ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKey, request) => (request.model || "gpt-5").startsWith("gpt-5") ? await callGPT5ResponsesAPI(apiKey, request) : await callChatCompletionsAPI(apiKey, request), callGPT5ResponsesAPI = async (apiKey, request) => {
1965
1965
  const model = request.model || "gpt-5", requestBody = {
1966
- model: "gpt-5.1",
1966
+ model: model === "gpt-5" ? "gpt-5.1" : model,
1967
+ // Map gpt-5 to gpt-5.1, use others as-is
1967
1968
  input: request.messages
1968
1969
  // Uses 'input' instead of 'messages'
1969
1970
  };
@@ -2053,9 +2054,29 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2053
2054
  }, calculateCost = (tokens, model) => {
2054
2055
  const pricing = {
2055
2056
  "gpt-5": {
2056
- input: 2e-6,
2057
- output: 1e-5,
2058
- reasoning: 5e-6
2057
+ input: 125e-5,
2058
+ output: 0.01,
2059
+ reasoning: 0
2060
+ },
2061
+ "gpt-5.1": {
2062
+ input: 125e-5,
2063
+ output: 0.01,
2064
+ reasoning: 0
2065
+ },
2066
+ "gpt-5-mini": {
2067
+ input: 25e-5,
2068
+ output: 2e-3,
2069
+ reasoning: 0
2070
+ },
2071
+ "gpt-5-nano": {
2072
+ input: 5e-5,
2073
+ output: 4e-4,
2074
+ reasoning: 0
2075
+ },
2076
+ "gpt-5-pro": {
2077
+ input: 0.015,
2078
+ output: 0.12,
2079
+ reasoning: 0
2059
2080
  },
2060
2081
  "gpt-4o": {
2061
2082
  input: 5e-6,
@@ -2089,7 +2110,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2089
2110
  type: "field",
2090
2111
  name: fieldName,
2091
2112
  text: unescapeXml(text2),
2092
- attributes: {},
2113
+ attributes: { type: "string" },
2093
2114
  children: []
2094
2115
  };
2095
2116
  if (stack.length > 0) {
@@ -2162,7 +2183,13 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2162
2183
  parent && parent.children.push(node);
2163
2184
  } else
2164
2185
  root.push(node);
2165
- } else
2186
+ } else if (attributes.type === "string" && content && content.includes(closingTag))
2187
+ if (node.text = unescapeXml(content.replace(closingTag, "")), stack.length > 0) {
2188
+ const parent = stack[stack.length - 1];
2189
+ parent && parent.children.push(node);
2190
+ } else
2191
+ root.push(node);
2192
+ else
2166
2193
  stack.push(node);
2167
2194
  continue;
2168
2195
  }
@@ -2294,7 +2321,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2294
2321
  return;
2295
2322
  }
2296
2323
  if (typeof value == "string") {
2297
- value.length > 0 && lines.push(`${fieldName}: ${escapeXml(value)}`);
2324
+ value.length > 0 && lines.push(`${indentStr}<field name="${fieldName}" type="string">${escapeXml(value)}</field>`);
2298
2325
  return;
2299
2326
  }
2300
2327
  (typeof value == "number" || typeof value == "boolean") && lines.push(`${indentStr}<field name="${fieldName}" type="${typeof value}" readonly="true">${value}</field>`);
@@ -2331,7 +2358,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2331
2358
  return;
2332
2359
  }
2333
2360
  if (typeof value == "string" && value.length > 0) {
2334
- lines.push(`${indentStr}<field name="${fieldName}">${escapeXml(value)}</field>`);
2361
+ lines.push(`${indentStr}<field name="${fieldName}" type="string">${escapeXml(value)}</field>`);
2335
2362
  return;
2336
2363
  }
2337
2364
  (typeof value == "number" || typeof value == "boolean") && lines.push(`${indentStr}<field name="${fieldName}" type="${typeof value}" readonly="true">${value}</field>`);
@@ -2473,7 +2500,19 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
2473
2500
  `Structure validation failed: ${unexpectedErrors.length} errors. First: ${unexpectedErrors[0].type} at ${unexpectedErrors[0].path.join(".")}`
2474
2501
  );
2475
2502
  }
2476
- return setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai", {
2503
+ const translationValidation = validateTranslationCompleteness(sourceDocument, result, languageFieldName);
2504
+ return translationValidation.valid || (devLog.warn("Translation completeness warning", {
2505
+ ...logContext,
2506
+ skippedFields: translationValidation.skippedFields,
2507
+ warning: "GPT-5 may have skipped some important fields"
2508
+ }), devLog.info("Skipped field details", {
2509
+ ...logContext,
2510
+ skippedFields: translationValidation.skippedFields.map((field) => ({
2511
+ path: field.path,
2512
+ originalValue: field.originalValue,
2513
+ translatedValue: field.translatedValue
2514
+ }))
2515
+ })), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai", {
2477
2516
  tokensUsed: response.tokensUsed.total,
2478
2517
  estimatedCost: response.cost
2479
2518
  }), result;
@@ -2514,12 +2553,19 @@ ${sourceText}
2514
2553
 
2515
2554
  ## Instructions
2516
2555
 
2517
- 1. Translate all text content into ${targetLanguage}
2518
- 2. Follow the guidelines and terminology requirements above
2519
- 3. Preserve protected phrases exactly as they appear
2520
- 4. Return the document in THE EXACT SAME FORMAT
2521
- 5. **CRITICAL**: Only modify text content between tags
2522
- 6. Never add, remove, or change any tags, attributes, or structure
2556
+ 1. **TRANSLATE ALL TEXT FIELDS**: Every field marked with type="string" or containing text must be translated
2557
+ 2. **DO NOT SKIP SIMPLE FIELDS**: Pay special attention to simple fields like "title", "name", "description" - these are often the most important
2558
+ 3. Follow the guidelines and terminology requirements above
2559
+ 4. Preserve protected phrases exactly as they appear
2560
+ 5. Return the document in THE EXACT SAME FORMAT
2561
+ 6. **CRITICAL**: Only modify text content between tags
2562
+ 7. Never add, remove, or change any tags, attributes, or structure
2563
+
2564
+ **Common fields that must always be translated:**
2565
+ - title, name, heading, label
2566
+ - description, caption, alt text
2567
+ - All text within <field> tags
2568
+ - All text within <span> tags
2523
2569
 
2524
2570
  If you used significant reasoning to make translation decisions, you may include a brief summary:
2525
2571
  <reasoning>
@@ -2527,6 +2573,43 @@ Brief summary of key translation decisions and reasoning
2527
2573
  </reasoning>
2528
2574
 
2529
2575
  Then return the translated document:`;
2576
+ }, validateTranslationCompleteness = (sourceDoc, translatedDoc, languageFieldName) => {
2577
+ const skippedFields = [], checkField = (source, translated, path = []) => {
2578
+ if (path.length > 0) {
2579
+ const fieldName = path[path.length - 1];
2580
+ if (fieldName.startsWith("_") || fieldName === languageFieldName)
2581
+ return;
2582
+ }
2583
+ if (typeof source == "string" && typeof translated == "string") {
2584
+ if (source.length > 0 && source === translated) {
2585
+ const fieldName = path[path.length - 1] || "unknown";
2586
+ (["title", "name", "heading", "label", "description", "caption", "alt"].includes(
2587
+ fieldName
2588
+ ) || source.length > 10) && skippedFields.push({
2589
+ path: path.join("."),
2590
+ originalValue: source,
2591
+ translatedValue: translated
2592
+ });
2593
+ }
2594
+ return;
2595
+ }
2596
+ if (source && translated && typeof source == "object" && typeof translated == "object") {
2597
+ if (Array.isArray(source) && Array.isArray(translated)) {
2598
+ const maxLength = Math.min(source.length, translated.length);
2599
+ for (let i = 0; i < maxLength; i++)
2600
+ checkField(source[i], translated[i], [...path, String(i)]);
2601
+ } else if (!Array.isArray(source) && !Array.isArray(translated))
2602
+ for (const key of Object.keys(source))
2603
+ !key.startsWith("_") && key !== languageFieldName && checkField(source[key], translated[key], [
2604
+ ...path,
2605
+ key
2606
+ ]);
2607
+ }
2608
+ };
2609
+ return checkField(sourceDoc, translatedDoc), {
2610
+ valid: skippedFields.length === 0,
2611
+ skippedFields
2612
+ };
2530
2613
  }, assembleLanguageSpecs = ({
2531
2614
  sourceDocument,
2532
2615
  languageFieldName,