@lang-tag/cli 0.16.0 → 0.17.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/README.md +23 -14
- package/algorithms/collector/dictionary-collector.d.ts +2 -2
- package/algorithms/collector/index.d.ts +3 -3
- package/algorithms/collector/namespace-collector.d.ts +2 -2
- package/algorithms/collector/type.d.ts +2 -2
- package/algorithms/config-generation/config-keeper.d.ts +1 -1
- package/algorithms/config-generation/index.d.ts +3 -3
- package/algorithms/config-generation/path-based-config-generator.d.ts +1 -1
- package/algorithms/config-generation/prepend-namespace-to-path.d.ts +1 -1
- package/algorithms/import/flexible-import-algorithm.d.ts +1 -1
- package/algorithms/import/index.d.ts +2 -2
- package/algorithms/import/simple-mapping-import-algorithm.d.ts +1 -1
- package/algorithms/index.cjs +380 -27
- package/algorithms/index.d.ts +3 -3
- package/algorithms/index.js +361 -25
- package/chunks/namespace-collector.cjs +75 -0
- package/chunks/namespace-collector.js +76 -0
- package/index.cjs +1172 -830
- package/index.js +1171 -829
- package/logger.d.ts +1 -1
- package/package.json +1 -1
- package/{config.d.ts → type.d.ts} +3 -3
- package/flexible-import-algorithm-C-S1c742.js +0 -311
- package/flexible-import-algorithm-Fa-l4jWj.cjs +0 -327
package/index.js
CHANGED
|
@@ -1,21 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from "commander";
|
|
3
|
+
import * as process$1 from "node:process";
|
|
4
|
+
import process__default from "node:process";
|
|
3
5
|
import fs, { readFileSync, existsSync } from "fs";
|
|
4
|
-
import { writeFile, readFile } from "fs/promises";
|
|
5
|
-
import JSON5 from "json5";
|
|
6
|
-
import * as path from "path";
|
|
7
|
-
import path__default, { sep, join, dirname } from "path";
|
|
8
6
|
import { globby } from "globby";
|
|
9
|
-
import
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import path__default, { dirname, resolve, join, sep } from "path";
|
|
9
|
+
import JSON5 from "json5";
|
|
10
|
+
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
11
|
+
import path$1, { resolve as resolve$1 } from "pathe";
|
|
10
12
|
import { pathToFileURL, fileURLToPath } from "url";
|
|
11
13
|
import "case";
|
|
12
|
-
import
|
|
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";
|
|
15
|
-
import * as acorn from "acorn";
|
|
14
|
+
import { N as NamespaceCollector } from "./chunks/namespace-collector.js";
|
|
16
15
|
import micromatch from "micromatch";
|
|
17
|
-
import
|
|
16
|
+
import * as acorn from "acorn";
|
|
18
17
|
import mustache from "mustache";
|
|
18
|
+
import chokidar from "chokidar";
|
|
19
|
+
function $LT_FilterInvalidTags(tags, config, logger) {
|
|
20
|
+
return tags.filter((tag) => {
|
|
21
|
+
if (tag.validity === "invalid-param-1")
|
|
22
|
+
logger.debug(
|
|
23
|
+
'Skipping tag "{fullMatch}". Invalid JSON: "{invalid}"',
|
|
24
|
+
{
|
|
25
|
+
fullMatch: tag.fullMatch.trim(),
|
|
26
|
+
invalid: tag.parameter1Text
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
if (tag.validity === "invalid-param-2")
|
|
30
|
+
logger.debug(
|
|
31
|
+
'Skipping tag "{fullMatch}". Invalid JSON: "{invalid}"',
|
|
32
|
+
{
|
|
33
|
+
fullMatch: tag.fullMatch.trim(),
|
|
34
|
+
invalid: tag.parameter2Text
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
if (tag.validity === "translations-not-found")
|
|
38
|
+
logger.debug(
|
|
39
|
+
'Skipping tag "{fullMatch}". Translations not found at parameter position: {pos}',
|
|
40
|
+
{
|
|
41
|
+
fullMatch: tag.fullMatch.trim(),
|
|
42
|
+
pos: config.translationArgPosition
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
return tag.validity === "ok";
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function $LT_FilterEmptyNamespaceTags(tags, logger) {
|
|
49
|
+
return tags.filter((tag) => {
|
|
50
|
+
if (!tag.parameterConfig) {
|
|
51
|
+
logger.warn(
|
|
52
|
+
'Skipping tag "{fullMatch}". Tag configuration not defined. (Check lang-tag config at collect.onCollectConfigFix)',
|
|
53
|
+
{
|
|
54
|
+
fullMatch: tag.fullMatch.trim()
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (!tag.parameterConfig.namespace) {
|
|
60
|
+
logger.warn(
|
|
61
|
+
'Skipping tag "{fullMatch}". Tag configuration namespace not defined. (Check lang-tag config at collect.onCollectConfigFix)',
|
|
62
|
+
{
|
|
63
|
+
fullMatch: tag.fullMatch.trim()
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
19
71
|
class $LT_TagProcessor {
|
|
20
72
|
constructor(config) {
|
|
21
73
|
this.config = config;
|
|
@@ -26,7 +78,10 @@ class $LT_TagProcessor {
|
|
|
26
78
|
const matches = [];
|
|
27
79
|
let currentIndex = 0;
|
|
28
80
|
const skipRanges = this.buildSkipRanges(fileContent);
|
|
29
|
-
const startPattern = new RegExp(
|
|
81
|
+
const startPattern = new RegExp(
|
|
82
|
+
`${optionalVariableAssignment}${tagName}\\(\\s*\\{`,
|
|
83
|
+
"g"
|
|
84
|
+
);
|
|
30
85
|
while (true) {
|
|
31
86
|
startPattern.lastIndex = currentIndex;
|
|
32
87
|
const startMatch = startPattern.exec(fileContent);
|
|
@@ -48,7 +103,10 @@ class $LT_TagProcessor {
|
|
|
48
103
|
currentIndex = matchStartIndex + 1;
|
|
49
104
|
continue;
|
|
50
105
|
}
|
|
51
|
-
let parameter1Text = fileContent.substring(
|
|
106
|
+
let parameter1Text = fileContent.substring(
|
|
107
|
+
matchStartIndex + startMatch[0].length - 1,
|
|
108
|
+
i
|
|
109
|
+
);
|
|
52
110
|
let parameter2Text;
|
|
53
111
|
while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
|
|
54
112
|
i++;
|
|
@@ -100,7 +158,10 @@ class $LT_TagProcessor {
|
|
|
100
158
|
}
|
|
101
159
|
i++;
|
|
102
160
|
const fullMatch = fileContent.substring(matchStartIndex, i);
|
|
103
|
-
const { line, column } = getLineAndColumn(
|
|
161
|
+
const { line, column } = getLineAndColumn(
|
|
162
|
+
fileContent,
|
|
163
|
+
matchStartIndex
|
|
164
|
+
);
|
|
104
165
|
let validity = "ok";
|
|
105
166
|
let parameter1 = void 0;
|
|
106
167
|
let parameter2 = void 0;
|
|
@@ -115,7 +176,9 @@ class $LT_TagProcessor {
|
|
|
115
176
|
parameter2 = JSON5.parse(parameter2Text);
|
|
116
177
|
} catch (error) {
|
|
117
178
|
try {
|
|
118
|
-
parameter2 = JSON5.parse(
|
|
179
|
+
parameter2 = JSON5.parse(
|
|
180
|
+
this.escapeNewlinesInStrings(parameter2Text)
|
|
181
|
+
);
|
|
119
182
|
} catch {
|
|
120
183
|
validity = "invalid-param-2";
|
|
121
184
|
}
|
|
@@ -123,7 +186,9 @@ class $LT_TagProcessor {
|
|
|
123
186
|
}
|
|
124
187
|
} catch (error) {
|
|
125
188
|
try {
|
|
126
|
-
parameter1 = JSON5.parse(
|
|
189
|
+
parameter1 = JSON5.parse(
|
|
190
|
+
this.escapeNewlinesInStrings(parameter1Text)
|
|
191
|
+
);
|
|
127
192
|
} catch {
|
|
128
193
|
validity = "invalid-param-1";
|
|
129
194
|
}
|
|
@@ -157,22 +222,31 @@ class $LT_TagProcessor {
|
|
|
157
222
|
}
|
|
158
223
|
const tag = R.tag;
|
|
159
224
|
let newTranslationsString = R.translations;
|
|
160
|
-
if (!newTranslationsString)
|
|
161
|
-
|
|
162
|
-
if (
|
|
225
|
+
if (!newTranslationsString)
|
|
226
|
+
newTranslationsString = this.config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text || "{}";
|
|
227
|
+
else if (typeof newTranslationsString !== "string")
|
|
228
|
+
newTranslationsString = JSON5.stringify(newTranslationsString);
|
|
229
|
+
if (!newTranslationsString)
|
|
230
|
+
throw new Error("Tag must have translations provided!");
|
|
163
231
|
try {
|
|
164
232
|
JSON5.parse(newTranslationsString);
|
|
165
233
|
} catch (error) {
|
|
166
|
-
throw new Error(
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Tag translations are invalid object! Translations: ${newTranslationsString}`
|
|
236
|
+
);
|
|
167
237
|
}
|
|
168
238
|
let newConfigString = R.config;
|
|
169
|
-
if (!newConfigString && newConfigString !== null)
|
|
239
|
+
if (!newConfigString && newConfigString !== null)
|
|
240
|
+
newConfigString = tag.parameterConfig;
|
|
170
241
|
if (newConfigString) {
|
|
171
242
|
try {
|
|
172
|
-
if (typeof newConfigString === "string")
|
|
243
|
+
if (typeof newConfigString === "string")
|
|
244
|
+
JSON5.parse(newConfigString);
|
|
173
245
|
else newConfigString = JSON5.stringify(newConfigString);
|
|
174
246
|
} catch (error) {
|
|
175
|
-
throw new Error(
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Tag config is invalid object! Config: ${newConfigString}`
|
|
249
|
+
);
|
|
176
250
|
}
|
|
177
251
|
}
|
|
178
252
|
if (newConfigString === null && this.config.translationArgPosition === 2) {
|
|
@@ -183,7 +257,8 @@ class $LT_TagProcessor {
|
|
|
183
257
|
let tagFunction = `${this.config.tagName}(${arg1}`;
|
|
184
258
|
if (arg2) tagFunction += `, ${arg2}`;
|
|
185
259
|
tagFunction += ")";
|
|
186
|
-
if (tag.variableName)
|
|
260
|
+
if (tag.variableName)
|
|
261
|
+
replaceMap.set(tag, ` ${tag.variableName} = ${tagFunction}`);
|
|
187
262
|
else replaceMap.set(tag, tagFunction);
|
|
188
263
|
});
|
|
189
264
|
let offset = 0;
|
|
@@ -376,127 +451,397 @@ function getLineAndColumn(text, matchIndex) {
|
|
|
376
451
|
const column = lines[lines.length - 1].length + 1;
|
|
377
452
|
return { line, column };
|
|
378
453
|
}
|
|
379
|
-
function $
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
function $LT_FilterEmptyNamespaceTags(tags, logger) {
|
|
400
|
-
return tags.filter((tag) => {
|
|
401
|
-
if (!tag.parameterConfig) {
|
|
402
|
-
logger.warn('Skipping tag "{fullMatch}". Tag configuration not defined. (Check lang-tag config at collect.onCollectConfigFix)', {
|
|
403
|
-
fullMatch: tag.fullMatch.trim()
|
|
404
|
-
});
|
|
405
|
-
return false;
|
|
454
|
+
async function $LT_CollectCandidateFilesWithTags(props) {
|
|
455
|
+
const { config, logger } = props;
|
|
456
|
+
const processor = new $LT_TagProcessor(config);
|
|
457
|
+
const cwd = process__default.cwd();
|
|
458
|
+
let filesToScan = props.filesToScan;
|
|
459
|
+
if (!filesToScan) {
|
|
460
|
+
filesToScan = await globby(config.includes, {
|
|
461
|
+
cwd,
|
|
462
|
+
ignore: config.excludes,
|
|
463
|
+
absolute: true
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
const candidates = [];
|
|
467
|
+
for (const filePath of filesToScan) {
|
|
468
|
+
const fileContent = readFileSync(filePath, "utf-8");
|
|
469
|
+
let tags = processor.extractTags(fileContent);
|
|
470
|
+
if (!tags.length) {
|
|
471
|
+
continue;
|
|
406
472
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
411
|
-
return false;
|
|
473
|
+
tags = $LT_FilterInvalidTags(tags, config, logger);
|
|
474
|
+
if (!tags.length) {
|
|
475
|
+
continue;
|
|
412
476
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
for (const name of propNames) {
|
|
419
|
-
const value = obj[name];
|
|
420
|
-
if (value && typeof value === "object") {
|
|
421
|
-
deepFreezeObject(value);
|
|
477
|
+
for (let tag of tags) {
|
|
478
|
+
tag.parameterConfig = config.collect.onCollectConfigFix({
|
|
479
|
+
config: tag.parameterConfig,
|
|
480
|
+
langTagConfig: config
|
|
481
|
+
});
|
|
422
482
|
}
|
|
483
|
+
tags = $LT_FilterEmptyNamespaceTags(tags, logger);
|
|
484
|
+
const relativeFilePath = path__default.relative(cwd, filePath);
|
|
485
|
+
candidates.push({ relativeFilePath, tags });
|
|
423
486
|
}
|
|
424
|
-
return
|
|
487
|
+
return candidates;
|
|
425
488
|
}
|
|
426
|
-
async function
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
let
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
489
|
+
async function $LT_GroupTagsToCollections({
|
|
490
|
+
logger,
|
|
491
|
+
files,
|
|
492
|
+
config
|
|
493
|
+
}) {
|
|
494
|
+
let totalTags = 0;
|
|
495
|
+
const collections = {};
|
|
496
|
+
function getTranslationsCollection(namespace) {
|
|
497
|
+
const collectionName = config.collect.collector.aggregateCollection(namespace);
|
|
498
|
+
const collection = collections[collectionName] || {};
|
|
499
|
+
if (!(collectionName in collections)) {
|
|
500
|
+
collections[collectionName] = collection;
|
|
501
|
+
}
|
|
502
|
+
return collection;
|
|
435
503
|
}
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
for (
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
shouldUpdate = true;
|
|
454
|
-
event.isSaved = true;
|
|
455
|
-
event.savedConfig = updatedConfig;
|
|
456
|
-
logger.debug('Called save for "{path}" with config "{config}" triggered by: ("{trigger}")', { path: path2, config: JSON.stringify(updatedConfig), trigger: triggerName || "-" });
|
|
504
|
+
const allConflicts = [];
|
|
505
|
+
const existingValuesByNamespace = /* @__PURE__ */ new Map();
|
|
506
|
+
for (const file of files) {
|
|
507
|
+
totalTags += file.tags.length;
|
|
508
|
+
for (const _tag of file.tags) {
|
|
509
|
+
const tag = config.collect.collector.transformTag(_tag);
|
|
510
|
+
const tagConfig = tag.parameterConfig;
|
|
511
|
+
const collection = getTranslationsCollection(tagConfig.namespace);
|
|
512
|
+
let existingValues = existingValuesByNamespace.get(
|
|
513
|
+
tagConfig.namespace
|
|
514
|
+
);
|
|
515
|
+
if (!existingValues) {
|
|
516
|
+
existingValues = /* @__PURE__ */ new Map();
|
|
517
|
+
existingValuesByNamespace.set(
|
|
518
|
+
tagConfig.namespace,
|
|
519
|
+
existingValues
|
|
520
|
+
);
|
|
457
521
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
522
|
+
const valueTracker = {
|
|
523
|
+
get: (path2) => existingValues.get(path2),
|
|
524
|
+
trackValue: (path2, value) => {
|
|
525
|
+
existingValues.set(path2, {
|
|
526
|
+
tag,
|
|
527
|
+
relativeFilePath: file.relativeFilePath,
|
|
528
|
+
value
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
const addConflict = async (path2, tagA, tagBValue, conflictType) => {
|
|
533
|
+
if (conflictType === "path_overwrite" && config.collect?.ignoreConflictsWithMatchingValues !== false && tagA.value === tagBValue) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const conflict = {
|
|
537
|
+
path: path2,
|
|
538
|
+
tagA,
|
|
539
|
+
tagB: {
|
|
540
|
+
tag,
|
|
541
|
+
relativeFilePath: file.relativeFilePath,
|
|
542
|
+
value: tagBValue
|
|
543
|
+
},
|
|
544
|
+
conflictType
|
|
545
|
+
};
|
|
546
|
+
if (config.collect?.onConflictResolution) {
|
|
547
|
+
let shouldContinue = true;
|
|
548
|
+
await config.collect.onConflictResolution({
|
|
549
|
+
conflict,
|
|
550
|
+
logger,
|
|
551
|
+
exit() {
|
|
552
|
+
shouldContinue = false;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
if (!shouldContinue) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
`LangTagConflictResolution:Processing stopped due to conflict resolution: ${conflict.tagA.tag.parameterConfig.namespace}|${conflict.path}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
allConflicts.push(conflict);
|
|
562
|
+
};
|
|
563
|
+
const target = await ensureNestedObject(
|
|
564
|
+
tagConfig.path,
|
|
565
|
+
collection,
|
|
566
|
+
valueTracker,
|
|
567
|
+
addConflict
|
|
568
|
+
);
|
|
569
|
+
await mergeWithConflictDetection(
|
|
570
|
+
target,
|
|
571
|
+
tag.parameterTranslations,
|
|
572
|
+
tagConfig.path || "",
|
|
573
|
+
valueTracker,
|
|
574
|
+
addConflict
|
|
575
|
+
);
|
|
466
576
|
}
|
|
467
577
|
}
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
await writeFile(file, newContent, "utf-8");
|
|
471
|
-
const encodedFile = encodeURI(file);
|
|
472
|
-
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path2, file: encodedFile, line: lastUpdatedLine });
|
|
473
|
-
return true;
|
|
578
|
+
if (allConflicts.length > 0) {
|
|
579
|
+
logger.warn(`Found ${allConflicts.length} conflicts.`);
|
|
474
580
|
}
|
|
475
|
-
|
|
581
|
+
if (config.collect?.onCollectFinish) {
|
|
582
|
+
let shouldContinue = true;
|
|
583
|
+
config.collect.onCollectFinish({
|
|
584
|
+
totalTags,
|
|
585
|
+
namespaces: collections,
|
|
586
|
+
conflicts: allConflicts,
|
|
587
|
+
logger,
|
|
588
|
+
exit() {
|
|
589
|
+
shouldContinue = false;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
if (!shouldContinue) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`LangTagConflictResolution:Processing stopped due to collect finish handler`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return collections;
|
|
476
599
|
}
|
|
477
|
-
function
|
|
478
|
-
if (!
|
|
479
|
-
|
|
480
|
-
|
|
600
|
+
async function ensureNestedObject(path2, rootCollection, valueTracker, addConflict) {
|
|
601
|
+
if (!path2 || !path2.trim()) return rootCollection;
|
|
602
|
+
let current = rootCollection;
|
|
603
|
+
let currentPath = "";
|
|
604
|
+
for (const key of path2.split(".")) {
|
|
605
|
+
currentPath = currentPath ? `${currentPath}.${key}` : key;
|
|
606
|
+
if (current[key] !== void 0 && typeof current[key] !== "object") {
|
|
607
|
+
const existingInfo = valueTracker.get(currentPath);
|
|
608
|
+
if (existingInfo) {
|
|
609
|
+
await addConflict(
|
|
610
|
+
currentPath,
|
|
611
|
+
existingInfo,
|
|
612
|
+
{},
|
|
613
|
+
"type_mismatch"
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
return current;
|
|
617
|
+
}
|
|
618
|
+
current[key] = current[key] || {};
|
|
619
|
+
current = current[key];
|
|
620
|
+
}
|
|
621
|
+
return current;
|
|
481
622
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
623
|
+
async function mergeWithConflictDetection(target, source, basePath = "", valueTracker, addConflict) {
|
|
624
|
+
if (typeof target !== "object" || typeof source !== "object") {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
for (const key in source) {
|
|
628
|
+
if (!source.hasOwnProperty(key)) {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const currentPath = basePath ? `${basePath}.${key}` : key;
|
|
632
|
+
let targetValue = target[key];
|
|
633
|
+
const sourceValue = source[key];
|
|
634
|
+
if (Array.isArray(sourceValue)) {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (targetValue !== void 0) {
|
|
638
|
+
const targetType = typeof targetValue;
|
|
639
|
+
const sourceType = typeof sourceValue;
|
|
640
|
+
let existingInfo = valueTracker.get(currentPath);
|
|
641
|
+
if (!existingInfo && targetType === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
642
|
+
const findNestedInfo = (obj, prefix) => {
|
|
643
|
+
for (const key2 in obj) {
|
|
644
|
+
const path2 = prefix ? `${prefix}.${key2}` : key2;
|
|
645
|
+
const info = valueTracker.get(path2);
|
|
646
|
+
if (info) {
|
|
647
|
+
return info;
|
|
648
|
+
}
|
|
649
|
+
if (typeof obj[key2] === "object" && obj[key2] !== null && !Array.isArray(obj[key2])) {
|
|
650
|
+
const nestedInfo = findNestedInfo(obj[key2], path2);
|
|
651
|
+
if (nestedInfo) {
|
|
652
|
+
return nestedInfo;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return void 0;
|
|
657
|
+
};
|
|
658
|
+
existingInfo = findNestedInfo(targetValue, currentPath);
|
|
659
|
+
}
|
|
660
|
+
if (targetType !== sourceType) {
|
|
661
|
+
if (existingInfo) {
|
|
662
|
+
await addConflict(
|
|
663
|
+
currentPath,
|
|
664
|
+
existingInfo,
|
|
665
|
+
sourceValue,
|
|
666
|
+
"type_mismatch"
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (targetType !== "object") {
|
|
672
|
+
if (existingInfo) {
|
|
673
|
+
await addConflict(
|
|
674
|
+
currentPath,
|
|
675
|
+
existingInfo,
|
|
676
|
+
sourceValue,
|
|
677
|
+
"path_overwrite"
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
if (targetValue !== sourceValue) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue)) {
|
|
686
|
+
if (!targetValue) {
|
|
687
|
+
targetValue = {};
|
|
688
|
+
target[key] = targetValue;
|
|
689
|
+
}
|
|
690
|
+
await mergeWithConflictDetection(
|
|
691
|
+
targetValue,
|
|
692
|
+
sourceValue,
|
|
693
|
+
currentPath,
|
|
694
|
+
valueTracker,
|
|
695
|
+
addConflict
|
|
696
|
+
);
|
|
697
|
+
} else {
|
|
698
|
+
target[key] = sourceValue;
|
|
699
|
+
valueTracker.trackValue(currentPath, sourceValue);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const CONFIG_FILE_NAME = "lang-tag.config.js";
|
|
704
|
+
const EXPORTS_FILE_NAME = "lang-tags.json";
|
|
705
|
+
async function $LT_EnsureDirectoryExists(filePath) {
|
|
706
|
+
await mkdir(filePath, { recursive: true });
|
|
707
|
+
}
|
|
708
|
+
async function $LT_WriteJSON(filePath, data) {
|
|
709
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
710
|
+
}
|
|
711
|
+
async function $LT_ReadJSON(filePath) {
|
|
712
|
+
const content = await readFile(filePath, "utf-8");
|
|
713
|
+
return JSON.parse(content);
|
|
714
|
+
}
|
|
715
|
+
async function $LT_WriteFileWithDirs(filePath, content) {
|
|
716
|
+
const dir = dirname(filePath);
|
|
717
|
+
try {
|
|
718
|
+
await mkdir(dir, { recursive: true });
|
|
719
|
+
} catch (error) {
|
|
720
|
+
}
|
|
721
|
+
await writeFile(filePath, content, "utf-8");
|
|
722
|
+
}
|
|
723
|
+
async function $LT_ReadFileContent(relativeFilePath) {
|
|
724
|
+
const cwd = process.cwd();
|
|
725
|
+
const absolutePath = resolve(cwd, relativeFilePath);
|
|
726
|
+
return await readFile(absolutePath, "utf-8");
|
|
727
|
+
}
|
|
728
|
+
async function $LT_WriteAsExportFile({
|
|
729
|
+
config,
|
|
730
|
+
logger,
|
|
731
|
+
files
|
|
732
|
+
}) {
|
|
733
|
+
const packageJson = await $LT_ReadJSON(
|
|
734
|
+
path__default.resolve(process.cwd(), "package.json")
|
|
735
|
+
);
|
|
736
|
+
if (!packageJson) {
|
|
737
|
+
throw new Error("package.json not found");
|
|
738
|
+
}
|
|
739
|
+
const exportData = {
|
|
740
|
+
baseLanguageCode: config.baseLanguageCode,
|
|
741
|
+
files: files.map(({ relativeFilePath, tags }) => ({
|
|
742
|
+
relativeFilePath,
|
|
743
|
+
tags: tags.map((tag) => ({
|
|
744
|
+
variableName: tag.variableName,
|
|
745
|
+
config: tag.parameterConfig,
|
|
746
|
+
translations: tag.parameterTranslations
|
|
747
|
+
}))
|
|
748
|
+
}))
|
|
749
|
+
};
|
|
750
|
+
await writeFile(EXPORTS_FILE_NAME, JSON.stringify(exportData), "utf-8");
|
|
751
|
+
logger.success(`Written {file}`, { file: EXPORTS_FILE_NAME });
|
|
752
|
+
}
|
|
753
|
+
function deepMergeTranslations(target, source) {
|
|
754
|
+
if (typeof target !== "object") {
|
|
755
|
+
throw new Error("Target must be an object");
|
|
756
|
+
}
|
|
757
|
+
if (typeof source !== "object") {
|
|
758
|
+
throw new Error("Source must be an object");
|
|
759
|
+
}
|
|
760
|
+
let changed = false;
|
|
761
|
+
for (const key in source) {
|
|
762
|
+
if (!source.hasOwnProperty(key)) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
let targetValue = target[key];
|
|
766
|
+
const sourceValue = source[key];
|
|
767
|
+
if (typeof targetValue === "string" && typeof sourceValue === "object") {
|
|
768
|
+
throw new Error(
|
|
769
|
+
`Trying to write object into target key "${key}" which is translation already`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
if (Array.isArray(sourceValue)) {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`Trying to write array into target key "${key}", we do not allow arrays inside translations`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
if (typeof sourceValue === "object") {
|
|
778
|
+
if (!targetValue) {
|
|
779
|
+
targetValue = {};
|
|
780
|
+
target[key] = targetValue;
|
|
781
|
+
}
|
|
782
|
+
if (deepMergeTranslations(targetValue, sourceValue)) {
|
|
783
|
+
changed = true;
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
if (target[key] !== sourceValue) {
|
|
787
|
+
changed = true;
|
|
788
|
+
}
|
|
789
|
+
target[key] = sourceValue;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return changed;
|
|
793
|
+
}
|
|
794
|
+
async function $LT_WriteToCollections({
|
|
795
|
+
config,
|
|
796
|
+
collections,
|
|
797
|
+
logger,
|
|
798
|
+
clean
|
|
799
|
+
}) {
|
|
800
|
+
await config.collect.collector.preWrite(clean);
|
|
801
|
+
const changedCollections = [];
|
|
802
|
+
for (let collectionName of Object.keys(collections)) {
|
|
803
|
+
if (!collectionName) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const filePath = await config.collect.collector.resolveCollectionFilePath(
|
|
807
|
+
collectionName
|
|
808
|
+
);
|
|
809
|
+
let originalJSON = {};
|
|
810
|
+
try {
|
|
811
|
+
originalJSON = await $LT_ReadJSON(filePath);
|
|
812
|
+
} catch (e) {
|
|
813
|
+
await config.collect.collector.onMissingCollection(
|
|
814
|
+
collectionName
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
if (deepMergeTranslations(originalJSON, collections[collectionName])) {
|
|
818
|
+
changedCollections.push(collectionName);
|
|
819
|
+
await $LT_WriteJSON(filePath, originalJSON);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
await config.collect.collector.postWrite(changedCollections);
|
|
823
|
+
}
|
|
824
|
+
const LANG_TAG_DEFAULT_CONFIG = {
|
|
825
|
+
tagName: "lang",
|
|
826
|
+
isLibrary: false,
|
|
827
|
+
includes: ["src/**/*.{js,ts,jsx,tsx}"],
|
|
828
|
+
excludes: ["node_modules", "dist", "build"],
|
|
829
|
+
localesDirectory: "locales",
|
|
830
|
+
baseLanguageCode: "en",
|
|
831
|
+
collect: {
|
|
832
|
+
collector: new NamespaceCollector(),
|
|
833
|
+
defaultNamespace: "common",
|
|
494
834
|
ignoreConflictsWithMatchingValues: true,
|
|
495
835
|
onCollectConfigFix: ({ config, langTagConfig }) => {
|
|
496
836
|
if (langTagConfig.isLibrary) return config;
|
|
497
|
-
if (!config)
|
|
837
|
+
if (!config)
|
|
838
|
+
return {
|
|
839
|
+
path: "",
|
|
840
|
+
namespace: langTagConfig.collect.defaultNamespace
|
|
841
|
+
};
|
|
498
842
|
if (!config.path) config.path = "";
|
|
499
|
-
if (!config.namespace)
|
|
843
|
+
if (!config.namespace)
|
|
844
|
+
config.namespace = langTagConfig.collect.defaultNamespace;
|
|
500
845
|
return config;
|
|
501
846
|
},
|
|
502
847
|
onConflictResolution: async (event) => {
|
|
@@ -509,25 +854,44 @@ const LANG_TAG_DEFAULT_CONFIG = {
|
|
|
509
854
|
import: {
|
|
510
855
|
dir: "src/lang-libraries",
|
|
511
856
|
tagImportPath: 'import { lang } from "@/my-lang-tag-path"',
|
|
512
|
-
onImport:
|
|
513
|
-
|
|
514
|
-
|
|
857
|
+
onImport: (event) => {
|
|
858
|
+
for (let e of event.exports) {
|
|
859
|
+
event.logger.info(
|
|
860
|
+
"Detected lang tag exports at package {packageName}",
|
|
861
|
+
{ packageName: e.packageJSON.name }
|
|
862
|
+
);
|
|
515
863
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
864
|
+
event.logger.warn(
|
|
865
|
+
`
|
|
866
|
+
Import Algorithm Not Configured
|
|
867
|
+
|
|
868
|
+
To import external language tags, you need to configure an import algorithm.
|
|
869
|
+
|
|
870
|
+
Setup Instructions:
|
|
871
|
+
1. Add this import at the top of your configuration file:
|
|
872
|
+
{importStr}
|
|
873
|
+
|
|
874
|
+
2. Replace import.onImport function with:
|
|
875
|
+
{onImport}
|
|
876
|
+
|
|
877
|
+
This will enable import of language tags from external packages.
|
|
878
|
+
`.trim(),
|
|
879
|
+
{
|
|
880
|
+
importStr: "const { flexibleImportAlgorithm } = require('@lang-tag/cli/algorithms');",
|
|
881
|
+
onImport: "onImport: flexibleImportAlgorithm({ filePath: { includePackageInPath: true } })"
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
}
|
|
524
885
|
},
|
|
525
886
|
translationArgPosition: 1,
|
|
526
887
|
onConfigGeneration: async (event) => {
|
|
888
|
+
event.logger.info(
|
|
889
|
+
"Config generation event is not configured. Add onConfigGeneration handler to customize config generation."
|
|
890
|
+
);
|
|
527
891
|
}
|
|
528
892
|
};
|
|
529
893
|
async function $LT_ReadConfig(projectPath) {
|
|
530
|
-
const configPath = resolve(projectPath, CONFIG_FILE_NAME);
|
|
894
|
+
const configPath = resolve$1(projectPath, CONFIG_FILE_NAME);
|
|
531
895
|
if (!existsSync(configPath)) {
|
|
532
896
|
throw new Error(`No "${CONFIG_FILE_NAME}" detected`);
|
|
533
897
|
}
|
|
@@ -539,7 +903,9 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
539
903
|
const userConfig = configModule.default || {};
|
|
540
904
|
const tn = (userConfig.tagName || "").toLowerCase().replace(/[-_\s]/g, "");
|
|
541
905
|
if (tn === "langtag" || tn === "lang-tag") {
|
|
542
|
-
throw new Error(
|
|
906
|
+
throw new Error(
|
|
907
|
+
'Custom tagName cannot be "lang-tag" or "langtag"! (It is not recommended for use with libraries)\n'
|
|
908
|
+
);
|
|
543
909
|
}
|
|
544
910
|
const config = {
|
|
545
911
|
...LANG_TAG_DEFAULT_CONFIG,
|
|
@@ -561,6 +927,75 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
561
927
|
throw error;
|
|
562
928
|
}
|
|
563
929
|
}
|
|
930
|
+
const ANSI$1 = {
|
|
931
|
+
reset: "\x1B[0m",
|
|
932
|
+
white: "\x1B[97m",
|
|
933
|
+
brightCyan: "\x1B[96m",
|
|
934
|
+
green: "\x1B[92m",
|
|
935
|
+
gray: "\x1B[90m",
|
|
936
|
+
redBg: "\x1B[41m\x1B[97m",
|
|
937
|
+
bold: "\x1B[1m"
|
|
938
|
+
};
|
|
939
|
+
function colorizeFromAST(code, nodes) {
|
|
940
|
+
const ranges = [];
|
|
941
|
+
for (const node of nodes) {
|
|
942
|
+
const color = getColorForNodeType(node.type);
|
|
943
|
+
const priority = getPriorityForNodeType(node.type);
|
|
944
|
+
ranges.push({
|
|
945
|
+
start: node.start,
|
|
946
|
+
end: node.end,
|
|
947
|
+
color,
|
|
948
|
+
priority
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
ranges.sort((a, b) => {
|
|
952
|
+
if (b.start !== a.start) return b.start - a.start;
|
|
953
|
+
return b.priority - a.priority;
|
|
954
|
+
});
|
|
955
|
+
let colorized = code;
|
|
956
|
+
for (const range of ranges) {
|
|
957
|
+
const before = colorized.substring(0, range.start);
|
|
958
|
+
const text = colorized.substring(range.start, range.end);
|
|
959
|
+
const after = colorized.substring(range.end);
|
|
960
|
+
colorized = before + range.color + text + ANSI$1.reset + after;
|
|
961
|
+
}
|
|
962
|
+
return colorized;
|
|
963
|
+
}
|
|
964
|
+
function getColorForNodeType(type) {
|
|
965
|
+
switch (type) {
|
|
966
|
+
case "key":
|
|
967
|
+
return ANSI$1.brightCyan;
|
|
968
|
+
case "bracket":
|
|
969
|
+
case "colon":
|
|
970
|
+
return ANSI$1.gray;
|
|
971
|
+
case "value":
|
|
972
|
+
return ANSI$1.green;
|
|
973
|
+
case "comment":
|
|
974
|
+
return ANSI$1.gray;
|
|
975
|
+
case "error":
|
|
976
|
+
return ANSI$1.bold + ANSI$1.redBg;
|
|
977
|
+
default:
|
|
978
|
+
return ANSI$1.white;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function getPriorityForNodeType(type) {
|
|
982
|
+
switch (type) {
|
|
983
|
+
case "error":
|
|
984
|
+
return 3;
|
|
985
|
+
// Highest priority
|
|
986
|
+
case "key":
|
|
987
|
+
return 2;
|
|
988
|
+
case "value":
|
|
989
|
+
return 1;
|
|
990
|
+
case "bracket":
|
|
991
|
+
case "colon":
|
|
992
|
+
case "comment":
|
|
993
|
+
return 0;
|
|
994
|
+
// Lowest priority
|
|
995
|
+
default:
|
|
996
|
+
return 0;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
564
999
|
function parseObjectAST(code) {
|
|
565
1000
|
const nodes = [];
|
|
566
1001
|
try {
|
|
@@ -654,81 +1089,12 @@ function markConflictNodes(nodes, conflictPath) {
|
|
|
654
1089
|
return node;
|
|
655
1090
|
});
|
|
656
1091
|
}
|
|
657
|
-
const ANSI
|
|
1092
|
+
const ANSI = {
|
|
658
1093
|
reset: "\x1B[0m",
|
|
659
1094
|
white: "\x1B[97m",
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
redBg: "\x1B[41m\x1B[97m",
|
|
664
|
-
bold: "\x1B[1m"
|
|
665
|
-
};
|
|
666
|
-
function colorizeFromAST(code, nodes) {
|
|
667
|
-
const ranges = [];
|
|
668
|
-
for (const node of nodes) {
|
|
669
|
-
const color = getColorForNodeType(node.type);
|
|
670
|
-
const priority = getPriorityForNodeType(node.type);
|
|
671
|
-
ranges.push({
|
|
672
|
-
start: node.start,
|
|
673
|
-
end: node.end,
|
|
674
|
-
color,
|
|
675
|
-
priority
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
ranges.sort((a, b) => {
|
|
679
|
-
if (b.start !== a.start) return b.start - a.start;
|
|
680
|
-
return b.priority - a.priority;
|
|
681
|
-
});
|
|
682
|
-
let colorized = code;
|
|
683
|
-
for (const range of ranges) {
|
|
684
|
-
const before = colorized.substring(0, range.start);
|
|
685
|
-
const text = colorized.substring(range.start, range.end);
|
|
686
|
-
const after = colorized.substring(range.end);
|
|
687
|
-
colorized = before + range.color + text + ANSI$1.reset + after;
|
|
688
|
-
}
|
|
689
|
-
return colorized;
|
|
690
|
-
}
|
|
691
|
-
function getColorForNodeType(type) {
|
|
692
|
-
switch (type) {
|
|
693
|
-
case "key":
|
|
694
|
-
return ANSI$1.brightCyan;
|
|
695
|
-
case "bracket":
|
|
696
|
-
case "colon":
|
|
697
|
-
return ANSI$1.gray;
|
|
698
|
-
case "value":
|
|
699
|
-
return ANSI$1.green;
|
|
700
|
-
case "comment":
|
|
701
|
-
return ANSI$1.gray;
|
|
702
|
-
case "error":
|
|
703
|
-
return ANSI$1.bold + ANSI$1.redBg;
|
|
704
|
-
default:
|
|
705
|
-
return ANSI$1.white;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
function getPriorityForNodeType(type) {
|
|
709
|
-
switch (type) {
|
|
710
|
-
case "error":
|
|
711
|
-
return 3;
|
|
712
|
-
// Highest priority
|
|
713
|
-
case "key":
|
|
714
|
-
return 2;
|
|
715
|
-
case "value":
|
|
716
|
-
return 1;
|
|
717
|
-
case "bracket":
|
|
718
|
-
case "colon":
|
|
719
|
-
case "comment":
|
|
720
|
-
return 0;
|
|
721
|
-
// Lowest priority
|
|
722
|
-
default:
|
|
723
|
-
return 0;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const ANSI = {
|
|
727
|
-
reset: "\x1B[0m",
|
|
728
|
-
white: "\x1B[97m",
|
|
729
|
-
cyan: "\x1B[96m",
|
|
730
|
-
gray: "\x1B[37m",
|
|
731
|
-
darkGray: "\x1B[90m",
|
|
1095
|
+
cyan: "\x1B[96m",
|
|
1096
|
+
gray: "\x1B[37m",
|
|
1097
|
+
darkGray: "\x1B[90m",
|
|
732
1098
|
bold: "\x1B[1m"
|
|
733
1099
|
};
|
|
734
1100
|
function getVisibleLines(totalLines, errorLines, threshold = 10) {
|
|
@@ -754,18 +1120,24 @@ function printLines(lines, startLineNumber, errorLines = /* @__PURE__ */ new Set
|
|
|
754
1120
|
lines.forEach((line, i) => {
|
|
755
1121
|
const lineNumber = startLineNumber + i;
|
|
756
1122
|
const lineNumStr = String(lineNumber).padStart(3, " ");
|
|
757
|
-
console.log(
|
|
1123
|
+
console.log(
|
|
1124
|
+
`${ANSI.gray}${lineNumStr}${ANSI.reset} ${ANSI.darkGray}│${ANSI.reset} ${line}`
|
|
1125
|
+
);
|
|
758
1126
|
});
|
|
759
1127
|
} else {
|
|
760
1128
|
let lastPrinted = -2;
|
|
761
1129
|
lines.forEach((line, i) => {
|
|
762
1130
|
if (visibleLines.has(i)) {
|
|
763
1131
|
if (i > lastPrinted + 1 && lastPrinted >= 0) {
|
|
764
|
-
console.log(
|
|
1132
|
+
console.log(
|
|
1133
|
+
`${ANSI.gray} -${ANSI.reset} ${ANSI.darkGray}│${ANSI.reset} ${ANSI.gray}...${ANSI.reset}`
|
|
1134
|
+
);
|
|
765
1135
|
}
|
|
766
1136
|
const lineNumber = startLineNumber + i;
|
|
767
1137
|
const lineNumStr = String(lineNumber).padStart(3, " ");
|
|
768
|
-
console.log(
|
|
1138
|
+
console.log(
|
|
1139
|
+
`${ANSI.gray}${lineNumStr}${ANSI.reset} ${ANSI.darkGray}│${ANSI.reset} ${line}`
|
|
1140
|
+
);
|
|
769
1141
|
lastPrinted = i;
|
|
770
1142
|
}
|
|
771
1143
|
});
|
|
@@ -815,14 +1187,20 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
815
1187
|
const wholeTagCode = await getLangTagCodeSection(tagInfo);
|
|
816
1188
|
const translationTagCode = translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text;
|
|
817
1189
|
const configTagCode = translationArgPosition === 1 ? tag.parameter2Text : tag.parameter1Text;
|
|
818
|
-
const translationErrorPath = stripPrefix(
|
|
1190
|
+
const translationErrorPath = stripPrefix(
|
|
1191
|
+
conflictPath,
|
|
1192
|
+
tag.parameterConfig?.path
|
|
1193
|
+
);
|
|
819
1194
|
let colorizedWhole = wholeTagCode;
|
|
820
1195
|
let errorLines = /* @__PURE__ */ new Set();
|
|
821
1196
|
if (translationTagCode) {
|
|
822
1197
|
try {
|
|
823
1198
|
const translationNodes = parseObjectAST(translationTagCode);
|
|
824
1199
|
const markedTranslationNodes = translationErrorPath ? markConflictNodes(translationNodes, translationErrorPath) : translationNodes;
|
|
825
|
-
const translationErrorLines = getErrorLineNumbers(
|
|
1200
|
+
const translationErrorLines = getErrorLineNumbers(
|
|
1201
|
+
translationTagCode,
|
|
1202
|
+
markedTranslationNodes
|
|
1203
|
+
);
|
|
826
1204
|
const translationStartInWhole = wholeTagCode.indexOf(translationTagCode);
|
|
827
1205
|
if (translationStartInWhole >= 0) {
|
|
828
1206
|
const linesBeforeTranslation = wholeTagCode.substring(0, translationStartInWhole).split("\n").length - 1;
|
|
@@ -830,12 +1208,20 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
830
1208
|
errorLines.add(linesBeforeTranslation + lineNum2);
|
|
831
1209
|
});
|
|
832
1210
|
if (translationErrorLines.size > 0) {
|
|
833
|
-
const lastTranslationErrorLine = Math.max(
|
|
1211
|
+
const lastTranslationErrorLine = Math.max(
|
|
1212
|
+
...Array.from(translationErrorLines)
|
|
1213
|
+
);
|
|
834
1214
|
lineNum = startLine + linesBeforeTranslation + lastTranslationErrorLine;
|
|
835
1215
|
}
|
|
836
1216
|
}
|
|
837
|
-
const colorizedTranslation = colorizeFromAST(
|
|
838
|
-
|
|
1217
|
+
const colorizedTranslation = colorizeFromAST(
|
|
1218
|
+
translationTagCode,
|
|
1219
|
+
markedTranslationNodes
|
|
1220
|
+
);
|
|
1221
|
+
colorizedWhole = colorizedWhole.replace(
|
|
1222
|
+
translationTagCode,
|
|
1223
|
+
colorizedTranslation
|
|
1224
|
+
);
|
|
839
1225
|
} catch (error) {
|
|
840
1226
|
console.error("Failed to colorize translation:", error);
|
|
841
1227
|
}
|
|
@@ -857,7 +1243,10 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
857
1243
|
}
|
|
858
1244
|
return node;
|
|
859
1245
|
});
|
|
860
|
-
const configErrorLines = getErrorLineNumbers(
|
|
1246
|
+
const configErrorLines = getErrorLineNumbers(
|
|
1247
|
+
configTagCode,
|
|
1248
|
+
markedConfigNodes
|
|
1249
|
+
);
|
|
861
1250
|
const configStartInWhole = wholeTagCode.indexOf(configTagCode);
|
|
862
1251
|
if (configStartInWhole >= 0) {
|
|
863
1252
|
const linesBeforeConfig = wholeTagCode.substring(0, configStartInWhole).split("\n").length - 1;
|
|
@@ -865,14 +1254,22 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
865
1254
|
errorLines.add(linesBeforeConfig + lineNum2);
|
|
866
1255
|
});
|
|
867
1256
|
}
|
|
868
|
-
const colorizedConfig = colorizeFromAST(
|
|
869
|
-
|
|
1257
|
+
const colorizedConfig = colorizeFromAST(
|
|
1258
|
+
configTagCode,
|
|
1259
|
+
markedConfigNodes
|
|
1260
|
+
);
|
|
1261
|
+
colorizedWhole = colorizedWhole.replace(
|
|
1262
|
+
configTagCode,
|
|
1263
|
+
colorizedConfig
|
|
1264
|
+
);
|
|
870
1265
|
} catch (error) {
|
|
871
1266
|
console.error("Failed to colorize config:", error);
|
|
872
1267
|
}
|
|
873
1268
|
}
|
|
874
1269
|
const encodedPath = encodeURI(filePath);
|
|
875
|
-
console.log(
|
|
1270
|
+
console.log(
|
|
1271
|
+
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}file://${encodedPath}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
|
|
1272
|
+
);
|
|
876
1273
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
877
1274
|
} catch (error) {
|
|
878
1275
|
console.error("Error displaying conflict:", error);
|
|
@@ -880,8 +1277,20 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
880
1277
|
}
|
|
881
1278
|
async function $LT_LogConflict(conflict, translationArgPosition, condense) {
|
|
882
1279
|
const { path: conflictPath, tagA, tagB } = conflict;
|
|
883
|
-
await logTagConflictInfo(
|
|
884
|
-
|
|
1280
|
+
await logTagConflictInfo(
|
|
1281
|
+
tagA,
|
|
1282
|
+
"between",
|
|
1283
|
+
conflictPath,
|
|
1284
|
+
translationArgPosition,
|
|
1285
|
+
condense
|
|
1286
|
+
);
|
|
1287
|
+
await logTagConflictInfo(
|
|
1288
|
+
tagB,
|
|
1289
|
+
"and",
|
|
1290
|
+
conflictPath,
|
|
1291
|
+
translationArgPosition,
|
|
1292
|
+
condense
|
|
1293
|
+
);
|
|
885
1294
|
}
|
|
886
1295
|
const ANSI_COLORS = {
|
|
887
1296
|
reset: "\x1B[0m",
|
|
@@ -895,14 +1304,18 @@ const ANSI_COLORS = {
|
|
|
895
1304
|
cyan: "\x1B[36m"
|
|
896
1305
|
};
|
|
897
1306
|
function validateAndInterpolate(message, params) {
|
|
898
|
-
const placeholders = Array.from(message.matchAll(/\{(\w+)\}/g)).map(
|
|
1307
|
+
const placeholders = Array.from(message.matchAll(/\{(\w+)\}/g)).map(
|
|
1308
|
+
(m) => m[1]
|
|
1309
|
+
);
|
|
899
1310
|
const missing = placeholders.filter((p) => !(p in (params || {})));
|
|
900
1311
|
if (missing.length) {
|
|
901
1312
|
throw new Error(`Missing variables in message: ${missing.join(", ")}`);
|
|
902
1313
|
}
|
|
903
1314
|
const extra = params ? Object.keys(params).filter((k) => !placeholders.includes(k)) : [];
|
|
904
1315
|
if (extra.length) {
|
|
905
|
-
throw new Error(
|
|
1316
|
+
throw new Error(
|
|
1317
|
+
`Extra variables provided not used in message: ${extra.join(", ")}`
|
|
1318
|
+
);
|
|
906
1319
|
}
|
|
907
1320
|
const parts = [];
|
|
908
1321
|
let lastIndex = 0;
|
|
@@ -948,12 +1361,24 @@ function $LT_CreateDefaultLogger(debugMode, translationArgPosition = 1) {
|
|
|
948
1361
|
conflict: async (conflict, condense) => {
|
|
949
1362
|
const { path: path2, conflictType, tagA } = conflict;
|
|
950
1363
|
console.log();
|
|
951
|
-
console.log(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
console.log(
|
|
955
|
-
|
|
956
|
-
|
|
1364
|
+
console.log(
|
|
1365
|
+
`${ANSI_COLORS.bold}${ANSI_COLORS.red}⚠ Translation Conflict Detected${ANSI_COLORS.reset}`
|
|
1366
|
+
);
|
|
1367
|
+
console.log(
|
|
1368
|
+
`${ANSI_COLORS.gray}${"─".repeat(60)}${ANSI_COLORS.reset}`
|
|
1369
|
+
);
|
|
1370
|
+
console.log(
|
|
1371
|
+
` ${ANSI_COLORS.cyan}Conflict Type:${ANSI_COLORS.reset} ${ANSI_COLORS.white}${conflictType}${ANSI_COLORS.reset}`
|
|
1372
|
+
);
|
|
1373
|
+
console.log(
|
|
1374
|
+
` ${ANSI_COLORS.cyan}Translation Key:${ANSI_COLORS.reset} ${ANSI_COLORS.white}${path2}${ANSI_COLORS.reset}`
|
|
1375
|
+
);
|
|
1376
|
+
console.log(
|
|
1377
|
+
` ${ANSI_COLORS.cyan}Namespace:${ANSI_COLORS.reset} ${ANSI_COLORS.white}${tagA.tag.parameterConfig.namespace}${ANSI_COLORS.reset}`
|
|
1378
|
+
);
|
|
1379
|
+
console.log(
|
|
1380
|
+
`${ANSI_COLORS.gray}${"─".repeat(60)}${ANSI_COLORS.reset}`
|
|
1381
|
+
);
|
|
957
1382
|
await $LT_LogConflict(conflict, translationArgPosition, condense);
|
|
958
1383
|
console.log();
|
|
959
1384
|
}
|
|
@@ -961,7 +1386,10 @@ function $LT_CreateDefaultLogger(debugMode, translationArgPosition = 1) {
|
|
|
961
1386
|
}
|
|
962
1387
|
async function $LT_GetCommandEssentials() {
|
|
963
1388
|
const config = await $LT_ReadConfig(process__default.cwd());
|
|
964
|
-
const logger = $LT_CreateDefaultLogger(
|
|
1389
|
+
const logger = $LT_CreateDefaultLogger(
|
|
1390
|
+
config.debug,
|
|
1391
|
+
config.translationArgPosition
|
|
1392
|
+
);
|
|
965
1393
|
config.collect.collector.config = config;
|
|
966
1394
|
config.collect.collector.logger = logger;
|
|
967
1395
|
return {
|
|
@@ -969,398 +1397,255 @@ async function $LT_GetCommandEssentials() {
|
|
|
969
1397
|
logger
|
|
970
1398
|
};
|
|
971
1399
|
}
|
|
972
|
-
async function $
|
|
1400
|
+
async function $LT_CMD_Collect(options) {
|
|
973
1401
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const path2 = file.substring(charactersToSkip);
|
|
983
|
-
const localDirty = await checkAndRegenerateFileLangTags(config, logger, file, path2);
|
|
984
|
-
if (localDirty) {
|
|
985
|
-
dirty = true;
|
|
1402
|
+
logger.info("Collecting translations from source files...");
|
|
1403
|
+
const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
|
|
1404
|
+
if (config.debug) {
|
|
1405
|
+
for (let file of files) {
|
|
1406
|
+
logger.debug("Found {count} translations tags inside: {file}", {
|
|
1407
|
+
count: file.tags.length,
|
|
1408
|
+
file: file.relativeFilePath
|
|
1409
|
+
});
|
|
986
1410
|
}
|
|
987
1411
|
}
|
|
988
|
-
if (
|
|
989
|
-
|
|
1412
|
+
if (config.isLibrary) {
|
|
1413
|
+
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1414
|
+
return;
|
|
990
1415
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1416
|
+
try {
|
|
1417
|
+
const collections = await $LT_GroupTagsToCollections({
|
|
1418
|
+
logger,
|
|
1419
|
+
files,
|
|
1420
|
+
config
|
|
1421
|
+
});
|
|
1422
|
+
const totalTags = files.reduce(
|
|
1423
|
+
(sum, file) => sum + file.tags.length,
|
|
1424
|
+
0
|
|
1425
|
+
);
|
|
1426
|
+
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1427
|
+
await $LT_WriteToCollections({
|
|
1428
|
+
config,
|
|
1429
|
+
collections,
|
|
1430
|
+
logger,
|
|
1431
|
+
clean: options?.clean
|
|
1432
|
+
});
|
|
1433
|
+
} catch (e) {
|
|
1434
|
+
const prefix = "LangTagConflictResolution:";
|
|
1435
|
+
if (e.message.startsWith(prefix)) {
|
|
1436
|
+
logger.error(e.message.substring(prefix.length));
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
throw e;
|
|
995
1440
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1441
|
+
}
|
|
1442
|
+
async function $LT_CollectExportFiles(logger) {
|
|
1443
|
+
const nodeModulesPath = path$1.join(process__default.cwd(), "node_modules");
|
|
1444
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
1445
|
+
logger.error('"node_modules" directory not found');
|
|
1446
|
+
return [];
|
|
998
1447
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
}
|
|
1009
|
-
if (Array.isArray(sourceValue)) {
|
|
1010
|
-
throw new Error(`Trying to write array into target key "${key}", we do not allow arrays inside translations`);
|
|
1011
|
-
}
|
|
1012
|
-
if (typeof sourceValue === "object") {
|
|
1013
|
-
if (!targetValue) {
|
|
1014
|
-
targetValue = {};
|
|
1015
|
-
target[key] = targetValue;
|
|
1016
|
-
}
|
|
1017
|
-
if (deepMergeTranslations(targetValue, sourceValue)) {
|
|
1018
|
-
changed = true;
|
|
1448
|
+
const results = [];
|
|
1449
|
+
try {
|
|
1450
|
+
const exportFiles = await globby(
|
|
1451
|
+
[`node_modules/**/${EXPORTS_FILE_NAME}`],
|
|
1452
|
+
{
|
|
1453
|
+
cwd: process__default.cwd(),
|
|
1454
|
+
onlyFiles: true,
|
|
1455
|
+
ignore: ["**/node_modules/**/node_modules/**"],
|
|
1456
|
+
deep: 4
|
|
1019
1457
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1458
|
+
);
|
|
1459
|
+
for (const exportFile of exportFiles) {
|
|
1460
|
+
const fullExportPath = path$1.resolve(exportFile);
|
|
1461
|
+
const packageJsonPath = findPackageJsonForExport(
|
|
1462
|
+
fullExportPath,
|
|
1463
|
+
nodeModulesPath
|
|
1464
|
+
);
|
|
1465
|
+
if (packageJsonPath) {
|
|
1466
|
+
results.push({
|
|
1467
|
+
exportPath: fullExportPath,
|
|
1468
|
+
packageJsonPath
|
|
1469
|
+
});
|
|
1470
|
+
} else {
|
|
1471
|
+
logger.warn(
|
|
1472
|
+
"Found export file but could not match package.json: {exportPath}",
|
|
1473
|
+
{
|
|
1474
|
+
exportPath: fullExportPath
|
|
1475
|
+
}
|
|
1476
|
+
);
|
|
1023
1477
|
}
|
|
1024
|
-
target[key] = sourceValue;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
return changed;
|
|
1028
|
-
}
|
|
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) {
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
const filePath = await config.collect.collector.resolveCollectionFilePath(collectionName);
|
|
1037
|
-
let originalJSON = {};
|
|
1038
|
-
try {
|
|
1039
|
-
originalJSON = await $LT_ReadJSON(filePath);
|
|
1040
|
-
} catch (e) {
|
|
1041
|
-
await config.collect.collector.onMissingCollection(collectionName);
|
|
1042
|
-
}
|
|
1043
|
-
if (deepMergeTranslations(originalJSON, collections[collectionName])) {
|
|
1044
|
-
changedCollections.push(collectionName);
|
|
1045
|
-
await $LT_WriteJSON(filePath, originalJSON);
|
|
1046
1478
|
}
|
|
1479
|
+
logger.debug(
|
|
1480
|
+
"Found {count} export files with matching package.json in node_modules",
|
|
1481
|
+
{
|
|
1482
|
+
count: results.length
|
|
1483
|
+
}
|
|
1484
|
+
);
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
logger.error("Error scanning node_modules with globby: {error}", {
|
|
1487
|
+
error: String(error)
|
|
1488
|
+
});
|
|
1047
1489
|
}
|
|
1048
|
-
|
|
1490
|
+
return results;
|
|
1049
1491
|
}
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
if (!filesToScan) {
|
|
1056
|
-
filesToScan = await globby(config.includes, { cwd, ignore: config.excludes, absolute: true });
|
|
1492
|
+
function findPackageJsonForExport(exportPath, nodeModulesPath) {
|
|
1493
|
+
const relativePath = path$1.relative(nodeModulesPath, exportPath);
|
|
1494
|
+
const pathParts = relativePath.split(path$1.sep);
|
|
1495
|
+
if (pathParts.length < 2) {
|
|
1496
|
+
return null;
|
|
1057
1497
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1498
|
+
if (pathParts[0].startsWith("@")) {
|
|
1499
|
+
if (pathParts.length >= 3) {
|
|
1500
|
+
const packageDir = path$1.join(
|
|
1501
|
+
nodeModulesPath,
|
|
1502
|
+
pathParts[0],
|
|
1503
|
+
pathParts[1]
|
|
1504
|
+
);
|
|
1505
|
+
const packageJsonPath = path$1.join(packageDir, "package.json");
|
|
1506
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1507
|
+
return packageJsonPath;
|
|
1508
|
+
}
|
|
1068
1509
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1510
|
+
} else {
|
|
1511
|
+
const packageDir = path$1.join(nodeModulesPath, pathParts[0]);
|
|
1512
|
+
const packageJsonPath = path$1.join(packageDir, "package.json");
|
|
1513
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1514
|
+
return packageJsonPath;
|
|
1071
1515
|
}
|
|
1072
|
-
tags = $LT_FilterEmptyNamespaceTags(tags, logger);
|
|
1073
|
-
const relativeFilePath = path__default.relative(cwd, filePath);
|
|
1074
|
-
candidates.push({ relativeFilePath, tags });
|
|
1075
1516
|
}
|
|
1076
|
-
return
|
|
1517
|
+
return null;
|
|
1077
1518
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
config: tag.parameterConfig,
|
|
1090
|
-
translations: tag.parameterTranslations
|
|
1091
|
-
}))
|
|
1092
|
-
}))
|
|
1093
|
-
};
|
|
1094
|
-
await writeFile(EXPORTS_FILE_NAME, JSON.stringify(exportData), "utf-8");
|
|
1095
|
-
logger.success(`Written {file}`, { file: EXPORTS_FILE_NAME });
|
|
1519
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1520
|
+
const __dirname = dirname(__filename);
|
|
1521
|
+
const templatePath = join(
|
|
1522
|
+
__dirname,
|
|
1523
|
+
"templates",
|
|
1524
|
+
"import",
|
|
1525
|
+
"imported-tag.mustache"
|
|
1526
|
+
);
|
|
1527
|
+
const template = readFileSync(templatePath, "utf-8");
|
|
1528
|
+
function renderTemplate$1(data) {
|
|
1529
|
+
return mustache.render(template, data, {}, { escape: (text) => text });
|
|
1096
1530
|
}
|
|
1097
|
-
async function
|
|
1098
|
-
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
let existingValues = existingValuesByNamespace.get(tagConfig.namespace);
|
|
1117
|
-
if (!existingValues) {
|
|
1118
|
-
existingValues = /* @__PURE__ */ new Map();
|
|
1119
|
-
existingValuesByNamespace.set(tagConfig.namespace, existingValues);
|
|
1120
|
-
}
|
|
1121
|
-
const valueTracker = {
|
|
1122
|
-
get: (path2) => existingValues.get(path2),
|
|
1123
|
-
trackValue: (path2, value) => {
|
|
1124
|
-
existingValues.set(path2, { tag, relativeFilePath: file.relativeFilePath, value });
|
|
1125
|
-
}
|
|
1126
|
-
};
|
|
1127
|
-
const addConflict = async (path2, tagA, tagBValue, conflictType) => {
|
|
1128
|
-
if (conflictType === "path_overwrite" && config.collect?.ignoreConflictsWithMatchingValues !== false && tagA.value === tagBValue) {
|
|
1129
|
-
return;
|
|
1130
|
-
}
|
|
1131
|
-
const conflict = {
|
|
1132
|
-
path: path2,
|
|
1133
|
-
tagA,
|
|
1134
|
-
tagB: {
|
|
1135
|
-
tag,
|
|
1136
|
-
relativeFilePath: file.relativeFilePath,
|
|
1137
|
-
value: tagBValue
|
|
1138
|
-
},
|
|
1139
|
-
conflictType
|
|
1140
|
-
};
|
|
1141
|
-
if (config.collect?.onConflictResolution) {
|
|
1142
|
-
let shouldContinue = true;
|
|
1143
|
-
await config.collect.onConflictResolution({
|
|
1144
|
-
conflict,
|
|
1145
|
-
logger,
|
|
1146
|
-
exit() {
|
|
1147
|
-
shouldContinue = false;
|
|
1148
|
-
}
|
|
1149
|
-
});
|
|
1150
|
-
if (!shouldContinue) {
|
|
1151
|
-
throw new Error(`LangTagConflictResolution:Processing stopped due to conflict resolution: ${conflict.tagA.tag.parameterConfig.namespace}|${conflict.path}`);
|
|
1152
|
-
}
|
|
1531
|
+
async function generateImportFiles(config, logger, importManager) {
|
|
1532
|
+
const importedFiles = importManager.getImportedFiles();
|
|
1533
|
+
for (const importedFile of importedFiles) {
|
|
1534
|
+
const filePath = resolve$1(
|
|
1535
|
+
process$1.cwd(),
|
|
1536
|
+
config.import.dir,
|
|
1537
|
+
importedFile.pathRelativeToImportDir
|
|
1538
|
+
);
|
|
1539
|
+
const processedExports = importedFile.tags.map((tag) => {
|
|
1540
|
+
const parameter1 = config.translationArgPosition === 1 ? tag.translations : tag.config;
|
|
1541
|
+
const parameter2 = config.translationArgPosition === 1 ? tag.config : tag.translations;
|
|
1542
|
+
const hasParameter2 = parameter2 !== null && parameter2 !== void 0 && (typeof parameter2 !== "object" || Object.keys(parameter2).length > 0);
|
|
1543
|
+
return {
|
|
1544
|
+
name: tag.variableName,
|
|
1545
|
+
parameter1: JSON5.stringify(parameter1, void 0, 4),
|
|
1546
|
+
parameter2: hasParameter2 ? JSON5.stringify(parameter2, void 0, 4) : null,
|
|
1547
|
+
hasParameter2,
|
|
1548
|
+
config: {
|
|
1549
|
+
tagName: config.tagName
|
|
1153
1550
|
}
|
|
1154
|
-
allConflicts.push(conflict);
|
|
1155
1551
|
};
|
|
1156
|
-
const target = await ensureNestedObject(
|
|
1157
|
-
tagConfig.path,
|
|
1158
|
-
collection,
|
|
1159
|
-
valueTracker,
|
|
1160
|
-
addConflict
|
|
1161
|
-
);
|
|
1162
|
-
await mergeWithConflictDetection(
|
|
1163
|
-
target,
|
|
1164
|
-
tag.parameterTranslations,
|
|
1165
|
-
tagConfig.path || "",
|
|
1166
|
-
valueTracker,
|
|
1167
|
-
addConflict
|
|
1168
|
-
);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
if (allConflicts.length > 0) {
|
|
1172
|
-
logger.warn(`Found ${allConflicts.length} conflicts.`);
|
|
1173
|
-
}
|
|
1174
|
-
if (config.collect?.onCollectFinish) {
|
|
1175
|
-
let shouldContinue = true;
|
|
1176
|
-
config.collect.onCollectFinish({
|
|
1177
|
-
totalTags,
|
|
1178
|
-
namespaces: collections,
|
|
1179
|
-
conflicts: allConflicts,
|
|
1180
|
-
logger,
|
|
1181
|
-
exit() {
|
|
1182
|
-
shouldContinue = false;
|
|
1183
|
-
}
|
|
1184
1552
|
});
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
for (const key of path2.split(".")) {
|
|
1196
|
-
currentPath = currentPath ? `${currentPath}.${key}` : key;
|
|
1197
|
-
if (current[key] !== void 0 && typeof current[key] !== "object") {
|
|
1198
|
-
const existingInfo = valueTracker.get(currentPath);
|
|
1199
|
-
if (existingInfo) {
|
|
1200
|
-
await addConflict(currentPath, existingInfo, {}, "type_mismatch");
|
|
1201
|
-
}
|
|
1202
|
-
return current;
|
|
1203
|
-
}
|
|
1204
|
-
current[key] = current[key] || {};
|
|
1205
|
-
current = current[key];
|
|
1553
|
+
const templateData = {
|
|
1554
|
+
tagImportPath: config.import.tagImportPath,
|
|
1555
|
+
exports: processedExports
|
|
1556
|
+
};
|
|
1557
|
+
const content = renderTemplate$1(templateData);
|
|
1558
|
+
await $LT_EnsureDirectoryExists(dirname(filePath));
|
|
1559
|
+
await writeFile(filePath, content, "utf-8");
|
|
1560
|
+
logger.success('Created tag file: "{file}"', {
|
|
1561
|
+
file: importedFile.pathRelativeToImportDir
|
|
1562
|
+
});
|
|
1206
1563
|
}
|
|
1207
|
-
return current;
|
|
1208
1564
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1565
|
+
class ImportManager {
|
|
1566
|
+
importedFiles = [];
|
|
1567
|
+
constructor() {
|
|
1568
|
+
this.importedFiles = [];
|
|
1212
1569
|
}
|
|
1213
|
-
|
|
1214
|
-
if (!
|
|
1215
|
-
|
|
1570
|
+
importTag(pathRelativeToImportDir, tag) {
|
|
1571
|
+
if (!pathRelativeToImportDir) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
`pathRelativeToImportDir required, got: ${pathRelativeToImportDir}`
|
|
1574
|
+
);
|
|
1216
1575
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
continue;
|
|
1576
|
+
if (!tag?.variableName) {
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
`tag.variableName required, got: ${tag?.variableName}`
|
|
1579
|
+
);
|
|
1222
1580
|
}
|
|
1223
|
-
if (
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if (!existingInfo && targetType === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
1228
|
-
const findNestedInfo = (obj, prefix) => {
|
|
1229
|
-
for (const key2 in obj) {
|
|
1230
|
-
const path2 = prefix ? `${prefix}.${key2}` : key2;
|
|
1231
|
-
const info = valueTracker.get(path2);
|
|
1232
|
-
if (info) {
|
|
1233
|
-
return info;
|
|
1234
|
-
}
|
|
1235
|
-
if (typeof obj[key2] === "object" && obj[key2] !== null && !Array.isArray(obj[key2])) {
|
|
1236
|
-
const nestedInfo = findNestedInfo(obj[key2], path2);
|
|
1237
|
-
if (nestedInfo) {
|
|
1238
|
-
return nestedInfo;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
return void 0;
|
|
1243
|
-
};
|
|
1244
|
-
existingInfo = findNestedInfo(targetValue, currentPath);
|
|
1245
|
-
}
|
|
1246
|
-
if (targetType !== sourceType) {
|
|
1247
|
-
if (existingInfo) {
|
|
1248
|
-
await addConflict(currentPath, existingInfo, sourceValue, "type_mismatch");
|
|
1249
|
-
}
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
if (targetType !== "object") {
|
|
1253
|
-
if (existingInfo) {
|
|
1254
|
-
await addConflict(currentPath, existingInfo, sourceValue, "path_overwrite");
|
|
1255
|
-
}
|
|
1256
|
-
if (targetValue !== sourceValue) {
|
|
1257
|
-
continue;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1581
|
+
if (!this.isValidJavaScriptIdentifier(tag.variableName)) {
|
|
1582
|
+
throw new Error(
|
|
1583
|
+
`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.`
|
|
1584
|
+
);
|
|
1260
1585
|
}
|
|
1261
|
-
if (
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
valueTracker,
|
|
1271
|
-
addConflict
|
|
1586
|
+
if (tag.translations == null) {
|
|
1587
|
+
throw new Error(`tag.translations required`);
|
|
1588
|
+
}
|
|
1589
|
+
let importedFile = this.importedFiles.find(
|
|
1590
|
+
(file) => file.pathRelativeToImportDir === pathRelativeToImportDir
|
|
1591
|
+
);
|
|
1592
|
+
if (importedFile) {
|
|
1593
|
+
const duplicateTag = importedFile.tags.find(
|
|
1594
|
+
(existingTag) => existingTag.variableName === tag.variableName
|
|
1272
1595
|
);
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1596
|
+
if (duplicateTag) {
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
`Duplicate variable name "${tag.variableName}" in file "${pathRelativeToImportDir}". Variable names must be unique within the same file.`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1276
1601
|
}
|
|
1277
|
-
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1281
|
-
logger.info("Collecting translations from source files...");
|
|
1282
|
-
const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
|
|
1283
|
-
if (config.debug) {
|
|
1284
|
-
for (let file of files) {
|
|
1285
|
-
logger.debug("Found {count} translations tags inside: {file}", { count: file.tags.length, file: file.relativeFilePath });
|
|
1602
|
+
if (!importedFile) {
|
|
1603
|
+
importedFile = { pathRelativeToImportDir, tags: [] };
|
|
1604
|
+
this.importedFiles.push(importedFile);
|
|
1286
1605
|
}
|
|
1606
|
+
importedFile.tags.push(tag);
|
|
1287
1607
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
return;
|
|
1608
|
+
getImportedFiles() {
|
|
1609
|
+
return [...this.importedFiles];
|
|
1291
1610
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
|
|
1295
|
-
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1296
|
-
await $LT_WriteToCollections({ config, collections, logger, clean: options?.clean });
|
|
1297
|
-
} catch (e) {
|
|
1298
|
-
const prefix = "LangTagConflictResolution:";
|
|
1299
|
-
if (e.message.startsWith(prefix)) {
|
|
1300
|
-
logger.error(e.message.substring(prefix.length));
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
throw e;
|
|
1611
|
+
getImportedFilesCount() {
|
|
1612
|
+
return this.importedFiles.length;
|
|
1304
1613
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
const globStartIndex = pattern.indexOf("*");
|
|
1308
|
-
const braceStartIndex = pattern.indexOf("{");
|
|
1309
|
-
let endIndex = -1;
|
|
1310
|
-
if (globStartIndex !== -1 && braceStartIndex !== -1) {
|
|
1311
|
-
endIndex = Math.min(globStartIndex, braceStartIndex);
|
|
1312
|
-
} else if (globStartIndex !== -1) {
|
|
1313
|
-
endIndex = globStartIndex;
|
|
1314
|
-
} else if (braceStartIndex !== -1) {
|
|
1315
|
-
endIndex = braceStartIndex;
|
|
1614
|
+
hasImportedFiles() {
|
|
1615
|
+
return this.importedFiles.length > 0;
|
|
1316
1616
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
return lastSlashIndex !== -1 ? pattern.substring(0, lastSlashIndex) : ".";
|
|
1617
|
+
isValidJavaScriptIdentifier(name) {
|
|
1618
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
1320
1619
|
}
|
|
1321
|
-
const lastSeparatorIndex = pattern.lastIndexOf("/", endIndex);
|
|
1322
|
-
return lastSeparatorIndex === -1 ? "." : pattern.substring(0, lastSeparatorIndex);
|
|
1323
1620
|
}
|
|
1324
|
-
function $
|
|
1325
|
-
const
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
stabilityThreshold: 300,
|
|
1339
|
-
pollInterval: 100
|
|
1340
|
-
}
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
async function $LT_WatchTranslations() {
|
|
1344
|
-
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1345
|
-
await $LT_CMD_Collect();
|
|
1346
|
-
const watcher = $LT_CreateChokidarWatcher(config);
|
|
1347
|
-
logger.info("Starting watch mode for translations...");
|
|
1348
|
-
logger.info("Watching for changes...");
|
|
1349
|
-
logger.info("Press Ctrl+C to stop watching");
|
|
1350
|
-
watcher.on("change", async (filePath) => await handleFile(config, logger, filePath)).on("add", async (filePath) => await handleFile(config, logger, filePath)).on("error", (error) => {
|
|
1351
|
-
logger.error("Error in file watcher: {error}", { error });
|
|
1621
|
+
async function $LT_ImportLibraries(config, logger) {
|
|
1622
|
+
const exportFiles = await $LT_CollectExportFiles(logger);
|
|
1623
|
+
const importManager = new ImportManager();
|
|
1624
|
+
let exports = [];
|
|
1625
|
+
for (const { exportPath, packageJsonPath } of exportFiles) {
|
|
1626
|
+
const exportData = await $LT_ReadJSON(exportPath);
|
|
1627
|
+
const packageJSON = await $LT_ReadJSON(packageJsonPath);
|
|
1628
|
+
exports.push({ packageJSON, exportData });
|
|
1629
|
+
}
|
|
1630
|
+
config.import.onImport({
|
|
1631
|
+
exports,
|
|
1632
|
+
importManager,
|
|
1633
|
+
logger,
|
|
1634
|
+
langTagConfig: config
|
|
1352
1635
|
});
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (!micromatch.isMatch(cwdRelativeFilePath, config.includes)) {
|
|
1636
|
+
if (!importManager.hasImportedFiles()) {
|
|
1637
|
+
logger.warn("No tags were imported from any library files");
|
|
1356
1638
|
return;
|
|
1357
1639
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
const
|
|
1363
|
-
await $
|
|
1640
|
+
await generateImportFiles(config, logger, importManager);
|
|
1641
|
+
if (config.import.onImportFinish) config.import.onImportFinish();
|
|
1642
|
+
}
|
|
1643
|
+
async function $LT_ImportTranslations() {
|
|
1644
|
+
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1645
|
+
await $LT_EnsureDirectoryExists(config.import.dir);
|
|
1646
|
+
logger.info("Importing translations from libraries...");
|
|
1647
|
+
await $LT_ImportLibraries(config, logger);
|
|
1648
|
+
logger.success("Successfully imported translations from libraries.");
|
|
1364
1649
|
}
|
|
1365
1650
|
async function detectModuleSystem() {
|
|
1366
1651
|
const packageJsonPath = join(process.cwd(), "package.json");
|
|
@@ -1413,7 +1698,7 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1413
1698
|
});
|
|
1414
1699
|
const keeper = configKeeper({ propertyName: 'keep' });
|
|
1415
1700
|
|
|
1416
|
-
/** @type {import('@lang-tag/cli/
|
|
1701
|
+
/** @type {import('@lang-tag/cli/type').LangTagCLIConfig} */
|
|
1417
1702
|
const config = {
|
|
1418
1703
|
tagName: 'lang',
|
|
1419
1704
|
isLibrary: false,
|
|
@@ -1435,239 +1720,35 @@ const config = {
|
|
|
1435
1720
|
defaultNamespace: 'common',
|
|
1436
1721
|
onConflictResolution: async event => {
|
|
1437
1722
|
await event.logger.conflict(event.conflict, true);
|
|
1438
|
-
// By default, continue processing even if conflicts occur
|
|
1439
|
-
// Call event.exit(); to terminate the process upon the first conflict
|
|
1440
|
-
},
|
|
1441
|
-
onCollectFinish: event => {
|
|
1442
|
-
if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
|
|
1443
|
-
}
|
|
1444
|
-
},
|
|
1445
|
-
translationArgPosition: 1,
|
|
1446
|
-
debug: false,
|
|
1447
|
-
};
|
|
1448
|
-
|
|
1449
|
-
${exportStatement}`;
|
|
1450
|
-
}
|
|
1451
|
-
async function $LT_CMD_InitConfig() {
|
|
1452
|
-
const logger = $LT_CreateDefaultLogger();
|
|
1453
|
-
if (existsSync(CONFIG_FILE_NAME)) {
|
|
1454
|
-
logger.success("Configuration file already exists. Please remove the existing configuration file before creating a new default one");
|
|
1455
|
-
return;
|
|
1456
|
-
}
|
|
1457
|
-
try {
|
|
1458
|
-
const configContent = await generateDefaultConfig();
|
|
1459
|
-
await writeFile(CONFIG_FILE_NAME, configContent, "utf-8");
|
|
1460
|
-
logger.success("Configuration file created successfully");
|
|
1461
|
-
} catch (error) {
|
|
1462
|
-
logger.error(error?.message);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
async function $LT_CollectExportFiles(logger) {
|
|
1466
|
-
const nodeModulesPath = path$1.join(process__default.cwd(), "node_modules");
|
|
1467
|
-
if (!fs.existsSync(nodeModulesPath)) {
|
|
1468
|
-
logger.error('"node_modules" directory not found');
|
|
1469
|
-
return [];
|
|
1470
|
-
}
|
|
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
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
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
|
-
});
|
|
1502
|
-
}
|
|
1503
|
-
return results;
|
|
1504
|
-
}
|
|
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;
|
|
1517
|
-
}
|
|
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
|
-
}
|
|
1525
|
-
}
|
|
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) {
|
|
1538
|
-
const filePath = resolve(
|
|
1539
|
-
process$1.cwd(),
|
|
1540
|
-
config.import.dir,
|
|
1541
|
-
importedFile.pathRelativeToImportDir
|
|
1542
|
-
);
|
|
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));
|
|
1563
|
-
await writeFile(filePath, content, "utf-8");
|
|
1564
|
-
logger.success('Created tag file: "{file}"', { file: importedFile.pathRelativeToImportDir });
|
|
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
|
-
}
|
|
1723
|
+
// By default, continue processing even if conflicts occur
|
|
1724
|
+
// Call event.exit(); to terminate the process upon the first conflict
|
|
1725
|
+
},
|
|
1726
|
+
onCollectFinish: event => {
|
|
1727
|
+
if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
translationArgPosition: 1,
|
|
1731
|
+
debug: false,
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
${exportStatement}`;
|
|
1614
1735
|
}
|
|
1615
|
-
async function $
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
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");
|
|
1736
|
+
async function $LT_CMD_InitConfig() {
|
|
1737
|
+
const logger = $LT_CreateDefaultLogger();
|
|
1738
|
+
if (existsSync(CONFIG_FILE_NAME)) {
|
|
1739
|
+
logger.success(
|
|
1740
|
+
"Configuration file already exists. Please remove the existing configuration file before creating a new default one"
|
|
1741
|
+
);
|
|
1627
1742
|
return;
|
|
1628
1743
|
}
|
|
1629
|
-
await generateImportFiles(config, logger, importManager);
|
|
1630
|
-
if (config.import.onImportFinish) config.import.onImportFinish();
|
|
1631
|
-
}
|
|
1632
|
-
async function $LT_ImportTranslations() {
|
|
1633
|
-
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1634
|
-
await $LT_EnsureDirectoryExists(config.import.dir);
|
|
1635
|
-
logger.info("Importing translations from libraries...");
|
|
1636
|
-
await $LT_ImportLibraries(config, logger);
|
|
1637
|
-
logger.success("Successfully imported translations from libraries.");
|
|
1638
|
-
}
|
|
1639
|
-
function renderTemplate(template2, data) {
|
|
1640
|
-
return mustache.render(template2, data, {}, { escape: (text) => text });
|
|
1641
|
-
}
|
|
1642
|
-
function loadTemplate(templateName) {
|
|
1643
|
-
const __filename2 = fileURLToPath(import.meta.url);
|
|
1644
|
-
const __dirname2 = dirname(__filename2);
|
|
1645
|
-
const templatePath2 = join(__dirname2, "templates", "tag", `${templateName}.mustache`);
|
|
1646
1744
|
try {
|
|
1647
|
-
|
|
1745
|
+
const configContent = await generateDefaultConfig();
|
|
1746
|
+
await writeFile(CONFIG_FILE_NAME, configContent, "utf-8");
|
|
1747
|
+
logger.success("Configuration file created successfully");
|
|
1648
1748
|
} catch (error) {
|
|
1649
|
-
|
|
1749
|
+
logger.error(error?.message);
|
|
1650
1750
|
}
|
|
1651
1751
|
}
|
|
1652
|
-
function prepareTemplateData(options) {
|
|
1653
|
-
return {
|
|
1654
|
-
...options,
|
|
1655
|
-
tmpVariables: {
|
|
1656
|
-
key: "{{key}}",
|
|
1657
|
-
username: "{{username}}",
|
|
1658
|
-
processRegex: "{{(.*?)}}"
|
|
1659
|
-
}
|
|
1660
|
-
};
|
|
1661
|
-
}
|
|
1662
|
-
function renderInitTagTemplates(options) {
|
|
1663
|
-
const baseTemplateName = options.isLibrary ? "base-library" : "base-app";
|
|
1664
|
-
const baseTemplate = loadTemplate(baseTemplateName);
|
|
1665
|
-
const placeholderTemplate = loadTemplate("placeholder");
|
|
1666
|
-
const templateData = prepareTemplateData(options);
|
|
1667
|
-
const renderedBase = renderTemplate(baseTemplate, templateData);
|
|
1668
|
-
const renderedPlaceholders = renderTemplate(placeholderTemplate, templateData);
|
|
1669
|
-
return renderedBase + "\n\n" + renderedPlaceholders;
|
|
1670
|
-
}
|
|
1671
1752
|
async function readPackageJson() {
|
|
1672
1753
|
const packageJsonPath = join(process.cwd(), "package.json");
|
|
1673
1754
|
if (!existsSync(packageJsonPath)) {
|
|
@@ -1709,51 +1790,312 @@ async function detectInitTagOptions(options, config) {
|
|
|
1709
1790
|
packageVersion: packageJson?.version || "1.0.0"
|
|
1710
1791
|
};
|
|
1711
1792
|
}
|
|
1793
|
+
function renderTemplate(template2, data) {
|
|
1794
|
+
return mustache.render(template2, data, {}, { escape: (text) => text });
|
|
1795
|
+
}
|
|
1796
|
+
function loadTemplate(templateName) {
|
|
1797
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
1798
|
+
const __dirname2 = dirname(__filename2);
|
|
1799
|
+
const templatePath2 = join(
|
|
1800
|
+
__dirname2,
|
|
1801
|
+
"templates",
|
|
1802
|
+
"tag",
|
|
1803
|
+
`${templateName}.mustache`
|
|
1804
|
+
);
|
|
1805
|
+
try {
|
|
1806
|
+
return readFileSync(templatePath2, "utf-8");
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
throw new Error(`Failed to load template ${templateName}: ${error}`);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
function prepareTemplateData(options) {
|
|
1812
|
+
return {
|
|
1813
|
+
...options,
|
|
1814
|
+
tmpVariables: {
|
|
1815
|
+
key: "{{key}}",
|
|
1816
|
+
username: "{{username}}",
|
|
1817
|
+
processRegex: "{{(.*?)}}"
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
function renderInitTagTemplates(options) {
|
|
1822
|
+
const baseTemplateName = options.isLibrary ? "base-library" : "base-app";
|
|
1823
|
+
const baseTemplate = loadTemplate(baseTemplateName);
|
|
1824
|
+
const placeholderTemplate = loadTemplate("placeholder");
|
|
1825
|
+
const templateData = prepareTemplateData(options);
|
|
1826
|
+
const renderedBase = renderTemplate(baseTemplate, templateData);
|
|
1827
|
+
const renderedPlaceholders = renderTemplate(
|
|
1828
|
+
placeholderTemplate,
|
|
1829
|
+
templateData
|
|
1830
|
+
);
|
|
1831
|
+
return renderedBase + "\n\n" + renderedPlaceholders;
|
|
1832
|
+
}
|
|
1712
1833
|
async function $LT_CMD_InitTagFile(options = {}) {
|
|
1713
1834
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1714
1835
|
const renderOptions = await detectInitTagOptions(options, config);
|
|
1715
1836
|
const outputPath = options.output || `${renderOptions.tagName}.${renderOptions.fileExtension}`;
|
|
1716
1837
|
logger.info("Initializing lang-tag with the following options:");
|
|
1717
1838
|
logger.info(" Tag name: {tagName}", { tagName: renderOptions.tagName });
|
|
1718
|
-
logger.info(" Library mode: {isLibrary}", {
|
|
1719
|
-
|
|
1720
|
-
|
|
1839
|
+
logger.info(" Library mode: {isLibrary}", {
|
|
1840
|
+
isLibrary: renderOptions.isLibrary ? "Yes" : "No"
|
|
1841
|
+
});
|
|
1842
|
+
logger.info(" React: {isReact}", {
|
|
1843
|
+
isReact: renderOptions.isReact ? "Yes" : "No"
|
|
1844
|
+
});
|
|
1845
|
+
logger.info(" TypeScript: {isTypeScript}", {
|
|
1846
|
+
isTypeScript: renderOptions.isTypeScript ? "Yes" : "No"
|
|
1847
|
+
});
|
|
1721
1848
|
logger.info(" Output path: {outputPath}", { outputPath });
|
|
1722
1849
|
let renderedContent;
|
|
1723
1850
|
try {
|
|
1724
1851
|
renderedContent = renderInitTagTemplates(renderOptions);
|
|
1725
1852
|
} catch (error) {
|
|
1726
|
-
logger.error("Failed to render templates: {error}", {
|
|
1853
|
+
logger.error("Failed to render templates: {error}", {
|
|
1854
|
+
error: error?.message
|
|
1855
|
+
});
|
|
1727
1856
|
return;
|
|
1728
1857
|
}
|
|
1729
1858
|
if (existsSync(outputPath)) {
|
|
1730
1859
|
logger.warn("File already exists: {outputPath}", { outputPath });
|
|
1731
|
-
logger.info(
|
|
1860
|
+
logger.info(
|
|
1861
|
+
"Use --output to specify a different path or remove the existing file"
|
|
1862
|
+
);
|
|
1732
1863
|
return;
|
|
1733
1864
|
}
|
|
1734
1865
|
try {
|
|
1735
1866
|
await $LT_WriteFileWithDirs(outputPath, renderedContent);
|
|
1736
|
-
logger.success("Lang-tag file created successfully: {outputPath}", {
|
|
1867
|
+
logger.success("Lang-tag file created successfully: {outputPath}", {
|
|
1868
|
+
outputPath
|
|
1869
|
+
});
|
|
1737
1870
|
logger.info("Next steps:");
|
|
1738
|
-
logger.info("1. Import the {tagName} function in your files:", {
|
|
1871
|
+
logger.info("1. Import the {tagName} function in your files:", {
|
|
1872
|
+
tagName: renderOptions.tagName
|
|
1873
|
+
});
|
|
1739
1874
|
logger.info(" import { {tagName} } from './{importPath}';", {
|
|
1740
1875
|
tagName: renderOptions.tagName,
|
|
1741
1876
|
importPath: outputPath.replace(/^src\//, "")
|
|
1742
1877
|
});
|
|
1743
|
-
logger.info(
|
|
1878
|
+
logger.info(
|
|
1879
|
+
"2. Create your translation objects and use the tag function"
|
|
1880
|
+
);
|
|
1744
1881
|
logger.info('3. Run "lang-tag collect" to extract translations');
|
|
1745
1882
|
} catch (error) {
|
|
1746
|
-
logger.error("Failed to write file: {error}", {
|
|
1883
|
+
logger.error("Failed to write file: {error}", {
|
|
1884
|
+
error: error?.message
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function deepFreezeObject(obj) {
|
|
1889
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
1890
|
+
for (const name of propNames) {
|
|
1891
|
+
const value = obj[name];
|
|
1892
|
+
if (value && typeof value === "object") {
|
|
1893
|
+
deepFreezeObject(value);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return Object.freeze(obj);
|
|
1897
|
+
}
|
|
1898
|
+
async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
1899
|
+
let libraryImportsDir = config.import.dir;
|
|
1900
|
+
if (!libraryImportsDir.endsWith(sep)) libraryImportsDir += sep;
|
|
1901
|
+
const fileContent = readFileSync(file, "utf-8");
|
|
1902
|
+
const processor = new $LT_TagProcessor(config);
|
|
1903
|
+
let tags = processor.extractTags(fileContent);
|
|
1904
|
+
tags = $LT_FilterInvalidTags(tags, config, logger);
|
|
1905
|
+
if (!tags.length) {
|
|
1906
|
+
return false;
|
|
1907
|
+
}
|
|
1908
|
+
const replacements = [];
|
|
1909
|
+
let lastUpdatedLine = 0;
|
|
1910
|
+
for (let tag of tags) {
|
|
1911
|
+
let newConfig = void 0;
|
|
1912
|
+
let shouldUpdate = false;
|
|
1913
|
+
const frozenConfig = tag.parameterConfig ? deepFreezeObject(tag.parameterConfig) : tag.parameterConfig;
|
|
1914
|
+
const event = {
|
|
1915
|
+
langTagConfig: config,
|
|
1916
|
+
logger,
|
|
1917
|
+
config: frozenConfig,
|
|
1918
|
+
absolutePath: file,
|
|
1919
|
+
relativePath: path2,
|
|
1920
|
+
isImportedLibrary: path2.startsWith(libraryImportsDir),
|
|
1921
|
+
isSaved: false,
|
|
1922
|
+
savedConfig: void 0,
|
|
1923
|
+
save: (updatedConfig, triggerName) => {
|
|
1924
|
+
if (!updatedConfig && updatedConfig !== null)
|
|
1925
|
+
throw new Error("Wrong config data");
|
|
1926
|
+
newConfig = updatedConfig;
|
|
1927
|
+
shouldUpdate = true;
|
|
1928
|
+
event.isSaved = true;
|
|
1929
|
+
event.savedConfig = updatedConfig;
|
|
1930
|
+
logger.debug(
|
|
1931
|
+
'Called save for "{path}" with config "{config}" triggered by: ("{trigger}")',
|
|
1932
|
+
{
|
|
1933
|
+
path: path2,
|
|
1934
|
+
config: JSON.stringify(updatedConfig),
|
|
1935
|
+
trigger: triggerName || "-"
|
|
1936
|
+
}
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
await config.onConfigGeneration(event);
|
|
1941
|
+
if (!shouldUpdate) {
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
lastUpdatedLine = tag.line;
|
|
1945
|
+
if (!isConfigSame(tag.parameterConfig, newConfig)) {
|
|
1946
|
+
replacements.push({ tag, config: newConfig });
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
if (replacements.length) {
|
|
1950
|
+
const newContent = processor.replaceTags(fileContent, replacements);
|
|
1951
|
+
await writeFile(file, newContent, "utf-8");
|
|
1952
|
+
const encodedFile = encodeURI(file);
|
|
1953
|
+
logger.info(
|
|
1954
|
+
'Lang tag configurations written for file "{path}" (file://{file}:{line})',
|
|
1955
|
+
{ path: path2, file: encodedFile, line: lastUpdatedLine }
|
|
1956
|
+
);
|
|
1957
|
+
return true;
|
|
1958
|
+
}
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
function isConfigSame(c1, c2) {
|
|
1962
|
+
if (!c1 && !c2) return true;
|
|
1963
|
+
if (c1 && typeof c1 === "object" && c2 && typeof c2 === "object" && JSON5.stringify(c1) === JSON5.stringify(c2))
|
|
1964
|
+
return true;
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
async function $LT_CMD_RegenerateTags() {
|
|
1968
|
+
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1969
|
+
const files = await globby(config.includes, {
|
|
1970
|
+
cwd: process.cwd(),
|
|
1971
|
+
ignore: config.excludes,
|
|
1972
|
+
absolute: true
|
|
1973
|
+
});
|
|
1974
|
+
const charactersToSkip = process.cwd().length + 1;
|
|
1975
|
+
let dirty = false;
|
|
1976
|
+
for (const file of files) {
|
|
1977
|
+
const path2 = file.substring(charactersToSkip);
|
|
1978
|
+
const localDirty = await checkAndRegenerateFileLangTags(
|
|
1979
|
+
config,
|
|
1980
|
+
logger,
|
|
1981
|
+
file,
|
|
1982
|
+
path2
|
|
1983
|
+
);
|
|
1984
|
+
if (localDirty) {
|
|
1985
|
+
dirty = true;
|
|
1986
|
+
}
|
|
1747
1987
|
}
|
|
1988
|
+
if (!dirty) {
|
|
1989
|
+
logger.info(
|
|
1990
|
+
"No changes were made based on the current configuration and files"
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
function getBasePath(pattern) {
|
|
1995
|
+
const globStartIndex = pattern.indexOf("*");
|
|
1996
|
+
const braceStartIndex = pattern.indexOf("{");
|
|
1997
|
+
let endIndex = -1;
|
|
1998
|
+
if (globStartIndex !== -1 && braceStartIndex !== -1) {
|
|
1999
|
+
endIndex = Math.min(globStartIndex, braceStartIndex);
|
|
2000
|
+
} else if (globStartIndex !== -1) {
|
|
2001
|
+
endIndex = globStartIndex;
|
|
2002
|
+
} else if (braceStartIndex !== -1) {
|
|
2003
|
+
endIndex = braceStartIndex;
|
|
2004
|
+
}
|
|
2005
|
+
if (endIndex === -1) {
|
|
2006
|
+
const lastSlashIndex = pattern.lastIndexOf("/");
|
|
2007
|
+
return lastSlashIndex !== -1 ? pattern.substring(0, lastSlashIndex) : ".";
|
|
2008
|
+
}
|
|
2009
|
+
const lastSeparatorIndex = pattern.lastIndexOf("/", endIndex);
|
|
2010
|
+
return lastSeparatorIndex === -1 ? "." : pattern.substring(0, lastSeparatorIndex);
|
|
2011
|
+
}
|
|
2012
|
+
function $LT_CreateChokidarWatcher(config) {
|
|
2013
|
+
const cwd = process.cwd();
|
|
2014
|
+
const baseDirsToWatch = [
|
|
2015
|
+
...new Set(config.includes.map((pattern) => getBasePath(pattern)))
|
|
2016
|
+
];
|
|
2017
|
+
const finalDirsToWatch = baseDirsToWatch.map(
|
|
2018
|
+
(dir) => dir === "." ? cwd : dir
|
|
2019
|
+
);
|
|
2020
|
+
const ignored = [...config.excludes, "**/.git/**"];
|
|
2021
|
+
return chokidar.watch(finalDirsToWatch, {
|
|
2022
|
+
// Watch base directories
|
|
2023
|
+
cwd,
|
|
2024
|
+
ignored,
|
|
2025
|
+
persistent: true,
|
|
2026
|
+
ignoreInitial: true,
|
|
2027
|
+
awaitWriteFinish: {
|
|
2028
|
+
stabilityThreshold: 300,
|
|
2029
|
+
pollInterval: 100
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
async function $LT_WatchTranslations() {
|
|
2034
|
+
const { config, logger } = await $LT_GetCommandEssentials();
|
|
2035
|
+
await $LT_CMD_Collect();
|
|
2036
|
+
const watcher = $LT_CreateChokidarWatcher(config);
|
|
2037
|
+
logger.info("Starting watch mode for translations...");
|
|
2038
|
+
logger.info("Watching for changes...");
|
|
2039
|
+
logger.info("Press Ctrl+C to stop watching");
|
|
2040
|
+
watcher.on(
|
|
2041
|
+
"change",
|
|
2042
|
+
async (filePath) => await handleFile(config, logger, filePath)
|
|
2043
|
+
).on(
|
|
2044
|
+
"add",
|
|
2045
|
+
async (filePath) => await handleFile(config, logger, filePath)
|
|
2046
|
+
).on("error", (error) => {
|
|
2047
|
+
logger.error("Error in file watcher: {error}", { error });
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
2051
|
+
if (!micromatch.isMatch(cwdRelativeFilePath, config.includes)) {
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
const cwd = process.cwd();
|
|
2055
|
+
const absoluteFilePath = path__default.join(cwd, cwdRelativeFilePath);
|
|
2056
|
+
await checkAndRegenerateFileLangTags(
|
|
2057
|
+
config,
|
|
2058
|
+
logger,
|
|
2059
|
+
absoluteFilePath,
|
|
2060
|
+
cwdRelativeFilePath
|
|
2061
|
+
);
|
|
2062
|
+
const files = await $LT_CollectCandidateFilesWithTags({
|
|
2063
|
+
filesToScan: [cwdRelativeFilePath],
|
|
2064
|
+
config,
|
|
2065
|
+
logger
|
|
2066
|
+
});
|
|
2067
|
+
const namespaces = await $LT_GroupTagsToCollections({
|
|
2068
|
+
logger,
|
|
2069
|
+
files,
|
|
2070
|
+
config
|
|
2071
|
+
});
|
|
2072
|
+
await $LT_WriteToCollections({ config, collections: namespaces, logger });
|
|
1748
2073
|
}
|
|
1749
2074
|
function createCli() {
|
|
1750
2075
|
program.name("lang-tag").description("CLI to manage language translations").version("0.1.0");
|
|
1751
2076
|
program.command("collect").alias("c").description("Collect translations from source files").option("-c, --clean", "Remove output directory before collecting").action($LT_CMD_Collect);
|
|
1752
2077
|
program.command("import").alias("i").description("Import translations from libraries in node_modules").action($LT_ImportTranslations);
|
|
1753
2078
|
program.command("regenerate-tags").alias("rt").description("Regenerate configuration for language tags").action($LT_CMD_RegenerateTags);
|
|
1754
|
-
program.command("watch").alias("w").description(
|
|
2079
|
+
program.command("watch").alias("w").description(
|
|
2080
|
+
"Watch for changes in source files and automatically collect translations"
|
|
2081
|
+
).action($LT_WatchTranslations);
|
|
1755
2082
|
program.command("init").description("Initialize project with default configuration").action($LT_CMD_InitConfig);
|
|
1756
|
-
program.command("init-tag").description("Initialize a new lang-tag function file").option(
|
|
2083
|
+
program.command("init-tag").description("Initialize a new lang-tag function file").option(
|
|
2084
|
+
"-n, --name <name>",
|
|
2085
|
+
"Name of the tag function (default: from config)"
|
|
2086
|
+
).option(
|
|
2087
|
+
"-l, --library",
|
|
2088
|
+
"Generate library-style tag (default: from config)"
|
|
2089
|
+
).option(
|
|
2090
|
+
"-r, --react",
|
|
2091
|
+
"Include React-specific optimizations (default: auto-detect)"
|
|
2092
|
+
).option(
|
|
2093
|
+
"-t, --typescript",
|
|
2094
|
+
"Force TypeScript output (default: auto-detect)"
|
|
2095
|
+
).option(
|
|
2096
|
+
"-o, --output <path>",
|
|
2097
|
+
"Output file path (default: auto-generated)"
|
|
2098
|
+
).action(async (options) => {
|
|
1757
2099
|
await $LT_CMD_InitTagFile(options);
|
|
1758
2100
|
});
|
|
1759
2101
|
return program;
|