@lang-tag/cli 0.14.0 → 0.16.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.js CHANGED
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from "commander";
3
3
  import fs, { readFileSync, existsSync } from "fs";
4
- import { writeFile, mkdir, readFile, rm } from "fs/promises";
4
+ import { writeFile, readFile } from "fs/promises";
5
5
  import JSON5 from "json5";
6
6
  import * as path from "path";
7
- import path__default, { sep, dirname, resolve as resolve$1, join } from "path";
7
+ import path__default, { sep, join, dirname } from "path";
8
8
  import { globby } from "globby";
9
- import path$1, { resolve, dirname as dirname$1 } from "pathe";
9
+ import path$1, { resolve } from "pathe";
10
10
  import { pathToFileURL, fileURLToPath } from "url";
11
+ import "case";
11
12
  import * as process$1 from "node:process";
12
13
  import process__default from "node:process";
14
+ import { f as flexibleImportAlgorithm, N as NamespaceCollector, $ as $LT_ReadFileContent, a as $LT_ReadJSON, b as $LT_WriteJSON, c as $LT_EnsureDirectoryExists, d as $LT_WriteFileWithDirs } from "./flexible-import-algorithm-C-S1c742.js";
13
15
  import * as acorn from "acorn";
14
16
  import micromatch from "micromatch";
15
17
  import chokidar from "chokidar";
@@ -478,13 +480,16 @@ function isConfigSame(c1, c2) {
478
480
  return false;
479
481
  }
480
482
  const CONFIG_FILE_NAME = ".lang-tag.config.js";
