lingo.dev 0.117.4 → 0.117.5

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/build/cli.mjs CHANGED
@@ -3410,8 +3410,8 @@ function serializeElement(node) {
3410
3410
  const attrString = Object.entries(attributes).map(([key, value]) => ` ${key}="${escapeAttributeValue(String(value))}"`).join("");
3411
3411
  const children = Array.isArray(node.$$) ? node.$$ : [];
3412
3412
  if (children.length === 0) {
3413
- const textContent = node._ ?? "";
3414
- return `<${name}${attrString}>${textContent}</${name}>`;
3413
+ const textContent2 = node._ ?? "";
3414
+ return `<${name}${attrString}>${textContent2}</${name}>`;
3415
3415
  }
3416
3416
  const childContent = children.map(serializeElement).join("");
3417
3417
  return `<${name}${attrString}>${childContent}</${name}>`;
@@ -3500,135 +3500,335 @@ function createPullOutputCleaner() {
3500
3500
  }
3501
3501
 
3502
3502
  // src/cli/loaders/html.ts
3503
- import { JSDOM } from "jsdom";
3504
- function normalizeTextContent(text, isStandalone) {
3505
- const trimmed = text.trim();
3506
- if (!trimmed) return "";
3507
- return trimmed;
3508
- }
3503
+ import * as htmlparser2 from "htmlparser2";
3504
+ import { DomHandler } from "domhandler";
3505
+ import * as domutils from "domutils";
3506
+ import * as DomSerializer from "dom-serializer";
3509
3507
  function createHtmlLoader() {
3508
+ const PHRASING_ELEMENTS = /* @__PURE__ */ new Set([
3509
+ // Text-level semantics
3510
+ "a",
3511
+ "abbr",
3512
+ "b",
3513
+ "bdi",
3514
+ "bdo",
3515
+ "br",
3516
+ "cite",
3517
+ "code",
3518
+ "data",
3519
+ "dfn",
3520
+ "em",
3521
+ "i",
3522
+ "kbd",
3523
+ "mark",
3524
+ "q",
3525
+ "ruby",
3526
+ "s",
3527
+ "samp",
3528
+ "small",
3529
+ "span",
3530
+ "strong",
3531
+ "sub",
3532
+ "sup",
3533
+ "time",
3534
+ "u",
3535
+ "var",
3536
+ "wbr",
3537
+ // Media
3538
+ "audio",
3539
+ "img",
3540
+ "video",
3541
+ "picture",
3542
+ // Interactive
3543
+ "button",
3544
+ "input",
3545
+ "label",
3546
+ "select",
3547
+ "textarea",
3548
+ // Embedded
3549
+ "canvas",
3550
+ "iframe",
3551
+ "object",
3552
+ "svg",
3553
+ "math",
3554
+ // Other
3555
+ "del",
3556
+ "ins",
3557
+ "map",
3558
+ "area"
3559
+ ]);
3560
+ const BLOCK_ELEMENTS = /* @__PURE__ */ new Set([
3561
+ "div",
3562
+ "p",
3563
+ "h1",
3564
+ "h2",
3565
+ "h3",
3566
+ "h4",
3567
+ "h5",
3568
+ "h6",
3569
+ "ul",
3570
+ "ol",
3571
+ "li",
3572
+ "dl",
3573
+ "dt",
3574
+ "dd",
3575
+ "blockquote",
3576
+ "pre",
3577
+ "article",
3578
+ "aside",
3579
+ "nav",
3580
+ "section",
3581
+ "header",
3582
+ "footer",
3583
+ "main",
3584
+ "figure",
3585
+ "figcaption",
3586
+ "table",
3587
+ "thead",
3588
+ "tbody",
3589
+ "tfoot",
3590
+ "tr",
3591
+ "td",
3592
+ "th",
3593
+ "caption",
3594
+ "form",
3595
+ "fieldset",
3596
+ "legend",
3597
+ "details",
3598
+ "summary",
3599
+ "address",
3600
+ "hr",
3601
+ "search",
3602
+ "dialog",
3603
+ "noscript",
3604
+ "title"
3605
+ // <title> should be treated as a block element for translation
3606
+ ]);
3607
+ const UNLOCALIZABLE_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
3510
3608
  const LOCALIZABLE_ATTRIBUTES = {
3511
3609
  meta: ["content"],
3512
- img: ["alt"],
3513
- input: ["placeholder"],
3514
- a: ["title"]
3610
+ img: ["alt", "title"],
3611
+ input: ["placeholder", "title"],
3612
+ textarea: ["placeholder", "title"],
3613
+ a: ["title"],
3614
+ abbr: ["title"],
3615
+ button: ["title"],
3616
+ link: ["title"]
3515
3617
  };
3516
- const UNLOCALIZABLE_TAGS = ["script", "style"];
3517
3618
  return createLoader({
3518
3619
  async pull(locale, input2) {
3519
3620
  const result = {};
3520
- const dom = new JSDOM(input2);
3521
- const document = dom.window.document;
3522
- const getPath = (node, attribute) => {
3523
- const indices = [];
3524
- let current = node;
3525
- let rootParent = "";
3526
- while (current) {
3527
- const parent = current.parentElement;
3528
- if (!parent) break;
3529
- if (parent === document.documentElement) {
3530
- rootParent = current.nodeName.toLowerCase();
3531
- break;
3621
+ const handler = new DomHandler();
3622
+ const parser = new htmlparser2.Parser(handler, {
3623
+ lowerCaseTags: false,
3624
+ lowerCaseAttributeNames: false
3625
+ });
3626
+ parser.write(input2);
3627
+ parser.end();
3628
+ const dom = handler.dom;
3629
+ function isInsideUnlocalizableTag(element) {
3630
+ let current = element.parent;
3631
+ while (current && current.type === "tag") {
3632
+ if (UNLOCALIZABLE_TAGS.has(current.name.toLowerCase())) {
3633
+ return true;
3532
3634
  }
3533
- const siblings = Array.from(parent.childNodes).filter(
3534
- (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
3535
- );
3536
- const index = siblings.indexOf(current);
3537
- if (index !== -1) {
3538
- indices.unshift(index);
3635
+ current = current.parent;
3636
+ }
3637
+ return false;
3638
+ }
3639
+ function hasTranslatableContent(element) {
3640
+ const text = domutils.textContent(element);
3641
+ return text.trim().length > 0;
3642
+ }
3643
+ function isLeafBlock(element) {
3644
+ const childElements = element.children.filter(
3645
+ (child) => child.type === "tag"
3646
+ );
3647
+ for (const child of childElements) {
3648
+ if (BLOCK_ELEMENTS.has(child.name.toLowerCase())) {
3649
+ return false;
3539
3650
  }
3540
- current = parent;
3541
3651
  }
3542
- const basePath = rootParent ? `${rootParent}/${indices.join("/")}` : indices.join("/");
3543
- return attribute ? `${basePath}#${attribute}` : basePath;
3544
- };
3545
- const processNode = (node) => {
3546
- let parent = node.parentElement;
3547
- while (parent) {
3548
- if (UNLOCALIZABLE_TAGS.includes(parent.tagName.toLowerCase())) {
3549
- return;
3652
+ return hasTranslatableContent(element);
3653
+ }
3654
+ function getInnerHTML(element) {
3655
+ return element.children.map((child) => DomSerializer.default(child, { encodeEntities: false })).join("");
3656
+ }
3657
+ function extractAttributes(element, path19) {
3658
+ const tagName = element.name.toLowerCase();
3659
+ const attrs = LOCALIZABLE_ATTRIBUTES[tagName];
3660
+ if (!attrs) return;
3661
+ for (const attr of attrs) {
3662
+ const value = element.attribs?.[attr];
3663
+ if (value && value.trim()) {
3664
+ result[`${path19}#${attr}`] = value.trim();
3550
3665
  }
3551
- parent = parent.parentElement;
3552
3666
  }
3553
- if (node.nodeType === 3) {
3554
- const text = node.textContent || "";
3555
- const normalizedText = normalizeTextContent(text, true);
3556
- if (normalizedText) {
3557
- result[getPath(node)] = normalizedText;
3667
+ }
3668
+ function extractFromElement(element, pathParts) {
3669
+ const path19 = pathParts.join("/");
3670
+ if (isInsideUnlocalizableTag(element)) {
3671
+ return;
3672
+ }
3673
+ extractAttributes(element, path19);
3674
+ const tagName = element.name.toLowerCase();
3675
+ if (BLOCK_ELEMENTS.has(tagName) && isLeafBlock(element)) {
3676
+ const content = getInnerHTML(element).trim();
3677
+ if (content) {
3678
+ result[path19] = content;
3558
3679
  }
3559
- } else if (node.nodeType === 1) {
3560
- const element = node;
3561
- const tagName = element.tagName.toLowerCase();
3562
- const attributes = LOCALIZABLE_ATTRIBUTES[tagName] || [];
3563
- attributes.forEach((attr) => {
3564
- const value = element.getAttribute(attr);
3565
- if (value) {
3566
- result[getPath(element, attr)] = value;
3567
- }
3568
- });
3569
- Array.from(element.childNodes).filter(
3570
- (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
3571
- ).forEach(processNode);
3680
+ return;
3572
3681
  }
3573
- };
3574
- Array.from(document.head.childNodes).filter(
3575
- (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
3576
- ).forEach(processNode);
3577
- Array.from(document.body.childNodes).filter(
3578
- (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
3579
- ).forEach(processNode);
3682
+ if (PHRASING_ELEMENTS.has(tagName) && hasTranslatableContent(element)) {
3683
+ const content = getInnerHTML(element).trim();
3684
+ if (content) {
3685
+ result[path19] = content;
3686
+ }
3687
+ return;
3688
+ }
3689
+ let childIndex = 0;
3690
+ const childElements = element.children.filter(
3691
+ (child) => child.type === "tag"
3692
+ );
3693
+ for (const child of childElements) {
3694
+ extractFromElement(child, [...pathParts, childIndex++]);
3695
+ }
3696
+ }
3697
+ const html = domutils.findOne(
3698
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "html",
3699
+ dom,
3700
+ true
3701
+ );
3702
+ if (html) {
3703
+ const head = domutils.findOne(
3704
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "head",
3705
+ html.children,
3706
+ true
3707
+ );
3708
+ const body = domutils.findOne(
3709
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "body",
3710
+ html.children,
3711
+ true
3712
+ );
3713
+ if (head) {
3714
+ let headIndex = 0;
3715
+ const headChildren = head.children.filter(
3716
+ (child) => child.type === "tag"
3717
+ );
3718
+ for (const child of headChildren) {
3719
+ extractFromElement(child, ["head", headIndex++]);
3720
+ }
3721
+ }
3722
+ if (body) {
3723
+ let bodyIndex = 0;
3724
+ const bodyChildren = body.children.filter(
3725
+ (child) => child.type === "tag"
3726
+ );
3727
+ for (const child of bodyChildren) {
3728
+ extractFromElement(child, ["body", bodyIndex++]);
3729
+ }
3730
+ }
3731
+ } else {
3732
+ let rootIndex = 0;
3733
+ const rootElements = dom.filter(
3734
+ (child) => child.type === "tag"
3735
+ );
3736
+ for (const child of rootElements) {
3737
+ extractFromElement(child, [rootIndex++]);
3738
+ }
3739
+ }
3580
3740
  return result;
3581
3741
  },
3582
3742
  async push(locale, data, originalInput) {
3583
- const dom = new JSDOM(
3743
+ const handler = new DomHandler();
3744
+ const parser = new htmlparser2.Parser(handler, {
3745
+ lowerCaseTags: false,
3746
+ lowerCaseAttributeNames: false
3747
+ });
3748
+ parser.write(
3584
3749
  originalInput ?? "<!DOCTYPE html><html><head></head><body></body></html>"
3585
3750
  );
3586
- const document = dom.window.document;
3587
- const htmlElement = document.documentElement;
3588
- htmlElement.setAttribute("lang", locale);
3589
- const paths = Object.keys(data).sort((a, b) => {
3590
- const aDepth = a.split("/").length;
3591
- const bDepth = b.split("/").length;
3592
- return aDepth - bDepth;
3593
- });
3594
- paths.forEach((path19) => {
3595
- const value = data[path19];
3596
- const [nodePath, attribute] = path19.split("#");
3597
- const [rootTag, ...indices] = nodePath.split("/");
3598
- let parent = rootTag === "head" ? document.head : document.body;
3599
- let current = parent;
3600
- for (let i = 0; i < indices.length; i++) {
3601
- const index = parseInt(indices[i]);
3602
- const siblings = Array.from(parent.childNodes).filter(
3603
- (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
3751
+ parser.end();
3752
+ const dom = handler.dom;
3753
+ const html = domutils.findOne(
3754
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "html",
3755
+ dom,
3756
+ true
3757
+ );
3758
+ if (html) {
3759
+ html.attribs = html.attribs || {};
3760
+ html.attribs.lang = locale;
3761
+ }
3762
+ function traverseByIndices(element, indices) {
3763
+ let current = element;
3764
+ for (const indexStr of indices) {
3765
+ if (!current) return null;
3766
+ const index = parseInt(indexStr, 10);
3767
+ const children = current.children.filter(
3768
+ (child) => child.type === "tag"
3604
3769
  );
3605
- if (index >= siblings.length) {
3606
- if (i === indices.length - 1) {
3607
- const textNode = document.createTextNode("");
3608
- parent.appendChild(textNode);
3609
- current = textNode;
3610
- } else {
3611
- const element = document.createElement("div");
3612
- parent.appendChild(element);
3613
- current = element;
3614
- parent = element;
3615
- }
3616
- } else {
3617
- current = siblings[index];
3618
- if (current.nodeType === 1) {
3619
- parent = current;
3620
- }
3770
+ if (index >= children.length) {
3771
+ return null;
3621
3772
  }
3773
+ current = children[index];
3774
+ }
3775
+ return current;
3776
+ }
3777
+ function resolvePathToElement(path19) {
3778
+ const parts = path19.split("/");
3779
+ const [rootTag, ...indices] = parts;
3780
+ let current = null;
3781
+ if (html) {
3782
+ if (rootTag === "head") {
3783
+ current = domutils.findOne(
3784
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "head",
3785
+ html.children,
3786
+ true
3787
+ );
3788
+ } else if (rootTag === "body") {
3789
+ current = domutils.findOne(
3790
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "body",
3791
+ html.children,
3792
+ true
3793
+ );
3794
+ }
3795
+ if (!current) return null;
3796
+ return traverseByIndices(current, indices);
3797
+ } else {
3798
+ const rootElements = dom.filter(
3799
+ (child) => child.type === "tag"
3800
+ );
3801
+ const rootIndex = parseInt(rootTag, 10);
3802
+ if (rootIndex >= rootElements.length) {
3803
+ return null;
3804
+ }
3805
+ current = rootElements[rootIndex];
3806
+ return traverseByIndices(current, indices);
3622
3807
  }
3623
- if (current) {
3624
- if (attribute) {
3625
- current.setAttribute(attribute, value);
3808
+ }
3809
+ for (const [path19, value] of Object.entries(data)) {
3810
+ const [nodePath, attribute] = path19.split("#");
3811
+ const element = resolvePathToElement(nodePath);
3812
+ if (!element) {
3813
+ console.warn(`Path not found in original HTML: ${nodePath}`);
3814
+ continue;
3815
+ }
3816
+ if (attribute) {
3817
+ element.attribs = element.attribs || {};
3818
+ element.attribs[attribute] = value;
3819
+ } else {
3820
+ if (value) {
3821
+ const valueHandler = new DomHandler();
3822
+ const valueParser = new htmlparser2.Parser(valueHandler);
3823
+ valueParser.write(value);
3824
+ valueParser.end();
3825
+ element.children = valueHandler.dom;
3626
3826
  } else {
3627
- current.textContent = value;
3827
+ element.children = [];
3628
3828
  }
3629
3829
  }
3630
- });
3631
- return dom.serialize();
3830
+ }
3831
+ return DomSerializer.default(dom, { encodeEntities: false });
3632
3832
  }
3633
3833
  });
3634
3834
  }
@@ -4074,7 +4274,7 @@ function escapeString(str) {
4074
4274
  }
4075
4275
 
4076
4276
  // src/cli/loaders/xcode-strings/parser.ts
4077
- var Parser = class {
4277
+ var Parser2 = class {
4078
4278
  tokens;
4079
4279
  pos;
4080
4280
  constructor(tokens) {
@@ -4147,7 +4347,7 @@ function createXcodeStringsLoader() {
4147
4347
  async pull(locale, input2) {
4148
4348
  const tokenizer = new Tokenizer(input2);
4149
4349
  const tokens = tokenizer.tokenize();
4150
- const parser = new Parser(tokens);
4350
+ const parser = new Parser2(tokens);
4151
4351
  const result = parser.parse();
4152
4352
  return result;
4153
4353
  },
@@ -5011,7 +5211,7 @@ function preserveCommentOrder(section, originalSection) {
5011
5211
  }
5012
5212
 
5013
5213
  // src/cli/loaders/xliff.ts
5014
- import { JSDOM as JSDOM2 } from "jsdom";
5214
+ import { JSDOM } from "jsdom";
5015
5215
  function createXliffLoader() {
5016
5216
  return createLoader({
5017
5217
  async pull(locale, input2, _ctx, originalLocale) {
@@ -5020,7 +5220,7 @@ function createXliffLoader() {
5020
5220
  return createEmptyResult(originalLocale, locale);
5021
5221
  }
5022
5222
  try {
5023
- const dom = new JSDOM2(trimmedInput, { contentType: "text/xml" });
5223
+ const dom = new JSDOM(trimmedInput, { contentType: "text/xml" });
5024
5224
  const document = dom.window.document;
5025
5225
  const parserError = document.querySelector("parsererror");
5026
5226
  if (parserError) {
@@ -5046,7 +5246,7 @@ function createXliffLoader() {
5046
5246
  return pushNewFile(locale, translations, originalLocale);
5047
5247
  }
5048
5248
  try {
5049
- const dom = new JSDOM2(originalInput, { contentType: "text/xml" });
5249
+ const dom = new JSDOM(originalInput, { contentType: "text/xml" });
5050
5250
  const document = dom.window.document;
5051
5251
  const xliffElement = document.documentElement;
5052
5252
  const version = xliffElement.getAttribute("version") || "1.2";
@@ -5346,7 +5546,7 @@ ${serialized}`;
5346
5546
  return serialized;
5347
5547
  }
5348
5548
  function formatXml(xml) {
5349
- const dom = new JSDOM2(xml, { contentType: "text/xml" });
5549
+ const dom = new JSDOM(xml, { contentType: "text/xml" });
5350
5550
  const doc = dom.window.document;
5351
5551
  function formatElement(element, depth = 0) {
5352
5552
  const indent2 = " ".repeat(depth);
@@ -5359,10 +5559,10 @@ function formatXml(xml) {
5359
5559
  if (cdataNode) {
5360
5560
  return `${indent2}${openTag}<![CDATA[${cdataNode.nodeValue}]]></${tagName}>`;
5361
5561
  }
5362
- const textContent = element.textContent?.trim() || "";
5562
+ const textContent2 = element.textContent?.trim() || "";
5363
5563
  const hasOnlyText = element.childNodes.length === 1 && element.childNodes[0].nodeType === 3;
5364
- if (hasOnlyText && textContent) {
5365
- return `${indent2}${openTag}${textContent}</${tagName}>`;
5564
+ if (hasOnlyText && textContent2) {
5565
+ return `${indent2}${openTag}${textContent2}</${tagName}>`;
5366
5566
  }
5367
5567
  const children = Array.from(element.children);
5368
5568
  if (children.length === 0) {
@@ -5389,7 +5589,7 @@ function pushNewFile(locale, translations, originalLocale) {
5389
5589
  <body></body>
5390
5590
  </file>
5391
5591
  </xliff>`;
5392
- const dom = new JSDOM2(skeleton, { contentType: "text/xml" });
5592
+ const dom = new JSDOM(skeleton, { contentType: "text/xml" });
5393
5593
  const document = dom.window.document;
5394
5594
  const bodyElement = document.querySelector("body");
5395
5595
  Object.entries(translations).forEach(([key, value]) => {
@@ -8682,14 +8882,14 @@ function parseEjsForTranslation(input2) {
8682
8882
  if (part.type === "ejs") {
8683
8883
  template += part.content;
8684
8884
  } else {
8685
- const textContent = part.content;
8885
+ const textContent2 = part.content;
8686
8886
  const htmlTagRegex = /<[^>]+>/g;
8687
8887
  const textParts = [];
8688
8888
  let lastTextIndex = 0;
8689
8889
  let htmlMatch;
8690
- while ((htmlMatch = htmlTagRegex.exec(textContent)) !== null) {
8890
+ while ((htmlMatch = htmlTagRegex.exec(textContent2)) !== null) {
8691
8891
  if (htmlMatch.index > lastTextIndex) {
8692
- const textBefore = textContent.slice(lastTextIndex, htmlMatch.index);
8892
+ const textBefore = textContent2.slice(lastTextIndex, htmlMatch.index);
8693
8893
  if (textBefore.trim()) {
8694
8894
  textParts.push({ type: "text", content: textBefore });
8695
8895
  } else {
@@ -8699,8 +8899,8 @@ function parseEjsForTranslation(input2) {
8699
8899
  textParts.push({ type: "html", content: htmlMatch[0] });
8700
8900
  lastTextIndex = htmlMatch.index + htmlMatch[0].length;
8701
8901
  }
8702
- if (lastTextIndex < textContent.length) {
8703
- const remainingText = textContent.slice(lastTextIndex);
8902
+ if (lastTextIndex < textContent2.length) {
8903
+ const remainingText = textContent2.slice(lastTextIndex);
8704
8904
  if (remainingText.trim()) {
8705
8905
  textParts.push({ type: "text", content: remainingText });
8706
8906
  } else {
@@ -8708,11 +8908,11 @@ function parseEjsForTranslation(input2) {
8708
8908
  }
8709
8909
  }
8710
8910
  if (textParts.length === 0) {
8711
- const trimmedContent = textContent.trim();
8911
+ const trimmedContent = textContent2.trim();
8712
8912
  if (trimmedContent) {
8713
- textParts.push({ type: "text", content: textContent });
8913
+ textParts.push({ type: "text", content: textContent2 });
8714
8914
  } else {
8715
- textParts.push({ type: "html", content: textContent });
8915
+ textParts.push({ type: "html", content: textContent2 });
8716
8916
  }
8717
8917
  }
8718
8918
  for (const textPart of textParts) {
@@ -13639,7 +13839,7 @@ async function renderHero2() {
13639
13839
  // package.json
13640
13840
  var package_default = {
13641
13841
  name: "lingo.dev",
13642
- version: "0.117.4",
13842
+ version: "0.117.5",
13643
13843
  description: "Lingo.dev CLI",
13644
13844
  private: false,
13645
13845
  publishConfig: {
@@ -13797,6 +13997,9 @@ var package_default = {
13797
13997
  "date-fns": "4.1.0",
13798
13998
  dedent: "1.7.0",
13799
13999
  diff: "7.0.0",
14000
+ "dom-serializer": "2.0.0",
14001
+ domhandler: "5.0.3",
14002
+ domutils: "3.2.2",
13800
14003
  dotenv: "16.4.7",
13801
14004
  ejs: "3.1.10",
13802
14005
  express: "5.1.0",
@@ -13807,6 +14010,7 @@ var package_default = {
13807
14010
  glob: "11.1.0",
13808
14011
  "gradient-string": "3.0.0",
13809
14012
  "gray-matter": "4.0.3",
14013
+ htmlparser2: "10.0.0",
13810
14014
  ini: "5.0.0",
13811
14015
  ink: "4.2.0",
13812
14016
  "ink-progress-bar": "3.0.0",