lingo.dev 0.117.6 → 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}>`;
@@ -3598,7 +3598,7 @@ function createHtmlLoader() {
3598
3598
  // <title> should be treated as a block element for translation
3599
3599
  ]);
3600
3600
  const UNLOCALIZABLE_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
3601
- const LOCALIZABLE_ATTRIBUTES = {
3601
+ const LOCALIZABLE_ATTRIBUTES2 = {
3602
3602
  meta: ["content"],
3603
3603
  img: ["alt", "title"],
3604
3604
  input: ["placeholder", "title"],
@@ -3644,12 +3644,12 @@ function createHtmlLoader() {
3644
3644
  }
3645
3645
  return hasTranslatableContent(element);
3646
3646
  }
3647
- function getInnerHTML(element) {
3647
+ function getInnerHTML2(element) {
3648
3648
  return element.children.map((child) => DomSerializer.default(child, { encodeEntities: false })).join("");
3649
3649
  }
3650
3650
  function extractAttributes(element, path19) {
3651
3651
  const tagName = element.name.toLowerCase();
3652
- const attrs = LOCALIZABLE_ATTRIBUTES[tagName];
3652
+ const attrs = LOCALIZABLE_ATTRIBUTES2[tagName];
3653
3653
  if (!attrs) return;
3654
3654
  for (const attr of attrs) {
3655
3655
  const value = element.attribs?.[attr];
@@ -3666,14 +3666,14 @@ function createHtmlLoader() {
3666
3666
  extractAttributes(element, path19);
3667
3667
  const tagName = element.name.toLowerCase();
3668
3668
  if (BLOCK_ELEMENTS.has(tagName) && isLeafBlock(element)) {
3669
- const content = getInnerHTML(element).trim();
3669
+ const content = getInnerHTML2(element).trim();
3670
3670
  if (content) {
3671
3671
  result[path19] = content;
3672
3672
  }
3673
3673
  return;
3674
3674
  }
3675
3675
  if (PHRASING_ELEMENTS.has(tagName) && hasTranslatableContent(element)) {
3676
- const content = getInnerHTML(element).trim();
3676
+ const content = getInnerHTML2(element).trim();
3677
3677
  if (content) {
3678
3678
  result[path19] = content;
3679
3679
  }
@@ -4004,6 +4004,264 @@ function applyTranslations(node, path19, data, pathMap) {
4004
4004
  }
4005
4005
  }
4006
4006
 
4007
+ // src/cli/loaders/mjml.ts
4008
+ import { parseStringPromise as parseStringPromise2 } from "xml2js";
4009
+ import * as htmlparser22 from "htmlparser2";
4010
+ import { DomHandler as DomHandler2 } from "domhandler";
4011
+ var LOCALIZABLE_COMPONENTS = [
4012
+ "mj-text",
4013
+ "mj-button",
4014
+ "mj-title",
4015
+ "mj-preview",
4016
+ "mj-navbar-link",
4017
+ "mj-accordion-title",
4018
+ "mj-accordion-text",
4019
+ "p",
4020
+ "h1",
4021
+ "h2",
4022
+ "h3",
4023
+ "h4",
4024
+ "h5",
4025
+ "h6",
4026
+ "li"
4027
+ ];
4028
+ var LOCALIZABLE_ATTRIBUTES = {
4029
+ "mj-image": ["alt", "title"],
4030
+ "mj-button": ["title", "aria-label"],
4031
+ "mj-social-element": ["title", "alt"],
4032
+ "img": ["alt", "title"],
4033
+ "a": ["title", "aria-label"]
4034
+ };
4035
+ function createMjmlLoader() {
4036
+ return createLoader({
4037
+ async pull(locale, input2) {
4038
+ const result = {};
4039
+ try {
4040
+ const parsed = await parseStringPromise2(input2, {
4041
+ explicitArray: true,
4042
+ explicitChildren: true,
4043
+ preserveChildrenOrder: true,
4044
+ charsAsChildren: true,
4045
+ includeWhiteChars: true,
4046
+ mergeAttrs: false,
4047
+ trim: false,
4048
+ attrkey: "$",
4049
+ charkey: "_",
4050
+ childkey: "$$"
4051
+ });
4052
+ if (!parsed || typeof parsed !== "object") {
4053
+ console.error("Failed to parse MJML: invalid parsed structure");
4054
+ return result;
4055
+ }
4056
+ const rootKey = Object.keys(parsed).find((key) => !key.startsWith("_") && !key.startsWith("$"));
4057
+ const rootNode = rootKey ? parsed[rootKey] : parsed;
4058
+ const rootPath = rootNode["#name"] || rootKey || "";
4059
+ traverse(rootNode, (node, path19, componentName) => {
4060
+ if (typeof node !== "object") return;
4061
+ const localizableAttrs = LOCALIZABLE_ATTRIBUTES[componentName];
4062
+ if (localizableAttrs && node.$) {
4063
+ localizableAttrs.forEach((attr) => {
4064
+ const attrValue = node.$[attr];
4065
+ if (attrValue) {
4066
+ result[`${path19}#${attr}`] = attrValue;
4067
+ }
4068
+ });
4069
+ }
4070
+ if (LOCALIZABLE_COMPONENTS.includes(componentName)) {
4071
+ const innerHTML = getInnerHTML(node);
4072
+ if (innerHTML) {
4073
+ result[path19] = innerHTML;
4074
+ return "SKIP_CHILDREN";
4075
+ }
4076
+ }
4077
+ return void 0;
4078
+ }, rootPath);
4079
+ } catch (error) {
4080
+ console.error("Failed to parse MJML:", error);
4081
+ }
4082
+ return result;
4083
+ },
4084
+ async push(locale, data, originalInput) {
4085
+ try {
4086
+ const parsed = await parseStringPromise2(originalInput || "", {
4087
+ explicitArray: true,
4088
+ explicitChildren: true,
4089
+ preserveChildrenOrder: true,
4090
+ charsAsChildren: true,
4091
+ includeWhiteChars: true,
4092
+ mergeAttrs: false,
4093
+ trim: false,
4094
+ attrkey: "$",
4095
+ charkey: "_",
4096
+ childkey: "$$"
4097
+ });
4098
+ if (!parsed || typeof parsed !== "object") {
4099
+ console.error("Failed to parse MJML for push: invalid parsed structure");
4100
+ return originalInput || "";
4101
+ }
4102
+ const rootKey = Object.keys(parsed).find((key) => !key.startsWith("_") && !key.startsWith("$"));
4103
+ const rootNode = rootKey ? parsed[rootKey] : parsed;
4104
+ const rootPath = rootNode["#name"] || rootKey || "";
4105
+ traverse(rootNode, (node, path19, componentName) => {
4106
+ if (typeof node !== "object") return;
4107
+ const localizableAttrs = LOCALIZABLE_ATTRIBUTES[componentName];
4108
+ if (localizableAttrs && node.$) {
4109
+ localizableAttrs.forEach((attr) => {
4110
+ const attrKey = `${path19}#${attr}`;
4111
+ if (data[attrKey] !== void 0) {
4112
+ node.$[attr] = data[attrKey];
4113
+ }
4114
+ });
4115
+ }
4116
+ if (LOCALIZABLE_COMPONENTS.includes(componentName) && data[path19]) {
4117
+ setInnerHTML(node, data[path19]);
4118
+ return "SKIP_CHILDREN";
4119
+ }
4120
+ return void 0;
4121
+ }, rootPath);
4122
+ return serializeMjml(parsed);
4123
+ } catch (error) {
4124
+ console.error("Failed to build MJML:", error);
4125
+ return "";
4126
+ }
4127
+ }
4128
+ });
4129
+ }
4130
+ function traverse(node, visitor, path19 = "") {
4131
+ if (!node || typeof node !== "object") {
4132
+ return;
4133
+ }
4134
+ const children = node.$$;
4135
+ if (!Array.isArray(children)) {
4136
+ return;
4137
+ }
4138
+ const elementCounts = /* @__PURE__ */ new Map();
4139
+ children.forEach((child) => {
4140
+ const elementName = child["#name"];
4141
+ if (!elementName || elementName.startsWith("__")) {
4142
+ return;
4143
+ }
4144
+ const currentIndex = elementCounts.get(elementName) || 0;
4145
+ elementCounts.set(elementName, currentIndex + 1);
4146
+ const currentPath = path19 ? `${path19}/${elementName}/${currentIndex}` : `${elementName}/${currentIndex}`;
4147
+ const result = visitor(child, currentPath, elementName);
4148
+ if (result !== "SKIP_CHILDREN") {
4149
+ traverse(child, visitor, currentPath);
4150
+ }
4151
+ });
4152
+ }
4153
+ function getInnerHTML(node) {
4154
+ if (!node.$$ || !Array.isArray(node.$$)) {
4155
+ return null;
4156
+ }
4157
+ let html = "";
4158
+ node.$$.forEach((child) => {
4159
+ html += serializeXmlNode(child);
4160
+ });
4161
+ return html.trim() || null;
4162
+ }
4163
+ function setInnerHTML(node, htmlContent) {
4164
+ const handler = new DomHandler2();
4165
+ const parser = new htmlparser22.Parser(handler);
4166
+ parser.write(htmlContent);
4167
+ parser.end();
4168
+ const newChildren = [];
4169
+ for (const domNode of handler.dom) {
4170
+ const xmlNode = convertDomToXmlNode(domNode);
4171
+ if (xmlNode) {
4172
+ newChildren.push(xmlNode);
4173
+ }
4174
+ }
4175
+ node.$$ = newChildren;
4176
+ node._ = htmlContent;
4177
+ }
4178
+ function serializeXmlNode(node) {
4179
+ const name = node["#name"];
4180
+ if (name === "__text__") {
4181
+ return node._ || "";
4182
+ }
4183
+ if (name === "__cdata") {
4184
+ return `<![CDATA[${node._ || ""}]]>`;
4185
+ }
4186
+ if (!name || name.startsWith("__")) {
4187
+ return "";
4188
+ }
4189
+ const attrs = node.$ || {};
4190
+ const attrString = Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttributeValue2(String(value))}"`).join("");
4191
+ const children = node.$$ || [];
4192
+ if (children.length === 0) {
4193
+ const textContent3 = node._ || "";
4194
+ if (textContent3) {
4195
+ return `<${name}${attrString}>${textContent3}</${name}>`;
4196
+ }
4197
+ return `<${name}${attrString} />`;
4198
+ }
4199
+ const childContent = children.map(serializeXmlNode).join("");
4200
+ return `<${name}${attrString}>${childContent}</${name}>`;
4201
+ }
4202
+ function convertDomToXmlNode(domNode) {
4203
+ if (domNode.type === "text") {
4204
+ return {
4205
+ "#name": "__text__",
4206
+ "_": domNode.data
4207
+ };
4208
+ }
4209
+ if (domNode.type === "tag") {
4210
+ const xmlNode = {
4211
+ "#name": domNode.name,
4212
+ "$": domNode.attribs || {},
4213
+ "$$": []
4214
+ };
4215
+ if (domNode.children && domNode.children.length > 0) {
4216
+ for (const child of domNode.children) {
4217
+ const xmlChild = convertDomToXmlNode(child);
4218
+ if (xmlChild) {
4219
+ xmlNode.$$.push(xmlChild);
4220
+ }
4221
+ }
4222
+ }
4223
+ return xmlNode;
4224
+ }
4225
+ return null;
4226
+ }
4227
+ function serializeMjml(parsed) {
4228
+ const xmlDec = '<?xml version="1.0" encoding="UTF-8"?>\n';
4229
+ const rootKey = Object.keys(parsed).find((key) => !key.startsWith("_") && !key.startsWith("$"));
4230
+ const rootNode = rootKey ? parsed[rootKey] : parsed;
4231
+ const body = serializeElement2(rootNode);
4232
+ return xmlDec + body;
4233
+ }
4234
+ function serializeElement2(node, indent2 = "") {
4235
+ if (!node) {
4236
+ return "";
4237
+ }
4238
+ const name = node["#name"] ?? "mjml";
4239
+ if (name === "__text__") {
4240
+ return node._ ?? "";
4241
+ }
4242
+ if (name === "__cdata") {
4243
+ return `<![CDATA[${node._ ?? ""}]]>`;
4244
+ }
4245
+ if (name === "__comment__") {
4246
+ return `<!--${node._ ?? ""}-->`;
4247
+ }
4248
+ const attributes = node.$ ?? {};
4249
+ const attrString = Object.entries(attributes).map(([key, value]) => ` ${key}="${escapeAttributeValue2(String(value))}"`).join("");
4250
+ const children = Array.isArray(node.$$) ? node.$$ : [];
4251
+ if (children.length === 0) {
4252
+ const textContent3 = node._ ?? "";
4253
+ if (textContent3) {
4254
+ return `${indent2}<${name}${attrString}>${textContent3}</${name}>`;
4255
+ }
4256
+ return `${indent2}<${name}${attrString} />`;
4257
+ }
4258
+ const childContent = children.map((child) => serializeElement2(child, indent2)).join("");
4259
+ return `${indent2}<${name}${attrString}>${childContent}</${name}>`;
4260
+ }
4261
+ function escapeAttributeValue2(value) {
4262
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;");
4263
+ }
4264
+
4007
4265
  // src/cli/loaders/properties.ts
