lingo.dev 0.108.0 → 0.109.0

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
@@ -1525,6 +1525,16 @@ function composeLoaders(...loaders) {
1525
1525
  result = await loaders[i].push(locale, result);
1526
1526
  }
1527
1527
  return result;
1528
+ },
1529
+ pullHints: async (originalInput) => {
1530
+ let result = originalInput;
1531
+ for (let i = 0; i < loaders.length; i++) {
1532
+ const subResult = await loaders[i].pullHints?.(result);
1533
+ if (subResult) {
1534
+ result = subResult;
1535
+ }
1536
+ }
1537
+ return result;
1528
1538
  }
1529
1539
  };
1530
1540
  }
@@ -1551,6 +1561,9 @@ function createLoader(lDefinition) {
1551
1561
  state.defaultLocale = locale;
1552
1562
  return this;
1553
1563
  },
1564
+ async pullHints() {
1565
+ return lDefinition.pullHints?.(state.originalInput);
1566
+ },
1554
1567
  async pull(locale, input2) {
1555
1568
  if (!state.defaultLocale) {
1556
1569
  throw new Error("Default locale not set");
@@ -1613,12 +1626,220 @@ function createJsonLoader() {
1613
1626
  });
1614
1627
  }
1615
1628
 
1629
+ // src/cli/loaders/json5.ts
1630
+ import JSON5 from "json5";
1631
+ function createJson5Loader() {
1632
+ return createLoader({
1633
+ pull: async (locale, input2) => {
1634
+ const json5String = input2 || "{}";
1635
+ return JSON5.parse(json5String);
1636
+ },
1637
+ push: async (locale, data) => {
1638
+ const serializedData = JSON5.stringify(data, null, 2);
1639
+ return serializedData;
1640
+ }
1641
+ });
1642
+ }
1643
+
1644
+ // src/cli/loaders/jsonc.ts
1645
+ import { parse } from "jsonc-parser";
1646
+ function extractCommentsFromJsonc(jsoncString) {
1647
+ const lines = jsoncString.split("\n");
1648
+ const comments = {};
1649
+ const errors = [];
1650
+ const result = parse(jsoncString, errors, {
1651
+ allowTrailingComma: true,
1652
+ disallowComments: false,
1653
+ allowEmptyContent: true
1654
+ });
1655
+ if (errors.length > 0) {
1656
+ return {};
1657
+ }
1658
+ const contextStack = [];
1659
+ for (let i = 0; i < lines.length; i++) {
1660
+ const line = lines[i];
1661
+ const trimmedLine = line.trim();
1662
+ if (!trimmedLine) continue;
1663
+ const commentData = extractCommentFromLine(line, lines, i);
1664
+ if (commentData.hint) {
1665
+ let keyInfo;
1666
+ if (commentData.isInline) {
1667
+ const keyMatch = line.match(/^\s*["']?([^"':,\s]+)["']?\s*:/);
1668
+ if (keyMatch) {
1669
+ const key = keyMatch[1];
1670
+ const path17 = contextStack.map((ctx) => ctx.key).filter(Boolean);
1671
+ keyInfo = { key, path: path17 };
1672
+ }
1673
+ } else {
1674
+ keyInfo = findAssociatedKey(lines, commentData.lineIndex, contextStack);
1675
+ }
1676
+ if (keyInfo && keyInfo.key) {
1677
+ setCommentAtPath(comments, keyInfo.path, keyInfo.key, commentData.hint);
1678
+ }
1679
+ i = commentData.endIndex;
1680
+ continue;
1681
+ }
1682
+ updateContext(contextStack, line, result);
1683
+ }
1684
+ return comments;
1685
+ }
1686
+ function extractCommentFromLine(line, lines, lineIndex) {
1687
+ const trimmed = line.trim();
1688
+ if (trimmed.startsWith("//")) {
1689
+ const hint = trimmed.replace(/^\/\/\s*/, "").trim();
1690
+ return { hint, lineIndex, endIndex: lineIndex, isInline: false };
1691
+ }
1692
+ if (trimmed.startsWith("/*")) {
1693
+ const blockResult = extractBlockComment(lines, lineIndex);
1694
+ return { ...blockResult, isInline: false };
1695
+ }
1696
+ const singleInlineMatch = line.match(/^(.+?)\s*\/\/\s*(.+)$/);
1697
+ if (singleInlineMatch && singleInlineMatch[1].includes(":")) {
1698
+ const hint = singleInlineMatch[2].trim();
1699
+ return { hint, lineIndex, endIndex: lineIndex, isInline: true };
1700
+ }
1701
+ const blockInlineMatch = line.match(/^(.+?)\s*\/\*\s*(.*?)\s*\*\/.*$/);
1702
+ if (blockInlineMatch && blockInlineMatch[1].includes(":")) {
1703
+ const hint = blockInlineMatch[2].trim();
1704
+ return { hint, lineIndex, endIndex: lineIndex, isInline: true };
1705
+ }
1706
+ return { hint: null, lineIndex, endIndex: lineIndex, isInline: false };
1707
+ }
1708
+ function extractBlockComment(lines, startIndex) {
1709
+ const startLine = lines[startIndex];
1710
+ const singleMatch = startLine.match(/\/\*\s*(.*?)\s*\*\//);
1711
+ if (singleMatch) {
1712
+ return {
1713
+ hint: singleMatch[1].trim(),
1714
+ lineIndex: startIndex,
1715
+ endIndex: startIndex
1716
+ };
1717
+ }
1718
+ const commentParts = [];
1719
+ let endIndex = startIndex;
1720
+ const firstContent = startLine.replace(/.*?\/\*\s*/, "").trim();
1721
+ if (firstContent && !firstContent.includes("*/")) {
1722
+ commentParts.push(firstContent);
1723
+ }
1724
+ for (let i = startIndex + 1; i < lines.length; i++) {
1725
+ const line = lines[i];
1726
+ endIndex = i;
1727
+ if (line.includes("*/")) {
1728
+ const lastContent = line.replace(/\*\/.*$/, "").replace(/^\s*\*?\s*/, "").trim();
1729
+ if (lastContent) {
1730
+ commentParts.push(lastContent);
1731
+ }
1732
+ break;
1733
+ } else {
1734
+ const content = line.replace(/^\s*\*?\s*/, "").trim();
1735
+ if (content) {
1736
+ commentParts.push(content);
1737
+ }
1738
+ }
1739
+ }
1740
+ return {
1741
+ hint: commentParts.join(" ").trim() || null,
1742
+ lineIndex: startIndex,
1743
+ endIndex
1744
+ };
1745
+ }
1746
+ function findAssociatedKey(lines, commentLineIndex, contextStack) {
1747
+ for (let i = commentLineIndex + 1; i < lines.length; i++) {
1748
+ const line = lines[i].trim();
1749
+ if (!line || line.startsWith("//") || line.startsWith("/*") || line === "{" || line === "}") {
1750
+ continue;
1751
+ }
1752
+ const keyMatch = line.match(/^\s*["']?([^"':,\s]+)["']?\s*:/);
1753
+ if (keyMatch) {
1754
+ const key = keyMatch[1];
1755
+ const path17 = contextStack.map((ctx) => ctx.key).filter(Boolean);
1756
+ return { key, path: path17 };
1757
+ }
1758
+ }
1759
+ return { key: null, path: [] };
1760
+ }
1761
+ function updateContext(contextStack, line, parsedJson) {
1762
+ const openBraces = (line.match(/\{/g) || []).length;
1763
+ const closeBraces = (line.match(/\}/g) || []).length;
1764
+ if (openBraces > closeBraces) {
1765
+ const keyMatch = line.match(/^\s*["']?([^"':,\s]+)["']?\s*:\s*\{/);
1766
+ if (keyMatch) {
1767
+ contextStack.push({ key: keyMatch[1], isArray: false });
1768
+ }
1769
+ } else if (closeBraces > openBraces) {
1770
+ for (let i = 0; i < closeBraces - openBraces; i++) {
1771
+ contextStack.pop();
1772
+ }
1773
+ }
1774
+ }
1775
+ function setCommentAtPath(comments, path17, key, hint) {
1776
+ let current = comments;
1777
+ for (const pathKey of path17) {
1778
+ if (!current[pathKey]) {
1779
+ current[pathKey] = {};
1780
+ }
1781
+ current = current[pathKey];
1782
+ }
1783
+ if (!current[key]) {
1784
+ current[key] = {};
1785
+ }
1786
+ if (typeof current[key] === "object" && current[key] !== null) {
1787
+ current[key].hint = hint;
1788
+ } else {
1789
+ current[key] = { hint };
1790
+ }
1791
+ }
1792
+ function createJsoncLoader() {
1793
+ return createLoader({
1794
+ pull: async (locale, input2) => {
1795
+ const jsoncString = input2 || "{}";
1796
+ const errors = [];
1797
+ const result = parse(jsoncString, errors, {
1798
+ allowTrailingComma: true,
1799
+ disallowComments: false,
1800
+ allowEmptyContent: true
1801
+ });
1802
+ if (errors.length > 0) {
1803
+ throw new Error(`Failed to parse JSONC: ${errors[0].error}`);
1804
+ }
1805
+ return result || {};
1806
+ },
1807
+ push: async (locale, data) => {
1808
+ const serializedData = JSON.stringify(data, null, 2);
1809
+ return serializedData;
1810
+ },
1811
+ pullHints: async (input2) => {
1812
+ if (!input2 || typeof input2 !== "string") {
1813
+ return {};
1814
+ }
1815
+ try {
1816
+ return extractCommentsFromJsonc(input2);
1817
+ } catch (error) {
1818
+ console.warn("Failed to extract comments from JSONC:", error);
1819
+ return {};
1820
+ }
1821
+ }
1822
+ });
1823
+ }
1824
+
1616
1825
  // src/cli/loaders/flat.ts
1617
1826
  import { flatten, unflatten } from "flat";
1618
1827
  import _9 from "lodash";
1619
1828
  var OBJECT_NUMERIC_KEY_PREFIX = "__lingodotdev__obj__";
1620
1829
  function createFlatLoader() {
1621
- return composeLoaders(createDenormalizeLoader(), createNormalizeLoader());
1830
+ const composedLoader = composeLoaders(
1831
+ createDenormalizeLoader(),
1832
+ createNormalizeLoader()
1833
+ );
1834
+ return {
1835
+ ...composedLoader,
1836
+ pullHints: async (input2) => {
1837
+ if (!input2 || typeof input2 !== "object") {
1838
+ return {};
1839
+ }
1840
+ return flattenHints(input2);
1841
+ }
1842
+ };
1622
1843
  }
1623
1844
  function createDenormalizeLoader() {
1624
1845
  return createLoader({
@@ -1709,6 +1930,33 @@ function normalizeObjectKeys(obj) {
1709
1930
  return obj;
1710
1931
  }
1711
1932
  }
1933
+ function flattenHints(obj, parentHints = [], parentPath = "") {
1934
+ const result = {};
1935
+ for (const [key, _value] of Object.entries(obj)) {
1936
+ if (_9.isObject(_value) && !_9.isArray(_value)) {
1937
+ const value = _value;
1938
+ const currentHints = [...parentHints];
1939
+ const currentPath = parentPath ? `${parentPath}/${key}` : key;
1940
+ if (value.hint && typeof value.hint === "string") {
1941
+ currentHints.push(value.hint);
1942
+ }
1943
+ const nestedObj = _9.omit(value, "hint");
1944
+ if (Object.keys(nestedObj).length === 0) {
1945
+ if (currentHints.length > 0) {
1946
+ result[currentPath] = currentHints;
1947
+ }
1948
+ } else {
1949
+ const nestedComments = flattenHints(
1950
+ nestedObj,
1951
+ currentHints,
1952
+ currentPath
1953
+ );
1954
+ Object.assign(result, nestedComments);
1955
+ }
1956
+ }
1957
+ }
1958
+ return result;
1959
+ }
1712
1960
 
1713
1961
  // src/cli/loaders/text-file.ts
1714
1962
  import fs8 from "fs/promises";
@@ -2020,11 +2268,11 @@ function createAndroidLoader() {
2020
2268
  }
2021
2269
 
2022
2270
  // src/cli/loaders/csv.ts
2023
- import { parse } from "csv-parse/sync";
2271
+ import { parse as parse2 } from "csv-parse/sync";
2024
2272
  import { stringify } from "csv-stringify/sync";
2025
2273
  import _11 from "lodash";
2026
2274
  function detectKeyColumnName(csvString) {
2027
- const row = parse(csvString)[0];
2275
+ const row = parse2(csvString)[0];
2028
2276
  const firstColumn = row?.[0]?.trim();
2029
2277
  return firstColumn || "KEY";
2030
2278
  }
@@ -2037,7 +2285,7 @@ function _createCsvLoader() {
2037
2285
  const keyColumnName = detectKeyColumnName(
2038
2286
  input2.split("\n").find((l) => l.length)
2039
2287
  );
2040
- const inputParsed = parse(input2, {
2288
+ const inputParsed = parse2(input2, {
2041
2289
  columns: true,
2042
2290
  skip_empty_lines: true,
2043
2291
  relax_column_count_less: true
@@ -2474,6 +2722,36 @@ function createXcodeXcstringsLoader(defaultLocale) {
2474
2722
  const originalInputWithoutLocale = originalInput ? _removeLocale(originalInput, locale) : {};
2475
2723
  const result = _12.merge({}, originalInputWithoutLocale, langDataToMerge);
2476
2724
  return result;
2725
+ },
2726
+ async pullHints(originalInput) {
2727
+ if (!originalInput || !originalInput.strings) {
2728
+ return {};
2729
+ }
2730
+ const hints = {};
2731
+ for (const [translationKey, translationEntity] of Object.entries(
2732
+ originalInput.strings
2733
+ )) {
2734
+ const entity = translationEntity;
2735
+ if (entity.comment && typeof entity.comment === "string") {
2736
+ hints[translationKey] = { hint: entity.comment };
2737
+ }
2738
+ if (entity.localizations) {
2739
+ for (const [locale, localization] of Object.entries(
2740
+ entity.localizations
2741
+ )) {
2742
+ if (localization.variations?.plural) {
2743
+ const pluralForms = localization.variations.plural;
2744
+ for (const form in pluralForms) {
2745
+ const pluralKey = `${translationKey}/${form}`;
2746
+ if (entity.comment && typeof entity.comment === "string") {
2747
+ hints[pluralKey] = { hint: entity.comment };
2748
+ }
2749
+ }
2750
+ }
2751
+ }
2752
+ }
2753
+ }
2754
+ return hints;
2477
2755
  }
2478
2756
  });
2479
2757
  }
@@ -3238,7 +3516,7 @@ function createSrtLoader() {
3238
3516
 
3239
3517
  // src/cli/loaders/dato/index.ts
3240
3518
  import fs9 from "fs";
3241
- import JSON5 from "json5";
3519
+ import JSON52 from "json5";
3242
3520
 
3243
3521
  // src/cli/loaders/dato/_base.ts
3244
3522
  import Z2 from "zod";
@@ -3925,13 +4203,13 @@ function _isVideo(rawDatoValue) {
3925
4203
  function createDatoLoader(configFilePath) {
3926
4204
  try {
3927
4205
  const configContent = fs9.readFileSync(configFilePath, "utf-8");
3928
- const datoConfig = datoConfigSchema.parse(JSON5.parse(configContent));
4206
+ const datoConfig = datoConfigSchema.parse(JSON52.parse(configContent));
3929
4207
  return composeLoaders(
3930
4208
  createDatoApiLoader(
3931
4209
  datoConfig,
3932
4210
  (updatedConfig) => fs9.writeFileSync(
3933
4211
  configFilePath,
3934
- JSON5.stringify(updatedConfig, null, 2)
4212
+ JSON52.stringify(updatedConfig, null, 2)
3935
4213
  )
3936
4214
  ),
3937
4215
  createDatoFilterLoader(),
@@ -4256,7 +4534,7 @@ function parseVueFile(input2) {
4256
4534
  }
4257
4535
 
4258
4536
  // src/cli/loaders/typescript/index.ts
4259
- import { parse as parse2 } from "@babel/parser";
4537
+ import { parse as parse3 } from "@babel/parser";
4260
4538
  import _21 from "lodash";
4261
4539
  import babelTraverseModule from "@babel/traverse";
4262
4540
  import * as t from "@babel/types";
@@ -4305,7 +4583,7 @@ function createTypescriptLoader() {
4305
4583
  });
4306
4584
  }
4307
4585
  function parseTypeScript(input2) {
4308
- return parse2(input2, {
4586
+ return parse3(input2, {
4309
4587
  sourceType: "module",
4310
4588
  plugins: ["typescript"]
4311
4589
  });
@@ -6702,6 +6980,28 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
6702
6980
  createSyncLoader(),
6703
6981
  createUnlocalizableLoader(options.returnUnlocalizedKeys)
6704
6982
  );
6983
+ case "json5":
6984
+ return composeLoaders(
6985
+ createTextFileLoader(bucketPathPattern),
6986
+ createJson5Loader(),
6987
+ createEnsureKeyOrderLoader(),
6988
+ createFlatLoader(),
6989
+ createInjectLocaleLoader(options.injectLocale),
6990
+ createLockedKeysLoader(lockedKeys || []),
6991
+ createSyncLoader(),
6992
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
6993
+ );
6994
+ case "jsonc":
6995
+ return composeLoaders(
6996
+ createTextFileLoader(bucketPathPattern),
6997
+ createJsoncLoader(),
6998
+ createEnsureKeyOrderLoader(),
6999
+ createFlatLoader(),
7000
+ createInjectLocaleLoader(options.injectLocale),
7001
+ createLockedKeysLoader(lockedKeys || []),
7002
+ createSyncLoader(),
7003
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
7004
+ );
6705
7005
  case "markdown":
6706
7006
  return composeLoaders(
6707
7007
  createTextFileLoader(bucketPathPattern),
@@ -8292,7 +8592,8 @@ function createLingoDotDevLocalizer(explicitApiKey) {
8292
8592
  reference: {
8293
8593
  [input2.sourceLocale]: input2.sourceData,
8294
8594
  [input2.targetLocale]: input2.targetData
8295
- }
8595
+ },
8596
+ hints: input2.hints
8296
8597
  },
8297
8598
  onProgress
8298
8599
  );
@@ -8826,6 +9127,7 @@ function createWorkerTask(args) {
8826
9127
  const sourceData = await bucketLoader.pull(
8827
9128
  assignedTask.sourceLocale
8828
9129
  );
9130
+ const hints = await bucketLoader.pullHints();
8829
9131
  const targetData = await bucketLoader.pull(
8830
9132
  assignedTask.targetLocale
8831
9133
  );
@@ -8848,13 +9150,15 @@ function createWorkerTask(args) {
8848
9150
  });
8849
9151
  return { status: "skipped" };
8850
9152
  }
9153
+ const relevantHints = _34.pick(hints, Object.keys(processableData));
8851
9154
  const processedTargetData = await args.ctx.localizer.localize(
8852
9155
  {
8853
9156
  sourceLocale: assignedTask.sourceLocale,
8854
9157
  targetLocale: assignedTask.targetLocale,
8855
9158
  sourceData,
8856
9159
  targetData,
8857
- processableData
9160
+ processableData,
9161
+ hints: relevantHints
8858
9162
  },
8859
9163
  async (progress, _sourceChunk, processedChunk) => {
8860
9164
  await args.ioLimiter(async () => {
@@ -10496,7 +10800,7 @@ async function renderHero2() {
10496
10800
  // package.json
10497
10801
  var package_default = {
10498
10802
  name: "lingo.dev",
10499
- version: "0.108.0",
10803
+ version: "0.109.0",
10500
10804
  description: "Lingo.dev CLI",
10501
10805
  private: false,
10502
10806
  publishConfig: {
@@ -10660,6 +10964,7 @@ var package_default = {
10660
10964
  "is-url": "^1.2.4",
10661
10965
  jsdom: "^25.0.1",
10662
10966
  json5: "^2.2.3",
10967
+ "jsonc-parser": "^3.3.1",
10663
10968
  jsonrepair: "^3.11.2",
10664
10969
  listr2: "^8.3.2",
10665
10970
  lodash: "^4.17.21",