@braze/sanity-plugin-ai-translation 0.2.65 → 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 -133
- 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,77 +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
|
-
if (looksLikeXmlResponse(text))
|
|
1567
|
-
try {
|
|
1568
|
-
const wrapped = `<root>${text}</root>`, parsed = new XMLParser({
|
|
1569
|
-
ignoreAttributes: !1,
|
|
1570
|
-
attributeNamePrefix: "@_",
|
|
1571
|
-
textNodeName: "#text",
|
|
1572
|
-
parseTagValue: !1,
|
|
1573
|
-
trimValues: !1,
|
|
1574
|
-
// preserve exact whitespace in text (e.g. trailing space in spans)
|
|
1575
|
-
isArray: (tagName) => TAG_NAMES.includes(tagName)
|
|
1576
|
-
}).parse(wrapped);
|
|
1577
|
-
if (parsed && typeof parsed == "object" && parsed.root) {
|
|
1578
|
-
const nodes = convertXmlObjectToParsedNodes(parsed.root);
|
|
1579
|
-
if (nodes.length > 0)
|
|
1580
|
-
return devLog.debug("Parsed response with XML parser", { rootNodeCount: nodes.length }), nodes;
|
|
1581
|
-
}
|
|
1582
|
-
} catch (err) {
|
|
1583
|
-
devLog.warn("XML parse failed, falling back to regex parser", {
|
|
1584
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
|
-
return parseReviewFormat(text, originalDoc);
|
|
1588
|
-
};
|
|
1589
|
-
function normalizeToArray(value) {
|
|
1590
|
-
return Array.isArray(value) ? value : value != null && typeof value == "object" ? [value] : [];
|
|
1591
|
-
}
|
|
1592
|
-
function convertXmlObjectToParsedNodes(root) {
|
|
1593
|
-
const result = [];
|
|
1594
|
-
for (const tagName of TAG_NAMES) {
|
|
1595
|
-
const raw = root[tagName];
|
|
1596
|
-
if (raw !== void 0) {
|
|
1597
|
-
for (const item of normalizeToArray(raw))
|
|
1598
|
-
if (item != null && typeof item == "object") {
|
|
1599
|
-
const node = convertXmlElementToParsedNode(item, tagName);
|
|
1600
|
-
node && result.push(node);
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
return result;
|
|
1605
|
-
}
|
|
1606
|
-
function convertXmlElementToParsedNode(obj, tagName) {
|
|
1607
|
-
const attributes = {};
|
|
1608
|
-
for (const [key, value] of Object.entries(obj))
|
|
1609
|
-
if (key.startsWith("@_")) {
|
|
1610
|
-
const attrName = key.slice(2);
|
|
1611
|
-
attributes[attrName] = value != null ? String(value) : "";
|
|
1612
|
-
}
|
|
1613
|
-
let text;
|
|
1614
|
-
const textVal = obj["#text"];
|
|
1615
|
-
textVal != null && (text = typeof textVal == "string" ? textVal : String(textVal));
|
|
1616
|
-
const children = [];
|
|
1617
|
-
for (const childTag of TAG_NAMES) {
|
|
1618
|
-
const raw = obj[childTag];
|
|
1619
|
-
if (raw !== void 0) {
|
|
1620
|
-
for (const item of normalizeToArray(raw))
|
|
1621
|
-
if (item != null && typeof item == "object") {
|
|
1622
|
-
const child = convertXmlElementToParsedNode(item, childTag);
|
|
1623
|
-
child && children.push(child);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
return {
|
|
1628
|
-
type: tagName,
|
|
1629
|
-
name: attributes.name,
|
|
1630
|
-
text: text !== void 0 && text !== "" ? unescapeXml(text) : void 0,
|
|
1631
|
-
attributes,
|
|
1632
|
-
children
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
const parseReviewFormat = (text, originalDoc) => {
|
|
1564
|
+
}, parseReviewFormat = (text, originalDoc) => {
|
|
1636
1565
|
const root = [], stack = [], lines = text.split(`
|
|
1637
1566
|
`);
|
|
1638
1567
|
devLog.debug("Parsing response text", {
|
|
@@ -1720,25 +1649,34 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1720
1649
|
attributes,
|
|
1721
1650
|
children: []
|
|
1722
1651
|
}, closingTag = `</${tagName}>`;
|
|
1723
|
-
if (content.
|
|
1724
|
-
const
|
|
1725
|
-
|
|
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) {
|
|
1726
1657
|
const parent = stack[stack.length - 1];
|
|
1727
1658
|
parent && parent.children.push(node);
|
|
1728
1659
|
} else
|
|
1729
1660
|
root.push(node);
|
|
1730
|
-
|
|
1731
|
-
|
|
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) {
|
|
1732
1665
|
const parent = stack[stack.length - 1];
|
|
1733
1666
|
parent && parent.children.push(node);
|
|
1734
1667
|
} else
|
|
1735
1668
|
root.push(node);
|
|
1736
|
-
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.")) {
|
|
1737
1670
|
const contentLines = [];
|
|
1738
1671
|
for (i++; i < lines.length; ) {
|
|
1739
1672
|
const currentLine = lines[i];
|
|
1740
|
-
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--);
|
|
1741
1678
|
break;
|
|
1679
|
+
}
|
|
1742
1680
|
contentLines.push(currentLine), i++;
|
|
1743
1681
|
}
|
|
1744
1682
|
if (node.text = contentLines.join(`
|
|
@@ -1747,19 +1685,15 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1747
1685
|
parent && parent.children.push(node);
|
|
1748
1686
|
} else
|
|
1749
1687
|
root.push(node);
|
|
1750
|
-
} else if (attributes.type === "string" && content && content.includes(closingTag))
|
|
1751
|
-
if (node.text = unescapeXml(content.replace(closingTag, "")), stack.length > 0) {
|
|
1752
|
-
const parent = stack[stack.length - 1];
|
|
1753
|
-
parent && parent.children.push(node);
|
|
1754
|
-
} else
|
|
1755
|
-
root.push(node);
|
|
1756
|
-
else if (attributes.type === "string" && content && !content.includes(closingTag)) {
|
|
1688
|
+
} else if (attributes.type === "string" && content && !content.includes(closingTag)) {
|
|
1757
1689
|
const contentLines = [content];
|
|
1758
1690
|
for (i++; i < lines.length; ) {
|
|
1759
1691
|
const currentLine = lines[i];
|
|
1760
1692
|
if (currentLine.includes(closingTag)) {
|
|
1761
|
-
const closingIndex = currentLine.indexOf(closingTag), remainingContent = currentLine.substring(0, closingIndex);
|
|
1693
|
+
const closingIndex = currentLine.indexOf(closingTag), remainingContent = currentLine.substring(0, closingIndex).trimEnd();
|
|
1762
1694
|
remainingContent && contentLines.push(remainingContent);
|
|
1695
|
+
const after = currentLine.substring(closingIndex + closingTag.length).trim();
|
|
1696
|
+
after && (lines[i] = after, i--);
|
|
1763
1697
|
break;
|
|
1764
1698
|
}
|
|
1765
1699
|
contentLines.push(currentLine), i++;
|
|
@@ -1774,8 +1708,13 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1774
1708
|
const contentLines = [];
|
|
1775
1709
|
for (i++; i < lines.length; ) {
|
|
1776
1710
|
const currentLine = lines[i];
|
|
1777
|
-
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--);
|
|
1778
1716
|
break;
|
|
1717
|
+
}
|
|
1779
1718
|
contentLines.push(currentLine), i++;
|
|
1780
1719
|
}
|
|
1781
1720
|
if (node.text = contentLines.join(`
|
|
@@ -1788,32 +1727,36 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1788
1727
|
stack.push(node);
|
|
1789
1728
|
continue;
|
|
1790
1729
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
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", {
|
|
1795
1744
|
line: i,
|
|
1796
|
-
tagName:
|
|
1797
|
-
trimmed
|
|
1745
|
+
tagName: match[1],
|
|
1746
|
+
trimmed: afterClosingTag,
|
|
1747
|
+
stackLength: stack.length,
|
|
1748
|
+
stackTopName: stack[stack.length - 1]?.name
|
|
1798
1749
|
});
|
|
1799
|
-
|
|
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();
|
|
1800
1758
|
}
|
|
1801
|
-
|
|
1802
|
-
line: i,
|
|
1803
|
-
tagName: closeMatch[1],
|
|
1804
|
-
trimmed,
|
|
1805
|
-
stackLength: stack.length,
|
|
1806
|
-
stackTopName: stack[stack.length - 1]?.name
|
|
1807
|
-
});
|
|
1808
|
-
const node = stack.pop();
|
|
1809
|
-
if (node)
|
|
1810
|
-
if (stack.length > 0) {
|
|
1811
|
-
const parent = stack[stack.length - 1];
|
|
1812
|
-
parent && parent.children.push(node);
|
|
1813
|
-
} else
|
|
1814
|
-
root.push(node);
|
|
1815
|
-
let afterClosingTag = trimmed.substring(closeMatch[0].length).trim();
|
|
1816
|
-
if (afterClosingTag = afterClosingTag.replace(/<!--[\s\S]*?-->/g, "").trim(), afterClosingTag) {
|
|
1759
|
+
if (afterClosingTag) {
|
|
1817
1760
|
const remainingOpenMatch = afterClosingTag.match(/^<(field|block|span)([^>]*)>(.*)$/);
|
|
1818
1761
|
if (remainingOpenMatch) {
|
|
1819
1762
|
const [, tagName, attrsStr, content] = remainingOpenMatch, attributes = parseAttributes(attrsStr), remainingNode = {
|
|
@@ -1823,13 +1766,17 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1823
1766
|
attributes,
|
|
1824
1767
|
children: []
|
|
1825
1768
|
}, remainingClosingTag = `</${tagName}>`;
|
|
1826
|
-
if (content.includes(remainingClosingTag))
|
|
1827
|
-
|
|
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) {
|
|
1828
1774
|
const parent = stack[stack.length - 1];
|
|
1829
1775
|
parent && parent.children.push(remainingNode);
|
|
1830
1776
|
} else
|
|
1831
1777
|
root.push(remainingNode);
|
|
1832
|
-
|
|
1778
|
+
remainder && (lines[i] = remainder, i--);
|
|
1779
|
+
} else if (attributes.type === "string" && content)
|
|
1833
1780
|
if (remainingNode.text = unescapeXml(content), stack.length > 0) {
|
|
1834
1781
|
const parent = stack[stack.length - 1];
|
|
1835
1782
|
parent && parent.children.push(remainingNode);
|
|
@@ -1839,8 +1786,13 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1839
1786
|
const contentLines = [];
|
|
1840
1787
|
for (i++; i < lines.length; ) {
|
|
1841
1788
|
const currentLine = lines[i];
|
|
1842
|
-
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--);
|
|
1843
1794
|
break;
|
|
1795
|
+
}
|
|
1844
1796
|
contentLines.push(currentLine), i++;
|
|
1845
1797
|
}
|
|
1846
1798
|
if (remainingNode.text = contentLines.join(`
|
|
@@ -1851,7 +1803,8 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1851
1803
|
root.push(remainingNode);
|
|
1852
1804
|
} else
|
|
1853
1805
|
stack.push(remainingNode);
|
|
1854
|
-
}
|
|
1806
|
+
} else
|
|
1807
|
+
lines[i] = afterClosingTag, i--;
|
|
1855
1808
|
}
|
|
1856
1809
|
continue;
|
|
1857
1810
|
}
|
|
@@ -1968,22 +1921,10 @@ const parseReviewFormat = (text, originalDoc) => {
|
|
|
1968
1921
|
});
|
|
1969
1922
|
return;
|
|
1970
1923
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
path: path.join("."),
|
|
1976
|
-
attemptedValue: node.text
|
|
1977
|
-
});
|
|
1978
|
-
else if (typeof existingValue == "string") {
|
|
1979
|
-
const value = path.length === 1 ? node.text.trim() : node.text;
|
|
1980
|
-
set(doc, path, value);
|
|
1981
|
-
} else
|
|
1982
|
-
devLog.warn("Skipping text apply at path where value is not a string (prevents structure corruption)", {
|
|
1983
|
-
path: path.join("."),
|
|
1984
|
-
actualType: typeof existingValue
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
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
|
+
}));
|
|
1987
1928
|
}, applyBlockChanges = (block, blockNode) => {
|
|
1988
1929
|
if (blockNode.attributes._key && block._key !== blockNode.attributes._key) {
|
|
1989
1930
|
devLog.warn("Block key mismatch, skipping", {
|