@braze/sanity-plugin-ai-translation 0.2.31 → 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 +9 -1
- package/dist/index.js +267 -36
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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?:
|
|
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
|
@@ -1605,7 +1605,109 @@ const fetchTranslationPrompts = async ({
|
|
|
1605
1605
|
payload
|
|
1606
1606
|
});
|
|
1607
1607
|
}
|
|
1608
|
-
},
|
|
1608
|
+
}, DEFAULT_OPTIONS = {
|
|
1609
|
+
maxRetries: 3,
|
|
1610
|
+
initialDelayMs: 1e3,
|
|
1611
|
+
maxDelayMs: 3e4,
|
|
1612
|
+
backoffMultiplier: 2,
|
|
1613
|
+
jitter: !0
|
|
1614
|
+
};
|
|
1615
|
+
function isRetryableError(error) {
|
|
1616
|
+
if (error instanceof Error) {
|
|
1617
|
+
const message = error.message.toLowerCase(), name = error.name.toLowerCase(), nonRetryablePatterns = [
|
|
1618
|
+
"schema not found",
|
|
1619
|
+
"invalid schema",
|
|
1620
|
+
"not found",
|
|
1621
|
+
"unauthorized",
|
|
1622
|
+
"forbidden",
|
|
1623
|
+
"invalid api key",
|
|
1624
|
+
"missing api key",
|
|
1625
|
+
"validation failed",
|
|
1626
|
+
"invalid document",
|
|
1627
|
+
"source language not found",
|
|
1628
|
+
"invalid configuration",
|
|
1629
|
+
"type mismatch"
|
|
1630
|
+
];
|
|
1631
|
+
for (const pattern of nonRetryablePatterns)
|
|
1632
|
+
if (message.includes(pattern) || name.includes(pattern))
|
|
1633
|
+
return !1;
|
|
1634
|
+
const retryablePatterns = [
|
|
1635
|
+
"failed to fetch",
|
|
1636
|
+
"network",
|
|
1637
|
+
"timeout",
|
|
1638
|
+
"econnreset",
|
|
1639
|
+
"econnrefused",
|
|
1640
|
+
"enotfound",
|
|
1641
|
+
"socket",
|
|
1642
|
+
"rate limit",
|
|
1643
|
+
"too many requests",
|
|
1644
|
+
"429",
|
|
1645
|
+
"500",
|
|
1646
|
+
"502",
|
|
1647
|
+
"503",
|
|
1648
|
+
"504",
|
|
1649
|
+
"internal server error",
|
|
1650
|
+
"bad gateway",
|
|
1651
|
+
"service unavailable",
|
|
1652
|
+
"gateway timeout",
|
|
1653
|
+
"temporarily unavailable",
|
|
1654
|
+
"try again"
|
|
1655
|
+
];
|
|
1656
|
+
for (const pattern of retryablePatterns)
|
|
1657
|
+
if (message.includes(pattern) || name.includes(pattern))
|
|
1658
|
+
return !0;
|
|
1659
|
+
}
|
|
1660
|
+
if (typeof error == "object" && error !== null) {
|
|
1661
|
+
const statusCode = error.statusCode || error.status;
|
|
1662
|
+
if (statusCode) {
|
|
1663
|
+
if (statusCode === 429 || statusCode >= 500 && statusCode < 600)
|
|
1664
|
+
return !0;
|
|
1665
|
+
if (statusCode >= 400 && statusCode < 500)
|
|
1666
|
+
return !1;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return !0;
|
|
1670
|
+
}
|
|
1671
|
+
function calculateBackoffDelay(attempt, options) {
|
|
1672
|
+
const { initialDelayMs, maxDelayMs, backoffMultiplier, jitter } = options;
|
|
1673
|
+
let delay = initialDelayMs * backoffMultiplier ** attempt;
|
|
1674
|
+
if (delay = Math.min(delay, maxDelayMs), jitter) {
|
|
1675
|
+
const jitterFactor = 0.75 + Math.random() * 0.5;
|
|
1676
|
+
delay = Math.round(delay * jitterFactor);
|
|
1677
|
+
}
|
|
1678
|
+
return delay;
|
|
1679
|
+
}
|
|
1680
|
+
function sleep(ms) {
|
|
1681
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1682
|
+
}
|
|
1683
|
+
async function withRetry(fn, options = {}) {
|
|
1684
|
+
const opts = { ...DEFAULT_OPTIONS, ...options }, shouldRetry = opts.isRetryable ?? isRetryableError;
|
|
1685
|
+
let lastError, attempts = 0;
|
|
1686
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
1687
|
+
attempts = attempt + 1;
|
|
1688
|
+
try {
|
|
1689
|
+
return { success: !0, result: await fn(), attempts };
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
if (lastError = error, attempt >= opts.maxRetries) {
|
|
1692
|
+
devLog.debug("Max retries exhausted", { attempt: attempts, error });
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
if (!shouldRetry(error)) {
|
|
1696
|
+
devLog.debug("Error is not retryable", { attempt: attempts, error });
|
|
1697
|
+
break;
|
|
1698
|
+
}
|
|
1699
|
+
const delay = calculateBackoffDelay(attempt, opts);
|
|
1700
|
+
devLog.debug("Scheduling retry", {
|
|
1701
|
+
attempt: attempts,
|
|
1702
|
+
nextAttempt: attempt + 2,
|
|
1703
|
+
delayMs: delay,
|
|
1704
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1705
|
+
}), opts.onRetry && opts.onRetry(attempt + 1, error, delay), await sleep(delay);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return { success: !1, error: lastError, attempts };
|
|
1709
|
+
}
|
|
1710
|
+
const getExistingTranslationId = (translationMetadata, targetLanguage) => translationMetadata?.translations?.length && translationMetadata.translations.find(
|
|
1609
1711
|
(translation) => translation._key === targetLanguage
|
|
1610
1712
|
)?.value._ref || null, getTranslationMetadata = async ({
|
|
1611
1713
|
client,
|
|
@@ -1859,9 +1961,10 @@ class TransformRegistry {
|
|
|
1859
1961
|
}), matches[0];
|
|
1860
1962
|
}
|
|
1861
1963
|
}
|
|
1862
|
-
const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKey, request) => (request.model || "gpt-5")
|
|
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) => {
|
|
1863
1965
|
const model = request.model || "gpt-5", requestBody = {
|
|
1864
|
-
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
|
|
1865
1968
|
input: request.messages
|
|
1866
1969
|
// Uses 'input' instead of 'messages'
|
|
1867
1970
|
};
|
|
@@ -1951,9 +2054,29 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1951
2054
|
}, calculateCost = (tokens, model) => {
|
|
1952
2055
|
const pricing = {
|
|
1953
2056
|
"gpt-5": {
|
|
1954
|
-
input:
|
|
1955
|
-
output:
|
|
1956
|
-
reasoning:
|
|
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
|
|
1957
2080
|
},
|
|
1958
2081
|
"gpt-4o": {
|
|
1959
2082
|
input: 5e-6,
|
|
@@ -1987,7 +2110,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1987
2110
|
type: "field",
|
|
1988
2111
|
name: fieldName,
|
|
1989
2112
|
text: unescapeXml(text2),
|
|
1990
|
-
attributes: {},
|
|
2113
|
+
attributes: { type: "string" },
|
|
1991
2114
|
children: []
|
|
1992
2115
|
};
|
|
1993
2116
|
if (stack.length > 0) {
|
|
@@ -2060,7 +2183,13 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2060
2183
|
parent && parent.children.push(node);
|
|
2061
2184
|
} else
|
|
2062
2185
|
root.push(node);
|
|
2063
|
-
} 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
|
|
2064
2193
|
stack.push(node);
|
|
2065
2194
|
continue;
|
|
2066
2195
|
}
|
|
@@ -2192,7 +2321,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2192
2321
|
return;
|
|
2193
2322
|
}
|
|
2194
2323
|
if (typeof value == "string") {
|
|
2195
|
-
value.length > 0 && lines.push(`${
|
|
2324
|
+
value.length > 0 && lines.push(`${indentStr}<field name="${fieldName}" type="string">${escapeXml(value)}</field>`);
|
|
2196
2325
|
return;
|
|
2197
2326
|
}
|
|
2198
2327
|
(typeof value == "number" || typeof value == "boolean") && lines.push(`${indentStr}<field name="${fieldName}" type="${typeof value}" readonly="true">${value}</field>`);
|
|
@@ -2229,7 +2358,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2229
2358
|
return;
|
|
2230
2359
|
}
|
|
2231
2360
|
if (typeof value == "string" && value.length > 0) {
|
|
2232
|
-
lines.push(`${indentStr}<field name="${fieldName}">${escapeXml(value)}</field>`);
|
|
2361
|
+
lines.push(`${indentStr}<field name="${fieldName}" type="string">${escapeXml(value)}</field>`);
|
|
2233
2362
|
return;
|
|
2234
2363
|
}
|
|
2235
2364
|
(typeof value == "number" || typeof value == "boolean") && lines.push(`${indentStr}<field name="${fieldName}" type="${typeof value}" readonly="true">${value}</field>`);
|
|
@@ -2371,7 +2500,19 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2371
2500
|
`Structure validation failed: ${unexpectedErrors.length} errors. First: ${unexpectedErrors[0].type} at ${unexpectedErrors[0].path.join(".")}`
|
|
2372
2501
|
);
|
|
2373
2502
|
}
|
|
2374
|
-
|
|
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", {
|
|
2375
2516
|
tokensUsed: response.tokensUsed.total,
|
|
2376
2517
|
estimatedCost: response.cost
|
|
2377
2518
|
}), result;
|
|
@@ -2412,12 +2553,19 @@ ${sourceText}
|
|
|
2412
2553
|
|
|
2413
2554
|
## Instructions
|
|
2414
2555
|
|
|
2415
|
-
1.
|
|
2416
|
-
2.
|
|
2417
|
-
3.
|
|
2418
|
-
4.
|
|
2419
|
-
5.
|
|
2420
|
-
6.
|
|
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
|
|
2421
2569
|
|
|
2422
2570
|
If you used significant reasoning to make translation decisions, you may include a brief summary:
|
|
2423
2571
|
<reasoning>
|
|
@@ -2425,6 +2573,43 @@ Brief summary of key translation decisions and reasoning
|
|
|
2425
2573
|
</reasoning>
|
|
2426
2574
|
|
|
2427
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
|
+
};
|
|
2428
2613
|
}, assembleLanguageSpecs = ({
|
|
2429
2614
|
sourceDocument,
|
|
2430
2615
|
languageFieldName,
|
|
@@ -2643,23 +2828,50 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2643
2828
|
});
|
|
2644
2829
|
return;
|
|
2645
2830
|
}
|
|
2646
|
-
const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage),
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2831
|
+
const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage), translationResult = await withRetry(
|
|
2832
|
+
() => handleTranslation({
|
|
2833
|
+
dispatch,
|
|
2834
|
+
client,
|
|
2835
|
+
schemaId,
|
|
2836
|
+
sourceDocument,
|
|
2837
|
+
targetLanguage,
|
|
2838
|
+
existingTranslationId,
|
|
2839
|
+
languageFieldName,
|
|
2840
|
+
translationPrompts,
|
|
2841
|
+
translationMode,
|
|
2842
|
+
openaiConfig,
|
|
2843
|
+
releaseId
|
|
2844
|
+
}),
|
|
2845
|
+
{
|
|
2846
|
+
maxRetries: 3,
|
|
2847
|
+
initialDelayMs: 1e3,
|
|
2848
|
+
isRetryable: isRetryableError,
|
|
2849
|
+
onRetry: (attempt, error, delayMs) => {
|
|
2850
|
+
devLog.warn("Translation failed, scheduling retry", {
|
|
2851
|
+
...logContext,
|
|
2852
|
+
attempt,
|
|
2853
|
+
delayMs,
|
|
2854
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2855
|
+
}), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "retrying", {
|
|
2856
|
+
retryCount: attempt,
|
|
2857
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
);
|
|
2862
|
+
if (!translationResult.success || !translationResult.result) {
|
|
2863
|
+
const errorMessage = translationResult.error instanceof Error ? translationResult.error.message : String(translationResult.error || "Translation failed");
|
|
2864
|
+
devLog.error("Translation failed after retries", translationResult.error, {
|
|
2865
|
+
...logContext,
|
|
2866
|
+
attempts: translationResult.attempts
|
|
2867
|
+
}), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
2868
|
+
errorMessage,
|
|
2869
|
+
retryCount: translationResult.attempts - 1
|
|
2870
|
+
// -1 because attempts includes initial try
|
|
2871
|
+
}), devLog.timeEnd("translateDocument"), devLog.groupEnd();
|
|
2661
2872
|
return;
|
|
2662
2873
|
}
|
|
2874
|
+
const newDocument = translationResult.result;
|
|
2663
2875
|
return setJobStatus(dispatch, sourceDocument._id, targetLanguage, "updating_metadata", {
|
|
2664
2876
|
targetId: newDocument._id
|
|
2665
2877
|
}), await updateTranslationMetadata({
|
|
@@ -3239,12 +3451,17 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3239
3451
|
job.status === "error" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3240
3452
|
"Error: ",
|
|
3241
3453
|
job.errorMessage
|
|
3454
|
+
] }) }) : null,
|
|
3455
|
+
job.status === "retrying" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3456
|
+
"Retrying after: ",
|
|
3457
|
+
job.errorMessage
|
|
3242
3458
|
] }) }) : null
|
|
3243
3459
|
] })
|
|
3244
3460
|
] }),
|
|
3245
3461
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
|
|
3246
3462
|
job.status === "skipped" ? /* @__PURE__ */ jsx(SkippedBadge, {}) : null,
|
|
3247
|
-
job.status === "error" ? /* @__PURE__ */ jsx(ErrorBadge, {}) : null,
|
|
3463
|
+
job.status === "error" ? /* @__PURE__ */ jsx(ErrorBadge, { retryCount: job.retryCount }) : null,
|
|
3464
|
+
job.status === "retrying" ? /* @__PURE__ */ jsx(RetryingBadge, { retryCount: job.retryCount }) : null,
|
|
3248
3465
|
job.targetId ? /* @__PURE__ */ jsx(
|
|
3249
3466
|
TranslationOpenerButton,
|
|
3250
3467
|
{
|
|
@@ -3324,12 +3541,25 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3324
3541
|
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "There was already a fresh translation for this document and language." }) }),
|
|
3325
3542
|
children: /* @__PURE__ */ jsx(Badge, { tone: "neutral", children: "Skipped" })
|
|
3326
3543
|
}
|
|
3327
|
-
), ErrorBadge = () => /* @__PURE__ */ jsx(
|
|
3544
|
+
), ErrorBadge = ({ retryCount }) => /* @__PURE__ */ jsx(
|
|
3545
|
+
Tooltip,
|
|
3546
|
+
{
|
|
3547
|
+
portal: !0,
|
|
3548
|
+
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: retryCount && retryCount > 0 ? `This translation failed after ${retryCount} retry attempt${retryCount > 1 ? "s" : ""}.` : "This translation failed. Check the error message for details." }) }),
|
|
3549
|
+
children: /* @__PURE__ */ jsxs(Badge, { tone: "critical", children: [
|
|
3550
|
+
"Failed",
|
|
3551
|
+
retryCount && retryCount > 0 ? ` (${retryCount} retries)` : ""
|
|
3552
|
+
] })
|
|
3553
|
+
}
|
|
3554
|
+
), RetryingBadge = ({ retryCount }) => /* @__PURE__ */ jsx(
|
|
3328
3555
|
Tooltip,
|
|
3329
3556
|
{
|
|
3330
3557
|
portal: !0,
|
|
3331
|
-
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "
|
|
3332
|
-
children: /* @__PURE__ */
|
|
3558
|
+
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Retrying translation after a temporary failure." }) }),
|
|
3559
|
+
children: /* @__PURE__ */ jsxs(Badge, { tone: "caution", children: [
|
|
3560
|
+
"Retry ",
|
|
3561
|
+
retryCount || 1
|
|
3562
|
+
] })
|
|
3333
3563
|
}
|
|
3334
3564
|
), getStatusLabel = (status) => ({
|
|
3335
3565
|
idle: "Idle",
|
|
@@ -3339,6 +3569,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3339
3569
|
translating_with_openai: "Translating with GPT-5...",
|
|
3340
3570
|
reviewing_with_openai: "Reviewing translation with GPT-5...",
|
|
3341
3571
|
updating_metadata: "Saving metadata...",
|
|
3572
|
+
retrying: "Retrying...",
|
|
3342
3573
|
error: "Error",
|
|
3343
3574
|
skipped: "Skipped",
|
|
3344
3575
|
completed: "Completed"
|