481
- const EXPORTS_FILE_NAME = ".lang-tag.exports.json";
483
+ const EXPORTS_FILE_NAME = "lang-tags.json";
482
484
  const LANG_TAG_DEFAULT_CONFIG = {
483
485
  tagName: "lang",
486
+ isLibrary: false,
484
487
  includes: ["src/**/*.{js,ts,jsx,tsx}"],
485
488
  excludes: ["node_modules", "dist", "build"],
486
- outputDir: "locales/en",
489
+ localesDirectory: "locales",
490
+ baseLanguageCode: "en",
487
491
  collect: {
492
+ collector: new NamespaceCollector(),
488
493
  defaultNamespace: "common",
489
494
  ignoreConflictsWithMatchingValues: true,
490
495
  onCollectConfigFix: ({ config, langTagConfig }) => {
@@ -504,15 +509,19 @@ const LANG_TAG_DEFAULT_CONFIG = {
504
509
  import: {
505
510
  dir: "src/lang-libraries",
506
511
  tagImportPath: 'import { lang } from "@/my-lang-tag-path"',
507
- onImport: ({ importedRelativePath, fileGenerationData }, actions) => {
508
- const exportIndex = (fileGenerationData.index || 0) + 1;
509
- fileGenerationData.index = exportIndex;
510
- actions.setFile(path$1.basename(importedRelativePath));
511
- actions.setExportName(`translations${exportIndex}`);
512
- }
512
+ onImport: flexibleImportAlgorithm({
513
+ filePath: {
514
+ includePackageInPath: true
515
+ }
516
+ })
517
+ // onImport: ({importedRelativePath, fileGenerationData}: LangTagCLIOnImportParams, actions)=> {
518
+ // const exportIndex = (fileGenerationData.index || 0) + 1;
519
+ // fileGenerationData.index = exportIndex;
520
+ //
521
+ // actions.setFile(path.basename(importedRelativePath));
522
+ // actions.setExportName(`translations${exportIndex}`);
523
+ // }
513
524
  },
514
- isLibrary: false,
515
- language: "en",
516
525
  translationArgPosition: 1,
517
526
  onConfigGeneration: async (event) => {
518
527
  }
@@ -532,7 +541,7 @@ async function $LT_ReadConfig(projectPath) {
532
541
  if (tn === "langtag" || tn === "lang-tag") {
533
542
  throw new Error('Custom tagName cannot be "lang-tag" or "langtag"! (It is not recommended for use with libraries)\n');
534
543
  }
535
- return {
544
+ const config = {
536
545
  ...LANG_TAG_DEFAULT_CONFIG,
537
546
  ...userConfig,
538
547
  import: {
@@ -544,39 +553,14 @@ async function $LT_ReadConfig(projectPath) {
544
553
  ...userConfig.collect
545
554
  }
546
555
  };
556
+ if (!config.collect.collector) {
557
+ throw new Error("Collector not found! (config.collect.collector)");
558
+ }
559
+ return config;
547
560
  } catch (error) {
548
561
  throw error;
549
562
  }
550
563
  }
551
- async function $LT_EnsureDirectoryExists(filePath) {
552
- await mkdir(filePath, { recursive: true });
553
- }
554
- async function $LT_RemoveDirectory(dirPath) {
555
- try {
556
- await rm(dirPath, { recursive: true, force: true });
557
- } catch (error) {
558
- }
559
- }
560
- async function $LT_WriteJSON(filePath, data) {
561
- await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
562
- }
563
- async function $LT_ReadJSON(filePath) {
564
- const content = await readFile(filePath, "utf-8");
565
- return JSON.parse(content);
566
- }
567
- async function $LT_WriteFileWithDirs(filePath, content) {
568
- const dir = dirname(filePath);
569
- try {
570
- await mkdir(dir, { recursive: true });
571
- } catch (error) {
572
- }
573
- await writeFile(filePath, content, "utf-8");
574
- }
575
- async function $LT_ReadFileContent(relativeFilePath) {
576
- const cwd = process.cwd();
577
- const absolutePath = resolve$1(cwd, relativeFilePath);
578
- return await readFile(absolutePath, "utf-8");
579
- }
580
564
  function parseObjectAST(code) {
581
565
  const nodes = [];
582
566
  try {
@@ -978,6 +962,8 @@ function $LT_CreateDefaultLogger(debugMode, translationArgPosition = 1) {
978
962
  async function $LT_GetCommandEssentials() {
979
963
  const config = await $LT_ReadConfig(process__default.cwd());
980
964
  const logger = $LT_CreateDefaultLogger(config.debug, config.translationArgPosition);
965
+ config.collect.collector.config = config;
966
+ config.collect.collector.logger = logger;
981
967
  return {
982
968
  config,
983
969
  logger
@@ -1040,36 +1026,26 @@ function deepMergeTranslations(target, source) {
1040
1026
  }
1041
1027
  return changed;
1042
1028
  }
1043
- async function $LT_WriteToNamespaces({ config, namespaces, logger, clean }) {
1044
- const changedNamespaces = [];
1045
- if (clean) {
1046
- logger.info("Cleaning output directory...");
1047
- await $LT_RemoveDirectory(config.outputDir);
1048
- }
1049
- await $LT_EnsureDirectoryExists(config.outputDir);
1050
- for (let namespace of Object.keys(namespaces)) {
1051
- if (!namespace) {
1029
+ async function $LT_WriteToCollections({ config, collections, logger, clean }) {
1030
+ await config.collect.collector.preWrite(clean);
1031
+ const changedCollections = [];
1032
+ for (let collectionName of Object.keys(collections)) {
1033
+ if (!collectionName) {
1052
1034
  continue;
1053
1035
  }
1054
- const filePath = resolve(
1055
- process__default.cwd(),
1056
- config.outputDir,
1057
- namespace + ".json"
1058
- );
1036
+ const filePath = await config.collect.collector.resolveCollectionFilePath(collectionName);
1059
1037
  let originalJSON = {};
1060
1038
  try {
1061
1039
  originalJSON = await $LT_ReadJSON(filePath);
1062
1040
  } catch (e) {
1063
- if (!clean) {
1064
- logger.warn(`Original namespace file "{namespace}.json" not found. A new one will be created.`, { namespace });
1065
- }
1041
+ await config.collect.collector.onMissingCollection(collectionName);
1066
1042
  }
1067
- if (deepMergeTranslations(originalJSON, namespaces[namespace])) {
1068
- changedNamespaces.push(namespace);
1043
+ if (deepMergeTranslations(originalJSON, collections[collectionName])) {
1044
+ changedCollections.push(collectionName);
1069
1045
  await $LT_WriteJSON(filePath, originalJSON);
1070
1046
  }
1071
1047
  }
1072
- return changedNamespaces;
1048
+ await config.collect.collector.postWrite(changedCollections);
1073
1049
  }
1074
1050
  async function $LT_CollectCandidateFilesWithTags(props) {
1075
1051
  const { config, logger } = props;
@@ -1104,47 +1080,39 @@ async function $LT_WriteAsExportFile({ config, logger, files }) {
1104
1080
  if (!packageJson) {
1105
1081
  throw new Error("package.json not found");
1106
1082
  }
1107
- const langTagFiles = {};
1108
- for (const file of files) {
1109
- langTagFiles[file.relativeFilePath] = {
1110
- matches: file.tags.map((tag) => {
1111
- let T = config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text;
1112
- let C = config.translationArgPosition === 1 ? tag.parameter2Text : tag.parameter1Text;
1113
- if (!T) T = "{}";
1114
- if (!C) C = "{}";
1115
- return {
1116
- translations: T,
1117
- config: C,
1118
- variableName: tag.variableName
1119
- };
1120
- })
1121
- };
1122
- }
1123
- const data = {
1124
- language: config.language,
1125
- packageName: packageJson.name || "",
1126
- files: langTagFiles
1083
+ const exportData = {
1084
+ baseLanguageCode: config.baseLanguageCode,
1085
+ files: files.map(({ relativeFilePath, tags }) => ({
1086
+ relativeFilePath,
1087
+ tags: tags.map((tag) => ({
1088
+ variableName: tag.variableName,
1089
+ config: tag.parameterConfig,
1090
+ translations: tag.parameterTranslations
1091
+ }))
1092
+ }))
1127
1093
  };
1128
- await $LT_WriteJSON(EXPORTS_FILE_NAME, data);
1094
+ await writeFile(EXPORTS_FILE_NAME, JSON.stringify(exportData), "utf-8");
1129
1095
  logger.success(`Written {file}`, { file: EXPORTS_FILE_NAME });
1130
1096
  }
1131
- async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
1097
+ async function $LT_GroupTagsToCollections({ logger, files, config }) {
1132
1098
  let totalTags = 0;
1133
- const namespaces = {};
1134
- function getTranslations(namespace) {
1135
- const namespaceTranslations = namespaces[namespace] || {};
1136
- if (!(namespace in namespaces)) {
1137
- namespaces[namespace] = namespaceTranslations;
1099
+ const collections = {};
1100
+ function getTranslationsCollection(namespace) {
1101
+ const collectionName = config.collect.collector.aggregateCollection(namespace);
1102
+ const collection = collections[collectionName] || {};
1103
+ if (!(collectionName in collections)) {
1104
+ collections[collectionName] = collection;
1138
1105
  }
1139
- return namespaceTranslations;
1106
+ return collection;
1140
1107
  }
1141
1108
  const allConflicts = [];
1142
1109
  const existingValuesByNamespace = /* @__PURE__ */ new Map();
1143
1110
  for (const file of files) {
1144
1111
  totalTags += file.tags.length;
1145
- for (const tag of file.tags) {
1112
+ for (const _tag of file.tags) {
1113
+ const tag = config.collect.collector.transformTag(_tag);
1146
1114
  const tagConfig = tag.parameterConfig;
1147
- const namespaceTranslations = getTranslations(tagConfig.namespace);
1115
+ const collection = getTranslationsCollection(tagConfig.namespace);
1148
1116
  let existingValues = existingValuesByNamespace.get(tagConfig.namespace);
1149
1117
  if (!existingValues) {
1150
1118
  existingValues = /* @__PURE__ */ new Map();
@@ -1187,7 +1155,7 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
1187
1155
  };
1188
1156
  const target = await ensureNestedObject(
1189
1157
  tagConfig.path,
1190
- namespaceTranslations,
1158
+ collection,
1191
1159
  valueTracker,
1192
1160
  addConflict
1193
1161
  );
@@ -1207,7 +1175,7 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
1207
1175
  let shouldContinue = true;
1208
1176
  config.collect.onCollectFinish({
1209
1177
  totalTags,
1210
- namespaces,
1178
+ namespaces: collections,
1211
1179
  conflicts: allConflicts,
1212
1180
  logger,
1213
1181
  exit() {
@@ -1218,11 +1186,11 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
1218
1186
  throw new Error(`LangTagConflictResolution:Processing stopped due to collect finish handler`);
1219
1187
  }
1220
1188
  }
1221
- return namespaces;
1189
+ return collections;
1222
1190
  }
1223
- async function ensureNestedObject(path2, root, valueTracker, addConflict) {
1224
- if (!path2 || !path2.trim()) return root;
1225
- let current = root;
1191
+ async function ensureNestedObject(path2, rootCollection, valueTracker, addConflict) {
1192
+ if (!path2 || !path2.trim()) return rootCollection;
1193
+ let current = rootCollection;
1226
1194
  let currentPath = "";
1227
1195
  for (const key of path2.split(".")) {
1228
1196
  currentPath = currentPath ? `${currentPath}.${key}` : key;
@@ -1322,19 +1290,10 @@ async function $LT_CMD_Collect(options) {
1322
1290
  return;
1323
1291
  }
1324
1292
  try {
1325
- const namespaces = await $LT_GroupTagsToNamespaces({ logger, files, config });
1293
+ const collections = await $LT_GroupTagsToCollections({ logger, files, config });
1326
1294
  const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
1327
1295
  logger.debug("Found {totalTags} translation tags", { totalTags });
1328
- const changedNamespaces = await $LT_WriteToNamespaces({ config, namespaces, logger, clean: options?.clean });
1329
- if (!changedNamespaces?.length) {
1330
- logger.info("No changes were made based on the current configuration and files");
1331
- return;
1332
- }
1333
- const n = changedNamespaces.map((n2) => `"${n2}.json"`).join(", ");
1334
- logger.success("Updated namespaces {outputDir} ({namespaces})", {
1335
- outputDir: config.outputDir,
1336
- namespaces: n
1337
- });
1296
+ await $LT_WriteToCollections({ config, collections, logger, clean: options?.clean });
1338
1297
  } catch (e) {
1339
1298
  const prefix = "LangTagConflictResolution:";
1340
1299
  if (e.message.startsWith(prefix)) {
@@ -1400,15 +1359,8 @@ async function handleFile(config, logger, cwdRelativeFilePath, event) {
1400
1359
  const absoluteFilePath = path__default.join(cwd, cwdRelativeFilePath);
1401
1360
  await checkAndRegenerateFileLangTags(config, logger, absoluteFilePath, cwdRelativeFilePath);
1402
1361
  const files = await $LT_CollectCandidateFilesWithTags({ filesToScan: [cwdRelativeFilePath], config, logger });
1403
- const namespaces = await $LT_GroupTagsToNamespaces({ logger, files, config });
1404
- const changedNamespaces = await $LT_WriteToNamespaces({ config, namespaces, logger });
1405
- if (changedNamespaces.length > 0) {
1406
- const n = changedNamespaces.map((n2) => `"${n2}.json"`).join(", ");
1407
- logger.success("Updated namespaces {outputDir} ({namespaces})", {
1408
- outputDir: config.outputDir,
1409
- namespaces: n
1410
- });
1411
- }
1362
+ const namespaces = await $LT_GroupTagsToCollections({ logger, files, config });
1363
+ await $LT_WriteToCollections({ config, collections: namespaces, logger });
1412
1364
  }
1413
1365
  async function detectModuleSystem() {
1414
1366
  const packageJsonPath = join(process.cwd(), "package.json");
@@ -1448,7 +1400,13 @@ const generationAlgorithm = pathBasedConfigGenerator({
1448
1400
  // },
1449
1401
  // admin: {
1450
1402
  // '>': 'management', // rename "admin" to "management"
1451
- // users: false // ignore "users"
1403
+ // users: false // ignore "users",
1404
+ // ui: {
1405
+ // '>>': { // 'redirect' - ignore everything, jump to 'ui' namespace and prefix all paths with 'admin'
1406
+ // namespace: 'ui',
1407
+ // pathPrefix: 'admin'
1408
+ // }
1409
+ // }
1452
1410
  // }
1453
1411
  // }
1454
1412
  // }
@@ -1461,7 +1419,8 @@ const config = {
1461
1419
  isLibrary: false,
1462
1420
  includes: ['src/**/*.{js,ts,jsx,tsx}'],
1463
1421
  excludes: ['node_modules', 'dist', 'build', '**/*.test.ts'],
1464
- outputDir: 'public/locales/en',
1422
+ localesDirectory: 'public/locales',
1423
+ baseLanguageCode: 'en',
1465
1424
  onConfigGeneration: async event => {
1466
1425
  // We do not modify imported configurations
1467
1426
  if (event.isImportedLibrary) return;
@@ -1503,97 +1462,171 @@ async function $LT_CMD_InitConfig() {
1503
1462
  logger.error(error?.message);
1504
1463
  }
1505
1464
  }
1506
- function $LT_CollectNodeModulesExportFilePaths(logger) {
1465
+ async function $LT_CollectExportFiles(logger) {
1507
1466
  const nodeModulesPath = path$1.join(process__default.cwd(), "node_modules");
1508
1467
  if (!fs.existsSync(nodeModulesPath)) {
1509
1468
  logger.error('"node_modules" directory not found');
1510
1469
  return [];
1511
1470
  }
1512
- function findExportJson(dir, depth = 0, maxDepth = 3) {
1513
- if (depth > maxDepth) return [];
1514
- let results = [];
1515
- try {
1516
- const files = fs.readdirSync(dir);
1517
- for (const file of files) {
1518
- const fullPath = path$1.join(dir, file);
1519
- const stat = fs.statSync(fullPath);
1520
- if (stat.isDirectory()) {
1521
- results = results.concat(findExportJson(fullPath, depth + 1, maxDepth));
1522
- } else if (file === EXPORTS_FILE_NAME) {
1523
- results.push(fullPath);
1524
- }
1471
+ const results = [];
1472
+ try {
1473
+ const exportFiles = await globby([
1474
+ `node_modules/**/${EXPORTS_FILE_NAME}`
1475
+ ], {
1476
+ cwd: process__default.cwd(),
1477
+ onlyFiles: true,
1478
+ ignore: ["**/node_modules/**/node_modules/**"],
1479
+ deep: 4
1480
+ });
1481
+ for (const exportFile of exportFiles) {
1482
+ const fullExportPath = path$1.resolve(exportFile);
1483
+ const packageJsonPath = findPackageJsonForExport(fullExportPath, nodeModulesPath);
1484
+ if (packageJsonPath) {
1485
+ results.push({
1486
+ exportPath: fullExportPath,
1487
+ packageJsonPath
1488
+ });
1489
+ } else {
1490
+ logger.warn("Found export file but could not match package.json: {exportPath}", {
1491
+ exportPath: fullExportPath
1492
+ });
1525
1493
  }
1526
- } catch (error) {
1527
- logger.error('Error reading directory "{dir}": {error}', {
1528
- dir,
1529
- error: String(error)
1530
- });
1531
1494
  }
1532
- return results;
1495
+ logger.debug("Found {count} export files with matching package.json in node_modules", {
1496
+ count: results.length
1497
+ });
1498
+ } catch (error) {
1499
+ logger.error("Error scanning node_modules with globby: {error}", {
1500
+ error: String(error)
1501
+ });
1533
1502
  }
1534
- return findExportJson(nodeModulesPath);
1503
+ return results;
1535
1504
  }
1536
- async function $LT_ImportLibraries(config, logger) {
1537
- const files = $LT_CollectNodeModulesExportFilePaths(logger);
1538
- const generationFiles = {};
1539
- for (const filePath of files) {
1540
- const exportData = await $LT_ReadJSON(filePath);
1541
- for (let langTagFilePath in exportData.files) {
1542
- const fileGenerationData = {};
1543
- const matches = exportData.files[langTagFilePath].matches;
1544
- for (let match of matches) {
1545
- let parsedTranslations = typeof match.translations === "string" ? JSON5.parse(match.translations) : match.translations;
1546
- let parsedConfig = typeof match.config === "string" ? JSON5.parse(match.config) : match.config === void 0 ? {} : match.config;
1547
- let file = langTagFilePath;
1548
- let exportName = match.variableName || "";
1549
- config.import.onImport({
1550
- packageName: exportData.packageName,
1551
- importedRelativePath: langTagFilePath,
1552
- originalExportName: match.variableName,
1553
- translations: parsedTranslations,
1554
- config: parsedConfig,
1555
- fileGenerationData
1556
- }, {
1557
- setFile: (f) => {
1558
- file = f;
1559
- },
1560
- setExportName: (name) => {
1561
- exportName = name;
1562
- },
1563
- setConfig: (newConfig) => {
1564
- parsedConfig = newConfig;
1565
- }
1566
- });
1567
- if (!file || !exportName) {
1568
- throw new Error(`[lang-tag] onImport did not set fileName or exportName for package: ${exportData.packageName}, file: '${file}' (original: '${langTagFilePath}'), exportName: '${exportName}' (original: ${match.variableName})`);
1569
- }
1570
- let exports = generationFiles[file];
1571
- if (!exports) {
1572
- exports = {};
1573
- generationFiles[file] = exports;
1574
- }
1575
- const param1 = config.translationArgPosition === 1 ? parsedTranslations : parsedConfig;
1576
- const param2 = config.translationArgPosition === 1 ? parsedConfig : parsedTranslations;
1577
- exports[exportName] = `${config.tagName}(${JSON5.stringify(param1, void 0, 4)}, ${JSON5.stringify(param2, void 0, 4)})`;
1505
+ function findPackageJsonForExport(exportPath, nodeModulesPath) {
1506
+ const relativePath = path$1.relative(nodeModulesPath, exportPath);
1507
+ const pathParts = relativePath.split(path$1.sep);
1508
+ if (pathParts.length < 2) {
1509
+ return null;
1510
+ }
1511
+ if (pathParts[0].startsWith("@")) {
1512
+ if (pathParts.length >= 3) {
1513
+ const packageDir = path$1.join(nodeModulesPath, pathParts[0], pathParts[1]);
1514
+ const packageJsonPath = path$1.join(packageDir, "package.json");
1515
+ if (fs.existsSync(packageJsonPath)) {
1516
+ return packageJsonPath;
1578
1517
  }
1579
1518
  }
1519
+ } else {
1520
+ const packageDir = path$1.join(nodeModulesPath, pathParts[0]);
1521
+ const packageJsonPath = path$1.join(packageDir, "package.json");
1522
+ if (fs.existsSync(packageJsonPath)) {
1523
+ return packageJsonPath;
1524
+ }
1580
1525
  }
1581
- for (let fileName of Object.keys(generationFiles)) {
1526
+ return null;
1527
+ }
1528
+ const __filename = fileURLToPath(import.meta.url);
1529
+ const __dirname = dirname(__filename);
1530
+ const templatePath = join(__dirname, "templates", "import", "imported-tag.mustache");
1531
+ const template = readFileSync(templatePath, "utf-8");
1532
+ function renderTemplate$1(data) {
1533
+ return mustache.render(template, data, {}, { escape: (text) => text });
1534
+ }
1535
+ async function generateImportFiles(config, logger, importManager) {
1536
+ const importedFiles = importManager.getImportedFiles();
1537
+ for (const importedFile of importedFiles) {
1582
1538
  const filePath = resolve(
1583
1539
  process$1.cwd(),
1584
1540
  config.import.dir,
1585
- fileName
1541
+ importedFile.pathRelativeToImportDir
1586
1542
  );
1587
- const exports = Object.entries(generationFiles[fileName]).map(([name, tag]) => {
1588
- return `export const ${name} = ${tag};`;
1589
- }).join("\n\n");
1590
- const content = `${config.import.tagImportPath}
1591
-
1592
- ${exports}`;
1593
- await $LT_EnsureDirectoryExists(dirname$1(filePath));
1543
+ const processedExports = importedFile.tags.map((tag) => {
1544
+ const parameter1 = config.translationArgPosition === 1 ? tag.translations : tag.config;
1545
+ const parameter2 = config.translationArgPosition === 1 ? tag.config : tag.translations;
1546
+ const hasParameter2 = parameter2 !== null && parameter2 !== void 0 && (typeof parameter2 !== "object" || Object.keys(parameter2).length > 0);
1547
+ return {
1548
+ name: tag.variableName,
1549
+ parameter1: JSON5.stringify(parameter1, void 0, 4),
1550
+ parameter2: hasParameter2 ? JSON5.stringify(parameter2, void 0, 4) : null,
1551
+ hasParameter2,
1552
+ config: {
1553
+ tagName: config.tagName
1554
+ }
1555
+ };
1556
+ });
1557
+ const templateData = {
1558
+ tagImportPath: config.import.tagImportPath,
1559
+ exports: processedExports
1560
+ };
1561
+ const content = renderTemplate$1(templateData);
1562
+ await $LT_EnsureDirectoryExists(dirname(filePath));
1594
1563
  await writeFile(filePath, content, "utf-8");
1595
- logger.success('Imported node_modules file: "{fileName}"', { fileName });
1564
+ logger.success('Created tag file: "{file}"', { file: importedFile.pathRelativeToImportDir });
1596
1565
  }
1566
+ }
1567
+ class ImportManager {
1568
+ importedFiles = [];
1569
+ constructor() {
1570
+ this.importedFiles = [];
1571
+ }
1572
+ importTag(pathRelativeToImportDir, tag) {
1573
+ if (!pathRelativeToImportDir) {
1574
+ throw new Error(`pathRelativeToImportDir required, got: ${pathRelativeToImportDir}`);
1575
+ }
1576
+ if (!tag?.variableName) {
1577
+ throw new Error(`tag.variableName required, got: ${tag?.variableName}`);
1578
+ }
1579
+ if (!this.isValidJavaScriptIdentifier(tag.variableName)) {
1580
+ throw new Error(`Invalid JavaScript identifier: "${tag.variableName}". Variable names must start with a letter, underscore, or dollar sign, and contain only letters, digits, underscores, and dollar signs.`);
1581
+ }
1582
+ if (tag.translations == null) {
1583
+ throw new Error(`tag.translations required`);
1584
+ }
1585
+ let importedFile = this.importedFiles.find(
1586
+ (file) => file.pathRelativeToImportDir === pathRelativeToImportDir
1587
+ );
1588
+ if (importedFile) {
1589
+ const duplicateTag = importedFile.tags.find(
1590
+ (existingTag) => existingTag.variableName === tag.variableName
1591
+ );
1592
+ if (duplicateTag) {
1593
+ throw new Error(`Duplicate variable name "${tag.variableName}" in file "${pathRelativeToImportDir}". Variable names must be unique within the same file.`);
1594
+ }
1595
+ }
1596
+ if (!importedFile) {
1597
+ importedFile = { pathRelativeToImportDir, tags: [] };
1598
+ this.importedFiles.push(importedFile);
1599
+ }
1600
+ importedFile.tags.push(tag);
1601
+ }
1602
+ getImportedFiles() {
1603
+ return [...this.importedFiles];
1604
+ }
1605
+ getImportedFilesCount() {
1606
+ return this.importedFiles.length;
1607
+ }
1608
+ hasImportedFiles() {
1609
+ return this.importedFiles.length > 0;
1610
+ }
1611
+ isValidJavaScriptIdentifier(name) {
1612
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
1613
+ }
1614
+ }
1615
+ async function $LT_ImportLibraries(config, logger) {
1616
+ const exportFiles = await $LT_CollectExportFiles(logger);
1617
+ const importManager = new ImportManager();
1618
+ let exports = [];
1619
+ for (const { exportPath, packageJsonPath } of exportFiles) {
1620
+ const exportData = await $LT_ReadJSON(exportPath);
1621
+ const packageJSON = await $LT_ReadJSON(packageJsonPath);
1622
+ exports.push({ packageJSON, exportData });
1623
+ }
1624
+ config.import.onImport({ exports, importManager, logger, langTagConfig: config });
1625
+ if (!importManager.hasImportedFiles()) {
1626
+ logger.warn("No tags were imported from any library files");
1627
+ return;
1628
+ }
1629
+ await generateImportFiles(config, logger, importManager);
1597
1630
  if (config.import.onImportFinish) config.import.onImportFinish();
1598
1631
  }
1599
1632
  async function $LT_ImportTranslations() {
@@ -1603,15 +1636,15 @@ async function $LT_ImportTranslations() {
1603
1636
  await $LT_ImportLibraries(config, logger);
1604
1637
  logger.success("Successfully imported translations from libraries.");
1605
1638
  }
1606
- function renderTemplate(template, data) {
1607
- return mustache.render(template, data, {}, { escape: (text) => text });
1639
+ function renderTemplate(template2, data) {
1640
+ return mustache.render(template2, data, {}, { escape: (text) => text });
1608
1641
  }
1609
1642
  function loadTemplate(templateName) {
1610
- const __filename = fileURLToPath(import.meta.url);
1611
- const __dirname = dirname(__filename);
1612
- const templatePath = join(__dirname, "template", `${templateName}.mustache`);
1643
+ const __filename2 = fileURLToPath(import.meta.url);
1644
+ const __dirname2 = dirname(__filename2);
1645
+ const templatePath2 = join(__dirname2, "templates", "tag", `${templateName}.mustache`);
1613
1646
  try {
1614
- return readFileSync(templatePath, "utf-8");
1647
+ return readFileSync(templatePath2, "utf-8");
1615
1648
  } catch (error) {
1616
1649
  throw new Error(`Failed to load template ${templateName}: ${error}`);
1617
1650
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lang-tag/cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1 @@
1
+ -
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Imported library translations
3
+ *
4
+ * You can modify these translations as needed. On next import:
5
+ * - Your changes will be preserved ( for now only translations values are preserved )
6
+ * - New translations will be added
7
+ * - Unused translations will be commented out
8
+ */
9
+ {{tagImportPath}}
10
+
11
+ {{#exports}}
12
+ export const {{name}} = {{config.tagName}}({{parameter1}}{{#hasParameter2}}, {{parameter2}}{{/hasParameter2}});
13
+
14
+ {{/exports}}
File without changes