@lang-tag/cli 0.18.1 → 0.20.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/index.cjs CHANGED
@@ -8,12 +8,13 @@ const globby = require("globby");
8
8
  const path = require("path");
9
9
  const JSON5 = require("json5");
10
10
  const promises = require("fs/promises");
11
- const path$1 = require("pathe");
12
11
  const url = require("url");
12
+ const path$1 = require("pathe");
13
13
  require("case");
14
14
  const namespaceCollector = require("./chunks/namespace-collector.cjs");
15
15
  const micromatch = require("micromatch");
16
16
  const acorn = require("acorn");
17
+ const tsMorph = require("ts-morph");
17
18
  const mustache = require("mustache");
18
19
  const checkbox = require("@inquirer/checkbox");
19
20
  const confirm = require("@inquirer/confirm");
@@ -102,22 +103,83 @@ class $LT_TagProcessor {
102
103
  const matches = [];
103
104
  let currentIndex = 0;
104
105
  const skipRanges = this.buildSkipRanges(fileContent);
105
- const startPattern = new RegExp(
106
- `${optionalVariableAssignment}${tagName}\\(\\s*\\{`,
106
+ const tagNamePattern = new RegExp(
107
+ `${optionalVariableAssignment}${tagName}`,
107
108
  "g"
108
109
  );
109
110
  while (true) {
110
- startPattern.lastIndex = currentIndex;
111
- const startMatch = startPattern.exec(fileContent);
112
- if (!startMatch) break;
113
- const matchStartIndex = startMatch.index;
114
- const variableName = startMatch[1] || void 0;
111
+ tagNamePattern.lastIndex = currentIndex;
112
+ const tagNameMatch = tagNamePattern.exec(fileContent);
113
+ if (!tagNameMatch) break;
114
+ const tagNameStartIndex = tagNameMatch.index;
115
+ const variableName = tagNameMatch[1] || void 0;
116
+ if (this.isInSkipRange(tagNameStartIndex, skipRanges)) {
117
+ currentIndex = tagNameStartIndex + 1;
118
+ continue;
119
+ }
120
+ let i = tagNameStartIndex + tagNameMatch[0].length;
121
+ let genericType = void 0;
122
+ let matchStartIndex = tagNameStartIndex;
123
+ while (i < fileContent.length && /\s/.test(fileContent[i])) {
124
+ i++;
125
+ }
126
+ if (i < fileContent.length && fileContent[i] === "<") {
127
+ const genericStart = i;
128
+ let angleCount = 1;
129
+ i++;
130
+ while (i < fileContent.length && angleCount > 0) {
131
+ const char = fileContent[i];
132
+ if (char === "<") angleCount++;
133
+ else if (char === ">") angleCount--;
134
+ else if ((char === '"' || char === "'" || char === "`") && !this.isInSkipRange(i, skipRanges)) {
135
+ i++;
136
+ while (i < fileContent.length) {
137
+ if (fileContent[i] === "\\") {
138
+ i += 2;
139
+ continue;
140
+ }
141
+ if (fileContent[i] === char) {
142
+ i++;
143
+ break;
144
+ }
145
+ i++;
146
+ }
147
+ continue;
148
+ }
149
+ i++;
150
+ }
151
+ if (angleCount === 0) {
152
+ genericType = fileContent.substring(genericStart + 1, i - 1).trim();
153
+ matchStartIndex = tagNameStartIndex;
154
+ } else {
155
+ currentIndex = tagNameStartIndex + 1;
156
+ continue;
157
+ }
158
+ } else {
159
+ matchStartIndex = tagNameStartIndex;
160
+ }
161
+ while (i < fileContent.length && /\s/.test(fileContent[i])) {
162
+ i++;
163
+ }
164
+ if (i >= fileContent.length || fileContent[i] !== "(" || i + 1 >= fileContent.length) {
165
+ currentIndex = tagNameStartIndex + 1;
166
+ continue;
167
+ }
168
+ i++;
169
+ while (i < fileContent.length && /\s/.test(fileContent[i])) {
170
+ i++;
171
+ }
172
+ if (i >= fileContent.length || fileContent[i] !== "{") {
173
+ currentIndex = tagNameStartIndex + 1;
174
+ continue;
175
+ }
176
+ const braceStartIndex = i;
115
177
  if (this.isInSkipRange(matchStartIndex, skipRanges)) {
116
178
  currentIndex = matchStartIndex + 1;
117
179
  continue;
118
180
  }
119
181
  let braceCount = 1;
120
- let i = matchStartIndex + startMatch[0].length;
182
+ i++;
121
183
  while (i < fileContent.length && braceCount > 0) {
122
184
  if (fileContent[i] === "{") braceCount++;
123
185
  if (fileContent[i] === "}") braceCount--;
@@ -127,10 +189,7 @@ class $LT_TagProcessor {
127
189
  currentIndex = matchStartIndex + 1;
128
190
  continue;
129
191
  }
130
- let parameter1Text = fileContent.substring(
131
- matchStartIndex + startMatch[0].length - 1,
132
- i
133
- );
192
+ let parameter1Text = fileContent.substring(braceStartIndex, i);
134
193
  let parameter2Text;
135
194
  while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
136
195
  i++;
@@ -225,6 +284,7 @@ class $LT_TagProcessor {
225
284
  matches.push({
226
285
  fullMatch,
227
286
  variableName,
287
+ genericType,
228
288
  parameter1Text,
229
289
  parameter2Text,
230
290
  parameterTranslations,
@@ -278,7 +338,8 @@ class $LT_TagProcessor {
278
338
  }
279
339
  const arg1 = this.config.translationArgPosition === 1 ? newTranslationsString : newConfigString;
280
340
  const arg2 = this.config.translationArgPosition === 1 ? newConfigString : newTranslationsString;
281
- let tagFunction = `${this.config.tagName}(${arg1}`;
341
+ const genericTypePart = tag.genericType ? `<${tag.genericType}>` : "";
342
+ let tagFunction = `${this.config.tagName}${genericTypePart}(${arg1}`;
282
343
  if (arg2) tagFunction += `, ${arg2}`;
283
344
  tagFunction += ")";
284
345
  if (tag.variableName)
@@ -504,7 +565,9 @@ async function $LT_CollectCandidateFilesWithTags(props) {
504
565
  langTagConfig: config
505
566
  });
506
567
  }
507
- tags = $LT_FilterEmptyNamespaceTags(tags, logger);
568
+ if (!props.skipEmptyNamespaceCheck) {
569
+ tags = $LT_FilterEmptyNamespaceTags(tags, logger);
570
+ }
508
571
  const relativeFilePath = path.relative(cwd, filePath);
509
572
  candidates.push({ relativeFilePath, tags });
510
573
  }
@@ -845,9 +908,30 @@ async function $LT_WriteToCollections({
845
908
  }
846
909
  await config.collect.collector.postWrite(changedCollections);
847
910
  }
911
+ function deepFreezeObject(obj) {
912
+ const propNames = Object.getOwnPropertyNames(obj);
913
+ for (const name of propNames) {
914
+ const value = obj[name];
915
+ if (value && typeof value === "object") {
916
+ deepFreezeObject(value);
917
+ }
918
+ }
919
+ return Object.freeze(obj);
920
+ }
921
+ function formatFileUrlForDisplay(filePath) {
922
+ return url.pathToFileURL(filePath).href.replace(/\[/g, "%5B").replace(/\]/g, "%5D").replace(/\(/g, "%28").replace(/\)/g, "%29");
923
+ }
924
+ function formatExecutionTime(milliseconds) {
925
+ if (milliseconds >= 1e3) {
926
+ const seconds = milliseconds / 1e3;
927
+ return `${seconds.toFixed(1)}s`;
928
+ }
929
+ return `${Math.round(milliseconds)}ms`;
930
+ }
848
931
  const LANG_TAG_DEFAULT_CONFIG = {
849
932
  tagName: "lang",
850
933
  isLibrary: false,
934
+ enforceLibraryTagPrefix: true,
851
935
  includes: ["src/**/*.{js,ts,jsx,tsx}"],
852
936
  excludes: ["node_modules", "dist", "build"],
853
937
  localesDirectory: "locales",
@@ -908,6 +992,7 @@ This will enable import of language tags from external packages.
908
992
  }
909
993
  },
910
994
  translationArgPosition: 1,
995
+ hideDistDir: "dist",
911
996
  onConfigGeneration: async (event) => {
912
997
  event.logger.info(
913
998
  "Config generation event is not configured. Add onConfigGeneration handler to customize config generation."
@@ -946,6 +1031,9 @@ async function $LT_ReadConfig(projectPath) {
946
1031
  if (!config.collect.collector) {
947
1032
  throw new Error("Collector not found! (config.collect.collector)");
948
1033
  }
1034
+ if (config.isLibrary && (config.enforceLibraryTagPrefix ?? true) && config.tagName && !config.tagName.startsWith("_")) {
1035
+ config.tagName = `_${config.tagName}`;
1036
+ }
949
1037
  return config;
950
1038
  } catch (error) {
951
1039
  throw error;
@@ -1290,9 +1378,9 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
1290
1378
  console.error("Failed to colorize config:", error);
1291
1379
  }
1292
1380
  }
1293
- const encodedPath = encodeURI(filePath);
1381
+ const fileUrl = formatFileUrlForDisplay(filePath);
1294
1382
  console.log(
1295
- `${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}file://${encodedPath}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
1383
+ `${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}${fileUrl}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
1296
1384
  );
1297
1385
  printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
1298
1386
  } catch (error) {
@@ -1422,9 +1510,14 @@ async function $LT_GetCommandEssentials() {
1422
1510
  };
1423
1511
  }
1424
1512
  async function $LT_CMD_Collect(options) {
1513
+ const startTime = Date.now();
1425
1514
  const { config, logger } = await $LT_GetCommandEssentials();
1426
1515
  logger.info("Collecting translations from source files...");
1427
- const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
1516
+ const files = await $LT_CollectCandidateFilesWithTags({
1517
+ config,
1518
+ logger,
1519
+ skipEmptyNamespaceCheck: config.isLibrary
1520
+ });
1428
1521
  if (config.debug) {
1429
1522
  for (let file of files) {
1430
1523
  logger.debug("Found {count} translations tags inside: {file}", {
@@ -1433,8 +1526,19 @@ async function $LT_CMD_Collect(options) {
1433
1526
  });
1434
1527
  }
1435
1528
  }
1529
+ const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
1436
1530
  if (config.isLibrary) {
1531
+ if (totalTags === 0 && (config.enforceLibraryTagPrefix ?? true) && config.tagName) {
1532
+ const baseTagName = config.tagName.startsWith("_") ? config.tagName.substring(1) : config.tagName;
1533
+ console.log("");
1534
+ logger.warn(
1535
+ '⚠️ No translation tags found in your library code.\n This might be because enforceLibraryTagPrefix is enabled.\n Remember: your tag function must be named {prefixedBaseTagName} (with "_" prefix), not {baseTagName}.\n Example: export function {prefixedBaseTagName}(...) instead of export function {baseTagName}(...)\n The prefix prevents the tag from appearing in TypeScript autocomplete after compilation.',
1536
+ { prefixedBaseTagName: `_${baseTagName}`, baseTagName }
1537
+ );
1538
+ }
1437
1539
  await $LT_WriteAsExportFile({ config, logger, files });
1540
+ const executionTime = formatExecutionTime(Date.now() - startTime);
1541
+ logger.debug("Collection completed ({time})", { time: executionTime });
1438
1542
  return;
1439
1543
  }
1440
1544
  try {
@@ -1443,10 +1547,6 @@ async function $LT_CMD_Collect(options) {
1443
1547
  files,
1444
1548
  config
1445
1549
  });
1446
- const totalTags = files.reduce(
1447
- (sum, file) => sum + file.tags.length,
1448
- 0
1449
- );
1450
1550
  logger.debug("Found {totalTags} translation tags", { totalTags });
1451
1551
  await $LT_WriteToCollections({
1452
1552
  config,
@@ -1454,15 +1554,252 @@ async function $LT_CMD_Collect(options) {
1454
1554
  logger,
1455
1555
  clean: options?.clean
1456
1556
  });
1557
+ const executionTime = formatExecutionTime(Date.now() - startTime);
1558
+ logger.debug("Collection completed ({time})", { time: executionTime });
1457
1559
  } catch (e) {
1458
1560
  const prefix = "LangTagConflictResolution:";
1459
1561
  if (e.message.startsWith(prefix)) {
1460
1562
  logger.error(e.message.substring(prefix.length));
1563
+ const executionTime = formatExecutionTime(Date.now() - startTime);
1564
+ logger.debug("Collection completed ({time})", {
1565
+ time: executionTime
1566
+ });
1461
1567
  return;
1462
1568
  }
1463
1569
  throw e;
1464
1570
  }
1465
1571
  }
1572
+ function $LT_HideExportsInDtsFile(dtsFilePath, variableNames) {
1573
+ const originalContent = fs.readFileSync(dtsFilePath, "utf-8");
1574
+ const project = new tsMorph.Project({
1575
+ skipAddingFilesFromTsConfig: true,
1576
+ skipFileDependencyResolution: true,
1577
+ skipLoadingLibFiles: true
1578
+ });
1579
+ const sourceFile = project.addSourceFileAtPath(dtsFilePath);
1580
+ let hasChanges = false;
1581
+ const exportsToHide = [];
1582
+ const processedStatements = /* @__PURE__ */ new Set();
1583
+ for (const declaration of sourceFile.getVariableDeclarations()) {
1584
+ const name = declaration.getName();
1585
+ if (variableNames.has(name)) {
1586
+ const parent = declaration.getParent();
1587
+ if (parent && parent.getKindName() === "VariableDeclarationList") {
1588
+ const varList = parent;
1589
+ const grandParent = varList.getParent();
1590
+ if (grandParent && grandParent.getKindName() === "VariableStatement") {
1591
+ const varStatement = grandParent;
1592
+ if (varStatement.hasExportKeyword()) {
1593
+ if (!processedStatements.has(varStatement)) {
1594
+ processedStatements.add(varStatement);
1595
+ exportsToHide.push(name);
1596
+ varStatement.toggleModifier("export", false);
1597
+ hasChanges = true;
1598
+ }
1599
+ }
1600
+ }
1601
+ }
1602
+ }
1603
+ }
1604
+ if (!hasChanges) {
1605
+ return {
1606
+ hiddenCount: 0,
1607
+ modifiedContent: originalContent,
1608
+ originalContent
1609
+ };
1610
+ }
1611
+ const modifiedContent = sourceFile.getFullText();
1612
+ return {
1613
+ hiddenCount: exportsToHide.length,
1614
+ modifiedContent,
1615
+ originalContent
1616
+ };
1617
+ }
1618
+ function extractPrefixesFromIncludes(includes) {
1619
+ const prefixes = /* @__PURE__ */ new Set();
1620
+ for (const pattern of includes) {
1621
+ const groupMatch = pattern.match(/^\(([^)]+)\)\/\*\*/);
1622
+ if (groupMatch) {
1623
+ const options = groupMatch[1].split("|").map((s) => s.trim());
1624
+ options.forEach((opt) => prefixes.add(opt));
1625
+ continue;
1626
+ }
1627
+ const simpleMatch = pattern.match(/^([^/]+)\/\*\*/);
1628
+ if (simpleMatch) {
1629
+ prefixes.add(simpleMatch[1]);
1630
+ continue;
1631
+ }
1632
+ const singleMatch = pattern.match(/^([^/]+)\//);
1633
+ if (singleMatch) {
1634
+ prefixes.add(singleMatch[1]);
1635
+ }
1636
+ }
1637
+ return Array.from(prefixes);
1638
+ }
1639
+ function findMatchingDtsFile(sourceFilePath, sourceRelativePath, dtsFileMap, config) {
1640
+ const sourceBaseName = path.basename(
1641
+ sourceFilePath,
1642
+ path.extname(sourceFilePath)
1643
+ );
1644
+ const relativePathKey = sourceRelativePath.replace(
1645
+ /\.(ts|tsx|js|jsx)$/,
1646
+ ""
1647
+ );
1648
+ let dtsFilePath = dtsFileMap.get(relativePathKey);
1649
+ if (dtsFilePath) {
1650
+ return { dtsFilePath, strategy: "relative-path" };
1651
+ }
1652
+ if (config?.includes) {
1653
+ const prefixes = extractPrefixesFromIncludes(config.includes);
1654
+ for (const prefix of prefixes) {
1655
+ const prefixPattern = new RegExp(
1656
+ `^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`
1657
+ );
1658
+ if (prefixPattern.test(sourceRelativePath)) {
1659
+ const strippedPath = sourceRelativePath.replace(
1660
+ prefixPattern,
1661
+ ""
1662
+ );
1663
+ const strippedPathKey = strippedPath.replace(
1664
+ /\.(ts|tsx|js|jsx)$/,
1665
+ ""
1666
+ );
1667
+ dtsFilePath = dtsFileMap.get(strippedPathKey);
1668
+ if (dtsFilePath) {
1669
+ return {
1670
+ dtsFilePath,
1671
+ strategy: "includes-prefix-stripped"
1672
+ };
1673
+ }
1674
+ }
1675
+ }
1676
+ }
1677
+ dtsFilePath = dtsFileMap.get(sourceBaseName);
1678
+ if (dtsFilePath) {
1679
+ return { dtsFilePath, strategy: "base-name" };
1680
+ }
1681
+ return { dtsFilePath: null, strategy: null };
1682
+ }
1683
+ async function $LT_MatchSourceToDtsFiles(files, distPath, cwd, config) {
1684
+ const sourceFileToVariables = /* @__PURE__ */ new Map();
1685
+ for (const file of files) {
1686
+ const sourceFilePath = path.resolve(cwd, file.relativeFilePath);
1687
+ const variableNames = /* @__PURE__ */ new Set();
1688
+ for (const tag of file.tags) {
1689
+ if (tag.variableName) {
1690
+ variableNames.add(tag.variableName);
1691
+ }
1692
+ }
1693
+ if (variableNames.size > 0) {
1694
+ sourceFileToVariables.set(sourceFilePath, variableNames);
1695
+ }
1696
+ }
1697
+ if (sourceFileToVariables.size === 0) {
1698
+ return [];
1699
+ }
1700
+ const dtsFiles = await globby.globby("**/*.d.ts", {
1701
+ cwd: distPath,
1702
+ absolute: true
1703
+ });
1704
+ if (dtsFiles.length === 0) {
1705
+ return [];
1706
+ }
1707
+ const dtsFileMap = /* @__PURE__ */ new Map();
1708
+ for (const dtsFile of dtsFiles) {
1709
+ const relativeDtsPath = path.relative(distPath, dtsFile);
1710
+ const baseName = path.basename(dtsFile, ".d.ts");
1711
+ const relativePathWithoutExt = relativeDtsPath.replace(/\.d\.ts$/, "");
1712
+ dtsFileMap.set(relativePathWithoutExt, dtsFile);
1713
+ dtsFileMap.set(baseName, dtsFile);
1714
+ }
1715
+ const matches = [];
1716
+ const processedDtsFiles = /* @__PURE__ */ new Set();
1717
+ for (const [sourceFilePath, variableNames] of sourceFileToVariables) {
1718
+ const sourceRelativePath = path.relative(cwd, sourceFilePath);
1719
+ const match = findMatchingDtsFile(
1720
+ sourceFilePath,
1721
+ sourceRelativePath,
1722
+ dtsFileMap,
1723
+ config
1724
+ );
1725
+ if (!match.dtsFilePath) {
1726
+ continue;
1727
+ }
1728
+ if (processedDtsFiles.has(match.dtsFilePath)) {
1729
+ continue;
1730
+ }
1731
+ processedDtsFiles.add(match.dtsFilePath);
1732
+ matches.push({
1733
+ sourceFilePath,
1734
+ sourceRelativePath,
1735
+ dtsFilePath: match.dtsFilePath,
1736
+ variableNames
1737
+ });
1738
+ }
1739
+ return matches;
1740
+ }
1741
+ async function $LT_CMD_HideCompiledExports(options) {
1742
+ const { config, logger } = await $LT_GetCommandEssentials();
1743
+ const distDir = options?.distDir || config.hideDistDir || "dist";
1744
+ const distPath = path.resolve(process$1.cwd(), distDir);
1745
+ if (!fs.existsSync(distPath)) {
1746
+ logger.warn("Dist directory does not exist: {distPath}", { distPath });
1747
+ return;
1748
+ }
1749
+ logger.info("Scanning source files for lang-tag variables...");
1750
+ const files = await $LT_CollectCandidateFilesWithTags({
1751
+ config,
1752
+ logger,
1753
+ skipEmptyNamespaceCheck: true
1754
+ });
1755
+ const matches = await $LT_MatchSourceToDtsFiles(
1756
+ files,
1757
+ distPath,
1758
+ process$1.cwd(),
1759
+ config
1760
+ );
1761
+ if (matches.length === 0) {
1762
+ logger.info(
1763
+ "No lang-tag variables found in source files or no matching .d.ts files found."
1764
+ );
1765
+ return;
1766
+ }
1767
+ logger.info("Found {count} .d.ts files to process", {
1768
+ count: matches.length
1769
+ });
1770
+ let hiddenCount = 0;
1771
+ for (const match of matches) {
1772
+ try {
1773
+ const result = $LT_HideExportsInDtsFile(
1774
+ match.dtsFilePath,
1775
+ match.variableNames
1776
+ );
1777
+ if (!result.hiddenCount) {
1778
+ continue;
1779
+ }
1780
+ fs.writeFileSync(match.dtsFilePath, result.modifiedContent, "utf-8");
1781
+ hiddenCount += result.hiddenCount;
1782
+ const hiddenVariables = Array.from(match.variableNames).join(", ");
1783
+ logger.debug(
1784
+ "Hidden exports from {file}: {variables} (from {sourceFile})",
1785
+ {
1786
+ file: path.relative(process$1.cwd(), match.dtsFilePath),
1787
+ variables: hiddenVariables,
1788
+ sourceFile: match.sourceRelativePath
1789
+ }
1790
+ );
1791
+ } catch (error) {
1792
+ logger.warn("Error processing file {file}: {error}", {
1793
+ file: match.sourceRelativePath,
1794
+ error: error.message || String(error)
1795
+ });
1796
+ }
1797
+ }
1798
+ logger.success("Hidden {hiddenCount} exports from {fileCount} files.", {
1799
+ hiddenCount,
1800
+ fileCount: matches.length
1801
+ });
1802
+ }
1466
1803
  async function $LT_CollectExportFiles(logger) {
1467
1804
  const nodeModulesPath = path$1.join(process$1.cwd(), "node_modules");
1468
1805
  if (!fs.existsSync(nodeModulesPath)) {
@@ -1581,9 +1918,11 @@ async function generateImportFiles(config, logger, importManager) {
1581
1918
  const content = renderTemplate$2(templateData);
1582
1919
  await $LT_EnsureDirectoryExists(path.dirname(filePath));
1583
1920
  await promises.writeFile(filePath, content, "utf-8");
1921
+ const fileUrl = formatFileUrlForDisplay(filePath);
1584
1922
  logger.success('Created tag file: "{file}"', {
1585
1923
  file: importedFile.pathRelativeToImportDir
1586
1924
  });
1925
+ logger.debug(" └── link: {url}", { url: fileUrl });
1587
1926
  }
1588
1927
  }
1589
1928
  class ImportManager {
@@ -1661,12 +2000,12 @@ async function $LT_ImportLibraries(config, logger) {
1661
2000
  logger.warn("No tags were imported from any library files");
1662
2001
  return;
1663
2002
  }
2003
+ await $LT_EnsureDirectoryExists(config.import.dir);
1664
2004
  await generateImportFiles(config, logger, importManager);
1665
2005
  if (config.import.onImportFinish) config.import.onImportFinish();
1666
2006
  }
1667
2007
  async function $LT_ImportTranslations() {
1668
2008
  const { config, logger } = await $LT_GetCommandEssentials();
1669
- await $LT_EnsureDirectoryExists(config.import.dir);
1670
2009
  logger.info("Importing translations from libraries...");
1671
2010
  await $LT_ImportLibraries(config, logger);
1672
2011
  logger.success("Successfully imported translations from libraries.");
@@ -2082,7 +2421,10 @@ async function detectInitTagOptions(options, config) {
2082
2421
  const isTypeScript = options.typescript !== void 0 ? options.typescript : detectTypeScript(packageJson);
2083
2422
  const isReact = options.react !== void 0 ? options.react : detectReact(packageJson);
2084
2423
  const isLibrary = options.library !== void 0 ? options.library : config.isLibrary;
2085
- const tagName = options.name || config.tagName || "lang";
2424
+ let tagName = options.name || config.tagName || "lang";
2425
+ if (isLibrary && (config.enforceLibraryTagPrefix ?? true) && !tagName.startsWith("_")) {
2426
+ tagName = `_${tagName}`;
2427
+ }
2086
2428
  const fileExtension = isLibrary && isReact ? isTypeScript ? "tsx" : "jsx" : isTypeScript ? "ts" : "js";
2087
2429
  return {
2088
2430
  tagName,
@@ -2183,22 +2525,22 @@ async function $LT_CMD_InitTagFile(options = {}) {
2183
2525
  "2. Create your translation objects and use the tag function"
2184
2526
  );
2185
2527
  logger.info('3. Run "lang-tag collect" to extract translations');
2528
+ if (renderOptions.isLibrary && renderOptions.tagName.startsWith("_") && (config.enforceLibraryTagPrefix ?? true)) {
2529
+ console.log("");
2530
+ logger.info(
2531
+ '📌 Important: Library tag prefix enforcement is enabled\n Your tag uses "_" prefix: {tagName} (instead of {baseTagName})\n This prevents the tag from appearing in TypeScript autocomplete after compilation\n Always use {tagName} (with prefix) in your library code\n This is a best practice for library internals - it keeps your API clean\n The prefix is automatically added by the enforceLibraryTagPrefix option\n To disable this behavior, set enforceLibraryTagPrefix: false in your config',
2532
+ {
2533
+ tagName: renderOptions.tagName,
2534
+ baseTagName: renderOptions.tagName.substring(1)
2535
+ }
2536
+ );
2537
+ }
2186
2538
  } catch (error) {
2187
2539
  logger.error("Failed to write file: {error}", {
2188
2540
  error: error?.message
2189
2541
  });
2190
2542
  }
2191
2543
  }
2192
- function deepFreezeObject(obj) {
2193
- const propNames = Object.getOwnPropertyNames(obj);
2194
- for (const name of propNames) {
2195
- const value = obj[name];
2196
- if (value && typeof value === "object") {
2197
- deepFreezeObject(value);
2198
- }
2199
- }
2200
- return Object.freeze(obj);
2201
- }
2202
2544
  async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
2203
2545
  let libraryImportsDir = config.import.dir;
2204
2546
  if (!libraryImportsDir.endsWith(path.sep)) libraryImportsDir += path.sep;
@@ -2232,13 +2574,23 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
2232
2574
  event.isSaved = true;
2233
2575
  event.savedConfig = updatedConfig;
2234
2576
  logger.debug(
2235
- 'Called save for "{path}" with config "{config}" triggered by: ("{trigger}")',
2577
+ 'Called save for "{path}"{varName} with config "{config}" triggered by: ("{trigger}")',
2236
2578
  {
2237
2579
  path: path$12,
2238
2580
  config: JSON.stringify(updatedConfig),
2239
- trigger: triggerName || "-"
2581
+ trigger: triggerName || "-",
2582
+ varName: tag.variableName ? `(${tag.variableName})` : ""
2240
2583
  }
2241
2584
  );
2585
+ },
2586
+ getCurrentConfig: () => {
2587
+ if (event.savedConfig !== void 0 && event.savedConfig !== null) {
2588
+ return { ...event.savedConfig };
2589
+ }
2590
+ if (event.config) {
2591
+ return { ...event.config };
2592
+ }
2593
+ return {};
2242
2594
  }
2243
2595
  };
2244
2596
  await config.onConfigGeneration(event);
@@ -2253,10 +2605,10 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
2253
2605
  if (replacements.length) {
2254
2606
  const newContent = processor.replaceTags(fileContent, replacements);
2255
2607
  await promises.writeFile(file, newContent, "utf-8");
2256
- const encodedFile = encodeURI(file);
2608
+ const fileUrl = formatFileUrlForDisplay(file);
2257
2609
  logger.info(
2258
- 'Lang tag configurations written for file "{path}" (file://{file}:{line})',
2259
- { path: path$12, file: encodedFile, line: lastUpdatedLine }
2610
+ 'Lang tag configurations written for file "{path}" ({url}:{line})',
2611
+ { path: path$12, url: fileUrl, line: lastUpdatedLine }
2260
2612
  );
2261
2613
  return true;
2262
2614
  }
@@ -2269,6 +2621,7 @@ function isConfigSame(c1, c2) {
2269
2621
  return false;
2270
2622
  }
2271
2623
  async function $LT_CMD_RegenerateTags() {
2624
+ const startTime = Date.now();
2272
2625
  const { config, logger } = await $LT_GetCommandEssentials();
2273
2626
  const files = await globby.globby(config.includes, {
2274
2627
  cwd: process.cwd(),
@@ -2289,11 +2642,13 @@ async function $LT_CMD_RegenerateTags() {
2289
2642
  dirty = true;
2290
2643
  }
2291
2644
  }
2645
+ const executionTime = formatExecutionTime(Date.now() - startTime);
2292
2646
  if (!dirty) {
2293
2647
  logger.info(
2294
2648
  "No changes were made based on the current configuration and files"
2295
2649
  );
2296
2650
  }
2651
+ logger.debug("Regeneration completed ({time})", { time: executionTime });
2297
2652
  }
2298
2653
  function getBasePath(pattern) {
2299
2654
  const globStartIndex = pattern.indexOf("*");
@@ -2402,6 +2757,14 @@ function createCli() {
2402
2757
  ).action(async (options) => {
2403
2758
  await $LT_CMD_InitTagFile(options);
2404
2759
  });
2760
+ commander.program.command("hide-compiled-exports").alias("hce").description(
2761
+ "Hide compiled .d.ts exports of lang-tag variables (remove export modifier, keep type)"
2762
+ ).option(
2763
+ "-d, --dist-dir <dir>",
2764
+ 'Dist directory to process (default: from config or "dist")'
2765
+ ).action(async (options) => {
2766
+ await $LT_CMD_HideCompiledExports({ distDir: options.distDir });
2767
+ });
2405
2768
  return commander.program;
2406
2769
  }
2407
2770
  createCli().parse();