@lang-tag/cli 0.19.0 → 0.21.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 +2 -5
- package/algorithms/index.js +2 -5
- package/index.cjs +328 -27
- package/index.js +329 -28
- package/package.json +3 -2
- package/templates/tag/base-app.mustache +4 -4
- package/templates/tag/base-library.mustache +23 -7
- package/templates/tag/note.md +4 -0
- package/templates/tag/placeholder.mustache +15 -8
- package/type.d.ts +34 -0
package/algorithms/index.cjs
CHANGED
|
@@ -214,7 +214,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
214
214
|
);
|
|
215
215
|
path$1 = transformedParts.join(".");
|
|
216
216
|
}
|
|
217
|
-
const newConfig = event.
|
|
217
|
+
const newConfig = event.getCurrentConfig();
|
|
218
218
|
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
219
219
|
if (path$1) {
|
|
220
220
|
newConfig.path = path$1;
|
|
@@ -442,13 +442,10 @@ function configKeeper(options = {}) {
|
|
|
442
442
|
if (keepMode !== "namespace" && keepMode !== "path" && keepMode !== "both") {
|
|
443
443
|
return;
|
|
444
444
|
}
|
|
445
|
-
let restoredConfig;
|
|
445
|
+
let restoredConfig = event.getCurrentConfig();
|
|
446
446
|
if (event.savedConfig === null) {
|
|
447
|
-
restoredConfig = { ...event.config };
|
|
448
447
|
delete restoredConfig.namespace;
|
|
449
448
|
delete restoredConfig.path;
|
|
450
|
-
} else {
|
|
451
|
-
restoredConfig = { ...event.savedConfig };
|
|
452
449
|
}
|
|
453
450
|
let needsSave = false;
|
|
454
451
|
const restorePropertyIfNeeded = (propertyKey) => {
|
package/algorithms/index.js
CHANGED
|
@@ -196,7 +196,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
196
196
|
);
|
|
197
197
|
path2 = transformedParts.join(".");
|
|
198
198
|
}
|
|
199
|
-
const newConfig = event.
|
|
199
|
+
const newConfig = event.getCurrentConfig();
|
|
200
200
|
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
201
201
|
if (path2) {
|
|
202
202
|
newConfig.path = path2;
|
|
@@ -424,13 +424,10 @@ function configKeeper(options = {}) {
|
|
|
424
424
|
if (keepMode !== "namespace" && keepMode !== "path" && keepMode !== "both") {
|
|
425
425
|
return;
|
|
426
426
|
}
|
|
427
|
-
let restoredConfig;
|
|
427
|
+
let restoredConfig = event.getCurrentConfig();
|
|
428
428
|
if (event.savedConfig === null) {
|
|
429
|
-
restoredConfig = { ...event.config };
|
|
430
429
|
delete restoredConfig.namespace;
|
|
431
430
|
delete restoredConfig.path;
|
|
432
|
-
} else {
|
|
433
|
-
restoredConfig = { ...event.savedConfig };
|
|
434
431
|
}
|
|
435
432
|
let needsSave = false;
|
|
436
433
|
const restorePropertyIfNeeded = (propertyKey) => {
|
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");
|
|
@@ -564,7 +565,9 @@ async function $LT_CollectCandidateFilesWithTags(props) {
|
|
|
564
565
|
langTagConfig: config
|
|
565
566
|
});
|
|
566
567
|
}
|
|
567
|
-
|
|
568
|
+
if (!props.skipEmptyNamespaceCheck) {
|
|
569
|
+
tags = $LT_FilterEmptyNamespaceTags(tags, logger);
|
|
570
|
+
}
|
|
568
571
|
const relativeFilePath = path.relative(cwd, filePath);
|
|
569
572
|
candidates.push({ relativeFilePath, tags });
|
|
570
573
|
}
|
|
@@ -905,9 +908,30 @@ async function $LT_WriteToCollections({
|
|
|
905
908
|
}
|
|
906
909
|
await config.collect.collector.postWrite(changedCollections);
|
|
907
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
|
+
}
|
|
908
931
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
909
932
|
tagName: "lang",
|
|
910
933
|
isLibrary: false,
|
|
934
|
+
enforceLibraryTagPrefix: true,
|
|
911
935
|
includes: ["src/**/*.{js,ts,jsx,tsx}"],
|
|
912
936
|
excludes: ["node_modules", "dist", "build"],
|
|
913
937
|
localesDirectory: "locales",
|
|
@@ -968,6 +992,7 @@ This will enable import of language tags from external packages.
|
|
|
968
992
|
}
|
|
969
993
|
},
|
|
970
994
|
translationArgPosition: 1,
|
|
995
|
+
hideDistDir: "dist",
|
|
971
996
|
onConfigGeneration: async (event) => {
|
|
972
997
|
event.logger.info(
|
|
973
998
|
"Config generation event is not configured. Add onConfigGeneration handler to customize config generation."
|
|
@@ -1006,6 +1031,9 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
1006
1031
|
if (!config.collect.collector) {
|
|
1007
1032
|
throw new Error("Collector not found! (config.collect.collector)");
|
|
1008
1033
|
}
|
|
1034
|
+
if (config.isLibrary && (config.enforceLibraryTagPrefix ?? true) && config.tagName && !config.tagName.startsWith("_")) {
|
|
1035
|
+
config.tagName = `_${config.tagName}`;
|
|
1036
|
+
}
|
|
1009
1037
|
return config;
|
|
1010
1038
|
} catch (error) {
|
|
1011
1039
|
throw error;
|
|
@@ -1350,9 +1378,9 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
1350
1378
|
console.error("Failed to colorize config:", error);
|
|
1351
1379
|
}
|
|
1352
1380
|
}
|
|
1353
|
-
const
|
|
1381
|
+
const fileUrl = formatFileUrlForDisplay(filePath);
|
|
1354
1382
|
console.log(
|
|
1355
|
-
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}
|
|
1383
|
+
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}${fileUrl}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
|
|
1356
1384
|
);
|
|
1357
1385
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
1358
1386
|
} catch (error) {
|
|
@@ -1482,9 +1510,14 @@ async function $LT_GetCommandEssentials() {
|
|
|
1482
1510
|
};
|
|
1483
1511
|
}
|
|
1484
1512
|
async function $LT_CMD_Collect(options) {
|
|
1513
|
+
const startTime = Date.now();
|
|
1485
1514
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1486
1515
|
logger.info("Collecting translations from source files...");
|
|
1487
|
-
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1516
|
+
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1517
|
+
config,
|
|
1518
|
+
logger,
|
|
1519
|
+
skipEmptyNamespaceCheck: config.isLibrary
|
|
1520
|
+
});
|
|
1488
1521
|
if (config.debug) {
|
|
1489
1522
|
for (let file of files) {
|
|
1490
1523
|
logger.debug("Found {count} translations tags inside: {file}", {
|
|
@@ -1493,8 +1526,19 @@ async function $LT_CMD_Collect(options) {
|
|
|
1493
1526
|
});
|
|
1494
1527
|
}
|
|
1495
1528
|
}
|
|
1529
|
+
const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
|
|
1496
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
|
+
}
|
|
1497
1539
|
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1540
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1541
|
+
logger.debug("Collection completed ({time})", { time: executionTime });
|
|
1498
1542
|
return;
|
|
1499
1543
|
}
|
|
1500
1544
|
try {
|
|
@@ -1503,10 +1547,6 @@ async function $LT_CMD_Collect(options) {
|
|
|
1503
1547
|
files,
|
|
1504
1548
|
config
|
|
1505
1549
|
});
|
|
1506
|
-
const totalTags = files.reduce(
|
|
1507
|
-
(sum, file) => sum + file.tags.length,
|
|
1508
|
-
0
|
|
1509
|
-
);
|
|
1510
1550
|
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1511
1551
|
await $LT_WriteToCollections({
|
|
1512
1552
|
config,
|
|
@@ -1514,15 +1554,252 @@ async function $LT_CMD_Collect(options) {
|
|
|
1514
1554
|
logger,
|
|
1515
1555
|
clean: options?.clean
|
|
1516
1556
|
});
|
|
1557
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1558
|
+
logger.debug("Collection completed ({time})", { time: executionTime });
|
|
1517
1559
|
} catch (e) {
|
|
1518
1560
|
const prefix = "LangTagConflictResolution:";
|
|
1519
1561
|
if (e.message.startsWith(prefix)) {
|
|
1520
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
|
+
});
|
|
1521
1567
|
return;
|
|
1522
1568
|
}
|
|
1523
1569
|
throw e;
|
|
1524
1570
|
}
|
|
1525
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
|
+
}
|
|
1526
1803
|
async function $LT_CollectExportFiles(logger) {
|
|
1527
1804
|
const nodeModulesPath = path$1.join(process$1.cwd(), "node_modules");
|
|
1528
1805
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
@@ -1641,11 +1918,11 @@ async function generateImportFiles(config, logger, importManager) {
|
|
|
1641
1918
|
const content = renderTemplate$2(templateData);
|
|
1642
1919
|
await $LT_EnsureDirectoryExists(path.dirname(filePath));
|
|
1643
1920
|
await promises.writeFile(filePath, content, "utf-8");
|
|
1644
|
-
const
|
|
1921
|
+
const fileUrl = formatFileUrlForDisplay(filePath);
|
|
1645
1922
|
logger.success('Created tag file: "{file}"', {
|
|
1646
1923
|
file: importedFile.pathRelativeToImportDir
|
|
1647
1924
|
});
|
|
1648
|
-
logger.debug(" └── link:
|
|
1925
|
+
logger.debug(" └── link: {url}", { url: fileUrl });
|
|
1649
1926
|
}
|
|
1650
1927
|
}
|
|
1651
1928
|
class ImportManager {
|
|
@@ -2144,7 +2421,10 @@ async function detectInitTagOptions(options, config) {
|
|
|
2144
2421
|
const isTypeScript = options.typescript !== void 0 ? options.typescript : detectTypeScript(packageJson);
|
|
2145
2422
|
const isReact = options.react !== void 0 ? options.react : detectReact(packageJson);
|
|
2146
2423
|
const isLibrary = options.library !== void 0 ? options.library : config.isLibrary;
|
|
2147
|
-
|
|
2424
|
+
let tagName = options.name || config.tagName || "lang";
|
|
2425
|
+
if (isLibrary && (config.enforceLibraryTagPrefix ?? true) && !tagName.startsWith("_")) {
|
|
2426
|
+
tagName = `_${tagName}`;
|
|
2427
|
+
}
|
|
2148
2428
|
const fileExtension = isLibrary && isReact ? isTypeScript ? "tsx" : "jsx" : isTypeScript ? "ts" : "js";
|
|
2149
2429
|
return {
|
|
2150
2430
|
tagName,
|
|
@@ -2245,22 +2525,22 @@ async function $LT_CMD_InitTagFile(options = {}) {
|
|
|
2245
2525
|
"2. Create your translation objects and use the tag function"
|
|
2246
2526
|
);
|
|
2247
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
|
+
}
|
|
2248
2538
|
} catch (error) {
|
|
2249
2539
|
logger.error("Failed to write file: {error}", {
|
|
2250
2540
|
error: error?.message
|
|
2251
2541
|
});
|
|
2252
2542
|
}
|
|
2253
2543
|
}
|
|
2254
|
-
function deepFreezeObject(obj) {
|
|
2255
|
-
const propNames = Object.getOwnPropertyNames(obj);
|
|
2256
|
-
for (const name of propNames) {
|
|
2257
|
-
const value = obj[name];
|
|
2258
|
-
if (value && typeof value === "object") {
|
|
2259
|
-
deepFreezeObject(value);
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
return Object.freeze(obj);
|
|
2263
|
-
}
|
|
2264
2544
|
async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
2265
2545
|
let libraryImportsDir = config.import.dir;
|
|
2266
2546
|
if (!libraryImportsDir.endsWith(path.sep)) libraryImportsDir += path.sep;
|
|
@@ -2294,13 +2574,23 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
2294
2574
|
event.isSaved = true;
|
|
2295
2575
|
event.savedConfig = updatedConfig;
|
|
2296
2576
|
logger.debug(
|
|
2297
|
-
'Called save for "{path}" with config "{config}" triggered by: ("{trigger}")',
|
|
2577
|
+
'Called save for "{path}"{varName} with config "{config}" triggered by: ("{trigger}")',
|
|
2298
2578
|
{
|
|
2299
2579
|
path: path$12,
|
|
2300
2580
|
config: JSON.stringify(updatedConfig),
|
|
2301
|
-
trigger: triggerName || "-"
|
|
2581
|
+
trigger: triggerName || "-",
|
|
2582
|
+
varName: tag.variableName ? `(${tag.variableName})` : ""
|
|
2302
2583
|
}
|
|
2303
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 {};
|
|
2304
2594
|
}
|
|
2305
2595
|
};
|
|
2306
2596
|
await config.onConfigGeneration(event);
|
|
@@ -2315,10 +2605,10 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
2315
2605
|
if (replacements.length) {
|
|
2316
2606
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
2317
2607
|
await promises.writeFile(file, newContent, "utf-8");
|
|
2318
|
-
const
|
|
2608
|
+
const fileUrl = formatFileUrlForDisplay(file);
|
|
2319
2609
|
logger.info(
|
|
2320
|
-
'Lang tag configurations written for file "{path}" (
|
|
2321
|
-
{ path: path$12,
|
|
2610
|
+
'Lang tag configurations written for file "{path}" ({url}:{line})',
|
|
2611
|
+
{ path: path$12, url: fileUrl, line: lastUpdatedLine }
|
|
2322
2612
|
);
|
|
2323
2613
|
return true;
|
|
2324
2614
|
}
|
|
@@ -2331,6 +2621,7 @@ function isConfigSame(c1, c2) {
|
|
|
2331
2621
|
return false;
|
|
2332
2622
|
}
|
|
2333
2623
|
async function $LT_CMD_RegenerateTags() {
|
|
2624
|
+
const startTime = Date.now();
|
|
2334
2625
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
2335
2626
|
const files = await globby.globby(config.includes, {
|
|
2336
2627
|
cwd: process.cwd(),
|
|
@@ -2351,11 +2642,13 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
2351
2642
|
dirty = true;
|
|
2352
2643
|
}
|
|
2353
2644
|
}
|
|
2645
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
2354
2646
|
if (!dirty) {
|
|
2355
2647
|
logger.info(
|
|
2356
2648
|
"No changes were made based on the current configuration and files"
|
|
2357
2649
|
);
|
|
2358
2650
|
}
|
|
2651
|
+
logger.debug("Regeneration completed ({time})", { time: executionTime });
|
|
2359
2652
|
}
|
|
2360
2653
|
function getBasePath(pattern) {
|
|
2361
2654
|
const globStartIndex = pattern.indexOf("*");
|
|
@@ -2464,6 +2757,14 @@ function createCli() {
|
|
|
2464
2757
|
).action(async (options) => {
|
|
2465
2758
|
await $LT_CMD_InitTagFile(options);
|
|
2466
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
|
+
});
|
|
2467
2768
|
return commander.program;
|
|
2468
2769
|
}
|
|
2469
2770
|
createCli().parse();
|
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";
|
|
@@ -544,7 +545,9 @@ async function $LT_CollectCandidateFilesWithTags(props) {
|
|
|
544
545
|
langTagConfig: config
|
|
545
546
|
});
|
|
546
547
|
}
|
|
547
|
-
|
|
548
|
+
if (!props.skipEmptyNamespaceCheck) {
|
|
549
|
+
tags = $LT_FilterEmptyNamespaceTags(tags, logger);
|
|
550
|
+
}
|
|
548
551
|
const relativeFilePath = path__default.relative(cwd, filePath);
|
|
549
552
|
candidates.push({ relativeFilePath, tags });
|
|
550
553
|
}
|
|
@@ -885,9 +888,30 @@ async function $LT_WriteToCollections({
|
|
|
885
888
|
}
|
|
886
889
|
await config.collect.collector.postWrite(changedCollections);
|
|
887
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
|
+
}
|
|
888
911
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
889
912
|
tagName: "lang",
|
|
890
913
|
isLibrary: false,
|
|
914
|
+
enforceLibraryTagPrefix: true,
|
|
891
915
|
includes: ["src/**/*.{js,ts,jsx,tsx}"],
|
|
892
916
|
excludes: ["node_modules", "dist", "build"],
|
|
893
917
|
localesDirectory: "locales",
|
|
@@ -948,6 +972,7 @@ This will enable import of language tags from external packages.
|
|
|
948
972
|
}
|
|
949
973
|
},
|
|
950
974
|
translationArgPosition: 1,
|
|
975
|
+
hideDistDir: "dist",
|
|
951
976
|
onConfigGeneration: async (event) => {
|
|
952
977
|
event.logger.info(
|
|
953
978
|
"Config generation event is not configured. Add onConfigGeneration handler to customize config generation."
|
|
@@ -986,6 +1011,9 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
986
1011
|
if (!config.collect.collector) {
|
|
987
1012
|
throw new Error("Collector not found! (config.collect.collector)");
|
|
988
1013
|
}
|
|
1014
|
+
if (config.isLibrary && (config.enforceLibraryTagPrefix ?? true) && config.tagName && !config.tagName.startsWith("_")) {
|
|
1015
|
+
config.tagName = `_${config.tagName}`;
|
|
1016
|
+
}
|
|
989
1017
|
return config;
|
|
990
1018
|
} catch (error) {
|
|
991
1019
|
throw error;
|
|
@@ -1330,9 +1358,9 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
1330
1358
|
console.error("Failed to colorize config:", error);
|
|
1331
1359
|
}
|
|
1332
1360
|
}
|
|
1333
|
-
const
|
|
1361
|
+
const fileUrl = formatFileUrlForDisplay(filePath);
|
|
1334
1362
|
console.log(
|
|
1335
|
-
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}
|
|
1363
|
+
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}${fileUrl}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
|
|
1336
1364
|
);
|
|
1337
1365
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
1338
1366
|
} catch (error) {
|
|
@@ -1462,9 +1490,14 @@ async function $LT_GetCommandEssentials() {
|
|
|
1462
1490
|
};
|
|
1463
1491
|
}
|
|
1464
1492
|
async function $LT_CMD_Collect(options) {
|
|
1493
|
+
const startTime = Date.now();
|
|
1465
1494
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1466
1495
|
logger.info("Collecting translations from source files...");
|
|
1467
|
-
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1496
|
+
const files = await $LT_CollectCandidateFilesWithTags({
|
|
1497
|
+
config,
|
|
1498
|
+
logger,
|
|
1499
|
+
skipEmptyNamespaceCheck: config.isLibrary
|
|
1500
|
+
});
|
|
1468
1501
|
if (config.debug) {
|
|
1469
1502
|
for (let file of files) {
|
|
1470
1503
|
logger.debug("Found {count} translations tags inside: {file}", {
|
|
@@ -1473,8 +1506,19 @@ async function $LT_CMD_Collect(options) {
|
|
|
1473
1506
|
});
|
|
1474
1507
|
}
|
|
1475
1508
|
}
|
|
1509
|
+
const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
|
|
1476
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
|
+
}
|
|
1477
1519
|
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1520
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1521
|
+
logger.debug("Collection completed ({time})", { time: executionTime });
|
|
1478
1522
|
return;
|
|
1479
1523
|
}
|
|
1480
1524
|
try {
|
|
@@ -1483,10 +1527,6 @@ async function $LT_CMD_Collect(options) {
|
|
|
1483
1527
|
files,
|
|
1484
1528
|
config
|
|
1485
1529
|
});
|
|
1486
|
-
const totalTags = files.reduce(
|
|
1487
|
-
(sum, file) => sum + file.tags.length,
|
|
1488
|
-
0
|
|
1489
|
-
);
|
|
1490
1530
|
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1491
1531
|
await $LT_WriteToCollections({
|
|
1492
1532
|
config,
|
|
@@ -1494,15 +1534,252 @@ async function $LT_CMD_Collect(options) {
|
|
|
1494
1534
|
logger,
|
|
1495
1535
|
clean: options?.clean
|
|
1496
1536
|
});
|
|
1537
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
1538
|
+
logger.debug("Collection completed ({time})", { time: executionTime });
|
|
1497
1539
|
} catch (e) {
|
|
1498
1540
|
const prefix = "LangTagConflictResolution:";
|
|
1499
1541
|
if (e.message.startsWith(prefix)) {
|
|
1500
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
|
+
});
|
|
1501
1547
|
return;
|
|
1502
1548
|
}
|
|
1503
1549
|
throw e;
|
|
1504
1550
|
}
|
|
1505
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
|
+
}
|
|
1506
1783
|
async function $LT_CollectExportFiles(logger) {
|
|
1507
1784
|
const nodeModulesPath = path$1.join(process__default.cwd(), "node_modules");
|
|
1508
1785
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
@@ -1621,11 +1898,11 @@ async function generateImportFiles(config, logger, importManager) {
|
|
|
1621
1898
|
const content = renderTemplate$2(templateData);
|
|
1622
1899
|
await $LT_EnsureDirectoryExists(dirname(filePath));
|
|
1623
1900
|
await writeFile(filePath, content, "utf-8");
|
|
1624
|
-
const
|
|
1901
|
+
const fileUrl = formatFileUrlForDisplay(filePath);
|
|
1625
1902
|
logger.success('Created tag file: "{file}"', {
|
|
1626
1903
|
file: importedFile.pathRelativeToImportDir
|
|
1627
1904
|
});
|
|
1628
|
-
logger.debug(" └── link:
|
|
1905
|
+
logger.debug(" └── link: {url}", { url: fileUrl });
|
|
1629
1906
|
}
|
|
1630
1907
|
}
|
|
1631
1908
|
class ImportManager {
|
|
@@ -2124,7 +2401,10 @@ async function detectInitTagOptions(options, config) {
|
|
|
2124
2401
|
const isTypeScript = options.typescript !== void 0 ? options.typescript : detectTypeScript(packageJson);
|
|
2125
2402
|
const isReact = options.react !== void 0 ? options.react : detectReact(packageJson);
|
|
2126
2403
|
const isLibrary = options.library !== void 0 ? options.library : config.isLibrary;
|
|
2127
|
-
|
|
2404
|
+
let tagName = options.name || config.tagName || "lang";
|
|
2405
|
+
if (isLibrary && (config.enforceLibraryTagPrefix ?? true) && !tagName.startsWith("_")) {
|
|
2406
|
+
tagName = `_${tagName}`;
|
|
2407
|
+
}
|
|
2128
2408
|
const fileExtension = isLibrary && isReact ? isTypeScript ? "tsx" : "jsx" : isTypeScript ? "ts" : "js";
|
|
2129
2409
|
return {
|
|
2130
2410
|
tagName,
|
|
@@ -2225,22 +2505,22 @@ async function $LT_CMD_InitTagFile(options = {}) {
|
|
|
2225
2505
|
"2. Create your translation objects and use the tag function"
|
|
2226
2506
|
);
|
|
2227
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
|
+
}
|
|
2228
2518
|
} catch (error) {
|
|
2229
2519
|
logger.error("Failed to write file: {error}", {
|
|
2230
2520
|
error: error?.message
|
|
2231
2521
|
});
|
|
2232
2522
|
}
|
|
2233
2523
|
}
|
|
2234
|
-
function deepFreezeObject(obj) {
|
|
2235
|
-
const propNames = Object.getOwnPropertyNames(obj);
|
|
2236
|
-
for (const name of propNames) {
|
|
2237
|
-
const value = obj[name];
|
|
2238
|
-
if (value && typeof value === "object") {
|
|
2239
|
-
deepFreezeObject(value);
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
return Object.freeze(obj);
|
|
2243
|
-
}
|
|
2244
2524
|
async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
2245
2525
|
let libraryImportsDir = config.import.dir;
|
|
2246
2526
|
if (!libraryImportsDir.endsWith(sep)) libraryImportsDir += sep;
|
|
@@ -2274,13 +2554,23 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
2274
2554
|
event.isSaved = true;
|
|
2275
2555
|
event.savedConfig = updatedConfig;
|
|
2276
2556
|
logger.debug(
|
|
2277
|
-
'Called save for "{path}" with config "{config}" triggered by: ("{trigger}")',
|
|
2557
|
+
'Called save for "{path}"{varName} with config "{config}" triggered by: ("{trigger}")',
|
|
2278
2558
|
{
|
|
2279
2559
|
path: path2,
|
|
2280
2560
|
config: JSON.stringify(updatedConfig),
|
|
2281
|
-
trigger: triggerName || "-"
|
|
2561
|
+
trigger: triggerName || "-",
|
|
2562
|
+
varName: tag.variableName ? `(${tag.variableName})` : ""
|
|
2282
2563
|
}
|
|
2283
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 {};
|
|
2284
2574
|
}
|
|
2285
2575
|
};
|
|
2286
2576
|
await config.onConfigGeneration(event);
|
|
@@ -2295,10 +2585,10 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
2295
2585
|
if (replacements.length) {
|
|
2296
2586
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
2297
2587
|
await writeFile(file, newContent, "utf-8");
|
|
2298
|
-
const
|
|
2588
|
+
const fileUrl = formatFileUrlForDisplay(file);
|
|
2299
2589
|
logger.info(
|
|
2300
|
-
'Lang tag configurations written for file "{path}" (
|
|
2301
|
-
{ path: path2,
|
|
2590
|
+
'Lang tag configurations written for file "{path}" ({url}:{line})',
|
|
2591
|
+
{ path: path2, url: fileUrl, line: lastUpdatedLine }
|
|
2302
2592
|
);
|
|
2303
2593
|
return true;
|
|
2304
2594
|
}
|
|
@@ -2311,6 +2601,7 @@ function isConfigSame(c1, c2) {
|
|
|
2311
2601
|
return false;
|
|
2312
2602
|
}
|
|
2313
2603
|
async function $LT_CMD_RegenerateTags() {
|
|
2604
|
+
const startTime = Date.now();
|
|
2314
2605
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
2315
2606
|
const files = await globby(config.includes, {
|
|
2316
2607
|
cwd: process.cwd(),
|
|
@@ -2331,11 +2622,13 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
2331
2622
|
dirty = true;
|
|
2332
2623
|
}
|
|
2333
2624
|
}
|
|
2625
|
+
const executionTime = formatExecutionTime(Date.now() - startTime);
|
|
2334
2626
|
if (!dirty) {
|
|
2335
2627
|
logger.info(
|
|
2336
2628
|
"No changes were made based on the current configuration and files"
|
|
2337
2629
|
);
|
|
2338
2630
|
}
|
|
2631
|
+
logger.debug("Regeneration completed ({time})", { time: executionTime });
|
|
2339
2632
|
}
|
|
2340
2633
|
function getBasePath(pattern) {
|
|
2341
2634
|
const globStartIndex = pattern.indexOf("*");
|
|
@@ -2444,6 +2737,14 @@ function createCli() {
|
|
|
2444
2737
|
).action(async (options) => {
|
|
2445
2738
|
await $LT_CMD_InitTagFile(options);
|
|
2446
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
|
+
});
|
|
2447
2748
|
return program;
|
|
2448
2749
|
}
|
|
2449
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.21.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}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{{#isTypeScript}}
|
|
2
2
|
import {
|
|
3
|
-
CallableTranslations,
|
|
4
|
-
LangTagTranslations,
|
|
5
|
-
LangTagTranslationsConfig,
|
|
6
|
-
PartialFlexibleTranslations,
|
|
3
|
+
type CallableTranslations,
|
|
4
|
+
type LangTagTranslations,
|
|
5
|
+
type LangTagTranslationsConfig,
|
|
6
|
+
type PartialFlexibleTranslations,
|
|
7
7
|
createCallableTranslations,
|
|
8
8
|
normalizeTranslations,
|
|
9
9
|
lookupTranslation
|
|
@@ -17,7 +17,7 @@ import React, {
|
|
|
17
17
|
createContext,
|
|
18
18
|
useContext,
|
|
19
19
|
useMemo,
|
|
20
|
-
ReactNode
|
|
20
|
+
type ReactNode
|
|
21
21
|
} from 'react';
|
|
22
22
|
{{/isReact}}
|
|
23
23
|
|
|
@@ -25,11 +25,27 @@ import React, {
|
|
|
25
25
|
interface TagConfig extends LangTagTranslationsConfig {
|
|
26
26
|
keep?: 'namespace' | 'path' | 'both'
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ensures the Tag type compiles into a named alias rather than an expanded structural type,
|
|
31
|
+
* preventing bloated .d.ts output during tag generation and keeping exported definitions clean.
|
|
32
|
+
*/
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
34
|
+
export namespace {{tagName}} {
|
|
35
|
+
export type Tag<T extends LangTagTranslations> = {
|
|
36
|
+
useTranslations: () => CallableTranslations<T>;
|
|
37
|
+
initTranslations: (translations?: PartialFlexibleTranslations<T>) => CallableTranslations<T>;
|
|
38
|
+
Provider(props: { translations?: PartialFlexibleTranslations<T>; children: ReactNode }): ReactNode;
|
|
39
|
+
InputType: PartialFlexibleTranslations<T>;
|
|
40
|
+
Type: CallableTranslations<T>;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
{{/isTypeScript}}
|
|
29
|
-
export function {{tagName}}<T extends LangTagTranslations>(
|
|
45
|
+
export function {{tagName}}{{#isTypeScript}}<T extends LangTagTranslations>{{/isTypeScript}}(
|
|
30
46
|
baseTranslations: T,
|
|
31
47
|
config?: TagConfig,
|
|
32
|
-
) {
|
|
48
|
+
){{#isTypeScript}}: {{tagName}}.Tag<T>{{/isTypeScript}} {
|
|
33
49
|
const createTranslationHelper = (normalized{{#isTypeScript}}: CallableTranslations<T> | null{{/isTypeScript}}) =>
|
|
34
50
|
createCallableTranslations(baseTranslations, config, {
|
|
35
51
|
transform: ({unprefixedPath, value, params}) => {
|
|
@@ -30,8 +30,16 @@ export function processPlaceholders(
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const key = placeholder.trim();
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const value = params?.[key];
|
|
34
|
+
|
|
35
|
+
if (React.isValidElement(value)) {
|
|
36
|
+
parts.push(value);
|
|
37
|
+
} else if (
|
|
38
|
+
typeof value === 'string' ||
|
|
39
|
+
typeof value === 'number' ||
|
|
40
|
+
typeof value === 'boolean'
|
|
41
|
+
) {
|
|
42
|
+
parts.push(String(value));
|
|
35
43
|
} else {
|
|
36
44
|
parts.push('');
|
|
37
45
|
}
|
|
@@ -49,12 +57,11 @@ export function processPlaceholders(
|
|
|
49
57
|
return parts.join('');
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
return parts.map((part, index) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}){{#isTypeScript}} as unknown as string{{/isTypeScript}};
|
|
60
|
+
return parts.map((part, index) =>
|
|
61
|
+
React.isValidElement(part)
|
|
62
|
+
? React.cloneElement(part, { key: index })
|
|
63
|
+
: React.createElement(React.Fragment, { key: index }, part)
|
|
64
|
+
){{#isTypeScript}} as unknown as string{{/isTypeScript}};
|
|
58
65
|
}
|
|
59
66
|
{{/isReact}}
|
|
60
67
|
|
package/type.d.ts
CHANGED
|
@@ -40,6 +40,17 @@ export interface LangTagCLIConfig {
|
|
|
40
40
|
* @default false
|
|
41
41
|
*/
|
|
42
42
|
isLibrary: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* When true and isLibrary is true, automatically adds "_" prefix to tagName
|
|
45
|
+
* to prevent the tag from being suggested in TypeScript autocomplete after compilation.
|
|
46
|
+
* This ensures that library tags remain internal and are not exposed in .d.ts files.
|
|
47
|
+
* @default true
|
|
48
|
+
* @example
|
|
49
|
+
* // With enforceLibraryTagPrefix: true and tagName: "lang"
|
|
50
|
+
* // Generated tag function will be: export function _lang(...)
|
|
51
|
+
* // Config tagName also will be automatically set to "_lang"
|
|
52
|
+
*/
|
|
53
|
+
enforceLibraryTagPrefix?: boolean;
|
|
43
54
|
collect?: {
|
|
44
55
|
/**
|
|
45
56
|
* Translation collector that defines how translation tags are organized into output files.
|
|
@@ -134,6 +145,12 @@ export interface LangTagCLIConfig {
|
|
|
134
145
|
* @default 1
|
|
135
146
|
*/
|
|
136
147
|
translationArgPosition: 1 | 2;
|
|
148
|
+
/**
|
|
149
|
+
* Directory containing compiled TypeScript declaration files (.d.ts) to remove export modifier.
|
|
150
|
+
* Used by the `hide-compiled-exports` command to remove exports of lang-tag variables.
|
|
151
|
+
* @default 'dist'
|
|
152
|
+
*/
|
|
153
|
+
hideDistDir?: string;
|
|
137
154
|
debug?: boolean;
|
|
138
155
|
}
|
|
139
156
|
type Validity = 'ok' | 'invalid-param-1' | 'invalid-param-2' | 'translations-not-found';
|
|
@@ -234,6 +251,23 @@ export interface LangTagCLIConfigGenerationEvent {
|
|
|
234
251
|
* null = means configuration will be removed
|
|
235
252
|
**/
|
|
236
253
|
save(config: LangTagTranslationsConfig | null, triggerName?: string): void;
|
|
254
|
+
/**
|
|
255
|
+
* Returns the current configuration object that should be used as a base for modifications.
|
|
256
|
+
* This method provides a reusable way to get the active configuration:
|
|
257
|
+
* - If `save()` was called, returns `savedConfig` (a mutable copy)
|
|
258
|
+
* - Otherwise, returns `config` (a mutable copy)
|
|
259
|
+
* - If neither exists, returns an empty object `{}`
|
|
260
|
+
*
|
|
261
|
+
* The returned object is always a shallow copy, so it can be safely modified.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* const currentConfig = event.getCurrentConfig();
|
|
266
|
+
* currentConfig.namespace = 'new-namespace';
|
|
267
|
+
* event.save(currentConfig);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
getCurrentConfig(): LangTagTranslationsConfig;
|
|
237
271
|
}
|
|
238
272
|
export interface LangTagCLICollectConfigFixEvent {
|
|
239
273
|
config: LangTagTranslationsConfig;
|