lingo.dev 0.117.7 → 0.117.8

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
@@ -3403,8 +3403,8 @@ function serializeElement(node) {
3403
3403
  const attrString = Object.entries(attributes).map(([key, value]) => ` ${key}="${escapeAttributeValue(String(value))}"`).join("");
3404
3404
  const children = Array.isArray(node.$$) ? node.$$ : [];
3405
3405
  if (children.length === 0) {
3406
- const textContent2 = node._ ?? "";
3407
- return `<${name}${attrString}>${textContent2}</${name}>`;
3406
+ const textContent3 = node._ ?? "";
3407
+ return `<${name}${attrString}>${textContent3}</${name}>`;
3408
3408
  }
3409
3409
  const childContent = children.map(serializeElement).join("");
3410
3410
  return `<${name}${attrString}>${childContent}</${name}>`;
@@ -4190,9 +4190,9 @@ function serializeXmlNode(node) {
4190
4190
  const attrString = Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttributeValue2(String(value))}"`).join("");
4191
4191
  const children = node.$$ || [];
4192
4192
  if (children.length === 0) {
4193
- const textContent2 = node._ || "";
4194
- if (textContent2) {
4195
- return `<${name}${attrString}>${textContent2}</${name}>`;
4193
+ const textContent3 = node._ || "";
4194
+ if (textContent3) {
4195
+ return `<${name}${attrString}>${textContent3}</${name}>`;
4196
4196
  }
4197
4197
  return `<${name}${attrString} />`;
4198
4198
  }
@@ -4249,9 +4249,9 @@ function serializeElement2(node, indent2 = "") {
4249
4249
  const attrString = Object.entries(attributes).map(([key, value]) => ` ${key}="${escapeAttributeValue2(String(value))}"`).join("");
4250
4250
  const children = Array.isArray(node.$$) ? node.$$ : [];
4251
4251
  if (children.length === 0) {
4252
- const textContent2 = node._ ?? "";
4253
- if (textContent2) {
4254
- return `${indent2}<${name}${attrString}>${textContent2}</${name}>`;
4252
+ const textContent3 = node._ ?? "";
4253
+ if (textContent3) {
4254
+ return `${indent2}<${name}${attrString}>${textContent3}</${name}>`;
4255
4255
  }
4256
4256
  return `${indent2}<${name}${attrString} />`;
4257
4257
  }
@@ -5810,10 +5810,10 @@ function formatXml(xml) {
5810
5810
  if (cdataNode) {
5811
5811
  return `${indent2}${openTag}<![CDATA[${cdataNode.nodeValue}]]></${tagName}>`;
5812
5812
  }
5813
- const textContent2 = element.textContent?.trim() || "";
5813
+ const textContent3 = element.textContent?.trim() || "";
5814
5814
  const hasOnlyText = element.childNodes.length === 1 && element.childNodes[0].nodeType === 3;
5815
- if (hasOnlyText && textContent2) {
5816
- return `${indent2}${openTag}${textContent2}</${tagName}>`;
5815
+ if (hasOnlyText && textContent3) {
5816
+ return `${indent2}${openTag}${textContent3}</${tagName}>`;
5817
5817
  }
5818
5818
  const children = Array.from(element.children);
5819
5819
  if (children.length === 0) {
@@ -9133,14 +9133,14 @@ function parseEjsForTranslation(input2) {
9133
9133
  if (part.type === "ejs") {
9134
9134
  template += part.content;
9135
9135
  } else {
9136
- const textContent2 = part.content;
9136
+ const textContent3 = part.content;
9137
9137
  const htmlTagRegex = /<[^>]+>/g;
9138
9138
  const textParts = [];
9139
9139
  let lastTextIndex = 0;
9140
9140
  let htmlMatch;
9141
- while ((htmlMatch = htmlTagRegex.exec(textContent2)) !== null) {
9141
+ while ((htmlMatch = htmlTagRegex.exec(textContent3)) !== null) {
9142
9142
  if (htmlMatch.index > lastTextIndex) {
9143
- const textBefore = textContent2.slice(lastTextIndex, htmlMatch.index);
9143
+ const textBefore = textContent3.slice(lastTextIndex, htmlMatch.index);
9144
9144
  if (textBefore.trim()) {
9145
9145
  textParts.push({ type: "text", content: textBefore });
9146
9146
  } else {
@@ -9150,8 +9150,8 @@ function parseEjsForTranslation(input2) {
9150
9150
  textParts.push({ type: "html", content: htmlMatch[0] });
9151
9151
  lastTextIndex = htmlMatch.index + htmlMatch[0].length;
9152
9152
  }
9153
- if (lastTextIndex < textContent2.length) {
9154
- const remainingText = textContent2.slice(lastTextIndex);
9153
+ if (lastTextIndex < textContent3.length) {
9154
+ const remainingText = textContent3.slice(lastTextIndex);
9155
9155
  if (remainingText.trim()) {
9156
9156
  textParts.push({ type: "text", content: remainingText });
9157
9157
  } else {
@@ -9159,11 +9159,11 @@ function parseEjsForTranslation(input2) {
9159
9159
  }
9160
9160
  }
9161
9161
  if (textParts.length === 0) {
9162
- const trimmedContent = textContent2.trim();
9162
+ const trimmedContent = textContent3.trim();
9163
9163
  if (trimmedContent) {
9164
- textParts.push({ type: "text", content: textContent2 });
9164
+ textParts.push({ type: "text", content: textContent3 });
9165
9165
  } else {
9166
- textParts.push({ type: "html", content: textContent2 });
9166
+ textParts.push({ type: "html", content: textContent3 });
9167
9167
  }
9168
9168
  }
9169
9169
  for (const textPart of textParts) {
@@ -9232,6 +9232,368 @@ function createEjsLoader() {
9232
9232
  });
9233
9233
  }
9234
9234
 
9235
+ // src/cli/loaders/twig.ts
9236
+ import * as htmlparser23 from "htmlparser2";
9237
+ import { DomHandler as DomHandler3 } from "domhandler";
9238
+ import * as domutils2 from "domutils";
9239
+ import * as DomSerializer2 from "dom-serializer";
9240
+ function createTwigLoader() {
9241
+ const PHRASING_ELEMENTS = /* @__PURE__ */ new Set([
9242
+ // Text-level semantics
9243
+ "a",
9244
+ "abbr",
9245
+ "b",
9246
+ "bdi",
9247
+ "bdo",
9248
+ "br",
9249
+ "cite",
9250
+ "code",
9251
+ "data",
9252
+ "dfn",
9253
+ "em",
9254
+ "i",
9255
+ "kbd",
9256
+ "mark",
9257
+ "q",
9258
+ "ruby",
9259
+ "s",
9260
+ "samp",
9261
+ "small",
9262
+ "span",
9263
+ "strong",
9264
+ "sub",
9265
+ "sup",
9266
+ "time",
9267
+ "u",
9268
+ "var",
9269
+ "wbr",
9270
+ // Media
9271
+ "audio",
9272
+ "img",
9273
+ "video",
9274
+ "picture",
9275
+ // Interactive
9276
+ "button",
9277
+ "input",
9278
+ "label",
9279
+ "select",
9280
+ "textarea",
9281
+ // Embedded
9282
+ "canvas",
9283
+ "iframe",
9284
+ "object",
9285
+ "svg",
9286
+ "math",
9287
+ // Other
9288
+ "del",
9289
+ "ins",
9290
+ "map",
9291
+ "area"
9292
+ ]);
9293
+ const BLOCK_ELEMENTS = /* @__PURE__ */ new Set([
9294
+ "div",
9295
+ "p",
9296
+ "h1",
9297
+ "h2",
9298
+ "h3",
9299
+ "h4",
9300
+ "h5",
9301
+ "h6",
9302
+ "ul",
9303
+ "ol",
9304
+ "li",
9305
+ "dl",
9306
+ "dt",
9307
+ "dd",
9308
+ "blockquote",
9309
+ "pre",
9310
+ "article",
9311
+ "aside",
9312
+ "nav",
9313
+ "section",
9314
+ "header",
9315
+ "footer",
9316
+ "main",
9317
+ "figure",
9318
+ "figcaption",
9319
+ "table",
9320
+ "thead",
9321
+ "tbody",
9322
+ "tfoot",
9323
+ "tr",
9324
+ "td",
9325
+ "th",
9326
+ "caption",
9327
+ "form",
9328
+ "fieldset",
9329
+ "legend",
9330
+ "details",
9331
+ "summary",
9332
+ "address",
9333
+ "hr",
9334
+ "search",
9335
+ "dialog",
9336
+ "noscript",
9337
+ "title"
9338
+ ]);
9339
+ const UNLOCALIZABLE_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
9340
+ const LOCALIZABLE_ATTRIBUTES2 = {
9341
+ meta: ["content"],
9342
+ img: ["alt", "title"],
9343
+ input: ["placeholder", "title", "aria-label"],
9344
+ textarea: ["placeholder", "title", "aria-label"],
9345
+ button: ["title", "aria-label"],
9346
+ a: ["title", "aria-label"],
9347
+ abbr: ["title"],
9348
+ link: ["title"]
9349
+ };
9350
+ function preprocessTwig(input2) {
9351
+ const twigBlocks = [];
9352
+ let counter = 0;
9353
+ const processed = input2.replace(/\{%[\s\S]*?%\}/g, (match2) => {
9354
+ twigBlocks.push(match2);
9355
+ return `__TWIG_BLOCK_${counter++}__`;
9356
+ });
9357
+ return {
9358
+ processed: processed.replace(/\{#[\s\S]*?#\}/g, (match2) => {
9359
+ twigBlocks.push(match2);
9360
+ return `__TWIG_BLOCK_${counter++}__`;
9361
+ }),
9362
+ twigBlocks
9363
+ };
9364
+ }
9365
+ function postprocessTwig(text, twigBlocks) {
9366
+ return text.replace(/__TWIG_BLOCK_(\d+)__/g, (_37, index) => {
9367
+ return twigBlocks[parseInt(index, 10)] || "";
9368
+ });
9369
+ }
9370
+ return createLoader({
9371
+ async pull(locale, input2) {
9372
+ const result = {};
9373
+ const { processed, twigBlocks } = preprocessTwig(input2);
9374
+ const handler = new DomHandler3();
9375
+ const parser = new htmlparser23.Parser(handler, {
9376
+ lowerCaseTags: false,
9377
+ lowerCaseAttributeNames: false
9378
+ });
9379
+ parser.write(processed);
9380
+ parser.end();
9381
+ const dom = handler.dom;
9382
+ function isInsideUnlocalizableTag(element) {
9383
+ let current = element.parent;
9384
+ while (current && current.type === "tag") {
9385
+ if (UNLOCALIZABLE_TAGS.has(current.name.toLowerCase())) {
9386
+ return true;
9387
+ }
9388
+ current = current.parent;
9389
+ }
9390
+ return false;
9391
+ }
9392
+ function hasTranslatableContent(element) {
9393
+ const text = domutils2.textContent(element);
9394
+ return text.trim().length > 0;
9395
+ }
9396
+ function isLeafBlock(element) {
9397
+ const childElements = element.children.filter(
9398
+ (child) => child.type === "tag"
9399
+ );
9400
+ for (const child of childElements) {
9401
+ if (BLOCK_ELEMENTS.has(child.name.toLowerCase())) {
9402
+ return false;
9403
+ }
9404
+ }
9405
+ return hasTranslatableContent(element);
9406
+ }
9407
+ function getInnerHTML2(element) {
9408
+ const html2 = element.children.map((child) => DomSerializer2.default(child, { encodeEntities: false })).join("");
9409
+ return postprocessTwig(html2, twigBlocks);
9410
+ }
9411
+ function extractAttributes(element, path19) {
9412
+ const tagName = element.name.toLowerCase();
9413
+ const attrs = LOCALIZABLE_ATTRIBUTES2[tagName];
9414
+ if (!attrs) return;
9415
+ for (const attr of attrs) {
9416
+ const value = element.attribs?.[attr];
9417
+ if (value && value.trim()) {
9418
+ const restoredValue = postprocessTwig(value.trim(), twigBlocks);
9419
+ result[`${path19}#${attr}`] = restoredValue;
9420
+ }
9421
+ }
9422
+ }
9423
+ function extractFromElement(element, pathParts) {
9424
+ const path19 = pathParts.join("/");
9425
+ if (isInsideUnlocalizableTag(element)) {
9426
+ return;
9427
+ }
9428
+ extractAttributes(element, path19);
9429
+ const tagName = element.name.toLowerCase();
9430
+ if (BLOCK_ELEMENTS.has(tagName) && isLeafBlock(element)) {
9431
+ const content = getInnerHTML2(element).trim();
9432
+ if (content) {
9433
+ result[path19] = content;
9434
+ }
9435
+ return;
9436
+ }
9437
+ if (PHRASING_ELEMENTS.has(tagName) && hasTranslatableContent(element)) {
9438
+ const content = getInnerHTML2(element).trim();
9439
+ if (content) {
9440
+ result[path19] = content;
9441
+ }
9442
+ return;
9443
+ }
9444
+ let childIndex = 0;
9445
+ const childElements = element.children.filter(
9446
+ (child) => child.type === "tag"
9447
+ );
9448
+ for (const child of childElements) {
9449
+ extractFromElement(child, [...pathParts, childIndex++]);
9450
+ }
9451
+ }
9452
+ const html = domutils2.findOne(
9453
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "html",
9454
+ dom,
9455
+ true
9456
+ );
9457
+ if (html) {
9458
+ const head = domutils2.findOne(
9459
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "head",
9460
+ html.children,
9461
+ true
9462
+ );
9463
+ const body = domutils2.findOne(
9464
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "body",
9465
+ html.children,
9466
+ true
9467
+ );
9468
+ if (head) {
9469
+ let headIndex = 0;
9470
+ const headChildren = head.children.filter(
9471
+ (child) => child.type === "tag"
9472
+ );
9473
+ for (const child of headChildren) {
9474
+ extractFromElement(child, ["head", headIndex++]);
9475
+ }
9476
+ }
9477
+ if (body) {
9478
+ let bodyIndex = 0;
9479
+ const bodyChildren = body.children.filter(
9480
+ (child) => child.type === "tag"
9481
+ );
9482
+ for (const child of bodyChildren) {
9483
+ extractFromElement(child, ["body", bodyIndex++]);
9484
+ }
9485
+ }
9486
+ } else {
9487
+ let rootIndex = 0;
9488
+ const rootElements = dom.filter(
9489
+ (child) => child.type === "tag"
9490
+ );
9491
+ for (const child of rootElements) {
9492
+ extractFromElement(child, [rootIndex++]);
9493
+ }
9494
+ }
9495
+ return result;
9496
+ },
9497
+ async push(locale, data, originalInput) {
9498
+ const { processed, twigBlocks } = preprocessTwig(originalInput || "");
9499
+ const handler = new DomHandler3();
9500
+ const parser = new htmlparser23.Parser(handler, {
9501
+ lowerCaseTags: false,
9502
+ lowerCaseAttributeNames: false
9503
+ });
9504
+ parser.write(processed || "<!DOCTYPE html><html><head></head><body></body></html>");
9505
+ parser.end();
9506
+ const dom = handler.dom;
9507
+ const html = domutils2.findOne(
9508
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "html",
9509
+ dom,
9510
+ true
9511
+ );
9512
+ if (html) {
9513
+ html.attribs = html.attribs || {};
9514
+ html.attribs.lang = locale;
9515
+ }
9516
+ function traverseByIndices(element, indices) {
9517
+ let current = element;
9518
+ for (const indexStr of indices) {
9519
+ if (!current) return null;
9520
+ const index = parseInt(indexStr, 10);
9521
+ const children = current.children.filter(
9522
+ (child) => child.type === "tag"
9523
+ );
9524
+ if (index >= children.length) {
9525
+ return null;
9526
+ }
9527
+ current = children[index];
9528
+ }
9529
+ return current;
9530
+ }
9531
+ function resolvePathToElement(path19) {
9532
+ const parts = path19.split("/");
9533
+ const [rootTag, ...indices] = parts;
9534
+ let current = null;
9535
+ if (html) {
9536
+ if (rootTag === "head") {
9537
+ current = domutils2.findOne(
9538
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "head",
9539
+ html.children,
9540
+ true
9541
+ );
9542
+ } else if (rootTag === "body") {
9543
+ current = domutils2.findOne(
9544
+ (elem) => elem.type === "tag" && elem.name.toLowerCase() === "body",
9545
+ html.children,
9546
+ true
9547
+ );
9548
+ }
9549
+ if (!current) return null;
9550
+ return traverseByIndices(current, indices);
9551
+ } else {
9552
+ const rootElements = dom.filter(
9553
+ (child) => child.type === "tag"
9554
+ );
9555
+ const rootIndex = parseInt(rootTag, 10);
9556
+ if (rootIndex >= rootElements.length) {
9557
+ return null;
9558
+ }
9559
+ current = rootElements[rootIndex];
9560
+ return traverseByIndices(current, indices);
9561
+ }
9562
+ }
9563
+ for (const [path19, value] of Object.entries(data)) {
9564
+ const [nodePath, attribute] = path19.split("#");
9565
+ const element = resolvePathToElement(nodePath);
9566
+ if (!element) {
9567
+ console.warn(`Path not found in original template: ${nodePath}`);
9568
+ continue;
9569
+ }
9570
+ if (attribute) {
9571
+ element.attribs = element.attribs || {};
9572
+ element.attribs[attribute] = value;
9573
+ } else {
9574
+ if (value) {
9575
+ const { processed: processedValue, twigBlocks: valueTwigBlocks } = preprocessTwig(value);
9576
+ const valueHandler = new DomHandler3();
9577
+ const valueParser = new htmlparser23.Parser(valueHandler);
9578
+ valueParser.write(processedValue);
9579
+ valueParser.end();
9580
+ element.children = valueHandler.dom;
9581
+ element.children.forEach((child) => {
9582
+ if (child.type === "text" && child.data) {
9583
+ child.data = postprocessTwig(child.data, valueTwigBlocks);
9584
+ }
9585
+ });
9586
+ } else {
9587
+ element.children = [];
9588
+ }
9589
+ }
9590
+ }
9591
+ const serialized = DomSerializer2.default(dom, { encodeEntities: false });
9592
+ return postprocessTwig(serialized, twigBlocks);
9593
+ }
9594
+ });
9595
+ }
9596
+
9235
9597
  // src/cli/loaders/ensure-key-order.ts
9236
9598
  import _25 from "lodash";
9237
9599
  function createEnsureKeyOrderLoader() {
@@ -9730,6 +10092,16 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
9730
10092
  createIgnoredKeysLoader(ignoredKeys || []),
9731
10093
  createUnlocalizableLoader(options.returnUnlocalizedKeys)
9732
10094
  );
10095
+ case "twig":
10096
+ return composeLoaders(
10097
+ createTextFileLoader(bucketPathPattern),
10098
+ createLockedPatternsLoader(lockedPatterns),
10099
+ createTwigLoader(),
10100
+ createLockedKeysLoader(lockedKeys || []),
10101
+ createIgnoredKeysLoader(ignoredKeys || []),
10102
+ createSyncLoader(),
10103
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
10104
+ );
9733
10105
  case "txt":
9734
10106
  return composeLoaders(
9735
10107
  createTextFileLoader(bucketPathPattern),
@@ -14101,7 +14473,7 @@ async function renderHero2() {
14101
14473
  // package.json
14102
14474
  var package_default = {
14103
14475
  name: "lingo.dev",
14104
- version: "0.117.7",
14476
+ version: "0.117.8",
14105
14477
  description: "Lingo.dev CLI",
14106
14478
  private: false,
14107
14479
  publishConfig: {