@braze/sanity-plugin-ai-translation 0.2.66 → 0.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +74 -174
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -6,7 +6,6 @@ import defaults from "lodash.defaults";
|
|
|
6
6
|
import { Card, Stack, Flex, Text, Box, Heading, Button, Spinner, Inline, Checkbox, Grid, Radio, TextInput, Badge, Select, Label, Code, Tooltip } from "@sanity/ui";
|
|
7
7
|
import { toPlainText } from "@portabletext/react";
|
|
8
8
|
import { uuid } from "@sanity/uuid";
|
|
9
|
-
import { XMLParser } from "fast-xml-parser";
|
|
10
9
|
import cloneDeep from "lodash.clonedeep";
|
|
11
10
|
import get from "lodash.get";
|
|
12
11
|
import set from "lodash.set";
|
|
@@ -1540,7 +1539,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1540
1539
|
cleanedLength: cleanedText.length,
|
|
1541
1540
|
preview: cleanedText.substring(0, 500)
|
|
1542
1541
|
});
|
|
1543
|
-
const parsed =
|
|
1542
|
+
const parsed = parseReviewFormat(cleanedText, originalDoc);
|
|
1544
1543
|
devLog.debug("Parsed response structure", {
|
|
1545
1544
|
rootNodeCount: parsed.length,
|
|
1546
1545
|
rootNodeNames: parsed.map((n) => n.type === "field" && n.name ? n.name : "unknown")
|
|
@@ -1562,118 +1561,7 @@ const globalTransformRegistry = new TransformRegistry(), callGPT5 = async (apiKe
|
|
|
1562
1561
|
codeBlocksRemoved,
|
|
1563
1562
|
preview: cleaned.trim().substring(0, 200)
|
|
1564
1563
|
}), cleaned.trim();
|
|
1565
|
-
},
|
|
1566
|
-
function parseCompactFormatSections(text, originalDoc) {
|
|
1567
|
-
const nodes = [], lines = text.split(`
|
|
1568
|
-
`), compactStartRegex = /^---\s+(\w+)\s+---$/;
|
|
1569
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1570
|
-
const match = lines[i].trim().match(compactStartRegex);
|
|
1571
|
-
if (!match) continue;
|
|
1572
|
-
const fieldName = match[1], contentLines = [];
|
|
1573
|
-
for (i++; i < lines.length; ) {
|
|
1574
|
-
const currentLine = lines[i];
|
|
1575
|
-
if (currentLine.trim().match(new RegExp(`^---\\s+/${fieldName}\\s+---$`)))
|
|
1576
|
-
break;
|
|
1577
|
-
contentLines.push(currentLine), i++;
|
|
1578
|
-
}
|
|
1579
|
-
const content = contentLines.join(`
|
|
1580
|
-
`);
|
|
1581
|
-
let contentType;
|
|
1582
|
-
try {
|
|
1583
|
-
const fieldValue = get(originalDoc, [fieldName]);
|
|
1584
|
-
if (fieldValue && typeof fieldValue == "object" && fieldValue !== null) {
|
|
1585
|
-
const fieldType = fieldValue._type;
|
|
1586
|
-
typeof fieldType == "string" && fieldType.startsWith("translatable.") && (contentType = fieldType);
|
|
1587
|
-
}
|
|
1588
|
-
} catch {
|
|
1589
|
-
}
|
|
1590
|
-
contentType || (content.includes("<block") || content.includes("<span") ? contentType = "translatable.richtext" : contentType = "translatable.vtt.captions"), nodes.push({
|
|
1591
|
-
type: "field",
|
|
1592
|
-
name: fieldName,
|
|
1593
|
-
text: content,
|
|
1594
|
-
attributes: { type: contentType },
|
|
1595
|
-
children: []
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
return nodes;
|
|
1599
|
-
}
|
|
1600
|
-
const parseResponseToNodes = (text, originalDoc) => {
|
|
1601
|
-
if (looksLikeXmlResponse(text))
|
|
1602
|
-
try {
|
|
1603
|
-
const wrapped = `<root>${text}</root>`, parsed = new XMLParser({
|
|
1604
|
-
ignoreAttributes: !1,
|
|
1605
|
-
attributeNamePrefix: "@_",
|
|
1606
|
-
textNodeName: "#text",
|
|
1607
|
-
parseTagValue: !1,
|
|
1608
|
-
trimValues: !1,
|
|
1609
|
-
// preserve exact whitespace in text (e.g. trailing space in spans)
|
|
1610
|
-
isArray: (tagName) => TAG_NAMES.includes(tagName)
|
|
1611
|
-
}).parse(wrapped);
|
|
1612
|
-
if (parsed && typeof parsed == "object" && parsed.root) {
|
|
1613
|
-
const xmlNodes = convertXmlObjectToParsedNodes(parsed.root), compactNodes = parseCompactFormatSections(text, originalDoc);
|
|
1614
|
-
if (xmlNodes.length > 0 || compactNodes.length > 0) {
|
|
1615
|
-
const merged = [...xmlNodes, ...compactNodes];
|
|
1616
|
-
return devLog.debug("Parsed response with XML parser + compact sections", {
|
|
1617
|
-
xmlNodeCount: xmlNodes.length,
|
|
1618
|
-
compactNodeCount: compactNodes.length,
|
|
1619
|
-
total: merged.length
|
|
1620
|
-
}), merged;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
} catch (err) {
|
|
1624
|
-
devLog.warn("XML parse failed, falling back to regex parser", {
|
|
1625
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
return parseReviewFormat(text, originalDoc);
|
|
1629
|
-
};
|
|
1630
|
-
function normalizeToArray(value) {
|
|
1631
|
-
return Array.isArray(value) ? value : value != null && typeof value == "object" ? [value] : [];
|
|
1632
|
-
}
|
|
1633
|
-
function convertXmlObjectToParsedNodes(root) {
|
|
1634
|
-
const result = [];
|
|
1635
|
-
for (const tagName of TAG_NAMES) {
|
|
1636
|
-
const raw = root[tagName];
|
|
1637
|
-
if (raw !== void 0) {
|
|
1638
|
-
for (const item of normalizeToArray(raw))
|
|
1639
|
-
if (item != null && typeof item == "object") {
|
|
1640
|
-
const node = convertXmlElementToParsedNode(item, tagName);
|
|
1641
|
-
node && result.push(node);
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
return result;
|
|
1646
|
-
}
|
|
1647
|
-
function convertXmlElementToParsedNode(obj, tagName) {
|
|
1648
|
-
const attributes = {};
|
|
1649
|
-
for (const [key, value] of Object.entries(obj))
|
|
1650
|
-
if (key.startsWith("@_")) {
|
|
1651
|
-
const attrName = key.slice(2);
|
|
1652
|
-
attributes[attrName] = value != null ? String(value) : "";
|
|
1653
|
-
}
|
|
1654
|
-
let text;
|
|
1655
|
-
const textVal = obj["#text"];
|
|
1656
|
-
textVal != null && (text = typeof textVal == "string" ? textVal : String(textVal));
|
|
1657
|
-
const children = [];
|
|
1658
|
-
for (const childTag of TAG_NAMES) {
|
|
1659
|
-
const raw = obj[childTag];
|
|
1660
|
-
if (raw !== void 0) {
|
|
1661
|
-
for (const item of normalizeToArray(raw))
|
|
1662
|
-
if (item != null && typeof item == "object") {
|
|
1663
|
-
const child = convertXmlElementToParsedNode(item, childTag);
|
|
1664
|
-
child && children.push(child);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
return {
|
|
1669
|
-
type: tagName,
|
|
1670
|
-
name: attributes.name,
|
|
1671
|
-
text: text !== void 0 && text !== "" ? unescapeXml(text) : void 0,
|
|
1672
|
-
attributes,
|
|
1673
|
-
children
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
const parseReviewFormat = (text, originalDoc) => {
|
|
1564
|
+
}, parseReviewFormat = (text, originalDoc) => {
|
|
1677
1565
|
const root = [], stack = [], lines = text.split(`
|
|
1678
1566
|
`);
|
|
1679
1567
|
devLog.debug("Parsing response text", {
|
|
@@ -1761,25 +1649,34 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1761
1649
|
attributes,
|
|
1762
1650
|
children: []
|
|
1763
1651
|
}, closingTag = `</${tagName}>`;
|
|
1764
|
-
if (content.
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1652
|
+
if (content.includes(closingTag)) {
|
|
1653
|
+
const closeIdx = content.indexOf(closingTag);
|
|
1654
|
+
node.text = unescapeXml(content.substring(0, closeIdx));
|
|
1655
|
+
const remainder = content.substring(closeIdx + closingTag.length).trim();
|
|
1656
|
+
if (stack.length > 0) {
|
|
1767
1657
|
const parent = stack[stack.length - 1];
|
|
1768
1658
|
parent && parent.children.push(node);
|
|
1769
1659
|
} else
|
|
1770
1660
|
root.push(node);
|
|
1771
|
-
|
|
1772
|
-
|
|
1661
|
+
remainder && (lines[i] = remainder, i--);
|
|
1662
|
+
} else if (content.endsWith(closingTag)) {
|
|
1663
|
+
const textContent = content.substring(0, content.lastIndexOf("<"));
|
|
1664
|
+
if (node.text = unescapeXml(textContent), stack.length > 0) {
|
|
1773
1665
|
const parent = stack[stack.length - 1];
|
|
1774
1666
|
parent && parent.children.push(node);
|
|
1775
1667
|
} else
|
|
1776
1668
|
root.push(node);
|
|
1777
|
-
else if (attributes.type && typeof attributes.type == "string" && attributes.type.startsWith("translatable.")) {
|
|
1669
|
+
} else if (attributes.type && typeof attributes.type == "string" && attributes.type.startsWith("translatable.")) {
|
|
1778
1670
|
const contentLines = [];
|
|
1779
1671
|
for (i++; i < lines.length; ) {
|
|
1780
1672
|
const currentLine = lines[i];
|
|
1781
|
-
if (currentLine.
|
|
1673
|
+
if (currentLine.includes("</field>")) {
|
|
1674
|
+
const idx = currentLine.indexOf("</field>"), before = currentLine.substring(0, idx).trimEnd();
|
|
1675
|
+
before.trim() && contentLines.push(before);
|
|
1676
|
+
const after = currentLine.substring(idx + 8).trim();
|
|
1677
|
+
after && (lines[i] = after, i--);
|
|
1782
1678
|
break;
|
|
1679
|
+
}
|
|
1783
1680
|
contentLines.push(currentLine), i++;
|
|
1784
1681
|
}
|
|
1785
1682
|
if (node.text = contentLines.join(`
|
|
@@ -1788,19 +1685,15 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1788
1685
|
parent && parent.children.push(node);
|
|
1789
1686
|
} else
|
|
1790
1687
|
root.push(node);
|
|
1791
|
-
} else if (attributes.type === "string" && content && content.includes(closingTag))
|
|
1792
|
-
if (node.text = unescapeXml(content.replace(closingTag, "")), stack.length > 0) {
|
|
1793
|
-
const parent = stack[stack.length - 1];
|
|
1794
|
-
parent && parent.children.push(node);
|
|
1795
|
-
} else
|
|
1796
|
-
root.push(node);
|
|
1797
|
-
else if (attributes.type === "string" && content && !content.includes(closingTag)) {
|
|
1688
|
+
} else if (attributes.type === "string" && content && !content.includes(closingTag)) {
|
|
1798
1689
|
const contentLines = [content];
|
|
1799
1690
|
for (i++; i < lines.length; ) {
|
|
1800
1691
|
const currentLine = lines[i];
|
|
1801
1692
|
if (currentLine.includes(closingTag)) {
|
|
1802
|
-
const closingIndex = currentLine.indexOf(closingTag), remainingContent = currentLine.substring(0, closingIndex);
|
|
1693
|
+
const closingIndex = currentLine.indexOf(closingTag), remainingContent = currentLine.substring(0, closingIndex).trimEnd();
|
|
1803
1694
|
remainingContent && contentLines.push(remainingContent);
|
|
1695
|
+
const after = currentLine.substring(closingIndex + closingTag.length).trim();
|
|
1696
|
+
after && (lines[i] = after, i--);
|
|
1804
1697
|
break;
|
|
1805
1698
|
}
|
|
1806
1699
|
contentLines.push(currentLine), i++;
|
|
@@ -1815,8 +1708,13 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1815
1708
|
const contentLines = [];
|
|
1816
1709
|
for (i++; i < lines.length; ) {
|
|
1817
1710
|
const currentLine = lines[i];
|
|
1818
|
-
if (currentLine.
|
|
1711
|
+
if (currentLine.includes("</field>")) {
|
|
1712
|
+
const idx = currentLine.indexOf("</field>"), before = currentLine.substring(0, idx).trimEnd();
|
|
1713
|
+
before.trim() && contentLines.push(before);
|
|
1714
|
+
const after = currentLine.substring(idx + 8).trim();
|
|
1715
|
+
after && (lines[i] = after, i--);
|
|
1819
1716
|
break;
|
|
1717
|
+
}
|
|
1820
1718
|
contentLines.push(currentLine), i++;
|
|
1821
1719
|
}
|
|
1822
1720
|
if (node.text = contentLines.join(`
|
|
@@ -1829,32 +1727,36 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1829
1727
|
stack.push(node);
|
|
1830
1728
|
continue;
|
|
1831
1729
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1730
|
+
if (trimmed.match(/^<\/(field|block|span)>/)) {
|
|
1731
|
+
let afterClosingTag = trimmed;
|
|
1732
|
+
for (; ; ) {
|
|
1733
|
+
const match = afterClosingTag.match(/^<\/(field|block|span)>/);
|
|
1734
|
+
if (!match) break;
|
|
1735
|
+
if (stack.length === 0) {
|
|
1736
|
+
devLog.warn("Closing tag found but stack is empty", {
|
|
1737
|
+
line: i,
|
|
1738
|
+
tagName: match[1],
|
|
1739
|
+
trimmed: afterClosingTag
|
|
1740
|
+
}), afterClosingTag = afterClosingTag.substring(match[0].length).trim(), afterClosingTag = afterClosingTag.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1743
|
+
matchedPatternCount++, devLog.debug("Matched closing tag", {
|
|
1836
1744
|
line: i,
|
|
1837
|
-
tagName:
|
|
1838
|
-
trimmed
|
|
1745
|
+
tagName: match[1],
|
|
1746
|
+
trimmed: afterClosingTag,
|
|
1747
|
+
stackLength: stack.length,
|
|
1748
|
+
stackTopName: stack[stack.length - 1]?.name
|
|
1839
1749
|
});
|
|
1840
|
-
|
|
1750
|
+
const node = stack.pop();
|
|
1751
|
+
if (node)
|
|
1752
|
+
if (stack.length > 0) {
|
|
1753
|
+
const parent = stack[stack.length - 1];
|
|
1754
|
+
parent && parent.children.push(node);
|
|
1755
|
+
} else
|
|
1756
|
+
root.push(node);
|
|
1757
|
+
afterClosingTag = afterClosingTag.substring(match[0].length).trim(), afterClosingTag = afterClosingTag.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
1841
1758
|
}
|
|
1842
|
-
|
|
1843
|
-
line: i,
|
|
1844
|
-
tagName: closeMatch[1],
|
|
1845
|
-
trimmed,
|
|
1846
|
-
stackLength: stack.length,
|
|
1847
|
-
stackTopName: stack[stack.length - 1]?.name
|
|
1848
|
-
});
|
|
1849
|
-
const node = stack.pop();
|
|
1850
|
-
if (node)
|
|
1851
|
-
if (stack.length > 0) {
|
|
1852
|
-
const parent = stack[stack.length - 1];
|
|
1853
|
-
parent && parent.children.push(node);
|
|
1854
|
-
} else
|
|
1855
|
-
root.push(node);
|
|
1856
|
-
let afterClosingTag = trimmed.substring(closeMatch[0].length).trim();
|
|
1857
|
-
if (afterClosingTag = afterClosingTag.replace(/<!--[\s\S]*?-->/g, "").trim(), afterClosingTag) {
|
|
1759
|
+
if (afterClosingTag) {
|
|
1858
1760
|
const remainingOpenMatch = afterClosingTag.match(/^<(field|block|span)([^>]*)>(.*)$/);
|
|
1859
1761
|
if (remainingOpenMatch) {
|
|
1860
1762
|
const [, tagName, attrsStr, content] = remainingOpenMatch, attributes = parseAttributes(attrsStr), remainingNode = {
|
|
@@ -1864,13 +1766,17 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1864
1766
|
attributes,
|
|
1865
1767
|
children: []
|
|
1866
1768
|
}, remainingClosingTag = `</${tagName}>`;
|
|
1867
|
-
if (content.includes(remainingClosingTag))
|
|
1868
|
-
|
|
1769
|
+
if (content.includes(remainingClosingTag)) {
|
|
1770
|
+
const closeIdx = content.indexOf(remainingClosingTag);
|
|
1771
|
+
remainingNode.text = unescapeXml(content.substring(0, closeIdx));
|
|
1772
|
+
const remainder = content.substring(closeIdx + remainingClosingTag.length).trim();
|
|
1773
|
+
if (stack.length > 0) {
|
|
1869
1774
|
const parent = stack[stack.length - 1];
|
|
1870
1775
|
parent && parent.children.push(remainingNode);
|
|
1871
1776
|
} else
|
|
1872
1777
|
root.push(remainingNode);
|
|
1873
|
-
|
|
1778
|
+
remainder && (lines[i] = remainder, i--);
|
|
1779
|
+
} else if (attributes.type === "string" && content)
|
|
1874
1780
|
if (remainingNode.text = unescapeXml(content), stack.length > 0) {
|
|
1875
1781
|
const parent = stack[stack.length - 1];
|
|
1876
1782
|
parent && parent.children.push(remainingNode);
|
|
@@ -1880,8 +1786,13 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1880
1786
|
const contentLines = [];
|
|
1881
1787
|
for (i++; i < lines.length; ) {
|
|
1882
1788
|
const currentLine = lines[i];
|
|
1883
|
-
if (currentLine.
|
|
1789
|
+
if (currentLine.includes(`</${tagName}>`)) {
|
|
1790
|
+
const idx = currentLine.indexOf(`</${tagName}>`), before = currentLine.substring(0, idx).trimEnd();
|
|
1791
|
+
before.trim() && contentLines.push(before);
|
|
1792
|
+
const after = currentLine.substring(idx + `</${tagName}>`.length).trim();
|
|
1793
|
+
after && (lines[i] = after, i--);
|
|
1884
1794
|
break;
|
|
1795
|
+
}
|
|
1885
1796
|
contentLines.push(currentLine), i++;
|
|
1886
1797
|
}
|
|
1887
1798
|
if (remainingNode.text = contentLines.join(`
|
|
@@ -1892,7 +1803,8 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1892
1803
|
root.push(remainingNode);
|
|
1893
1804
|
} else
|
|
1894
1805
|
stack.push(remainingNode);
|
|
1895
|
-
}
|
|
1806
|
+
} else
|
|
1807
|
+
lines[i] = afterClosingTag, i--;
|
|
1896
1808
|
}
|
|
1897
1809
|
continue;
|
|
1898
1810
|
}
|
|
@@ -2009,22 +1921,10 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
2009
1921
|
});
|
|
2010
1922
|
return;
|
|
2011
1923
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
path: path.join("."),
|
|
2017
|
-
attemptedValue: node.text
|
|
2018
|
-
});
|
|
2019
|
-
else if (typeof existingValue == "string") {
|
|
2020
|
-
const value = path.length === 1 ? node.text.trim() : node.text;
|
|
2021
|
-
set(doc, path, value);
|
|
2022
|
-
} else
|
|
2023
|
-
devLog.warn("Skipping text apply at path where value is not a string (prevents structure corruption)", {
|
|
2024
|
-
path: path.join("."),
|
|
2025
|
-
actualType: typeof existingValue
|
|
2026
|
-
});
|
|
2027
|
-
}
|
|
1924
|
+
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", {
|
|
1925
|
+
path: path.join("."),
|
|
1926
|
+
attemptedValue: node.text
|
|
1927
|
+
}));
|
|
2028
1928
|
}, applyBlockChanges = (block, blockNode) => {
|
|
2029
1929
|
if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {
|
|
2030
1930
|
devLog.warn("Block key mismatch, skipping", {
|