@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/algorithms/index.cjs +3 -6
- package/algorithms/index.js +3 -6
- package/index.cjs +402 -39
- package/index.js +403 -40
- package/package.json +3 -2
- package/templates/tag/base-app.mustache +4 -4
- package/templates/tag/base-library.mustache +5 -5
- package/templates/tag/placeholder.mustache +15 -8
- package/type.d.ts +36 -0
package/index.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
import { program } from "commander";
|
|
3
3
|
import * as process$1 from "node:process";
|
|
4
4
|
import process__default from "node:process";
|
|
5
|
-
import fs, { readFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
5
|
+
import fs, { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
6
6
|
import { globby } from "globby";
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import path__default, { dirname, resolve, join, sep } from "path";
|
|
9
9
|
import JSON5 from "json5";
|
|
10
10
|
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
11
|
-
import path$1, { resolve as resolve$1 } from "pathe";
|
|
12
11
|
import { pathToFileURL, fileURLToPath } from "url";
|
|
12
|
+
import path$1, { resolve as resolve$1 } from "pathe";
|
|
13
13
|
import "case";
|
|
14
14
|
import { N as NamespaceCollector } from "./chunks/namespace-collector.js";
|
|
15
15
|
import micromatch from "micromatch";
|
|
16
16
|
import * as acorn from "acorn";
|
|
17
|
+
import { Project } from "ts-morph";
|
|
17
18
|
import mustache from "mustache";
|
|
18
19
|
import checkbox from "@inquirer/checkbox";
|
|
19
20
|
import confirm from "@inquirer/confirm";
|
|
@@ -82,22 +83,83 @@ class $LT_TagProcessor {
|
|
|
82
83
|
const matches = [];
|
|
83
84
|
let currentIndex = 0;
|
|
84
85
|
const skipRanges = this.buildSkipRanges(fileContent);
|
|
85
|
-
const
|
|
86
|
-
`${optionalVariableAssignment}${tagName}
|
|
86
|
+
const tagNamePattern = new RegExp(
|
|
87
|
+
`${optionalVariableAssignment}${tagName}`,
|
|
87
88
|
"g"
|
|
88
89
|
);
|
|
89
90
|
while (true) {
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
93
|
-
const
|
|
94
|
-
const variableName =
|
|
91
|
+
tagNamePattern.lastIndex = currentIndex;
|
|
92
|
+
const tagNameMatch = tagNamePattern.exec(fileContent);
|
|
93
|
+
if (!tagNameMatch) break;
|
|
94
|
+
const tagNameStartIndex = tagNameMatch.index;
|
|
95
|
+
const variableName = tagNameMatch[1] || void 0;
|
|
96
|
+
if (this.isInSkipRange(tagNameStartIndex, skipRanges)) {
|
|
97
|
+
currentIndex = tagNameStartIndex + 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
let i = tagNameStartIndex + tagNameMatch[0].length;
|
|
101
|
+
let genericType = void 0;
|
|
102
|
+
let matchStartIndex = tagNameStartIndex;
|
|
103
|
+
while (i < fileContent.length && /\s/.test(fileContent[i])) {
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
if (i < fileContent.length && fileContent[i] === "<") {
|
|
107
|
+
const genericStart = i;
|
|
108
|
+
let angleCount = 1;
|
|
109
|
+
i++;
|
|
110
|
+
while (i < fileContent.length && angleCount > 0) {
|
|
111
|
+
const char = fileContent[i];
|
|
112
|
+
if (char === "<") angleCount++;
|
|
113
|
+
else if (char === ">") angleCount--;
|
|
114
|
+
else if ((char === '"' || char === "'" || char === "`") && !this.isInSkipRange(i, skipRanges)) {
|
|
115
|
+
i++;
|
|
116
|
+
while (i < fileContent.length) {
|
|
117
|
+
if (fileContent[i] === "\\") {
|
|
118
|
+
i += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (fileContent[i] === char) {
|
|
122
|
+
i++;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
if (angleCount === 0) {
|
|
132
|
+
genericType = fileContent.substring(genericStart + 1, i - 1).trim();
|
|
133
|
+
matchStartIndex = tagNameStartIndex;
|
|
134
|
+
} else {
|
|
135
|
+
currentIndex = tagNameStartIndex + 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
matchStartIndex = tagNameStartIndex;
|
|
140
|
+
}
|
|
141
|
+
while (i < fileContent.length && /\s/.test(fileContent[i])) {
|
|
142
|
+
i++;
|
|
143
|
+
}
|
|
144
|
+
if (i >= fileContent.length || fileContent[i] !== "(" || i + 1 >= fileContent.length) {
|
|
145
|
+
currentIndex = tagNameStartIndex + 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
i++;
|
|
149
|
+
while (i < fileContent.length && /\s/.test(fileContent[i])) {
|
|
150
|
+
i++;
|
|
151
|
+
}
|
|
152
|
+
if (i >= fileContent.length || fileContent[i] !== "{") {
|
|
153
|
+
currentIndex = tagNameStartIndex + 1;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const braceStartIndex = i;
|
|
95
157
|
if (this.isInSkipRange(matchStartIndex, skipRanges)) {
|
|
96
158
|
currentIndex = matchStartIndex + 1;
|
|
97
159
|
continue;
|
|
98
160
|
}
|
|
99
161
|
let braceCount = 1;
|
|
100
|
-
|
|
162
|
+
i++;
|
|
101
163
|
while (i < fileContent.length && braceCount > 0) {
|
|
102
164
|
if (fileContent[i] === "{") braceCount++;
|
|
103
165
|
if (fileContent[i] === "}") braceCount--;
|
|
@@ -107,10 +169,7 @@ class $LT_TagProcessor {
|
|
|
107
169
|
currentIndex = matchStartIndex + 1;
|
|
108
170
|
continue;
|
|
109
171
|
}
|
|
110
|
-
let parameter1Text = fileContent.substring(
|
|
111
|
-
matchStartIndex + startMatch[0].length - 1,
|
|
112
|
-
i
|
|
113
|
-
);
|
|
172
|
+
let parameter1Text = fileContent.substring(braceStartIndex, i);
|
|
114
173
|
let parameter2Text;
|
|
115
174
|
while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
|
|
116
175
|
i++;
|
|
@@ -205,6 +264,7 @@ class $LT_TagProcessor {
|
|
|
205
264
|
matches.push({
|
|
206
265
|
fullMatch,
|
|
207
266
|
variableName,
|
|
267
|
+
genericType,
|
|
208
268
|
parameter1Text,
|
|
209
269
|
parameter2Text,
|
|
210
270
|
parameterTranslations,
|
|
@@ -258,7 +318,8 @@ class $LT_TagProcessor {
|
|
|
258
318
|
}
|
|
259
319
|
const arg1 = this.config.translationArgPosition === 1 ? newTranslationsString : newConfigString;
|
|
260
320
|
const arg2 = this.config.translationArgPosition === 1 ? newConfigString : newTranslationsString;
|
|
261
|
-
|
|
321
|
+
const genericTypePart = tag.genericType ? `<${tag.genericType}>` : "";
|
|
322
|
+
let tagFunction = `${this.config.tagName}${genericTypePart}(${arg1}`;
|
|
262
323
|
if (arg2) tagFunction += `, ${arg2}`;
|
|
263
324
|
tagFunction += ")";
|
|
264
325
|
if (tag.variableName)
|
|
@@ -484,7 +545,9 @@ async function $LT_CollectCandidateFilesWithTags(props) {
|
|
|
484
545
|
langTagConfig: config
|
|
485
546
|
});
|
|
486
547
|
}
|
|
487
|
-
|
|
548
|
+
if (!props.skipEmptyNamespaceCheck) {
|
|
549
|
+
tags = $LT_FilterEmptyNamespaceTags(tags, logger);
|
|
550
|
+
}
|
|
488
551
|
const relativeFilePath = path__default.relative(cwd, filePath);
|
|
489
552
|
candidates.push({ relativeFilePath, tags });
|
|
490
553
|
}
|
|
@@ -825,9 +888,30 @@ async function $LT_WriteToCollections({
|
|
|
825
888
|
}
|
|
826
889
|
await config.collect.collector.postWrite(changedCollections);
|
|
827
890
|
}
|
|
891
|
+
function deepFreezeObject(obj) {
|
|
892
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
893
|
+
for (const name of propNames) {
|
|
894
|
+
const value = obj[name];
|
|
895
|
+
if (value && typeof value === "object") {
|
|
896
|
+
deepFreezeObject(value);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return Object.freeze(obj);
|
|
900
|
+
}
|
|
901
|
+
function formatFileUrlForDisplay(filePath) {
|
|
902
|
+
return pathToFileURL(filePath).href.replace(/\[/g, "%5B").replace(/\]/g, "%5D").replace(/\(/g, "%28").replace(/\)/g, "%29");
|
|
903
|
+
}
|
|
904
|
+
function formatExecutionTime(milliseconds) {
|
|
905
|
+
if (milliseconds >= 1e3) {
|
|
906
|
+
const seconds = milliseconds / 1e3;
|
|
907
|
+
return `${seconds.toFixed(1)}s`;
|
|
908
|
+
}
|
|
909
|
+
return `${Math.round(milliseconds)}ms`;
|
|
910
|
+
}
|
|
828
911
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
829
912
|
tagName: "lang",
|
|
830
913
|
isLibrary: false,
|
|
914
|
+
enforceLibraryTagPrefix: true,
|
|
831
915
|
includes: ["src/**/*.{js,ts,jsx,tsx}"],
|
|
832
916
|
excludes: ["node_modules", "dist", "build"],
|
|
833
917
|
localesDirectory: "locales",
|
|
@@ -888,6 +972,7 @@ This will enable import of language tags from external packages.
|
|
|
888
972
|
}
|
|
889
973
|
},
|
|
890
974
|
translationArgPosition: 1,
|
|
975
|
+
hideDistDir: "dist",
|
|
891
976
|
onConfigGeneration: async (event) => {
|
|
892
977
|
event.logger.info(
|
|
893
978
|
"Config generation event is not configured. Add onConfigGeneration handler to customize config generation."
|
|
@@ -926,6 +1011,9 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
926
1011
|
if (!config.collect.collector) {
|
|
927
1012
|
throw new Error("Collector not found! (config.collect.collector)");
|
|
928
1013
|
}
|
|
1014
|
+
if (config.isLibrary && (config.enforceLibraryTagPrefix ?? true) && config.tagName && !config.tagName.startsWith("_")) {
|
|
1015
|
+
config.tagName = `_${config.tagName}`;
|
|
1016
|
+
}
|
|
929
1017
|
return config;
|
|
930
1018
|
} catch (error) {
|
|
931
1019
|
throw error;
|
|
@@ -1270,9 +1358,9 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
1270
1358
|
console.error("Failed to colorize config:", error);
|
|
1271
1359
|
}
|
|
1272
1360
|
}
|
|
1273
|
-
const
|
|
1361
|
+
const fileUrl = formatFileUrlForDisplay(filePath);
|
|
1274
1362
|
console.log(
|
|
1275
|
-
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}
|
|
1363
|
+
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}${fileUrl}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
|
|
1276
1364
|
);
|
|
1277
1365
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
1278
1366
|
} catch (error) {
|
|
@@ -1402,9 +1490,14 @@ async function $LT_GetCommandEssentials() {
|
|
|
1402
1490
|
};
|
|
1403
1491
|
}
|
|
1404
1492
|
async function $LT_CMD_Collect(options) {
|
|
1493
|
+
const startTime = Date.now();
|
|
1405
1494
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1406
1495
|
logger.info("Collecting translations from source files...");
|
|
1407
|
-
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1496
|
+
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1497
|
+
config,
|
|
1498
|
+
logger,
|
|
1499
|
+
skipEmptyNamespaceCheck: config.isLibrary
|
|
1500
|
+
});
|
|
1408
1501
|
if (config.debug) {
|
|
1409
1502
|
for (let file of files) {
|
|
1410
1503
|
logger.debug("Found {count} translations tags inside: {file}", {
|
|
@@ -1413,8 +1506,19 @@ async function $LT_CMD_Collect(options) {
|
|
|
1413
1506
|
});
|
|
1414
1507
|
}
|
|
1415
1508
|
}
|
|
1509
|
+
const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
|
|
1416
1510
|
if (config.isLibrary) {
|
|
1511
|
+
if (totalTags === 0 && (config.enforceLibraryTagPrefix ?? true) && config.tagName) {
|
|
1512
|
+
const baseTagName = config.tagName.startsWith("_") ? config.tagName.substring(1) : config.tagName;
|
|
1513
|
+
console.log("");
|
|
1514
|
+
logger.warn(
|
|
1515
|
+
'⚠️ 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.',
|
|
1516
|
+
{ prefixedBaseTagName: `_${baseTagName}`, baseTagName }
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1417
1519
|
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1520
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1521
|
+
logger.debug("Collection completed ({time})", { time: executionTime });
|
|
1418
1522
|
return;
|
|
1419
1523
|
}
|
|
1420
1524
|
try {
|
|
@@ -1423,10 +1527,6 @@ async function $LT_CMD_Collect(options) {
|
|
|
1423
1527
|
files,
|
|
1424
1528
|
config
|
|
1425
1529
|
});
|
|
1426
|
-
const totalTags = files.reduce(
|
|
1427
|
-
(sum, file) => sum + file.tags.length,
|
|
1428
|
-
0
|
|
1429
|
-
);
|
|
1430
1530
|
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1431
1531
|
await $LT_WriteToCollections({
|
|
1432
1532
|
config,
|
|
@@ -1434,15 +1534,252 @@ async function $LT_CMD_Collect(options) {
|
|
|
1434
1534
|
logger,
|
|
1435
1535
|
clean: options?.clean
|
|
1436
1536
|
});
|
|
1537
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1538
|
+
logger.debug("Collection completed ({time})", { time: executionTime });
|
|
1437
1539
|
} catch (e) {
|
|
1438
1540
|
const prefix = "LangTagConflictResolution:";
|
|
1439
1541
|
if (e.message.startsWith(prefix)) {
|
|
1440
1542
|
logger.error(e.message.substring(prefix.length));
|
|
1543
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1544
|
+
logger.debug("Collection completed ({time})", {
|
|
1545
|
+
time: executionTime
|
|
1546
|
+
});
|
|
1441
1547
|
return;
|
|
1442
1548
|
}
|
|
1443
1549
|
throw e;
|
|
1444
1550
|
}
|
|
1445
1551
|
}
|
|
1552
|
+
function $LT_HideExportsInDtsFile(dtsFilePath, variableNames) {
|
|
1553
|
+
const originalContent = readFileSync(dtsFilePath, "utf-8");
|
|
1554
|
+
const project = new Project({
|
|
1555
|
+
skipAddingFilesFromTsConfig: true,
|
|
1556
|
+
skipFileDependencyResolution: true,
|
|
1557
|
+
skipLoadingLibFiles: true
|
|
1558
|
+
});
|
|
1559
|
+
const sourceFile = project.addSourceFileAtPath(dtsFilePath);
|
|
1560
|
+
let hasChanges = false;
|
|
1561
|
+
const exportsToHide = [];
|
|
1562
|
+
const processedStatements = /* @__PURE__ */ new Set();
|
|
1563
|
+
for (const declaration of sourceFile.getVariableDeclarations()) {
|
|
1564
|
+
const name = declaration.getName();
|
|
1565
|
+
if (variableNames.has(name)) {
|
|
1566
|
+
const parent = declaration.getParent();
|
|
1567
|
+
if (parent && parent.getKindName() === "VariableDeclarationList") {
|
|
1568
|
+
const varList = parent;
|
|
1569
|
+
const grandParent = varList.getParent();
|
|
1570
|
+
if (grandParent && grandParent.getKindName() === "VariableStatement") {
|
|
1571
|
+
const varStatement = grandParent;
|
|
1572
|
+
if (varStatement.hasExportKeyword()) {
|
|
1573
|
+
if (!processedStatements.has(varStatement)) {
|
|
1574
|
+
processedStatements.add(varStatement);
|
|
1575
|
+
exportsToHide.push(name);
|
|
1576
|
+
varStatement.toggleModifier("export", false);
|
|
1577
|
+
hasChanges = true;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
if (!hasChanges) {
|
|
1585
|
+
return {
|
|
1586
|
+
hiddenCount: 0,
|
|
1587
|
+
modifiedContent: originalContent,
|
|
1588
|
+
originalContent
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
const modifiedContent = sourceFile.getFullText();
|
|
1592
|
+
return {
|
|
1593
|
+
hiddenCount: exportsToHide.length,
|
|
1594
|
+
modifiedContent,
|
|
1595
|
+
originalContent
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function extractPrefixesFromIncludes(includes) {
|
|
1599
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
1600
|
+
for (const pattern of includes) {
|
|
1601
|
+
const groupMatch = pattern.match(/^\(([^)]+)\)\/\*\*/);
|
|
1602
|
+
if (groupMatch) {
|
|
1603
|
+
const options = groupMatch[1].split("|").map((s) => s.trim());
|
|
1604
|
+
options.forEach((opt) => prefixes.add(opt));
|
|
1605
|
+
continue;
|
|
1606
|
+
}
|
|
1607
|
+
const simpleMatch = pattern.match(/^([^/]+)\/\*\*/);
|
|
1608
|
+
if (simpleMatch) {
|
|
1609
|
+
prefixes.add(simpleMatch[1]);
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
const singleMatch = pattern.match(/^([^/]+)\//);
|
|
1613
|
+
if (singleMatch) {
|
|
1614
|
+
prefixes.add(singleMatch[1]);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return Array.from(prefixes);
|
|
1618
|
+
}
|
|
1619
|
+
function findMatchingDtsFile(sourceFilePath, sourceRelativePath, dtsFileMap, config) {
|
|
1620
|
+
const sourceBaseName = path__default.basename(
|
|
1621
|
+
sourceFilePath,
|
|
1622
|
+
path__default.extname(sourceFilePath)
|
|
1623
|
+
);
|
|
1624
|
+
const relativePathKey = sourceRelativePath.replace(
|
|
1625
|
+
/\.(ts|tsx|js|jsx)$/,
|
|
1626
|
+
""
|
|
1627
|
+
);
|
|
1628
|
+
let dtsFilePath = dtsFileMap.get(relativePathKey);
|
|
1629
|
+
if (dtsFilePath) {
|
|
1630
|
+
return { dtsFilePath, strategy: "relative-path" };
|
|
1631
|
+
}
|
|
1632
|
+
if (config?.includes) {
|
|
1633
|
+
const prefixes = extractPrefixesFromIncludes(config.includes);
|
|
1634
|
+
for (const prefix of prefixes) {
|
|
1635
|
+
const prefixPattern = new RegExp(
|
|
1636
|
+
`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`
|
|
1637
|
+
);
|
|
1638
|
+
if (prefixPattern.test(sourceRelativePath)) {
|
|
1639
|
+
const strippedPath = sourceRelativePath.replace(
|
|
1640
|
+
prefixPattern,
|
|
1641
|
+
""
|
|
1642
|
+
);
|
|
1643
|
+
const strippedPathKey = strippedPath.replace(
|
|
1644
|
+
/\.(ts|tsx|js|jsx)$/,
|
|
1645
|
+
""
|
|
1646
|
+
);
|
|
1647
|
+
dtsFilePath = dtsFileMap.get(strippedPathKey);
|
|
1648
|
+
if (dtsFilePath) {
|
|
1649
|
+
return {
|
|
1650
|
+
dtsFilePath,
|
|
1651
|
+
strategy: "includes-prefix-stripped"
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
dtsFilePath = dtsFileMap.get(sourceBaseName);
|
|
1658
|
+
if (dtsFilePath) {
|
|
1659
|
+
return { dtsFilePath, strategy: "base-name" };
|
|
1660
|
+
}
|
|
1661
|
+
return { dtsFilePath: null, strategy: null };
|
|
1662
|
+
}
|
|
1663
|
+
async function $LT_MatchSourceToDtsFiles(files, distPath, cwd, config) {
|
|
1664
|
+
const sourceFileToVariables = /* @__PURE__ */ new Map();
|
|
1665
|
+
for (const file of files) {
|
|
1666
|
+
const sourceFilePath = path__default.resolve(cwd, file.relativeFilePath);
|
|
1667
|
+
const variableNames = /* @__PURE__ */ new Set();
|
|
1668
|
+
for (const tag of file.tags) {
|
|
1669
|
+
if (tag.variableName) {
|
|
1670
|
+
variableNames.add(tag.variableName);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
if (variableNames.size > 0) {
|
|
1674
|
+
sourceFileToVariables.set(sourceFilePath, variableNames);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
if (sourceFileToVariables.size === 0) {
|
|
1678
|
+
return [];
|
|
1679
|
+
}
|
|
1680
|
+
const dtsFiles = await globby("**/*.d.ts", {
|
|
1681
|
+
cwd: distPath,
|
|
1682
|
+
absolute: true
|
|
1683
|
+
});
|
|
1684
|
+
if (dtsFiles.length === 0) {
|
|
1685
|
+
return [];
|
|
1686
|
+
}
|
|
1687
|
+
const dtsFileMap = /* @__PURE__ */ new Map();
|
|
1688
|
+
for (const dtsFile of dtsFiles) {
|
|
1689
|
+
const relativeDtsPath = path__default.relative(distPath, dtsFile);
|
|
1690
|
+
const baseName = path__default.basename(dtsFile, ".d.ts");
|
|
1691
|
+
const relativePathWithoutExt = relativeDtsPath.replace(/\.d\.ts$/, "");
|
|
1692
|
+
dtsFileMap.set(relativePathWithoutExt, dtsFile);
|
|
1693
|
+
dtsFileMap.set(baseName, dtsFile);
|
|
1694
|
+
}
|
|
1695
|
+
const matches = [];
|
|
1696
|
+
const processedDtsFiles = /* @__PURE__ */ new Set();
|
|
1697
|
+
for (const [sourceFilePath, variableNames] of sourceFileToVariables) {
|
|
1698
|
+
const sourceRelativePath = path__default.relative(cwd, sourceFilePath);
|
|
1699
|
+
const match = findMatchingDtsFile(
|
|
1700
|
+
sourceFilePath,
|
|
1701
|
+
sourceRelativePath,
|
|
1702
|
+
dtsFileMap,
|
|
1703
|
+
config
|
|
1704
|
+
);
|
|
1705
|
+
if (!match.dtsFilePath) {
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
if (processedDtsFiles.has(match.dtsFilePath)) {
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
processedDtsFiles.add(match.dtsFilePath);
|
|
1712
|
+
matches.push({
|
|
1713
|
+
sourceFilePath,
|
|
1714
|
+
sourceRelativePath,
|
|
1715
|
+
dtsFilePath: match.dtsFilePath,
|
|
1716
|
+
variableNames
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
return matches;
|
|
1720
|
+
}
|
|
1721
|
+
async function $LT_CMD_HideCompiledExports(options) {
|
|
1722
|
+
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1723
|
+
const distDir = options?.distDir || config.hideDistDir || "dist";
|
|
1724
|
+
const distPath = path__default.resolve(process__default.cwd(), distDir);
|
|
1725
|
+
if (!existsSync(distPath)) {
|
|
1726
|
+
logger.warn("Dist directory does not exist: {distPath}", { distPath });
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
logger.info("Scanning source files for lang-tag variables...");
|
|
1730
|
+
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1731
|
+
config,
|
|
1732
|
+
logger,
|
|
1733
|
+
skipEmptyNamespaceCheck: true
|
|
1734
|
+
});
|
|
1735
|
+
const matches = await $LT_MatchSourceToDtsFiles(
|
|
1736
|
+
files,
|
|
1737
|
+
distPath,
|
|
1738
|
+
process__default.cwd(),
|
|
1739
|
+
config
|
|
1740
|
+
);
|
|
1741
|
+
if (matches.length === 0) {
|
|
1742
|
+
logger.info(
|
|
1743
|
+
"No lang-tag variables found in source files or no matching .d.ts files found."
|
|
1744
|
+
);
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
logger.info("Found {count} .d.ts files to process", {
|
|
1748
|
+
count: matches.length
|
|
1749
|
+
});
|
|
1750
|
+
let hiddenCount = 0;
|
|
1751
|
+
for (const match of matches) {
|
|
1752
|
+
try {
|
|
1753
|
+
const result = $LT_HideExportsInDtsFile(
|
|
1754
|
+
match.dtsFilePath,
|
|
1755
|
+
match.variableNames
|
|
1756
|
+
);
|
|
1757
|
+
if (!result.hiddenCount) {
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
writeFileSync(match.dtsFilePath, result.modifiedContent, "utf-8");
|
|
1761
|
+
hiddenCount += result.hiddenCount;
|
|
1762
|
+
const hiddenVariables = Array.from(match.variableNames).join(", ");
|
|
1763
|
+
logger.debug(
|
|
1764
|
+
"Hidden exports from {file}: {variables} (from {sourceFile})",
|
|
1765
|
+
{
|
|
1766
|
+
file: path__default.relative(process__default.cwd(), match.dtsFilePath),
|
|
1767
|
+
variables: hiddenVariables,
|
|
1768
|
+
sourceFile: match.sourceRelativePath
|
|
1769
|
+
}
|
|
1770
|
+
);
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
logger.warn("Error processing file {file}: {error}", {
|
|
1773
|
+
file: match.sourceRelativePath,
|
|
1774
|
+
error: error.message || String(error)
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
logger.success("Hidden {hiddenCount} exports from {fileCount} files.", {
|
|
1779
|
+
hiddenCount,
|
|
1780
|
+
fileCount: matches.length
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1446
1783
|
async function $LT_CollectExportFiles(logger) {
|
|
1447
1784
|
const nodeModulesPath = path$1.join(process__default.cwd(), "node_modules");
|
|
1448
1785
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
@@ -1561,9 +1898,11 @@ async function generateImportFiles(config, logger, importManager) {
|
|
|
1561
1898
|
const content = renderTemplate$2(templateData);
|
|
1562
1899
|
await $LT_EnsureDirectoryExists(dirname(filePath));
|
|
1563
1900
|
await writeFile(filePath, content, "utf-8");
|
|
1901
|
+
const fileUrl = formatFileUrlForDisplay(filePath);
|
|
1564
1902
|
logger.success('Created tag file: "{file}"', {
|
|
1565
1903
|
file: importedFile.pathRelativeToImportDir
|
|
1566
1904
|
});
|
|
1905
|
+
logger.debug(" └── link: {url}", { url: fileUrl });
|
|
1567
1906
|
}
|
|
1568
1907
|
}
|
|
1569
1908
|
class ImportManager {
|
|
@@ -1641,12 +1980,12 @@ async function $LT_ImportLibraries(config, logger) {
|
|
|
1641
1980
|
logger.warn("No tags were imported from any library files");
|
|
1642
1981
|
return;
|
|
1643
1982
|
}
|
|
1983
|
+
await $LT_EnsureDirectoryExists(config.import.dir);
|
|
1644
1984
|
await generateImportFiles(config, logger, importManager);
|
|
1645
1985
|
if (config.import.onImportFinish) config.import.onImportFinish();
|
|
1646
1986
|
}
|
|
1647
1987
|
async function $LT_ImportTranslations() {
|
|
1648
1988
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1649
|
-
await $LT_EnsureDirectoryExists(config.import.dir);
|
|
1650
1989
|
logger.info("Importing translations from libraries...");
|
|
1651
1990
|
await $LT_ImportLibraries(config, logger);
|
|
1652
1991
|
logger.success("Successfully imported translations from libraries.");
|
|
@@ -2062,7 +2401,10 @@ async function detectInitTagOptions(options, config) {
|
|
|
2062
2401
|
const isTypeScript = options.typescript !== void 0 ? options.typescript : detectTypeScript(packageJson);
|
|
2063
2402
|
const isReact = options.react !== void 0 ? options.react : detectReact(packageJson);
|
|
2064
2403
|
const isLibrary = options.library !== void 0 ? options.library : config.isLibrary;
|
|
2065
|
-
|
|
2404
|
+
let tagName = options.name || config.tagName || "lang";
|
|
2405
|
+
if (isLibrary && (config.enforceLibraryTagPrefix ?? true) && !tagName.startsWith("_")) {
|
|
2406
|
+
tagName = `_${tagName}`;
|
|
2407
|
+
}
|
|
2066
2408
|
const fileExtension = isLibrary && isReact ? isTypeScript ? "tsx" : "jsx" : isTypeScript ? "ts" : "js";
|
|
2067
2409
|
return {
|
|
2068
2410
|
tagName,
|
|
@@ -2163,22 +2505,22 @@ async function $LT_CMD_InitTagFile(options = {}) {
|
|
|
2163
2505
|
"2. Create your translation objects and use the tag function"
|
|
2164
2506
|
);
|
|
2165
2507
|
logger.info('3. Run "lang-tag collect" to extract translations');
|
|
2508
|
+
if (renderOptions.isLibrary && renderOptions.tagName.startsWith("_") && (config.enforceLibraryTagPrefix ?? true)) {
|
|
2509
|
+
console.log("");
|
|
2510
|
+
logger.info(
|
|
2511
|
+
'📌 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',
|
|
2512
|
+
{
|
|
2513
|
+
tagName: renderOptions.tagName,
|
|
2514
|
+
baseTagName: renderOptions.tagName.substring(1)
|
|
2515
|
+
}
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2166
2518
|
} catch (error) {
|
|
2167
2519
|
logger.error("Failed to write file: {error}", {
|
|
2168
2520
|
error: error?.message
|
|
2169
2521
|
});
|
|
2170
2522
|
}
|
|
2171
2523
|
}
|
|
2172
|
-
function deepFreezeObject(obj) {
|
|
2173
|
-
const propNames = Object.getOwnPropertyNames(obj);
|
|
2174
|
-
for (const name of propNames) {
|
|
2175
|
-
const value = obj[name];
|
|
2176
|
-
if (value && typeof value === "object") {
|
|
2177
|
-
deepFreezeObject(value);
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
return Object.freeze(obj);
|
|
2181
|
-
}
|
|
2182
2524
|
async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
2183
2525
|
let libraryImportsDir = config.import.dir;
|
|
2184
2526
|
if (!libraryImportsDir.endsWith(sep)) libraryImportsDir += sep;
|
|
@@ -2212,13 +2554,23 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
2212
2554
|
event.isSaved = true;
|
|
2213
2555
|
event.savedConfig = updatedConfig;
|
|
2214
2556
|
logger.debug(
|
|
2215
|
-
'Called save for "{path}" with config "{config}" triggered by: ("{trigger}")',
|
|
2557
|
+
'Called save for "{path}"{varName} with config "{config}" triggered by: ("{trigger}")',
|
|
2216
2558
|
{
|
|
2217
2559
|
path: path2,
|
|
2218
2560
|
config: JSON.stringify(updatedConfig),
|
|
2219
|
-
trigger: triggerName || "-"
|
|
2561
|
+
trigger: triggerName || "-",
|
|
2562
|
+
varName: tag.variableName ? `(${tag.variableName})` : ""
|
|
2220
2563
|
}
|
|
2221
2564
|
);
|
|
2565
|
+
},
|
|
2566
|
+
getCurrentConfig: () => {
|
|
2567
|
+
if (event.savedConfig !== void 0 && event.savedConfig !== null) {
|
|
2568
|
+
return { ...event.savedConfig };
|
|
2569
|
+
}
|
|
2570
|
+
if (event.config) {
|
|
2571
|
+
return { ...event.config };
|
|
2572
|
+
}
|
|
2573
|
+
return {};
|
|
2222
2574
|
}
|
|
2223
2575
|
};
|
|
2224
2576
|
await config.onConfigGeneration(event);
|
|
@@ -2233,10 +2585,10 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
2233
2585
|
if (replacements.length) {
|
|
2234
2586
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
2235
2587
|
await writeFile(file, newContent, "utf-8");
|
|
2236
|
-
const
|
|
2588
|
+
const fileUrl = formatFileUrlForDisplay(file);
|
|
2237
2589
|
logger.info(
|
|
2238
|
-
'Lang tag configurations written for file "{path}" (
|
|
2239
|
-
{ path: path2,
|
|
2590
|
+
'Lang tag configurations written for file "{path}" ({url}:{line})',
|
|
2591
|
+
{ path: path2, url: fileUrl, line: lastUpdatedLine }
|
|
2240
2592
|
);
|
|
2241
2593
|
return true;
|
|
2242
2594
|
}
|
|
@@ -2249,6 +2601,7 @@ function isConfigSame(c1, c2) {
|
|
|
2249
2601
|
return false;
|
|
2250
2602
|
}
|
|
2251
2603
|
async function $LT_CMD_RegenerateTags() {
|
|
2604
|
+
const startTime = Date.now();
|
|
2252
2605
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
2253
2606
|
const files = await globby(config.includes, {
|
|
2254
2607
|
cwd: process.cwd(),
|
|
@@ -2269,11 +2622,13 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
2269
2622
|
dirty = true;
|
|
2270
2623
|
}
|
|
2271
2624
|
}
|
|
2625
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
2272
2626
|
if (!dirty) {
|
|
2273
2627
|
logger.info(
|
|
2274
2628
|
"No changes were made based on the current configuration and files"
|
|
2275
2629
|
);
|
|
2276
2630
|
}
|
|
2631
|
+
logger.debug("Regeneration completed ({time})", { time: executionTime });
|
|
2277
2632
|
}
|
|
2278
2633
|
function getBasePath(pattern) {
|
|
2279
2634
|
const globStartIndex = pattern.indexOf("*");
|
|
@@ -2382,6 +2737,14 @@ function createCli() {
|
|
|
2382
2737
|
).action(async (options) => {
|
|
2383
2738
|
await $LT_CMD_InitTagFile(options);
|
|
2384
2739
|
});
|
|
2740
|
+
program.command("hide-compiled-exports").alias("hce").description(
|
|
2741
|
+
"Hide compiled .d.ts exports of lang-tag variables (remove export modifier, keep type)"
|
|
2742
|
+
).option(
|
|
2743
|
+
"-d, --dist-dir <dir>",
|
|
2744
|
+
'Dist directory to process (default: from config or "dist")'
|
|
2745
|
+
).action(async (options) => {
|
|
2746
|
+
await $LT_CMD_HideCompiledExports({ distDir: options.distDir });
|
|
2747
|
+
});
|
|
2385
2748
|
return program;
|
|
2386
2749
|
}
|
|
2387
2750
|
createCli().parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lang-tag/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"json5": "^2.2.3",
|
|
46
46
|
"micromatch": "^4.0.8",
|
|
47
47
|
"mustache": "^4.2.0",
|
|
48
|
-
"pathe": "^2.0.3"
|
|
48
|
+
"pathe": "^2.0.3",
|
|
49
|
+
"ts-morph": "^24.0.0"
|
|
49
50
|
},
|
|
50
51
|
"keywords": [
|
|
51
52
|
"i18n",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{{#isTypeScript}}
|
|
2
2
|
import {
|
|
3
|
-
CallableTranslations,
|
|
4
|
-
LangTagTranslations,
|
|
5
|
-
LangTagTranslationsConfig,
|
|
3
|
+
type CallableTranslations,
|
|
4
|
+
type LangTagTranslations,
|
|
5
|
+
type LangTagTranslationsConfig,
|
|
6
6
|
createCallableTranslations
|
|
7
7
|
} from 'lang-tag';
|
|
8
8
|
{{/isTypeScript}}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { createCallableTranslations } from 'lang-tag';
|
|
11
11
|
{{/isTypeScript}}
|
|
12
12
|
{{#isReact}}
|
|
13
|
-
import React, { ReactNode, useMemo } from 'react';
|
|
13
|
+
import React, { type ReactNode, useMemo } from 'react';
|
|
14
14
|
{{/isReact}}
|
|
15
15
|
|
|
16
16
|
{{#isTypeScript}}
|