@braze/sanity-plugin-ai-translation 0.2.3 → 0.2.4
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 +146 -1
- package/dist/index.js +532 -62
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1409,11 +1409,21 @@ const fetchTranslationPrompts = async ({
|
|
|
1409
1409
|
const sourceDocumentString = documentToPlainText(sourceDocument), translationSettings2 = await client.fetch(
|
|
1410
1410
|
`*[_id == "translation-settings"][0] {
|
|
1411
1411
|
globalPrompt,
|
|
1412
|
-
localePrompts[
|
|
1412
|
+
localePrompts[_key in $targetLanguages],
|
|
1413
1413
|
protectedPhrases
|
|
1414
1414
|
}`,
|
|
1415
1415
|
{ targetLanguages }
|
|
1416
|
-
)
|
|
1416
|
+
);
|
|
1417
|
+
devLog.debug("Fetched translation settings", {
|
|
1418
|
+
hasSettings: !!translationSettings2,
|
|
1419
|
+
hasGlobalPrompt: !!translationSettings2?.globalPrompt,
|
|
1420
|
+
globalPromptLength: translationSettings2?.globalPrompt?.length,
|
|
1421
|
+
localePromptsCount: translationSettings2?.localePrompts?.length,
|
|
1422
|
+
localePrompts: translationSettings2?.localePrompts,
|
|
1423
|
+
protectedPhrasesCount: translationSettings2?.protectedPhrases?.length,
|
|
1424
|
+
targetLanguages
|
|
1425
|
+
});
|
|
1426
|
+
const translationGlossaryTerms = await client.fetch(
|
|
1417
1427
|
`*[
|
|
1418
1428
|
_type == "translationGlossaryTerm"
|
|
1419
1429
|
&& count(translations[_key in $targetLanguages]) > 0
|
|
@@ -1433,9 +1443,21 @@ const fetchTranslationPrompts = async ({
|
|
|
1433
1443
|
)), glossaryTermsByLocale[targetLanguage] = relevantTranslations;
|
|
1434
1444
|
}
|
|
1435
1445
|
let filteredProtectedPhrases = translationSettings2?.protectedPhrases;
|
|
1436
|
-
return sourceDocument && filteredProtectedPhrases && (
|
|
1437
|
-
|
|
1438
|
-
|
|
1446
|
+
return sourceDocument && filteredProtectedPhrases && (devLog.debug("Filtering protected phrases", {
|
|
1447
|
+
totalPhrases: filteredProtectedPhrases.length,
|
|
1448
|
+
phrases: filteredProtectedPhrases,
|
|
1449
|
+
sourceDocumentStringLength: sourceDocumentString.length,
|
|
1450
|
+
sourceDocumentPreview: sourceDocumentString.substring(0, 500)
|
|
1451
|
+
}), filteredProtectedPhrases = filteredProtectedPhrases.filter((phrase) => {
|
|
1452
|
+
const appears = protectedPhraseAppearsInDocument(phrase, sourceDocumentString);
|
|
1453
|
+
return devLog.debug("Protected phrase match result", {
|
|
1454
|
+
phrase,
|
|
1455
|
+
appears
|
|
1456
|
+
}), appears;
|
|
1457
|
+
}), devLog.debug("Filtered protected phrases result", {
|
|
1458
|
+
remainingCount: filteredProtectedPhrases.length,
|
|
1459
|
+
remainingPhrases: filteredProtectedPhrases
|
|
1460
|
+
})), {
|
|
1439
1461
|
globalPrompt: translationSettings2?.globalPrompt,
|
|
1440
1462
|
protectedPhrases: filteredProtectedPhrases,
|
|
1441
1463
|
localeInstructions: Object.fromEntries(
|
|
@@ -1583,7 +1605,109 @@ const fetchTranslationPrompts = async ({
|
|
|
1583
1605
|
payload
|
|
1584
1606
|
});
|
|
1585
1607
|
}
|
|
1586
|
-
},
|
|
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(
|
|
1587
1711
|
(translation) => translation._key === targetLanguage
|
|
1588
1712
|
)?.value._ref || null, getTranslationMetadata = async ({
|
|
1589
1713
|
client,
|
|
@@ -1675,6 +1799,146 @@ const fetchTranslationPrompts = async ({
|
|
|
1675
1799
|
})
|
|
1676
1800
|
});
|
|
1677
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
|
+
};
|
|
1678
1942
|
function applyTransforms({
|
|
1679
1943
|
document,
|
|
1680
1944
|
registry,
|
|
@@ -1837,9 +2101,10 @@ class TransformRegistry {
|
|
|
1837
2101
|
}), matches[0];
|
|
1838
2102
|
}
|
|
1839
2103
|
}
|
|
1840
|
-
const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKey, request) => (request.model || "gpt-5")
|
|
2104
|
+
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) => {
|
|
1841
2105
|
const model = request.model || "gpt-5", requestBody = {
|
|
1842
|
-
model: "gpt-5.1",
|
|
2106
|
+
model: model === "gpt-5" ? "gpt-5.1" : model,
|
|
2107
|
+
// Map gpt-5 to gpt-5.1, use others as-is
|
|
1843
2108
|
input: request.messages
|
|
1844
2109
|
// Uses 'input' instead of 'messages'
|
|
1845
2110
|
};
|
|
@@ -1847,6 +2112,16 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1847
2112
|
effort: request.reasoningEffort,
|
|
1848
2113
|
summary: "auto"
|
|
1849
2114
|
// Enable automatic reasoning summaries
|
|
2115
|
+
}), devLog.debug("OpenAI GPT-5 Request", {
|
|
2116
|
+
model: requestBody.model,
|
|
2117
|
+
reasoningEffort: request.reasoningEffort,
|
|
2118
|
+
messages: request.messages.map((m) => ({
|
|
2119
|
+
role: m.role,
|
|
2120
|
+
contentLength: m.content.length,
|
|
2121
|
+
contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? "..." : "")
|
|
2122
|
+
}))
|
|
2123
|
+
}), devLog.debug("OpenAI GPT-5 Full Prompt", {
|
|
2124
|
+
messages: request.messages
|
|
1850
2125
|
});
|
|
1851
2126
|
const response = await fetch("https://api.openai.com/v1/responses", {
|
|
1852
2127
|
method: "POST",
|
|
@@ -1864,7 +2139,8 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1864
2139
|
prompt: result.usage?.input_tokens || 0,
|
|
1865
2140
|
completion: result.usage?.output_tokens || 0,
|
|
1866
2141
|
reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,
|
|
1867
|
-
total: result.usage?.total_tokens || 0
|
|
2142
|
+
total: result.usage?.total_tokens || 0,
|
|
2143
|
+
cached: result.usage?.input_tokens_details?.cached_tokens || 0
|
|
1868
2144
|
}, cost = calculateCost(tokensUsed, model);
|
|
1869
2145
|
let content = "";
|
|
1870
2146
|
if (result.output && Array.isArray(result.output)) {
|
|
@@ -1881,7 +2157,19 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1881
2157
|
model,
|
|
1882
2158
|
messages: request.messages,
|
|
1883
2159
|
temperature: request.temperature ?? 0.3
|
|
1884
|
-
}
|
|
2160
|
+
};
|
|
2161
|
+
devLog.debug("OpenAI Chat Completions Request", {
|
|
2162
|
+
model,
|
|
2163
|
+
temperature: request.temperature ?? 0.3,
|
|
2164
|
+
messages: request.messages.map((m) => ({
|
|
2165
|
+
role: m.role,
|
|
2166
|
+
contentLength: m.content.length,
|
|
2167
|
+
contentPreview: m.content.substring(0, 500) + (m.content.length > 500 ? "..." : "")
|
|
2168
|
+
}))
|
|
2169
|
+
}), devLog.debug("OpenAI Chat Completions Full Prompt", {
|
|
2170
|
+
messages: request.messages
|
|
2171
|
+
});
|
|
2172
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
1885
2173
|
method: "POST",
|
|
1886
2174
|
headers: {
|
|
1887
2175
|
"Content-Type": "application/json",
|
|
@@ -1897,7 +2185,8 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1897
2185
|
prompt: usage.prompt_tokens || 0,
|
|
1898
2186
|
completion: usage.completion_tokens || 0,
|
|
1899
2187
|
reasoning: usage.reasoning_tokens || 0,
|
|
1900
|
-
total: usage.total_tokens || 0
|
|
2188
|
+
total: usage.total_tokens || 0,
|
|
2189
|
+
cached: usage.prompt_tokens_details?.cached_tokens || 0
|
|
1901
2190
|
}, cost = calculateCost(tokensUsed, model);
|
|
1902
2191
|
return {
|
|
1903
2192
|
content: result.choices[0].message.content || "",
|
|
@@ -1907,27 +2196,47 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1907
2196
|
}, calculateCost = (tokens, model) => {
|
|
1908
2197
|
const pricing = {
|
|
1909
2198
|
"gpt-5": {
|
|
1910
|
-
input:
|
|
2199
|
+
input: 125e-8,
|
|
1911
2200
|
output: 1e-5,
|
|
1912
|
-
reasoning:
|
|
2201
|
+
reasoning: 0
|
|
2202
|
+
},
|
|
2203
|
+
"gpt-5.1": {
|
|
2204
|
+
input: 125e-8,
|
|
2205
|
+
output: 1e-5,
|
|
2206
|
+
reasoning: 0
|
|
2207
|
+
},
|
|
2208
|
+
"gpt-5-mini": {
|
|
2209
|
+
input: 25e-8,
|
|
2210
|
+
output: 2e-6,
|
|
2211
|
+
reasoning: 0
|
|
2212
|
+
},
|
|
2213
|
+
"gpt-5-nano": {
|
|
2214
|
+
input: 50000000000000004e-24,
|
|
2215
|
+
output: 40000000000000003e-23,
|
|
2216
|
+
reasoning: 0
|
|
2217
|
+
},
|
|
2218
|
+
"gpt-5-pro": {
|
|
2219
|
+
input: 15e-6,
|
|
2220
|
+
output: 12e-5,
|
|
2221
|
+
reasoning: 0
|
|
1913
2222
|
},
|
|
1914
2223
|
"gpt-4o": {
|
|
1915
|
-
input:
|
|
1916
|
-
output:
|
|
2224
|
+
input: 25e-7,
|
|
2225
|
+
output: 1e-5,
|
|
1917
2226
|
reasoning: 0
|
|
1918
2227
|
},
|
|
1919
2228
|
"gpt-4-turbo": {
|
|
1920
2229
|
input: 1e-5,
|
|
1921
|
-
output:
|
|
2230
|
+
output: 3e-5,
|
|
1922
2231
|
reasoning: 0
|
|
1923
2232
|
},
|
|
1924
2233
|
"gpt-4": {
|
|
1925
|
-
input:
|
|
1926
|
-
output:
|
|
2234
|
+
input: 3e-5,
|
|
2235
|
+
output: 6e-5,
|
|
1927
2236
|
reasoning: 0
|
|
1928
2237
|
}
|
|
1929
|
-
}, modelPricing = pricing[model] || pricing["gpt-5"];
|
|
1930
|
-
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;
|
|
1931
2240
|
}, deserializeDocument = (originalDoc, reviewedText) => {
|
|
1932
2241
|
const parsed = parseReviewFormat(reviewedText), result = cloneDeep(originalDoc);
|
|
1933
2242
|
return applyTextChanges(result, parsed), result;
|
|
@@ -1943,7 +2252,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1943
2252
|
type: "field",
|
|
1944
2253
|
name: fieldName,
|
|
1945
2254
|
text: unescapeXml(text2),
|
|
1946
|
-
attributes: {},
|
|
2255
|
+
attributes: { type: "string" },
|
|
1947
2256
|
children: []
|
|
1948
2257
|
};
|
|
1949
2258
|
if (stack.length > 0) {
|
|
@@ -2016,7 +2325,13 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2016
2325
|
parent && parent.children.push(node);
|
|
2017
2326
|
} else
|
|
2018
2327
|
root.push(node);
|
|
2019
|
-
} else
|
|
2328
|
+
} else if (attributes.type === "string" && content && content.includes(closingTag))
|
|
2329
|
+
if (node.text = unescapeXml(content.replace(closingTag, "")), stack.length > 0) {
|
|
2330
|
+
const parent = stack[stack.length - 1];
|
|
2331
|
+
parent && parent.children.push(node);
|
|
2332
|
+
} else
|
|
2333
|
+
root.push(node);
|
|
2334
|
+
else
|
|
2020
2335
|
stack.push(node);
|
|
2021
2336
|
continue;
|
|
2022
2337
|
}
|
|
@@ -2072,7 +2387,10 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2072
2387
|
});
|
|
2073
2388
|
return;
|
|
2074
2389
|
}
|
|
2075
|
-
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
|
+
}));
|
|
2076
2394
|
}, applyBlockChanges = (block, blockNode) => {
|
|
2077
2395
|
if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {
|
|
2078
2396
|
devLog.warn("Block key mismatch, skipping", {
|
|
@@ -2103,7 +2421,10 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2103
2421
|
});
|
|
2104
2422
|
return;
|
|
2105
2423
|
}
|
|
2106
|
-
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
|
+
}));
|
|
2107
2428
|
}, applySpanChanges = (span, spanNode) => {
|
|
2108
2429
|
if (spanNode.attributes._key && span._key !== spanNode.attributes._key) {
|
|
2109
2430
|
devLog.warn("Span key mismatch, skipping", {
|
|
@@ -2148,7 +2469,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2148
2469
|
return;
|
|
2149
2470
|
}
|
|
2150
2471
|
if (typeof value == "string") {
|
|
2151
|
-
value.length > 0 && lines.push(`${
|
|
2472
|
+
value.length > 0 && lines.push(`${indentStr}<field name="${fieldName}" type="string">${escapeXml(value)}</field>`);
|
|
2152
2473
|
return;
|
|
2153
2474
|
}
|
|
2154
2475
|
(typeof value == "number" || typeof value == "boolean") && lines.push(`${indentStr}<field name="${fieldName}" type="${typeof value}" readonly="true">${value}</field>`);
|
|
@@ -2185,7 +2506,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2185
2506
|
return;
|
|
2186
2507
|
}
|
|
2187
2508
|
if (typeof value == "string" && value.length > 0) {
|
|
2188
|
-
lines.push(`${indentStr}<field name="${fieldName}">${escapeXml(value)}</field>`);
|
|
2509
|
+
lines.push(`${indentStr}<field name="${fieldName}" type="string">${escapeXml(value)}</field>`);
|
|
2189
2510
|
return;
|
|
2190
2511
|
}
|
|
2191
2512
|
(typeof value == "number" || typeof value == "boolean") && lines.push(`${indentStr}<field name="${fieldName}" type="${typeof value}" readonly="true">${value}</field>`);
|
|
@@ -2327,7 +2648,19 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
2327
2648
|
`Structure validation failed: ${unexpectedErrors.length} errors. First: ${unexpectedErrors[0].type} at ${unexpectedErrors[0].path.join(".")}`
|
|
2328
2649
|
);
|
|
2329
2650
|
}
|
|
2330
|
-
|
|
2651
|
+
const translationValidation = validateTranslationCompleteness(sourceDocument, result, languageFieldName);
|
|
2652
|
+
return translationValidation.valid || (devLog.warn("Translation completeness warning", {
|
|
2653
|
+
...logContext,
|
|
2654
|
+
skippedFields: translationValidation.skippedFields,
|
|
2655
|
+
warning: "GPT-5 may have skipped some important fields"
|
|
2656
|
+
}), devLog.info("Skipped field details", {
|
|
2657
|
+
...logContext,
|
|
2658
|
+
skippedFields: translationValidation.skippedFields.map((field) => ({
|
|
2659
|
+
path: field.path,
|
|
2660
|
+
originalValue: field.originalValue,
|
|
2661
|
+
translatedValue: field.translatedValue
|
|
2662
|
+
}))
|
|
2663
|
+
})), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "translating_with_openai", {
|
|
2331
2664
|
tokensUsed: response.tokensUsed.total,
|
|
2332
2665
|
estimatedCost: response.cost
|
|
2333
2666
|
}), result;
|
|
@@ -2368,12 +2701,19 @@ ${sourceText}
|
|
|
2368
2701
|
|
|
2369
2702
|
## Instructions
|
|
2370
2703
|
|
|
2371
|
-
1.
|
|
2372
|
-
2.
|
|
2373
|
-
3.
|
|
2374
|
-
4.
|
|
2375
|
-
5.
|
|
2376
|
-
6.
|
|
2704
|
+
1. **TRANSLATE ALL TEXT FIELDS**: Every field marked with type="string" or containing text must be translated
|
|
2705
|
+
2. **DO NOT SKIP SIMPLE FIELDS**: Pay special attention to simple fields like "title", "name", "description" - these are often the most important
|
|
2706
|
+
3. Follow the guidelines and terminology requirements above
|
|
2707
|
+
4. Preserve protected phrases exactly as they appear
|
|
2708
|
+
5. Return the document in THE EXACT SAME FORMAT
|
|
2709
|
+
6. **CRITICAL**: Only modify text content between tags
|
|
2710
|
+
7. Never add, remove, or change any tags, attributes, or structure
|
|
2711
|
+
|
|
2712
|
+
**Common fields that must always be translated:**
|
|
2713
|
+
- title, name, heading, label
|
|
2714
|
+
- description, caption, alt text
|
|
2715
|
+
- All text within <field> tags
|
|
2716
|
+
- All text within <span> tags
|
|
2377
2717
|
|
|
2378
2718
|
If you used significant reasoning to make translation decisions, you may include a brief summary:
|
|
2379
2719
|
<reasoning>
|
|
@@ -2381,6 +2721,43 @@ Brief summary of key translation decisions and reasoning
|
|
|
2381
2721
|
</reasoning>
|
|
2382
2722
|
|
|
2383
2723
|
Then return the translated document:`;
|
|
2724
|
+
}, validateTranslationCompleteness = (sourceDoc, translatedDoc, languageFieldName) => {
|
|
2725
|
+
const skippedFields = [], checkField = (source, translated, path = []) => {
|
|
2726
|
+
if (path.length > 0) {
|
|
2727
|
+
const fieldName = path[path.length - 1];
|
|
2728
|
+
if (fieldName.startsWith("_") || fieldName === languageFieldName)
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
if (typeof source == "string" && typeof translated == "string") {
|
|
2732
|
+
if (source.length > 0 && source === translated) {
|
|
2733
|
+
const fieldName = path[path.length - 1] || "unknown";
|
|
2734
|
+
(["title", "name", "heading", "label", "description", "caption", "alt"].includes(
|
|
2735
|
+
fieldName
|
|
2736
|
+
) || source.length > 10) && skippedFields.push({
|
|
2737
|
+
path: path.join("."),
|
|
2738
|
+
originalValue: source,
|
|
2739
|
+
translatedValue: translated
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
if (source && translated && typeof source == "object" && typeof translated == "object") {
|
|
2745
|
+
if (Array.isArray(source) && Array.isArray(translated)) {
|
|
2746
|
+
const maxLength = Math.min(source.length, translated.length);
|
|
2747
|
+
for (let i = 0; i < maxLength; i++)
|
|
2748
|
+
checkField(source[i], translated[i], [...path, String(i)]);
|
|
2749
|
+
} else if (!Array.isArray(source) && !Array.isArray(translated))
|
|
2750
|
+
for (const key of Object.keys(source))
|
|
2751
|
+
!key.startsWith("_") && key !== languageFieldName && checkField(source[key], translated[key], [
|
|
2752
|
+
...path,
|
|
2753
|
+
key
|
|
2754
|
+
]);
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
return checkField(sourceDoc, translatedDoc), {
|
|
2758
|
+
valid: skippedFields.length === 0,
|
|
2759
|
+
skippedFields
|
|
2760
|
+
};
|
|
2384
2761
|
}, assembleLanguageSpecs = ({
|
|
2385
2762
|
sourceDocument,
|
|
2386
2763
|
languageFieldName,
|
|
@@ -2501,14 +2878,36 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2501
2878
|
translationPrompts,
|
|
2502
2879
|
translationMode,
|
|
2503
2880
|
openaiConfig,
|
|
2504
|
-
releaseId
|
|
2881
|
+
releaseId,
|
|
2882
|
+
excludeFields = []
|
|
2505
2883
|
}) => {
|
|
2506
|
-
|
|
2884
|
+
const safeExcludeFields = excludeFields.filter((field) => field !== languageFieldName);
|
|
2885
|
+
excludeFields.length !== safeExcludeFields.length && devLog.warn("Language field was in excludeFields - removing it to prevent translation errors", {
|
|
2886
|
+
documentId: sourceDocument._id,
|
|
2887
|
+
languageFieldName,
|
|
2888
|
+
originalExcludeFields: excludeFields,
|
|
2889
|
+
safeExcludeFields
|
|
2890
|
+
}), devLog.info("Filtering excluded fields before translation", {
|
|
2891
|
+
documentId: sourceDocument._id,
|
|
2892
|
+
documentType: sourceDocument._type,
|
|
2893
|
+
excludeFields: safeExcludeFields,
|
|
2894
|
+
excludeFieldsLength: safeExcludeFields?.length ?? 0,
|
|
2895
|
+
sourceHasTitle: "title" in sourceDocument,
|
|
2896
|
+
sourceTitleValue: sourceDocument.title
|
|
2897
|
+
});
|
|
2898
|
+
const { filteredDocument, excludedValues } = filterExcludedFields(sourceDocument, safeExcludeFields);
|
|
2899
|
+
devLog.info("After filtering excluded fields", {
|
|
2900
|
+
documentId: sourceDocument._id,
|
|
2901
|
+
filteredHasTitle: "title" in filteredDocument,
|
|
2902
|
+
filteredTitleValue: filteredDocument.title,
|
|
2903
|
+
excludedValuesKeys: Object.keys(excludedValues),
|
|
2904
|
+
excludedValues
|
|
2905
|
+
}), devLog.debug("Applying field transforms before translation", {
|
|
2507
2906
|
documentId: sourceDocument._id,
|
|
2508
2907
|
documentType: sourceDocument._type
|
|
2509
2908
|
});
|
|
2510
2909
|
const { transformedDocument, originalStructure } = applyTransforms({
|
|
2511
|
-
document:
|
|
2910
|
+
document: filteredDocument,
|
|
2512
2911
|
registry: globalTransformRegistry,
|
|
2513
2912
|
schemaType: sourceDocument._type
|
|
2514
2913
|
});
|
|
@@ -2549,12 +2948,27 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2549
2948
|
documentId: sourceDocument._id,
|
|
2550
2949
|
documentType: sourceDocument._type
|
|
2551
2950
|
});
|
|
2552
|
-
const
|
|
2951
|
+
const transformedResult = restoreTransforms({
|
|
2553
2952
|
translatedDocument: translatedDoc,
|
|
2554
2953
|
originalStructure,
|
|
2555
2954
|
registry: globalTransformRegistry,
|
|
2556
2955
|
schemaType: sourceDocument._type
|
|
2557
|
-
}),
|
|
2956
|
+
}), excludedValuesToRestore = { ...excludedValues };
|
|
2957
|
+
delete excludedValuesToRestore[languageFieldName], devLog.info("Before restoring excluded fields", {
|
|
2958
|
+
documentId: sourceDocument._id,
|
|
2959
|
+
excludedFieldCount: Object.keys(excludedValues).length,
|
|
2960
|
+
excludedValuesKeys: Object.keys(excludedValues),
|
|
2961
|
+
languageFieldExcluded: languageFieldName in excludedValues,
|
|
2962
|
+
transformedResultHasTitle: "title" in transformedResult,
|
|
2963
|
+
transformedResultTitle: transformedResult.title
|
|
2964
|
+
});
|
|
2965
|
+
const finalDoc = restoreExcludedFields(transformedResult, excludedValuesToRestore);
|
|
2966
|
+
devLog.info("After restoring excluded fields", {
|
|
2967
|
+
documentId: sourceDocument._id,
|
|
2968
|
+
finalDocHasTitle: "title" in finalDoc,
|
|
2969
|
+
finalDocTitle: finalDoc.title
|
|
2970
|
+
});
|
|
2971
|
+
const targetDocumentPublishedId = existingTranslationId || uuid(), targetDocumentVersionId = `versions.${releaseId}.${targetDocumentPublishedId}`, newDoc = {
|
|
2558
2972
|
...finalDoc,
|
|
2559
2973
|
_id: targetDocumentVersionId
|
|
2560
2974
|
};
|
|
@@ -2575,7 +2989,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2575
2989
|
translationPrompts,
|
|
2576
2990
|
translationMode,
|
|
2577
2991
|
openaiConfig,
|
|
2578
|
-
releaseId
|
|
2992
|
+
releaseId,
|
|
2993
|
+
excludeFields
|
|
2579
2994
|
}) => {
|
|
2580
2995
|
const logContext = {
|
|
2581
2996
|
documentId: sourceDocument._id,
|
|
@@ -2599,23 +3014,51 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2599
3014
|
});
|
|
2600
3015
|
return;
|
|
2601
3016
|
}
|
|
2602
|
-
const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage),
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3017
|
+
const existingTranslationId = getExistingTranslationId(translationMetadata, targetLanguage), translationResult = await withRetry(
|
|
3018
|
+
() => handleTranslation({
|
|
3019
|
+
dispatch,
|
|
3020
|
+
client,
|
|
3021
|
+
schemaId,
|
|
3022
|
+
sourceDocument,
|
|
3023
|
+
targetLanguage,
|
|
3024
|
+
existingTranslationId,
|
|
3025
|
+
languageFieldName,
|
|
3026
|
+
translationPrompts,
|
|
3027
|
+
translationMode,
|
|
3028
|
+
openaiConfig,
|
|
3029
|
+
releaseId,
|
|
3030
|
+
excludeFields
|
|
3031
|
+
}),
|
|
3032
|
+
{
|
|
3033
|
+
maxRetries: 3,
|
|
3034
|
+
initialDelayMs: 1e3,
|
|
3035
|
+
isRetryable: isRetryableError,
|
|
3036
|
+
onRetry: (attempt, error, delayMs) => {
|
|
3037
|
+
devLog.warn("Translation failed, scheduling retry", {
|
|
3038
|
+
...logContext,
|
|
3039
|
+
attempt,
|
|
3040
|
+
delayMs,
|
|
3041
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3042
|
+
}), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "retrying", {
|
|
3043
|
+
retryCount: attempt,
|
|
3044
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
);
|
|
3049
|
+
if (!translationResult.success || !translationResult.result) {
|
|
3050
|
+
const errorMessage = translationResult.error instanceof Error ? translationResult.error.message : String(translationResult.error || "Translation failed");
|
|
3051
|
+
devLog.error("Translation failed after retries", translationResult.error, {
|
|
3052
|
+
...logContext,
|
|
3053
|
+
attempts: translationResult.attempts
|
|
3054
|
+
}), setJobStatus(dispatch, sourceDocument._id, targetLanguage, "error", {
|
|
3055
|
+
errorMessage,
|
|
3056
|
+
retryCount: translationResult.attempts - 1
|
|
3057
|
+
// -1 because attempts includes initial try
|
|
3058
|
+
}), devLog.timeEnd("translateDocument"), devLog.groupEnd();
|
|
2617
3059
|
return;
|
|
2618
3060
|
}
|
|
3061
|
+
const newDocument = translationResult.result;
|
|
2619
3062
|
return setJobStatus(dispatch, sourceDocument._id, targetLanguage, "updating_metadata", {
|
|
2620
3063
|
targetId: newDocument._id
|
|
2621
3064
|
}), await updateTranslationMetadata({
|
|
@@ -2650,7 +3093,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2650
3093
|
references = [],
|
|
2651
3094
|
maxConcurrentTranslationJobs,
|
|
2652
3095
|
translationMode,
|
|
2653
|
-
openaiConfig
|
|
3096
|
+
openaiConfig,
|
|
3097
|
+
excludeFields
|
|
2654
3098
|
}) => {
|
|
2655
3099
|
const logContext = {
|
|
2656
3100
|
documentId: sourceDocument._id,
|
|
@@ -2716,7 +3160,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2716
3160
|
translationPrompts,
|
|
2717
3161
|
translationMode,
|
|
2718
3162
|
openaiConfig,
|
|
2719
|
-
releaseId: releaseId2
|
|
3163
|
+
releaseId: releaseId2,
|
|
3164
|
+
excludeFields
|
|
2720
3165
|
});
|
|
2721
3166
|
versionedTargetDocumentId && versionedTargetDocumentIds.push(versionedTargetDocumentId);
|
|
2722
3167
|
}
|
|
@@ -2789,7 +3234,8 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2789
3234
|
maxConcurrentTranslationJobs: config.maxConcurrentTranslationJobs,
|
|
2790
3235
|
maxReferenceDepth: config.maxReferenceDepth,
|
|
2791
3236
|
translationMode,
|
|
2792
|
-
openaiConfig: config.openai
|
|
3237
|
+
openaiConfig: config.openai,
|
|
3238
|
+
excludeFields: config.excludeFields
|
|
2793
3239
|
});
|
|
2794
3240
|
}, [
|
|
2795
3241
|
document,
|
|
@@ -2801,6 +3247,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
2801
3247
|
config.maxConcurrentTranslationJobs,
|
|
2802
3248
|
config.maxReferenceDepth,
|
|
2803
3249
|
config.openai,
|
|
3250
|
+
config.excludeFields,
|
|
2804
3251
|
references,
|
|
2805
3252
|
shouldOverwriteFreshTranslations,
|
|
2806
3253
|
translationMode
|
|
@@ -3183,7 +3630,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3183
3630
|
/* @__PURE__ */ jsxs(Code, { size: 1, children: [
|
|
3184
3631
|
job._type,
|
|
3185
3632
|
" \u2014 ",
|
|
3186
|
-
job._id
|
|
3633
|
+
job._id?.slice(-6) || "unknown"
|
|
3187
3634
|
] }),
|
|
3188
3635
|
!["completed", "skipped", "error"].includes(job.status) && /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: getStatusLabel(job.status) }),
|
|
3189
3636
|
job.status === "completed" && job.tokensUsed ? /* @__PURE__ */ jsxs(Text, { size: 1, muted: !0, children: [
|
|
@@ -3195,12 +3642,17 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3195
3642
|
job.status === "error" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "critical", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3196
3643
|
"Error: ",
|
|
3197
3644
|
job.errorMessage
|
|
3645
|
+
] }) }) : null,
|
|
3646
|
+
job.status === "retrying" && job.errorMessage ? /* @__PURE__ */ jsx(Card, { tone: "caution", padding: 2, radius: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
3647
|
+
"Retrying after: ",
|
|
3648
|
+
job.errorMessage
|
|
3198
3649
|
] }) }) : null
|
|
3199
3650
|
] })
|
|
3200
3651
|
] }),
|
|
3201
3652
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, align: "center", children: [
|
|
3202
3653
|
job.status === "skipped" ? /* @__PURE__ */ jsx(SkippedBadge, {}) : null,
|
|
3203
|
-
job.status === "error" ? /* @__PURE__ */ jsx(ErrorBadge, {}) : null,
|
|
3654
|
+
job.status === "error" ? /* @__PURE__ */ jsx(ErrorBadge, { retryCount: job.retryCount }) : null,
|
|
3655
|
+
job.status === "retrying" ? /* @__PURE__ */ jsx(RetryingBadge, { retryCount: job.retryCount }) : null,
|
|
3204
3656
|
job.targetId ? /* @__PURE__ */ jsx(
|
|
3205
3657
|
TranslationOpenerButton,
|
|
3206
3658
|
{
|
|
@@ -3280,12 +3732,25 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3280
3732
|
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." }) }),
|
|
3281
3733
|
children: /* @__PURE__ */ jsx(Badge, { tone: "neutral", children: "Skipped" })
|
|
3282
3734
|
}
|
|
3283
|
-
), ErrorBadge = () => /* @__PURE__ */ jsx(
|
|
3735
|
+
), ErrorBadge = ({ retryCount }) => /* @__PURE__ */ jsx(
|
|
3284
3736
|
Tooltip,
|
|
3285
3737
|
{
|
|
3286
3738
|
portal: !0,
|
|
3287
|
-
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "This translation failed. Check the error message for details." }) }),
|
|
3288
|
-
children: /* @__PURE__ */
|
|
3739
|
+
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." }) }),
|
|
3740
|
+
children: /* @__PURE__ */ jsxs(Badge, { tone: "critical", children: [
|
|
3741
|
+
"Failed",
|
|
3742
|
+
retryCount && retryCount > 0 ? ` (${retryCount} retries)` : ""
|
|
3743
|
+
] })
|
|
3744
|
+
}
|
|
3745
|
+
), RetryingBadge = ({ retryCount }) => /* @__PURE__ */ jsx(
|
|
3746
|
+
Tooltip,
|
|
3747
|
+
{
|
|
3748
|
+
portal: !0,
|
|
3749
|
+
content: /* @__PURE__ */ jsx(Box, { padding: 1, children: /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Retrying translation after a temporary failure." }) }),
|
|
3750
|
+
children: /* @__PURE__ */ jsxs(Badge, { tone: "caution", children: [
|
|
3751
|
+
"Retry ",
|
|
3752
|
+
retryCount || 1
|
|
3753
|
+
] })
|
|
3289
3754
|
}
|
|
3290
3755
|
), getStatusLabel = (status) => ({
|
|
3291
3756
|
idle: "Idle",
|
|
@@ -3295,6 +3760,7 @@ ${protectedPhrases.join(", ")}`, buildStyleGuideSection = (globalPrompt, localeP
|
|
|
3295
3760
|
translating_with_openai: "Translating with GPT-5...",
|
|
3296
3761
|
reviewing_with_openai: "Reviewing translation with GPT-5...",
|
|
3297
3762
|
updating_metadata: "Saving metadata...",
|
|
3763
|
+
retrying: "Retrying...",
|
|
3298
3764
|
error: "Error",
|
|
3299
3765
|
skipped: "Skipped",
|
|
3300
3766
|
completed: "Completed"
|
|
@@ -3763,7 +4229,11 @@ const aiTranslationPlugin = definePlugin((config) => (config.fieldTransforms?.le
|
|
|
3763
4229
|
}
|
|
3764
4230
|
})), translationHistorySchema = translationHistory;
|
|
3765
4231
|
export {
|
|
4232
|
+
COMMON_ENUM_FIELDS,
|
|
4233
|
+
PREDEFINED_EXCLUSIONS,
|
|
3766
4234
|
aiTranslationPlugin,
|
|
4235
|
+
filterExcludedFields,
|
|
4236
|
+
restoreExcludedFields,
|
|
3767
4237
|
richTextTransform,
|
|
3768
4238
|
translationHistorySchema,
|
|
3769
4239
|
vttCaptionTransform
|