4008
4266
  function createPropertiesLoader() {
4009
4267
  return createLoader({
@@ -4267,7 +4525,7 @@ function escapeString(str) {
4267
4525
  }
4268
4526
 
4269
4527
  // src/cli/loaders/xcode-strings/parser.ts
4270
- var Parser2 = class {
4528
+ var Parser3 = class {
4271
4529
  tokens;
4272
4530
  pos;
4273
4531
  constructor(tokens) {
@@ -4340,7 +4598,7 @@ function createXcodeStringsLoader() {
4340
4598
  async pull(locale, input2) {
4341
4599
  const tokenizer = new Tokenizer(input2);
4342
4600
  const tokens = tokenizer.tokenize();
4343
- const parser = new Parser2(tokens);
4601
+ const parser = new Parser3(tokens);
4344
4602
  const result = parser.parse();
4345
4603
  return result;
4346
4604
  },
@@ -5552,10 +5810,10 @@ function formatXml(xml) {
5552
5810
  if (cdataNode) {
5553
5811
  return `${indent2}${openTag}<![CDATA[${cdataNode.nodeValue}]]></${tagName}>`;
5554
5812
  }
5555
- const textContent2 = element.textContent?.trim() || "";
5813
+ const textContent3 = element.textContent?.trim() || "";
5556
5814
  const hasOnlyText = element.childNodes.length === 1 && element.childNodes[0].nodeType === 3;
5557
- if (hasOnlyText && textContent2) {
5558
- return `${indent2}${openTag}${textContent2}</${tagName}>`;
5815
+ if (hasOnlyText && textContent3) {
5816
+ return `${indent2}${openTag}${textContent3}</${tagName}>`;
5559
5817
  }
5560
5818
  const children = Array.from(element.children);
5561
5819
  if (children.length === 0) {
@@ -5607,7 +5865,7 @@ function pushNewFile(locale, translations, originalLocale) {
5607
5865
  }
5608
5866
 
5609
5867
  // src/cli/loaders/xml.ts
5610
- import { parseStringPromise as parseStringPromise2, Builder } from "xml2js";
5868
+ import { parseStringPromise as parseStringPromise3, Builder as Builder2 } from "xml2js";
5611
5869
  function normalizeXMLString(xmlString) {
5612
5870
  return xmlString.replace(/\s+/g, " ").replace(/>\s+</g, "><").replace("\n", "").trim();
5613
5871
  }
@@ -5616,7 +5874,7 @@ function createXmlLoader() {
5616
5874
  async pull(locale, input2) {
5617
5875
  let result = {};
5618
5876
  try {
5619
- const parsed = await parseStringPromise2(input2, {
5877
+ const parsed = await parseStringPromise3(input2, {
5620
5878
  explicitArray: false,
5621
5879
  mergeAttrs: false,
5622
5880
  normalize: true,
@@ -5634,7 +5892,7 @@ function createXmlLoader() {
5634
5892
  },
5635
5893
  async push(locale, data) {
5636
5894
  try {
5637
- const builder = new Builder({ headless: true });
5895
+ const builder = new Builder2({ headless: true });
5638
5896
  const xmlOutput = builder.buildObject(data);
5639
5897
  const expectedOutput = normalizeXMLString(xmlOutput);
5640
5898
  return expectedOutput;
@@ -6727,7 +6985,7 @@ function resolveCjsExport(mod, name = "module") {
6727
6985
  }
6728
6986
 
6729
6987
  // src/cli/loaders/typescript/index.ts
6730
- var traverse = resolveCjsExport(babelTraverseModule, "@babel/traverse");
6988
+ var traverse2 = resolveCjsExport(babelTraverseModule, "@babel/traverse");
6731
6989
  var generate = resolveCjsExport(babelGenerateModule, "@babel/generator");
6732
6990
  function createTypescriptLoader() {
6733
6991
  return createLoader({
@@ -6760,7 +7018,7 @@ function parseTypeScript(input2) {
6760
7018
  }
6761
7019
  function extractStringsFromDefaultExport(ast) {
6762
7020
  let extracted = {};
6763
- traverse(ast, {
7021
+ traverse2(ast, {
6764
7022
  ExportDefaultDeclaration(path19) {
6765
7023
  const { declaration } = path19.node;
6766
7024
  const decl = unwrapTSAsExpression(declaration);
@@ -6833,7 +7091,7 @@ function arrayExpressionToArray(arrayExpression) {
6833
7091
  }
6834
7092
  function updateStringsInDefaultExport(ast, data) {
6835
7093
  let modified = false;
6836
- traverse(ast, {
7094
+ traverse2(ast, {
6837
7095
  ExportDefaultDeclaration(path19) {
6838
7096
  const { declaration } = path19.node;
6839
7097
  const decl = unwrapTSAsExpression(declaration);
@@ -8875,14 +9133,14 @@ function parseEjsForTranslation(input2) {
8875
9133
  if (part.type === "ejs") {
8876
9134
  template += part.content;
8877
9135
  } else {
8878
- const textContent2 = part.content;
9136
+ const textContent3 = part.content;
8879
9137
  const htmlTagRegex = /<[^>]+>/g;
8880
9138
  const textParts = [];
8881
9139
  let lastTextIndex = 0;
8882
9140
  let htmlMatch;
8883
- while ((htmlMatch = htmlTagRegex.exec(textContent2)) !== null) {
9141
+ while ((htmlMatch = htmlTagRegex.exec(textContent3)) !== null) {
8884
9142
  if (htmlMatch.index > lastTextIndex) {
8885
- const textBefore = textContent2.slice(lastTextIndex, htmlMatch.index);
9143
+ const textBefore = textContent3.slice(lastTextIndex, htmlMatch.index);
8886
9144
  if (textBefore.trim()) {
8887
9145
  textParts.push({ type: "text", content: textBefore });
8888
9146
  } else {
@@ -8892,8 +9150,8 @@ function parseEjsForTranslation(input2) {
8892
9150
  textParts.push({ type: "html", content: htmlMatch[0] });
8893
9151
  lastTextIndex = htmlMatch.index + htmlMatch[0].length;
8894
9152
  }
8895
- if (lastTextIndex < textContent2.length) {
8896
- const remainingText = textContent2.slice(lastTextIndex);
9153
+ if (lastTextIndex < textContent3.length) {
9154
+ const remainingText = textContent3.slice(lastTextIndex);
8897
9155
  if (remainingText.trim()) {
8898
9156
  textParts.push({ type: "text", content: remainingText });
8899
9157
  } else {
@@ -8901,11 +9159,11 @@ function parseEjsForTranslation(input2) {
8901
9159
  }
8902
9160
  }
8903
9161
  if (textParts.length === 0) {
8904
- const trimmedContent = textContent2.trim();
9162
+ const trimmedContent = textContent3.trim();
8905
9163
  if (trimmedContent) {
8906
- textParts.push({ type: "text", content: textContent2 });
9164
+ textParts.push({ type: "text", content: textContent3 });
8907
9165
  } else {
8908
- textParts.push({ type: "html", content: textContent2 });
9166
+ textParts.push({ type: "html", content: textContent3 });
8909
9167
  }
8910
9168
  }
8911
9169
  for (const textPart of textParts) {
@@ -8974,6 +9232,368 @@ function createEjsLoader() {
8974
9232
  });
8975
9233
  }
8976
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
+
8977
9597
  // src/cli/loaders/ensure-key-order.ts
8978
9598
  import _25 from "lodash";
8979
9599
  function createEnsureKeyOrderLoader() {
@@ -9250,6 +9870,17 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
9250
9870
  createSyncLoader(),
9251
9871
  createUnlocalizableLoader(options.returnUnlocalizedKeys)
9252
9872
  );
9873
+ case "mjml":
9874
+ return composeLoaders(
9875
+ createTextFileLoader(bucketPathPattern),
9876
+ createFormatterLoader(options.formatter, "html", bucketPathPattern),
9877
+ createLockedPatternsLoader(lockedPatterns),
9878
+ createMjmlLoader(),
9879
+ createLockedKeysLoader(lockedKeys || []),
9880
+ createIgnoredKeysLoader(ignoredKeys || []),
9881
+ createSyncLoader(),
9882
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
9883
+ );
9253
9884
  case "po":
9254
9885
  return composeLoaders(
9255
9886
  createTextFileLoader(bucketPathPattern),
@@ -9461,6 +10092,16 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
9461
10092
  createIgnoredKeysLoader(ignoredKeys || []),
9462
10093
  createUnlocalizableLoader(options.returnUnlocalizedKeys)
9463
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
+ );
9464
10105
  case "txt":
9465
10106
  return composeLoaders(
9466
10107
  createTextFileLoader(bucketPathPattern),
@@ -13832,7 +14473,7 @@ async function renderHero2() {
13832
14473
  // package.json
13833
14474
  var package_default = {
13834
14475
  name: "lingo.dev",
13835
- version: "0.117.6",
14476
+ version: "0.117.8",
13836
14477
  description: "Lingo.dev CLI",
13837
14478
  private: false,
13838
14479
  publishConfig: {