@lang-tag/cli 0.16.0 → 0.18.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 +1463 -862
- package/index.js +1425 -824
- package/logger.d.ts +1 -1
- package/package.json +5 -1
- package/templates/config/config.mustache +180 -0
- package/templates/config/generation-algorithm.mustache +73 -0
- 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/templates/config/init-config.mustache +0 -1
package/index.cjs
CHANGED
|
@@ -2,20 +2,24 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
4
|
const commander = require("commander");
|
|
5
|
+
const process$1 = require("node:process");
|
|
5
6
|
const fs = require("fs");
|
|
6
|
-
const promises = require("fs/promises");
|
|
7
|
-
const JSON5 = require("json5");
|
|
8
|
-
const path = require("path");
|
|
9
7
|
const globby = require("globby");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const JSON5 = require("json5");
|
|
10
|
+
const promises = require("fs/promises");
|
|
10
11
|
const path$1 = require("pathe");
|
|
11
12
|
const url = require("url");
|
|
12
13
|
require("case");
|
|
13
|
-
const
|
|
14
|
-
const flexibleImportAlgorithm = require("./flexible-import-algorithm-Fa-l4jWj.cjs");
|
|
15
|
-
const acorn = require("acorn");
|
|
14
|
+
const namespaceCollector = require("./chunks/namespace-collector.cjs");
|
|
16
15
|
const micromatch = require("micromatch");
|
|
17
|
-
const
|
|
16
|
+
const acorn = require("acorn");
|
|
18
17
|
const mustache = require("mustache");
|
|
18
|
+
const checkbox = require("@inquirer/checkbox");
|
|
19
|
+
const confirm = require("@inquirer/confirm");
|
|
20
|
+
const input = require("@inquirer/input");
|
|
21
|
+
const select = require("@inquirer/select");
|
|
22
|
+
const chokidar = require("chokidar");
|
|
19
23
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
20
24
|
function _interopNamespaceDefault(e) {
|
|
21
25
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -33,9 +37,61 @@ function _interopNamespaceDefault(e) {
|
|
|
33
37
|
n.default = e;
|
|
34
38
|
return Object.freeze(n);
|
|
35
39
|
}
|
|
36
|
-
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
|
|
37
40
|
const process__namespace = /* @__PURE__ */ _interopNamespaceDefault(process$1);
|
|
41
|
+
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
|
|
38
42
|
const acorn__namespace = /* @__PURE__ */ _interopNamespaceDefault(acorn);
|
|
43
|
+
function $LT_FilterInvalidTags(tags, config, logger) {
|
|
44
|
+
return tags.filter((tag) => {
|
|
45
|
+
if (tag.validity === "invalid-param-1")
|
|
46
|
+
logger.debug(
|
|
47
|
+
'Skipping tag "{fullMatch}". Invalid JSON: "{invalid}"',
|
|
48
|
+
{
|
|
49
|
+
fullMatch: tag.fullMatch.trim(),
|
|
50
|
+
invalid: tag.parameter1Text
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
if (tag.validity === "invalid-param-2")
|
|
54
|
+
logger.debug(
|
|
55
|
+
'Skipping tag "{fullMatch}". Invalid JSON: "{invalid}"',
|
|
56
|
+
{
|
|
57
|
+
fullMatch: tag.fullMatch.trim(),
|
|
58
|
+
invalid: tag.parameter2Text
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
if (tag.validity === "translations-not-found")
|
|
62
|
+
logger.debug(
|
|
63
|
+
'Skipping tag "{fullMatch}". Translations not found at parameter position: {pos}',
|
|
64
|
+
{
|
|
65
|
+
fullMatch: tag.fullMatch.trim(),
|
|
66
|
+
pos: config.translationArgPosition
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
return tag.validity === "ok";
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function $LT_FilterEmptyNamespaceTags(tags, logger) {
|
|
73
|
+
return tags.filter((tag) => {
|
|
74
|
+
if (!tag.parameterConfig) {
|
|
75
|
+
logger.warn(
|
|
76
|
+
'Skipping tag "{fullMatch}". Tag configuration not defined. (Check lang-tag config at collect.onCollectConfigFix)',
|
|
77
|
+
{
|
|
78
|
+
fullMatch: tag.fullMatch.trim()
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (!tag.parameterConfig.namespace) {
|
|
84
|
+
logger.warn(
|
|
85
|
+
'Skipping tag "{fullMatch}". Tag configuration namespace not defined. (Check lang-tag config at collect.onCollectConfigFix)',
|
|
86
|
+
{
|
|
87
|
+
fullMatch: tag.fullMatch.trim()
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
39
95
|
class $LT_TagProcessor {
|
|
40
96
|
constructor(config) {
|
|
41
97
|
this.config = config;
|
|
@@ -46,7 +102,10 @@ class $LT_TagProcessor {
|
|
|
46
102
|
const matches = [];
|
|
47
103
|
let currentIndex = 0;
|
|
48
104
|
const skipRanges = this.buildSkipRanges(fileContent);
|
|
49
|
-
const startPattern = new RegExp(
|
|
105
|
+
const startPattern = new RegExp(
|
|
106
|
+
`${optionalVariableAssignment}${tagName}\\(\\s*\\{`,
|
|
107
|
+
"g"
|
|
108
|
+
);
|
|
50
109
|
while (true) {
|
|
51
110
|
startPattern.lastIndex = currentIndex;
|
|
52
111
|
const startMatch = startPattern.exec(fileContent);
|
|
@@ -68,7 +127,10 @@ class $LT_TagProcessor {
|
|
|
68
127
|
currentIndex = matchStartIndex + 1;
|
|
69
128
|
continue;
|
|
70
129
|
}
|
|
71
|
-
let parameter1Text = fileContent.substring(
|
|
130
|
+
let parameter1Text = fileContent.substring(
|
|
131
|
+
matchStartIndex + startMatch[0].length - 1,
|
|
132
|
+
i
|
|
133
|
+
);
|
|
72
134
|
let parameter2Text;
|
|
73
135
|
while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
|
|
74
136
|
i++;
|
|
@@ -120,7 +182,10 @@ class $LT_TagProcessor {
|
|
|
120
182
|
}
|
|
121
183
|
i++;
|
|
122
184
|
const fullMatch = fileContent.substring(matchStartIndex, i);
|
|
123
|
-
const { line, column } = getLineAndColumn(
|
|
185
|
+
const { line, column } = getLineAndColumn(
|
|
186
|
+
fileContent,
|
|
187
|
+
matchStartIndex
|
|
188
|
+
);
|
|
124
189
|
let validity = "ok";
|
|
125
190
|
let parameter1 = void 0;
|
|
126
191
|
let parameter2 = void 0;
|
|
@@ -135,7 +200,9 @@ class $LT_TagProcessor {
|
|
|
135
200
|
parameter2 = JSON5.parse(parameter2Text);
|
|
136
201
|
} catch (error) {
|
|
137
202
|
try {
|
|
138
|
-
parameter2 = JSON5.parse(
|
|
203
|
+
parameter2 = JSON5.parse(
|
|
204
|
+
this.escapeNewlinesInStrings(parameter2Text)
|
|
205
|
+
);
|
|
139
206
|
} catch {
|
|
140
207
|
validity = "invalid-param-2";
|
|
141
208
|
}
|
|
@@ -143,7 +210,9 @@ class $LT_TagProcessor {
|
|
|
143
210
|
}
|
|
144
211
|
} catch (error) {
|
|
145
212
|
try {
|
|
146
|
-
parameter1 = JSON5.parse(
|
|
213
|
+
parameter1 = JSON5.parse(
|
|
214
|
+
this.escapeNewlinesInStrings(parameter1Text)
|
|
215
|
+
);
|
|
147
216
|
} catch {
|
|
148
217
|
validity = "invalid-param-1";
|
|
149
218
|
}
|
|
@@ -177,22 +246,31 @@ class $LT_TagProcessor {
|
|
|
177
246
|
}
|
|
178
247
|
const tag = R.tag;
|
|
179
248
|
let newTranslationsString = R.translations;
|
|
180
|
-
if (!newTranslationsString)
|
|
181
|
-
|
|
182
|
-
if (
|
|
249
|
+
if (!newTranslationsString)
|
|
250
|
+
newTranslationsString = this.config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text || "{}";
|
|
251
|
+
else if (typeof newTranslationsString !== "string")
|
|
252
|
+
newTranslationsString = JSON5.stringify(newTranslationsString);
|
|
253
|
+
if (!newTranslationsString)
|
|
254
|
+
throw new Error("Tag must have translations provided!");
|
|
183
255
|
try {
|
|
184
256
|
JSON5.parse(newTranslationsString);
|
|
185
257
|
} catch (error) {
|
|
186
|
-
throw new Error(
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Tag translations are invalid object! Translations: ${newTranslationsString}`
|
|
260
|
+
);
|
|
187
261
|
}
|
|
188
262
|
let newConfigString = R.config;
|
|
189
|
-
if (!newConfigString && newConfigString !== null)
|
|
263
|
+
if (!newConfigString && newConfigString !== null)
|
|
264
|
+
newConfigString = tag.parameterConfig;
|
|
190
265
|
if (newConfigString) {
|
|
191
266
|
try {
|
|
192
|
-
if (typeof newConfigString === "string")
|
|
267
|
+
if (typeof newConfigString === "string")
|
|
268
|
+
JSON5.parse(newConfigString);
|
|
193
269
|
else newConfigString = JSON5.stringify(newConfigString);
|
|
194
270
|
} catch (error) {
|
|
195
|
-
throw new Error(
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Tag config is invalid object! Config: ${newConfigString}`
|
|
273
|
+
);
|
|
196
274
|
}
|
|
197
275
|
}
|
|
198
276
|
if (newConfigString === null && this.config.translationArgPosition === 2) {
|
|
@@ -203,7 +281,8 @@ class $LT_TagProcessor {
|
|
|
203
281
|
let tagFunction = `${this.config.tagName}(${arg1}`;
|
|
204
282
|
if (arg2) tagFunction += `, ${arg2}`;
|
|
205
283
|
tagFunction += ")";
|
|
206
|
-
if (tag.variableName)
|
|
284
|
+
if (tag.variableName)
|
|
285
|
+
replaceMap.set(tag, ` ${tag.variableName} = ${tagFunction}`);
|
|
207
286
|
else replaceMap.set(tag, tagFunction);
|
|
208
287
|
});
|
|
209
288
|
let offset = 0;
|
|
@@ -396,127 +475,397 @@ function getLineAndColumn(text, matchIndex) {
|
|
|
396
475
|
const column = lines[lines.length - 1].length + 1;
|
|
397
476
|
return { line, column };
|
|
398
477
|
}
|
|
399
|
-
function $
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
function $LT_FilterEmptyNamespaceTags(tags, logger) {
|
|
420
|
-
return tags.filter((tag) => {
|
|
421
|
-
if (!tag.parameterConfig) {
|
|
422
|
-
logger.warn('Skipping tag "{fullMatch}". Tag configuration not defined. (Check lang-tag config at collect.onCollectConfigFix)', {
|
|
423
|
-
fullMatch: tag.fullMatch.trim()
|
|
424
|
-
});
|
|
425
|
-
return false;
|
|
478
|
+
async function $LT_CollectCandidateFilesWithTags(props) {
|
|
479
|
+
const { config, logger } = props;
|
|
480
|
+
const processor = new $LT_TagProcessor(config);
|
|
481
|
+
const cwd = process$1.cwd();
|
|
482
|
+
let filesToScan = props.filesToScan;
|
|
483
|
+
if (!filesToScan) {
|
|
484
|
+
filesToScan = await globby.globby(config.includes, {
|
|
485
|
+
cwd,
|
|
486
|
+
ignore: config.excludes,
|
|
487
|
+
absolute: true
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const candidates = [];
|
|
491
|
+
for (const filePath of filesToScan) {
|
|
492
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
493
|
+
let tags = processor.extractTags(fileContent);
|
|
494
|
+
if (!tags.length) {
|
|
495
|
+
continue;
|
|
426
496
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
});
|
|
431
|
-
return false;
|
|
497
|
+
tags = $LT_FilterInvalidTags(tags, config, logger);
|
|
498
|
+
if (!tags.length) {
|
|
499
|
+
continue;
|
|
432
500
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
for (const name of propNames) {
|
|
439
|
-
const value = obj[name];
|
|
440
|
-
if (value && typeof value === "object") {
|
|
441
|
-
deepFreezeObject(value);
|
|
501
|
+
for (let tag of tags) {
|
|
502
|
+
tag.parameterConfig = config.collect.onCollectConfigFix({
|
|
503
|
+
config: tag.parameterConfig,
|
|
504
|
+
langTagConfig: config
|
|
505
|
+
});
|
|
442
506
|
}
|
|
507
|
+
tags = $LT_FilterEmptyNamespaceTags(tags, logger);
|
|
508
|
+
const relativeFilePath = path.relative(cwd, filePath);
|
|
509
|
+
candidates.push({ relativeFilePath, tags });
|
|
443
510
|
}
|
|
444
|
-
return
|
|
511
|
+
return candidates;
|
|
445
512
|
}
|
|
446
|
-
async function
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
let
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
513
|
+
async function $LT_GroupTagsToCollections({
|
|
514
|
+
logger,
|
|
515
|
+
files,
|
|
516
|
+
config
|
|
517
|
+
}) {
|
|
518
|
+
let totalTags = 0;
|
|
519
|
+
const collections = {};
|
|
520
|
+
function getTranslationsCollection(namespace) {
|
|
521
|
+
const collectionName = config.collect.collector.aggregateCollection(namespace);
|
|
522
|
+
const collection = collections[collectionName] || {};
|
|
523
|
+
if (!(collectionName in collections)) {
|
|
524
|
+
collections[collectionName] = collection;
|
|
525
|
+
}
|
|
526
|
+
return collection;
|
|
455
527
|
}
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
for (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
shouldUpdate = true;
|
|
474
|
-
event.isSaved = true;
|
|
475
|
-
event.savedConfig = updatedConfig;
|
|
476
|
-
logger.debug('Called save for "{path}" with config "{config}" triggered by: ("{trigger}")', { path: path$12, config: JSON.stringify(updatedConfig), trigger: triggerName || "-" });
|
|
528
|
+
const allConflicts = [];
|
|
529
|
+
const existingValuesByNamespace = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const file of files) {
|
|
531
|
+
totalTags += file.tags.length;
|
|
532
|
+
for (const _tag of file.tags) {
|
|
533
|
+
const tag = config.collect.collector.transformTag(_tag);
|
|
534
|
+
const tagConfig = tag.parameterConfig;
|
|
535
|
+
const collection = getTranslationsCollection(tagConfig.namespace);
|
|
536
|
+
let existingValues = existingValuesByNamespace.get(
|
|
537
|
+
tagConfig.namespace
|
|
538
|
+
);
|
|
539
|
+
if (!existingValues) {
|
|
540
|
+
existingValues = /* @__PURE__ */ new Map();
|
|
541
|
+
existingValuesByNamespace.set(
|
|
542
|
+
tagConfig.namespace,
|
|
543
|
+
existingValues
|
|
544
|
+
);
|
|
477
545
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
546
|
+
const valueTracker = {
|
|
547
|
+
get: (path2) => existingValues.get(path2),
|
|
548
|
+
trackValue: (path2, value) => {
|
|
549
|
+
existingValues.set(path2, {
|
|
550
|
+
tag,
|
|
551
|
+
relativeFilePath: file.relativeFilePath,
|
|
552
|
+
value
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const addConflict = async (path2, tagA, tagBValue, conflictType) => {
|
|
557
|
+
if (conflictType === "path_overwrite" && config.collect?.ignoreConflictsWithMatchingValues !== false && tagA.value === tagBValue) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const conflict = {
|
|
561
|
+
path: path2,
|
|
562
|
+
tagA,
|
|
563
|
+
tagB: {
|
|
564
|
+
tag,
|
|
565
|
+
relativeFilePath: file.relativeFilePath,
|
|
566
|
+
value: tagBValue
|
|
567
|
+
},
|
|
568
|
+
conflictType
|
|
569
|
+
};
|
|
570
|
+
if (config.collect?.onConflictResolution) {
|
|
571
|
+
let shouldContinue = true;
|
|
572
|
+
await config.collect.onConflictResolution({
|
|
573
|
+
conflict,
|
|
574
|
+
logger,
|
|
575
|
+
exit() {
|
|
576
|
+
shouldContinue = false;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (!shouldContinue) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`LangTagConflictResolution:Processing stopped due to conflict resolution: ${conflict.tagA.tag.parameterConfig.namespace}|${conflict.path}`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
allConflicts.push(conflict);
|
|
586
|
+
};
|
|
587
|
+
const target = await ensureNestedObject(
|
|
588
|
+
tagConfig.path,
|
|
589
|
+
collection,
|
|
590
|
+
valueTracker,
|
|
591
|
+
addConflict
|
|
592
|
+
);
|
|
593
|
+
await mergeWithConflictDetection(
|
|
594
|
+
target,
|
|
595
|
+
tag.parameterTranslations,
|
|
596
|
+
tagConfig.path || "",
|
|
597
|
+
valueTracker,
|
|
598
|
+
addConflict
|
|
599
|
+
);
|
|
486
600
|
}
|
|
487
601
|
}
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
await promises.writeFile(file, newContent, "utf-8");
|
|
491
|
-
const encodedFile = encodeURI(file);
|
|
492
|
-
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path$12, file: encodedFile, line: lastUpdatedLine });
|
|
493
|
-
return true;
|
|
602
|
+
if (allConflicts.length > 0) {
|
|
603
|
+
logger.warn(`Found ${allConflicts.length} conflicts.`);
|
|
494
604
|
}
|
|
495
|
-
|
|
605
|
+
if (config.collect?.onCollectFinish) {
|
|
606
|
+
let shouldContinue = true;
|
|
607
|
+
config.collect.onCollectFinish({
|
|
608
|
+
totalTags,
|
|
609
|
+
namespaces: collections,
|
|
610
|
+
conflicts: allConflicts,
|
|
611
|
+
logger,
|
|
612
|
+
exit() {
|
|
613
|
+
shouldContinue = false;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
if (!shouldContinue) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`LangTagConflictResolution:Processing stopped due to collect finish handler`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return collections;
|
|
496
623
|
}
|
|
497
|
-
function
|
|
498
|
-
if (!
|
|
499
|
-
|
|
500
|
-
|
|
624
|
+
async function ensureNestedObject(path2, rootCollection, valueTracker, addConflict) {
|
|
625
|
+
if (!path2 || !path2.trim()) return rootCollection;
|
|
626
|
+
let current = rootCollection;
|
|
627
|
+
let currentPath = "";
|
|
628
|
+
for (const key of path2.split(".")) {
|
|
629
|
+
currentPath = currentPath ? `${currentPath}.${key}` : key;
|
|
630
|
+
if (current[key] !== void 0 && typeof current[key] !== "object") {
|
|
631
|
+
const existingInfo = valueTracker.get(currentPath);
|
|
632
|
+
if (existingInfo) {
|
|
633
|
+
await addConflict(
|
|
634
|
+
currentPath,
|
|
635
|
+
existingInfo,
|
|
636
|
+
{},
|
|
637
|
+
"type_mismatch"
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return current;
|
|
641
|
+
}
|
|
642
|
+
current[key] = current[key] || {};
|
|
643
|
+
current = current[key];
|
|
644
|
+
}
|
|
645
|
+
return current;
|
|
501
646
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
647
|
+
async function mergeWithConflictDetection(target, source, basePath = "", valueTracker, addConflict) {
|
|
648
|
+
if (typeof target !== "object" || typeof source !== "object") {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
for (const key in source) {
|
|
652
|
+
if (!source.hasOwnProperty(key)) {
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
const currentPath = basePath ? `${basePath}.${key}` : key;
|
|
656
|
+
let targetValue = target[key];
|
|
657
|
+
const sourceValue = source[key];
|
|
658
|
+
if (Array.isArray(sourceValue)) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (targetValue !== void 0) {
|
|
662
|
+
const targetType = typeof targetValue;
|
|
663
|
+
const sourceType = typeof sourceValue;
|
|
664
|
+
let existingInfo = valueTracker.get(currentPath);
|
|
665
|
+
if (!existingInfo && targetType === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
666
|
+
const findNestedInfo = (obj, prefix) => {
|
|
667
|
+
for (const key2 in obj) {
|
|
668
|
+
const path2 = prefix ? `${prefix}.${key2}` : key2;
|
|
669
|
+
const info = valueTracker.get(path2);
|
|
670
|
+
if (info) {
|
|
671
|
+
return info;
|
|
672
|
+
}
|
|
673
|
+
if (typeof obj[key2] === "object" && obj[key2] !== null && !Array.isArray(obj[key2])) {
|
|
674
|
+
const nestedInfo = findNestedInfo(obj[key2], path2);
|
|
675
|
+
if (nestedInfo) {
|
|
676
|
+
return nestedInfo;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return void 0;
|
|
681
|
+
};
|
|
682
|
+
existingInfo = findNestedInfo(targetValue, currentPath);
|
|
683
|
+
}
|
|
684
|
+
if (targetType !== sourceType) {
|
|
685
|
+
if (existingInfo) {
|
|
686
|
+
await addConflict(
|
|
687
|
+
currentPath,
|
|
688
|
+
existingInfo,
|
|
689
|
+
sourceValue,
|
|
690
|
+
"type_mismatch"
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (targetType !== "object") {
|
|
696
|
+
if (existingInfo) {
|
|
697
|
+
await addConflict(
|
|
698
|
+
currentPath,
|
|
699
|
+
existingInfo,
|
|
700
|
+
sourceValue,
|
|
701
|
+
"path_overwrite"
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (targetValue !== sourceValue) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue)) {
|
|
710
|
+
if (!targetValue) {
|
|
711
|
+
targetValue = {};
|
|
712
|
+
target[key] = targetValue;
|
|
713
|
+
}
|
|
714
|
+
await mergeWithConflictDetection(
|
|
715
|
+
targetValue,
|
|
716
|
+
sourceValue,
|
|
717
|
+
currentPath,
|
|
718
|
+
valueTracker,
|
|
719
|
+
addConflict
|
|
720
|
+
);
|
|
721
|
+
} else {
|
|
722
|
+
target[key] = sourceValue;
|
|
723
|
+
valueTracker.trackValue(currentPath, sourceValue);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const CONFIG_FILE_NAME = "lang-tag.config.js";
|
|
728
|
+
const EXPORTS_FILE_NAME = "lang-tags.json";
|
|
729
|
+
async function $LT_EnsureDirectoryExists(filePath) {
|
|
730
|
+
await promises.mkdir(filePath, { recursive: true });
|
|
731
|
+
}
|
|
732
|
+
async function $LT_WriteJSON(filePath, data) {
|
|
733
|
+
await promises.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
734
|
+
}
|
|
735
|
+
async function $LT_ReadJSON(filePath) {
|
|
736
|
+
const content = await promises.readFile(filePath, "utf-8");
|
|
737
|
+
return JSON.parse(content);
|
|
738
|
+
}
|
|
739
|
+
async function $LT_WriteFileWithDirs(filePath, content) {
|
|
740
|
+
const dir = path.dirname(filePath);
|
|
741
|
+
try {
|
|
742
|
+
await promises.mkdir(dir, { recursive: true });
|
|
743
|
+
} catch (error) {
|
|
744
|
+
}
|
|
745
|
+
await promises.writeFile(filePath, content, "utf-8");
|
|
746
|
+
}
|
|
747
|
+
async function $LT_ReadFileContent(relativeFilePath) {
|
|
748
|
+
const cwd = process.cwd();
|
|
749
|
+
const absolutePath = path.resolve(cwd, relativeFilePath);
|
|
750
|
+
return await promises.readFile(absolutePath, "utf-8");
|
|
751
|
+
}
|
|
752
|
+
async function $LT_WriteAsExportFile({
|
|
753
|
+
config,
|
|
754
|
+
logger,
|
|
755
|
+
files
|
|
756
|
+
}) {
|
|
757
|
+
const packageJson = await $LT_ReadJSON(
|
|
758
|
+
path.resolve(process.cwd(), "package.json")
|
|
759
|
+
);
|
|
760
|
+
if (!packageJson) {
|
|
761
|
+
throw new Error("package.json not found");
|
|
762
|
+
}
|
|
763
|
+
const exportData = {
|
|
764
|
+
baseLanguageCode: config.baseLanguageCode,
|
|
765
|
+
files: files.map(({ relativeFilePath, tags }) => ({
|
|
766
|
+
relativeFilePath,
|
|
767
|
+
tags: tags.map((tag) => ({
|
|
768
|
+
variableName: tag.variableName,
|
|
769
|
+
config: tag.parameterConfig,
|
|
770
|
+
translations: tag.parameterTranslations
|
|
771
|
+
}))
|
|
772
|
+
}))
|
|
773
|
+
};
|
|
774
|
+
await promises.writeFile(EXPORTS_FILE_NAME, JSON.stringify(exportData), "utf-8");
|
|
775
|
+
logger.success(`Written {file}`, { file: EXPORTS_FILE_NAME });
|
|
776
|
+
}
|
|
777
|
+
function deepMergeTranslations(target, source) {
|
|
778
|
+
if (typeof target !== "object") {
|
|
779
|
+
throw new Error("Target must be an object");
|
|
780
|
+
}
|
|
781
|
+
if (typeof source !== "object") {
|
|
782
|
+
throw new Error("Source must be an object");
|
|
783
|
+
}
|
|
784
|
+
let changed = false;
|
|
785
|
+
for (const key in source) {
|
|
786
|
+
if (!source.hasOwnProperty(key)) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
let targetValue = target[key];
|
|
790
|
+
const sourceValue = source[key];
|
|
791
|
+
if (typeof targetValue === "string" && typeof sourceValue === "object") {
|
|
792
|
+
throw new Error(
|
|
793
|
+
`Trying to write object into target key "${key}" which is translation already`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
if (Array.isArray(sourceValue)) {
|
|
797
|
+
throw new Error(
|
|
798
|
+
`Trying to write array into target key "${key}", we do not allow arrays inside translations`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (typeof sourceValue === "object") {
|
|
802
|
+
if (!targetValue) {
|
|
803
|
+
targetValue = {};
|
|
804
|
+
target[key] = targetValue;
|
|
805
|
+
}
|
|
806
|
+
if (deepMergeTranslations(targetValue, sourceValue)) {
|
|
807
|
+
changed = true;
|
|
808
|
+
}
|
|
809
|
+
} else {
|
|
810
|
+
if (target[key] !== sourceValue) {
|
|
811
|
+
changed = true;
|
|
812
|
+
}
|
|
813
|
+
target[key] = sourceValue;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return changed;
|
|
817
|
+
}
|
|
818
|
+
async function $LT_WriteToCollections({
|
|
819
|
+
config,
|
|
820
|
+
collections,
|
|
821
|
+
logger,
|
|
822
|
+
clean
|
|
823
|
+
}) {
|
|
824
|
+
await config.collect.collector.preWrite(clean);
|
|
825
|
+
const changedCollections = [];
|
|
826
|
+
for (let collectionName of Object.keys(collections)) {
|
|
827
|
+
if (!collectionName) {
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
const filePath = await config.collect.collector.resolveCollectionFilePath(
|
|
831
|
+
collectionName
|
|
832
|
+
);
|
|
833
|
+
let originalJSON = {};
|
|
834
|
+
try {
|
|
835
|
+
originalJSON = await $LT_ReadJSON(filePath);
|
|
836
|
+
} catch (e) {
|
|
837
|
+
await config.collect.collector.onMissingCollection(
|
|
838
|
+
collectionName
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
if (deepMergeTranslations(originalJSON, collections[collectionName])) {
|
|
842
|
+
changedCollections.push(collectionName);
|
|
843
|
+
await $LT_WriteJSON(filePath, originalJSON);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
await config.collect.collector.postWrite(changedCollections);
|
|
847
|
+
}
|
|
848
|
+
const LANG_TAG_DEFAULT_CONFIG = {
|
|
849
|
+
tagName: "lang",
|
|
850
|
+
isLibrary: false,
|
|
851
|
+
includes: ["src/**/*.{js,ts,jsx,tsx}"],
|
|
852
|
+
excludes: ["node_modules", "dist", "build"],
|
|
853
|
+
localesDirectory: "locales",
|
|
854
|
+
baseLanguageCode: "en",
|
|
855
|
+
collect: {
|
|
856
|
+
collector: new namespaceCollector.NamespaceCollector(),
|
|
513
857
|
defaultNamespace: "common",
|
|
514
858
|
ignoreConflictsWithMatchingValues: true,
|
|
515
859
|
onCollectConfigFix: ({ config, langTagConfig }) => {
|
|
516
860
|
if (langTagConfig.isLibrary) return config;
|
|
517
|
-
if (!config)
|
|
861
|
+
if (!config)
|
|
862
|
+
return {
|
|
863
|
+
path: "",
|
|
864
|
+
namespace: langTagConfig.collect.defaultNamespace
|
|
865
|
+
};
|
|
518
866
|
if (!config.path) config.path = "";
|
|
519
|
-
if (!config.namespace)
|
|
867
|
+
if (!config.namespace)
|
|
868
|
+
config.namespace = langTagConfig.collect.defaultNamespace;
|
|
520
869
|
return config;
|
|
521
870
|
},
|
|
522
871
|
onConflictResolution: async (event) => {
|
|
@@ -529,21 +878,40 @@ const LANG_TAG_DEFAULT_CONFIG = {
|
|
|
529
878
|
import: {
|
|
530
879
|
dir: "src/lang-libraries",
|
|
531
880
|
tagImportPath: 'import { lang } from "@/my-lang-tag-path"',
|
|
532
|
-
onImport:
|
|
533
|
-
|
|
534
|
-
|
|
881
|
+
onImport: (event) => {
|
|
882
|
+
for (let e of event.exports) {
|
|
883
|
+
event.logger.info(
|
|
884
|
+
"Detected lang tag exports at package {packageName}",
|
|
885
|
+
{ packageName: e.packageJSON.name }
|
|
886
|
+
);
|
|
535
887
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
888
|
+
event.logger.warn(
|
|
889
|
+
`
|
|
890
|
+
Import Algorithm Not Configured
|
|
891
|
+
|
|
892
|
+
To import external language tags, you need to configure an import algorithm.
|
|
893
|
+
|
|
894
|
+
Setup Instructions:
|
|
895
|
+
1. Add this import at the top of your configuration file:
|
|
896
|
+
{importStr}
|
|
897
|
+
|
|
898
|
+
2. Replace import.onImport function with:
|
|
899
|
+
{onImport}
|
|
900
|
+
|
|
901
|
+
This will enable import of language tags from external packages.
|
|
902
|
+
`.trim(),
|
|
903
|
+
{
|
|
904
|
+
importStr: "const { flexibleImportAlgorithm } = require('@lang-tag/cli/algorithms');",
|
|
905
|
+
onImport: "onImport: flexibleImportAlgorithm({ filePath: { includePackageInPath: true } })"
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
}
|
|
544
909
|
},
|
|
545
910
|
translationArgPosition: 1,
|
|
546
911
|
onConfigGeneration: async (event) => {
|
|
912
|
+
event.logger.info(
|
|
913
|
+
"Config generation event is not configured. Add onConfigGeneration handler to customize config generation."
|
|
914
|
+
);
|
|
547
915
|
}
|
|
548
916
|
};
|
|
549
917
|
async function $LT_ReadConfig(projectPath) {
|
|
@@ -559,7 +927,9 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
559
927
|
const userConfig = configModule.default || {};
|
|
560
928
|
const tn = (userConfig.tagName || "").toLowerCase().replace(/[-_\s]/g, "");
|
|
561
929
|
if (tn === "langtag" || tn === "lang-tag") {
|
|
562
|
-
throw new Error(
|
|
930
|
+
throw new Error(
|
|
931
|
+
'Custom tagName cannot be "lang-tag" or "langtag"! (It is not recommended for use with libraries)\n'
|
|
932
|
+
);
|
|
563
933
|
}
|
|
564
934
|
const config = {
|
|
565
935
|
...LANG_TAG_DEFAULT_CONFIG,
|
|
@@ -581,6 +951,75 @@ async function $LT_ReadConfig(projectPath) {
|
|
|
581
951
|
throw error;
|
|
582
952
|
}
|
|
583
953
|
}
|
|
954
|
+
const ANSI$1 = {
|
|
955
|
+
reset: "\x1B[0m",
|
|
956
|
+
white: "\x1B[97m",
|
|
957
|
+
brightCyan: "\x1B[96m",
|
|
958
|
+
green: "\x1B[92m",
|
|
959
|
+
gray: "\x1B[90m",
|
|
960
|
+
redBg: "\x1B[41m\x1B[97m",
|
|
961
|
+
bold: "\x1B[1m"
|
|
962
|
+
};
|
|
963
|
+
function colorizeFromAST(code, nodes) {
|
|
964
|
+
const ranges = [];
|
|
965
|
+
for (const node of nodes) {
|
|
966
|
+
const color = getColorForNodeType(node.type);
|
|
967
|
+
const priority = getPriorityForNodeType(node.type);
|
|
968
|
+
ranges.push({
|
|
969
|
+
start: node.start,
|
|
970
|
+
end: node.end,
|
|
971
|
+
color,
|
|
972
|
+
priority
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
ranges.sort((a, b) => {
|
|
976
|
+
if (b.start !== a.start) return b.start - a.start;
|
|
977
|
+
return b.priority - a.priority;
|
|
978
|
+
});
|
|
979
|
+
let colorized = code;
|
|
980
|
+
for (const range of ranges) {
|
|
981
|
+
const before = colorized.substring(0, range.start);
|
|
982
|
+
const text = colorized.substring(range.start, range.end);
|
|
983
|
+
const after = colorized.substring(range.end);
|
|
984
|
+
colorized = before + range.color + text + ANSI$1.reset + after;
|
|
985
|
+
}
|
|
986
|
+
return colorized;
|
|
987
|
+
}
|
|
988
|
+
function getColorForNodeType(type) {
|
|
989
|
+
switch (type) {
|
|
990
|
+
case "key":
|
|
991
|
+
return ANSI$1.brightCyan;
|
|
992
|
+
case "bracket":
|
|
993
|
+
case "colon":
|
|
994
|
+
return ANSI$1.gray;
|
|
995
|
+
case "value":
|
|
996
|
+
return ANSI$1.green;
|
|
997
|
+
case "comment":
|
|
998
|
+
return ANSI$1.gray;
|
|
999
|
+
case "error":
|
|
1000
|
+
return ANSI$1.bold + ANSI$1.redBg;
|
|
1001
|
+
default:
|
|
1002
|
+
return ANSI$1.white;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
function getPriorityForNodeType(type) {
|
|
1006
|
+
switch (type) {
|
|
1007
|
+
case "error":
|
|
1008
|
+
return 3;
|
|
1009
|
+
// Highest priority
|
|
1010
|
+
case "key":
|
|
1011
|
+
return 2;
|
|
1012
|
+
case "value":
|
|
1013
|
+
return 1;
|
|
1014
|
+
case "bracket":
|
|
1015
|
+
case "colon":
|
|
1016
|
+
case "comment":
|
|
1017
|
+
return 0;
|
|
1018
|
+
// Lowest priority
|
|
1019
|
+
default:
|
|
1020
|
+
return 0;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
584
1023
|
function parseObjectAST(code) {
|
|
585
1024
|
const nodes = [];
|
|
586
1025
|
try {
|
|
@@ -674,81 +1113,12 @@ function markConflictNodes(nodes, conflictPath) {
|
|
|
674
1113
|
return node;
|
|
675
1114
|
});
|
|
676
1115
|
}
|
|
677
|
-
const ANSI
|
|
1116
|
+
const ANSI = {
|
|
678
1117
|
reset: "\x1B[0m",
|
|
679
1118
|
white: "\x1B[97m",
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
redBg: "\x1B[41m\x1B[97m",
|
|
684
|
-
bold: "\x1B[1m"
|
|
685
|
-
};
|
|
686
|
-
function colorizeFromAST(code, nodes) {
|
|
687
|
-
const ranges = [];
|
|
688
|
-
for (const node of nodes) {
|
|
689
|
-
const color = getColorForNodeType(node.type);
|
|
690
|
-
const priority = getPriorityForNodeType(node.type);
|
|
691
|
-
ranges.push({
|
|
692
|
-
start: node.start,
|
|
693
|
-
end: node.end,
|
|
694
|
-
color,
|
|
695
|
-
priority
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
ranges.sort((a, b) => {
|
|
699
|
-
if (b.start !== a.start) return b.start - a.start;
|
|
700
|
-
return b.priority - a.priority;
|
|
701
|
-
});
|
|
702
|
-
let colorized = code;
|
|
703
|
-
for (const range of ranges) {
|
|
704
|
-
const before = colorized.substring(0, range.start);
|
|
705
|
-
const text = colorized.substring(range.start, range.end);
|
|
706
|
-
const after = colorized.substring(range.end);
|
|
707
|
-
colorized = before + range.color + text + ANSI$1.reset + after;
|
|
708
|
-
}
|
|
709
|
-
return colorized;
|
|
710
|
-
}
|
|
711
|
-
function getColorForNodeType(type) {
|
|
712
|
-
switch (type) {
|
|
713
|
-
case "key":
|
|
714
|
-
return ANSI$1.brightCyan;
|
|
715
|
-
case "bracket":
|
|
716
|
-
case "colon":
|
|
717
|
-
return ANSI$1.gray;
|
|
718
|
-
case "value":
|
|
719
|
-
return ANSI$1.green;
|
|
720
|
-
case "comment":
|
|
721
|
-
return ANSI$1.gray;
|
|
722
|
-
case "error":
|
|
723
|
-
return ANSI$1.bold + ANSI$1.redBg;
|
|
724
|
-
default:
|
|
725
|
-
return ANSI$1.white;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
function getPriorityForNodeType(type) {
|
|
729
|
-
switch (type) {
|
|
730
|
-
case "error":
|
|
731
|
-
return 3;
|
|
732
|
-
// Highest priority
|
|
733
|
-
case "key":
|
|
734
|
-
return 2;
|
|
735
|
-
case "value":
|
|
736
|
-
return 1;
|
|
737
|
-
case "bracket":
|
|
738
|
-
case "colon":
|
|
739
|
-
case "comment":
|
|
740
|
-
return 0;
|
|
741
|
-
// Lowest priority
|
|
742
|
-
default:
|
|
743
|
-
return 0;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
const ANSI = {
|
|
747
|
-
reset: "\x1B[0m",
|
|
748
|
-
white: "\x1B[97m",
|
|
749
|
-
cyan: "\x1B[96m",
|
|
750
|
-
gray: "\x1B[37m",
|
|
751
|
-
darkGray: "\x1B[90m",
|
|
1119
|
+
cyan: "\x1B[96m",
|
|
1120
|
+
gray: "\x1B[37m",
|
|
1121
|
+
darkGray: "\x1B[90m",
|
|
752
1122
|
bold: "\x1B[1m"
|
|
753
1123
|
};
|
|
754
1124
|
function getVisibleLines(totalLines, errorLines, threshold = 10) {
|
|
@@ -774,18 +1144,24 @@ function printLines(lines, startLineNumber, errorLines = /* @__PURE__ */ new Set
|
|
|
774
1144
|
lines.forEach((line, i) => {
|
|
775
1145
|
const lineNumber = startLineNumber + i;
|
|
776
1146
|
const lineNumStr = String(lineNumber).padStart(3, " ");
|
|
777
|
-
console.log(
|
|
1147
|
+
console.log(
|
|
1148
|
+
`${ANSI.gray}${lineNumStr}${ANSI.reset} ${ANSI.darkGray}│${ANSI.reset} ${line}`
|
|
1149
|
+
);
|
|
778
1150
|
});
|
|
779
1151
|
} else {
|
|
780
1152
|
let lastPrinted = -2;
|
|
781
1153
|
lines.forEach((line, i) => {
|
|
782
1154
|
if (visibleLines.has(i)) {
|
|
783
1155
|
if (i > lastPrinted + 1 && lastPrinted >= 0) {
|
|
784
|
-
console.log(
|
|
1156
|
+
console.log(
|
|
1157
|
+
`${ANSI.gray} -${ANSI.reset} ${ANSI.darkGray}│${ANSI.reset} ${ANSI.gray}...${ANSI.reset}`
|
|
1158
|
+
);
|
|
785
1159
|
}
|
|
786
1160
|
const lineNumber = startLineNumber + i;
|
|
787
1161
|
const lineNumStr = String(lineNumber).padStart(3, " ");
|
|
788
|
-
console.log(
|
|
1162
|
+
console.log(
|
|
1163
|
+
`${ANSI.gray}${lineNumStr}${ANSI.reset} ${ANSI.darkGray}│${ANSI.reset} ${line}`
|
|
1164
|
+
);
|
|
789
1165
|
lastPrinted = i;
|
|
790
1166
|
}
|
|
791
1167
|
});
|
|
@@ -793,7 +1169,7 @@ function printLines(lines, startLineNumber, errorLines = /* @__PURE__ */ new Set
|
|
|
793
1169
|
}
|
|
794
1170
|
async function getLangTagCodeSection(tagInfo) {
|
|
795
1171
|
const { tag, relativeFilePath } = tagInfo;
|
|
796
|
-
const fileContent = await
|
|
1172
|
+
const fileContent = await $LT_ReadFileContent(relativeFilePath);
|
|
797
1173
|
const fileLines = fileContent.split("\n");
|
|
798
1174
|
const startLine = tag.line;
|
|
799
1175
|
const endLine = tag.line + tag.fullMatch.split("\n").length - 1;
|
|
@@ -835,14 +1211,20 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
835
1211
|
const wholeTagCode = await getLangTagCodeSection(tagInfo);
|
|
836
1212
|
const translationTagCode = translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text;
|
|
837
1213
|
const configTagCode = translationArgPosition === 1 ? tag.parameter2Text : tag.parameter1Text;
|
|
838
|
-
const translationErrorPath = stripPrefix(
|
|
1214
|
+
const translationErrorPath = stripPrefix(
|
|
1215
|
+
conflictPath,
|
|
1216
|
+
tag.parameterConfig?.path
|
|
1217
|
+
);
|
|
839
1218
|
let colorizedWhole = wholeTagCode;
|
|
840
1219
|
let errorLines = /* @__PURE__ */ new Set();
|
|
841
1220
|
if (translationTagCode) {
|
|
842
1221
|
try {
|
|
843
1222
|
const translationNodes = parseObjectAST(translationTagCode);
|
|
844
1223
|
const markedTranslationNodes = translationErrorPath ? markConflictNodes(translationNodes, translationErrorPath) : translationNodes;
|
|
845
|
-
const translationErrorLines = getErrorLineNumbers(
|
|
1224
|
+
const translationErrorLines = getErrorLineNumbers(
|
|
1225
|
+
translationTagCode,
|
|
1226
|
+
markedTranslationNodes
|
|
1227
|
+
);
|
|
846
1228
|
const translationStartInWhole = wholeTagCode.indexOf(translationTagCode);
|
|
847
1229
|
if (translationStartInWhole >= 0) {
|
|
848
1230
|
const linesBeforeTranslation = wholeTagCode.substring(0, translationStartInWhole).split("\n").length - 1;
|
|
@@ -850,12 +1232,20 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
850
1232
|
errorLines.add(linesBeforeTranslation + lineNum2);
|
|
851
1233
|
});
|
|
852
1234
|
if (translationErrorLines.size > 0) {
|
|
853
|
-
const lastTranslationErrorLine = Math.max(
|
|
1235
|
+
const lastTranslationErrorLine = Math.max(
|
|
1236
|
+
...Array.from(translationErrorLines)
|
|
1237
|
+
);
|
|
854
1238
|
lineNum = startLine + linesBeforeTranslation + lastTranslationErrorLine;
|
|
855
1239
|
}
|
|
856
1240
|
}
|
|
857
|
-
const colorizedTranslation = colorizeFromAST(
|
|
858
|
-
|
|
1241
|
+
const colorizedTranslation = colorizeFromAST(
|
|
1242
|
+
translationTagCode,
|
|
1243
|
+
markedTranslationNodes
|
|
1244
|
+
);
|
|
1245
|
+
colorizedWhole = colorizedWhole.replace(
|
|
1246
|
+
translationTagCode,
|
|
1247
|
+
colorizedTranslation
|
|
1248
|
+
);
|
|
859
1249
|
} catch (error) {
|
|
860
1250
|
console.error("Failed to colorize translation:", error);
|
|
861
1251
|
}
|
|
@@ -877,7 +1267,10 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
877
1267
|
}
|
|
878
1268
|
return node;
|
|
879
1269
|
});
|
|
880
|
-
const configErrorLines = getErrorLineNumbers(
|
|
1270
|
+
const configErrorLines = getErrorLineNumbers(
|
|
1271
|
+
configTagCode,
|
|
1272
|
+
markedConfigNodes
|
|
1273
|
+
);
|
|
881
1274
|
const configStartInWhole = wholeTagCode.indexOf(configTagCode);
|
|
882
1275
|
if (configStartInWhole >= 0) {
|
|
883
1276
|
const linesBeforeConfig = wholeTagCode.substring(0, configStartInWhole).split("\n").length - 1;
|
|
@@ -885,14 +1278,22 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
885
1278
|
errorLines.add(linesBeforeConfig + lineNum2);
|
|
886
1279
|
});
|
|
887
1280
|
}
|
|
888
|
-
const colorizedConfig = colorizeFromAST(
|
|
889
|
-
|
|
1281
|
+
const colorizedConfig = colorizeFromAST(
|
|
1282
|
+
configTagCode,
|
|
1283
|
+
markedConfigNodes
|
|
1284
|
+
);
|
|
1285
|
+
colorizedWhole = colorizedWhole.replace(
|
|
1286
|
+
configTagCode,
|
|
1287
|
+
colorizedConfig
|
|
1288
|
+
);
|
|
890
1289
|
} catch (error) {
|
|
891
1290
|
console.error("Failed to colorize config:", error);
|
|
892
1291
|
}
|
|
893
1292
|
}
|
|
894
1293
|
const encodedPath = encodeURI(filePath);
|
|
895
|
-
console.log(
|
|
1294
|
+
console.log(
|
|
1295
|
+
`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}file://${encodedPath}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`
|
|
1296
|
+
);
|
|
896
1297
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
897
1298
|
} catch (error) {
|
|
898
1299
|
console.error("Error displaying conflict:", error);
|
|
@@ -900,8 +1301,20 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
900
1301
|
}
|
|
901
1302
|
async function $LT_LogConflict(conflict, translationArgPosition, condense) {
|
|
902
1303
|
const { path: conflictPath, tagA, tagB } = conflict;
|
|
903
|
-
await logTagConflictInfo(
|
|
904
|
-
|
|
1304
|
+
await logTagConflictInfo(
|
|
1305
|
+
tagA,
|
|
1306
|
+
"between",
|
|
1307
|
+
conflictPath,
|
|
1308
|
+
translationArgPosition,
|
|
1309
|
+
condense
|
|
1310
|
+
);
|
|
1311
|
+
await logTagConflictInfo(
|
|
1312
|
+
tagB,
|
|
1313
|
+
"and",
|
|
1314
|
+
conflictPath,
|
|
1315
|
+
translationArgPosition,
|
|
1316
|
+
condense
|
|
1317
|
+
);
|
|
905
1318
|
}
|
|
906
1319
|
const ANSI_COLORS = {
|
|
907
1320
|
reset: "\x1B[0m",
|
|
@@ -915,14 +1328,18 @@ const ANSI_COLORS = {
|
|
|
915
1328
|
cyan: "\x1B[36m"
|
|
916
1329
|
};
|
|
917
1330
|
function validateAndInterpolate(message, params) {
|
|
918
|
-
const placeholders = Array.from(message.matchAll(/\{(\w+)\}/g)).map(
|
|
1331
|
+
const placeholders = Array.from(message.matchAll(/\{(\w+)\}/g)).map(
|
|
1332
|
+
(m) => m[1]
|
|
1333
|
+
);
|
|
919
1334
|
const missing = placeholders.filter((p) => !(p in (params || {})));
|
|
920
1335
|
if (missing.length) {
|
|
921
1336
|
throw new Error(`Missing variables in message: ${missing.join(", ")}`);
|
|
922
1337
|
}
|
|
923
1338
|
const extra = params ? Object.keys(params).filter((k) => !placeholders.includes(k)) : [];
|
|
924
1339
|
if (extra.length) {
|
|
925
|
-
throw new Error(
|
|
1340
|
+
throw new Error(
|
|
1341
|
+
`Extra variables provided not used in message: ${extra.join(", ")}`
|
|
1342
|
+
);
|
|
926
1343
|
}
|
|
927
1344
|
const parts = [];
|
|
928
1345
|
let lastIndex = 0;
|
|
@@ -968,12 +1385,24 @@ function $LT_CreateDefaultLogger(debugMode, translationArgPosition = 1) {
|
|
|
968
1385
|
conflict: async (conflict, condense) => {
|
|
969
1386
|
const { path: path2, conflictType, tagA } = conflict;
|
|
970
1387
|
console.log();
|
|
971
|
-
console.log(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
console.log(
|
|
975
|
-
|
|
976
|
-
|
|
1388
|
+
console.log(
|
|
1389
|
+
`${ANSI_COLORS.bold}${ANSI_COLORS.red}⚠ Translation Conflict Detected${ANSI_COLORS.reset}`
|
|
1390
|
+
);
|
|
1391
|
+
console.log(
|
|
1392
|
+
`${ANSI_COLORS.gray}${"─".repeat(60)}${ANSI_COLORS.reset}`
|
|
1393
|
+
);
|
|
1394
|
+
console.log(
|
|
1395
|
+
` ${ANSI_COLORS.cyan}Conflict Type:${ANSI_COLORS.reset} ${ANSI_COLORS.white}${conflictType}${ANSI_COLORS.reset}`
|
|
1396
|
+
);
|
|
1397
|
+
console.log(
|
|
1398
|
+
` ${ANSI_COLORS.cyan}Translation Key:${ANSI_COLORS.reset} ${ANSI_COLORS.white}${path2}${ANSI_COLORS.reset}`
|
|
1399
|
+
);
|
|
1400
|
+
console.log(
|
|
1401
|
+
` ${ANSI_COLORS.cyan}Namespace:${ANSI_COLORS.reset} ${ANSI_COLORS.white}${tagA.tag.parameterConfig.namespace}${ANSI_COLORS.reset}`
|
|
1402
|
+
);
|
|
1403
|
+
console.log(
|
|
1404
|
+
`${ANSI_COLORS.gray}${"─".repeat(60)}${ANSI_COLORS.reset}`
|
|
1405
|
+
);
|
|
977
1406
|
await $LT_LogConflict(conflict, translationArgPosition, condense);
|
|
978
1407
|
console.log();
|
|
979
1408
|
}
|
|
@@ -981,7 +1410,10 @@ function $LT_CreateDefaultLogger(debugMode, translationArgPosition = 1) {
|
|
|
981
1410
|
}
|
|
982
1411
|
async function $LT_GetCommandEssentials() {
|
|
983
1412
|
const config = await $LT_ReadConfig(process$1.cwd());
|
|
984
|
-
const logger = $LT_CreateDefaultLogger(
|
|
1413
|
+
const logger = $LT_CreateDefaultLogger(
|
|
1414
|
+
config.debug,
|
|
1415
|
+
config.translationArgPosition
|
|
1416
|
+
);
|
|
985
1417
|
config.collect.collector.config = config;
|
|
986
1418
|
config.collect.collector.logger = logger;
|
|
987
1419
|
return {
|
|
@@ -989,615 +1421,191 @@ async function $LT_GetCommandEssentials() {
|
|
|
989
1421
|
logger
|
|
990
1422
|
};
|
|
991
1423
|
}
|
|
992
|
-
async function $
|
|
1424
|
+
async function $LT_CMD_Collect(options) {
|
|
993
1425
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const path2 = file.substring(charactersToSkip);
|
|
1003
|
-
const localDirty = await checkAndRegenerateFileLangTags(config, logger, file, path2);
|
|
1004
|
-
if (localDirty) {
|
|
1005
|
-
dirty = true;
|
|
1426
|
+
logger.info("Collecting translations from source files...");
|
|
1427
|
+
const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
|
|
1428
|
+
if (config.debug) {
|
|
1429
|
+
for (let file of files) {
|
|
1430
|
+
logger.debug("Found {count} translations tags inside: {file}", {
|
|
1431
|
+
count: file.tags.length,
|
|
1432
|
+
file: file.relativeFilePath
|
|
1433
|
+
});
|
|
1006
1434
|
}
|
|
1007
1435
|
}
|
|
1008
|
-
if (
|
|
1009
|
-
|
|
1436
|
+
if (config.isLibrary) {
|
|
1437
|
+
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1438
|
+
return;
|
|
1010
1439
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1440
|
+
try {
|
|
1441
|
+
const collections = await $LT_GroupTagsToCollections({
|
|
1442
|
+
logger,
|
|
1443
|
+
files,
|
|
1444
|
+
config
|
|
1445
|
+
});
|
|
1446
|
+
const totalTags = files.reduce(
|
|
1447
|
+
(sum, file) => sum + file.tags.length,
|
|
1448
|
+
0
|
|
1449
|
+
);
|
|
1450
|
+
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1451
|
+
await $LT_WriteToCollections({
|
|
1452
|
+
config,
|
|
1453
|
+
collections,
|
|
1454
|
+
logger,
|
|
1455
|
+
clean: options?.clean
|
|
1456
|
+
});
|
|
1457
|
+
} catch (e) {
|
|
1458
|
+
const prefix = "LangTagConflictResolution:";
|
|
1459
|
+
if (e.message.startsWith(prefix)) {
|
|
1460
|
+
logger.error(e.message.substring(prefix.length));
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
throw e;
|
|
1015
1464
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1465
|
+
}
|
|
1466
|
+
async function $LT_CollectExportFiles(logger) {
|
|
1467
|
+
const nodeModulesPath = path$1.join(process$1.cwd(), "node_modules");
|
|
1468
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
1469
|
+
logger.error('"node_modules" directory not found');
|
|
1470
|
+
return [];
|
|
1018
1471
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
}
|
|
1029
|
-
if (Array.isArray(sourceValue)) {
|
|
1030
|
-
throw new Error(`Trying to write array into target key "${key}", we do not allow arrays inside translations`);
|
|
1031
|
-
}
|
|
1032
|
-
if (typeof sourceValue === "object") {
|
|
1033
|
-
if (!targetValue) {
|
|
1034
|
-
targetValue = {};
|
|
1035
|
-
target[key] = targetValue;
|
|
1036
|
-
}
|
|
1037
|
-
if (deepMergeTranslations(targetValue, sourceValue)) {
|
|
1038
|
-
changed = true;
|
|
1472
|
+
const results = [];
|
|
1473
|
+
try {
|
|
1474
|
+
const exportFiles = await globby.globby(
|
|
1475
|
+
[`node_modules/**/${EXPORTS_FILE_NAME}`],
|
|
1476
|
+
{
|
|
1477
|
+
cwd: process$1.cwd(),
|
|
1478
|
+
onlyFiles: true,
|
|
1479
|
+
ignore: ["**/node_modules/**/node_modules/**"],
|
|
1480
|
+
deep: 4
|
|
1039
1481
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1482
|
+
);
|
|
1483
|
+
for (const exportFile of exportFiles) {
|
|
1484
|
+
const fullExportPath = path$1.resolve(exportFile);
|
|
1485
|
+
const packageJsonPath = findPackageJsonForExport(
|
|
1486
|
+
fullExportPath,
|
|
1487
|
+
nodeModulesPath
|
|
1488
|
+
);
|
|
1489
|
+
if (packageJsonPath) {
|
|
1490
|
+
results.push({
|
|
1491
|
+
exportPath: fullExportPath,
|
|
1492
|
+
packageJsonPath
|
|
1493
|
+
});
|
|
1494
|
+
} else {
|
|
1495
|
+
logger.warn(
|
|
1496
|
+
"Found export file but could not match package.json: {exportPath}",
|
|
1497
|
+
{
|
|
1498
|
+
exportPath: fullExportPath
|
|
1499
|
+
}
|
|
1500
|
+
);
|
|
1043
1501
|
}
|
|
1044
|
-
target[key] = sourceValue;
|
|
1045
1502
|
}
|
|
1503
|
+
logger.debug(
|
|
1504
|
+
"Found {count} export files with matching package.json in node_modules",
|
|
1505
|
+
{
|
|
1506
|
+
count: results.length
|
|
1507
|
+
}
|
|
1508
|
+
);
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
logger.error("Error scanning node_modules with globby: {error}", {
|
|
1511
|
+
error: String(error)
|
|
1512
|
+
});
|
|
1046
1513
|
}
|
|
1047
|
-
return
|
|
1514
|
+
return results;
|
|
1048
1515
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1516
|
+
function findPackageJsonForExport(exportPath, nodeModulesPath) {
|
|
1517
|
+
const relativePath = path$1.relative(nodeModulesPath, exportPath);
|
|
1518
|
+
const pathParts = relativePath.split(path$1.sep);
|
|
1519
|
+
if (pathParts.length < 2) {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
if (pathParts[0].startsWith("@")) {
|
|
1523
|
+
if (pathParts.length >= 3) {
|
|
1524
|
+
const packageDir = path$1.join(
|
|
1525
|
+
nodeModulesPath,
|
|
1526
|
+
pathParts[0],
|
|
1527
|
+
pathParts[1]
|
|
1528
|
+
);
|
|
1529
|
+
const packageJsonPath = path$1.join(packageDir, "package.json");
|
|
1530
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1531
|
+
return packageJsonPath;
|
|
1532
|
+
}
|
|
1062
1533
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1534
|
+
} else {
|
|
1535
|
+
const packageDir = path$1.join(nodeModulesPath, pathParts[0]);
|
|
1536
|
+
const packageJsonPath = path$1.join(packageDir, "package.json");
|
|
1537
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1538
|
+
return packageJsonPath;
|
|
1066
1539
|
}
|
|
1067
1540
|
}
|
|
1068
|
-
|
|
1541
|
+
return null;
|
|
1069
1542
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1543
|
+
const __filename$1 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
1544
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
1545
|
+
const templatePath = path.join(
|
|
1546
|
+
__dirname$1,
|
|
1547
|
+
"templates",
|
|
1548
|
+
"import",
|
|
1549
|
+
"imported-tag.mustache"
|
|
1550
|
+
);
|
|
1551
|
+
const template = fs.readFileSync(templatePath, "utf-8");
|
|
1552
|
+
function renderTemplate$2(data) {
|
|
1553
|
+
return mustache.render(template, data, {}, { escape: (text) => text });
|
|
1554
|
+
}
|
|
1555
|
+
async function generateImportFiles(config, logger, importManager) {
|
|
1556
|
+
const importedFiles = importManager.getImportedFiles();
|
|
1557
|
+
for (const importedFile of importedFiles) {
|
|
1558
|
+
const filePath = path$1.resolve(
|
|
1559
|
+
process__namespace.cwd(),
|
|
1560
|
+
config.import.dir,
|
|
1561
|
+
importedFile.pathRelativeToImportDir
|
|
1562
|
+
);
|
|
1563
|
+
const processedExports = importedFile.tags.map((tag) => {
|
|
1564
|
+
const parameter1 = config.translationArgPosition === 1 ? tag.translations : tag.config;
|
|
1565
|
+
const parameter2 = config.translationArgPosition === 1 ? tag.config : tag.translations;
|
|
1566
|
+
const hasParameter2 = parameter2 !== null && parameter2 !== void 0 && (typeof parameter2 !== "object" || Object.keys(parameter2).length > 0);
|
|
1567
|
+
return {
|
|
1568
|
+
name: tag.variableName,
|
|
1569
|
+
parameter1: JSON5.stringify(parameter1, void 0, 4),
|
|
1570
|
+
parameter2: hasParameter2 ? JSON5.stringify(parameter2, void 0, 4) : null,
|
|
1571
|
+
hasParameter2,
|
|
1572
|
+
config: {
|
|
1573
|
+
tagName: config.tagName
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
});
|
|
1577
|
+
const templateData = {
|
|
1578
|
+
tagImportPath: config.import.tagImportPath,
|
|
1579
|
+
exports: processedExports
|
|
1580
|
+
};
|
|
1581
|
+
const content = renderTemplate$2(templateData);
|
|
1582
|
+
await $LT_EnsureDirectoryExists(path.dirname(filePath));
|
|
1583
|
+
await promises.writeFile(filePath, content, "utf-8");
|
|
1584
|
+
logger.success('Created tag file: "{file}"', {
|
|
1585
|
+
file: importedFile.pathRelativeToImportDir
|
|
1586
|
+
});
|
|
1077
1587
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1588
|
+
}
|
|
1589
|
+
class ImportManager {
|
|
1590
|
+
importedFiles = [];
|
|
1591
|
+
constructor() {
|
|
1592
|
+
this.importedFiles = [];
|
|
1593
|
+
}
|
|
1594
|
+
importTag(pathRelativeToImportDir, tag) {
|
|
1595
|
+
if (!pathRelativeToImportDir) {
|
|
1596
|
+
throw new Error(
|
|
1597
|
+
`pathRelativeToImportDir required, got: ${pathRelativeToImportDir}`
|
|
1598
|
+
);
|
|
1088
1599
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const relativeFilePath = path.relative(cwd, filePath);
|
|
1094
|
-
candidates.push({ relativeFilePath, tags });
|
|
1095
|
-
}
|
|
1096
|
-
return candidates;
|
|
1097
|
-
}
|
|
1098
|
-
async function $LT_WriteAsExportFile({ config, logger, files }) {
|
|
1099
|
-
const packageJson = await flexibleImportAlgorithm.$LT_ReadJSON(path.resolve(process.cwd(), "package.json"));
|
|
1100
|
-
if (!packageJson) {
|
|
1101
|
-
throw new Error("package.json not found");
|
|
1102
|
-
}
|
|
1103
|
-
const exportData = {
|
|
1104
|
-
baseLanguageCode: config.baseLanguageCode,
|
|
1105
|
-
files: files.map(({ relativeFilePath, tags }) => ({
|
|
1106
|
-
relativeFilePath,
|
|
1107
|
-
tags: tags.map((tag) => ({
|
|
1108
|
-
variableName: tag.variableName,
|
|
1109
|
-
config: tag.parameterConfig,
|
|
1110
|
-
translations: tag.parameterTranslations
|
|
1111
|
-
}))
|
|
1112
|
-
}))
|
|
1113
|
-
};
|
|
1114
|
-
await promises.writeFile(EXPORTS_FILE_NAME, JSON.stringify(exportData), "utf-8");
|
|
1115
|
-
logger.success(`Written {file}`, { file: EXPORTS_FILE_NAME });
|
|
1116
|
-
}
|
|
1117
|
-
async function $LT_GroupTagsToCollections({ logger, files, config }) {
|
|
1118
|
-
let totalTags = 0;
|
|
1119
|
-
const collections = {};
|
|
1120
|
-
function getTranslationsCollection(namespace) {
|
|
1121
|
-
const collectionName = config.collect.collector.aggregateCollection(namespace);
|
|
1122
|
-
const collection = collections[collectionName] || {};
|
|
1123
|
-
if (!(collectionName in collections)) {
|
|
1124
|
-
collections[collectionName] = collection;
|
|
1125
|
-
}
|
|
1126
|
-
return collection;
|
|
1127
|
-
}
|
|
1128
|
-
const allConflicts = [];
|
|
1129
|
-
const existingValuesByNamespace = /* @__PURE__ */ new Map();
|
|
1130
|
-
for (const file of files) {
|
|
1131
|
-
totalTags += file.tags.length;
|
|
1132
|
-
for (const _tag of file.tags) {
|
|
1133
|
-
const tag = config.collect.collector.transformTag(_tag);
|
|
1134
|
-
const tagConfig = tag.parameterConfig;
|
|
1135
|
-
const collection = getTranslationsCollection(tagConfig.namespace);
|
|
1136
|
-
let existingValues = existingValuesByNamespace.get(tagConfig.namespace);
|
|
1137
|
-
if (!existingValues) {
|
|
1138
|
-
existingValues = /* @__PURE__ */ new Map();
|
|
1139
|
-
existingValuesByNamespace.set(tagConfig.namespace, existingValues);
|
|
1140
|
-
}
|
|
1141
|
-
const valueTracker = {
|
|
1142
|
-
get: (path2) => existingValues.get(path2),
|
|
1143
|
-
trackValue: (path2, value) => {
|
|
1144
|
-
existingValues.set(path2, { tag, relativeFilePath: file.relativeFilePath, value });
|
|
1145
|
-
}
|
|
1146
|
-
};
|
|
1147
|
-
const addConflict = async (path2, tagA, tagBValue, conflictType) => {
|
|
1148
|
-
if (conflictType === "path_overwrite" && config.collect?.ignoreConflictsWithMatchingValues !== false && tagA.value === tagBValue) {
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
const conflict = {
|
|
1152
|
-
path: path2,
|
|
1153
|
-
tagA,
|
|
1154
|
-
tagB: {
|
|
1155
|
-
tag,
|
|
1156
|
-
relativeFilePath: file.relativeFilePath,
|
|
1157
|
-
value: tagBValue
|
|
1158
|
-
},
|
|
1159
|
-
conflictType
|
|
1160
|
-
};
|
|
1161
|
-
if (config.collect?.onConflictResolution) {
|
|
1162
|
-
let shouldContinue = true;
|
|
1163
|
-
await config.collect.onConflictResolution({
|
|
1164
|
-
conflict,
|
|
1165
|
-
logger,
|
|
1166
|
-
exit() {
|
|
1167
|
-
shouldContinue = false;
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
if (!shouldContinue) {
|
|
1171
|
-
throw new Error(`LangTagConflictResolution:Processing stopped due to conflict resolution: ${conflict.tagA.tag.parameterConfig.namespace}|${conflict.path}`);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
allConflicts.push(conflict);
|
|
1175
|
-
};
|
|
1176
|
-
const target = await ensureNestedObject(
|
|
1177
|
-
tagConfig.path,
|
|
1178
|
-
collection,
|
|
1179
|
-
valueTracker,
|
|
1180
|
-
addConflict
|
|
1181
|
-
);
|
|
1182
|
-
await mergeWithConflictDetection(
|
|
1183
|
-
target,
|
|
1184
|
-
tag.parameterTranslations,
|
|
1185
|
-
tagConfig.path || "",
|
|
1186
|
-
valueTracker,
|
|
1187
|
-
addConflict
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
if (allConflicts.length > 0) {
|
|
1192
|
-
logger.warn(`Found ${allConflicts.length} conflicts.`);
|
|
1193
|
-
}
|
|
1194
|
-
if (config.collect?.onCollectFinish) {
|
|
1195
|
-
let shouldContinue = true;
|
|
1196
|
-
config.collect.onCollectFinish({
|
|
1197
|
-
totalTags,
|
|
1198
|
-
namespaces: collections,
|
|
1199
|
-
conflicts: allConflicts,
|
|
1200
|
-
logger,
|
|
1201
|
-
exit() {
|
|
1202
|
-
shouldContinue = false;
|
|
1203
|
-
}
|
|
1204
|
-
});
|
|
1205
|
-
if (!shouldContinue) {
|
|
1206
|
-
throw new Error(`LangTagConflictResolution:Processing stopped due to collect finish handler`);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
return collections;
|
|
1210
|
-
}
|
|
1211
|
-
async function ensureNestedObject(path2, rootCollection, valueTracker, addConflict) {
|
|
1212
|
-
if (!path2 || !path2.trim()) return rootCollection;
|
|
1213
|
-
let current = rootCollection;
|
|
1214
|
-
let currentPath = "";
|
|
1215
|
-
for (const key of path2.split(".")) {
|
|
1216
|
-
currentPath = currentPath ? `${currentPath}.${key}` : key;
|
|
1217
|
-
if (current[key] !== void 0 && typeof current[key] !== "object") {
|
|
1218
|
-
const existingInfo = valueTracker.get(currentPath);
|
|
1219
|
-
if (existingInfo) {
|
|
1220
|
-
await addConflict(currentPath, existingInfo, {}, "type_mismatch");
|
|
1221
|
-
}
|
|
1222
|
-
return current;
|
|
1223
|
-
}
|
|
1224
|
-
current[key] = current[key] || {};
|
|
1225
|
-
current = current[key];
|
|
1226
|
-
}
|
|
1227
|
-
return current;
|
|
1228
|
-
}
|
|
1229
|
-
async function mergeWithConflictDetection(target, source, basePath = "", valueTracker, addConflict) {
|
|
1230
|
-
if (typeof target !== "object" || typeof source !== "object") {
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
for (const key in source) {
|
|
1234
|
-
if (!source.hasOwnProperty(key)) {
|
|
1235
|
-
continue;
|
|
1236
|
-
}
|
|
1237
|
-
const currentPath = basePath ? `${basePath}.${key}` : key;
|
|
1238
|
-
let targetValue = target[key];
|
|
1239
|
-
const sourceValue = source[key];
|
|
1240
|
-
if (Array.isArray(sourceValue)) {
|
|
1241
|
-
continue;
|
|
1242
|
-
}
|
|
1243
|
-
if (targetValue !== void 0) {
|
|
1244
|
-
const targetType = typeof targetValue;
|
|
1245
|
-
const sourceType = typeof sourceValue;
|
|
1246
|
-
let existingInfo = valueTracker.get(currentPath);
|
|
1247
|
-
if (!existingInfo && targetType === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
1248
|
-
const findNestedInfo = (obj, prefix) => {
|
|
1249
|
-
for (const key2 in obj) {
|
|
1250
|
-
const path2 = prefix ? `${prefix}.${key2}` : key2;
|
|
1251
|
-
const info = valueTracker.get(path2);
|
|
1252
|
-
if (info) {
|
|
1253
|
-
return info;
|
|
1254
|
-
}
|
|
1255
|
-
if (typeof obj[key2] === "object" && obj[key2] !== null && !Array.isArray(obj[key2])) {
|
|
1256
|
-
const nestedInfo = findNestedInfo(obj[key2], path2);
|
|
1257
|
-
if (nestedInfo) {
|
|
1258
|
-
return nestedInfo;
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
return void 0;
|
|
1263
|
-
};
|
|
1264
|
-
existingInfo = findNestedInfo(targetValue, currentPath);
|
|
1265
|
-
}
|
|
1266
|
-
if (targetType !== sourceType) {
|
|
1267
|
-
if (existingInfo) {
|
|
1268
|
-
await addConflict(currentPath, existingInfo, sourceValue, "type_mismatch");
|
|
1269
|
-
}
|
|
1270
|
-
continue;
|
|
1271
|
-
}
|
|
1272
|
-
if (targetType !== "object") {
|
|
1273
|
-
if (existingInfo) {
|
|
1274
|
-
await addConflict(currentPath, existingInfo, sourceValue, "path_overwrite");
|
|
1275
|
-
}
|
|
1276
|
-
if (targetValue !== sourceValue) {
|
|
1277
|
-
continue;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
if (typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue)) {
|
|
1282
|
-
if (!targetValue) {
|
|
1283
|
-
targetValue = {};
|
|
1284
|
-
target[key] = targetValue;
|
|
1285
|
-
}
|
|
1286
|
-
await mergeWithConflictDetection(
|
|
1287
|
-
targetValue,
|
|
1288
|
-
sourceValue,
|
|
1289
|
-
currentPath,
|
|
1290
|
-
valueTracker,
|
|
1291
|
-
addConflict
|
|
1292
|
-
);
|
|
1293
|
-
} else {
|
|
1294
|
-
target[key] = sourceValue;
|
|
1295
|
-
valueTracker.trackValue(currentPath, sourceValue);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
async function $LT_CMD_Collect(options) {
|
|
1300
|
-
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1301
|
-
logger.info("Collecting translations from source files...");
|
|
1302
|
-
const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
|
|
1303
|
-
if (config.debug) {
|
|
1304
|
-
for (let file of files) {
|
|
1305
|
-
logger.debug("Found {count} translations tags inside: {file}", { count: file.tags.length, file: file.relativeFilePath });
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
if (config.isLibrary) {
|
|
1309
|
-
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
try {
|
|
1313
|
-
const collections = await $LT_GroupTagsToCollections({ logger, files, config });
|
|
1314
|
-
const totalTags = files.reduce((sum, file) => sum + file.tags.length, 0);
|
|
1315
|
-
logger.debug("Found {totalTags} translation tags", { totalTags });
|
|
1316
|
-
await $LT_WriteToCollections({ config, collections, logger, clean: options?.clean });
|
|
1317
|
-
} catch (e) {
|
|
1318
|
-
const prefix = "LangTagConflictResolution:";
|
|
1319
|
-
if (e.message.startsWith(prefix)) {
|
|
1320
|
-
logger.error(e.message.substring(prefix.length));
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
throw e;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
function getBasePath(pattern) {
|
|
1327
|
-
const globStartIndex = pattern.indexOf("*");
|
|
1328
|
-
const braceStartIndex = pattern.indexOf("{");
|
|
1329
|
-
let endIndex = -1;
|
|
1330
|
-
if (globStartIndex !== -1 && braceStartIndex !== -1) {
|
|
1331
|
-
endIndex = Math.min(globStartIndex, braceStartIndex);
|
|
1332
|
-
} else if (globStartIndex !== -1) {
|
|
1333
|
-
endIndex = globStartIndex;
|
|
1334
|
-
} else if (braceStartIndex !== -1) {
|
|
1335
|
-
endIndex = braceStartIndex;
|
|
1336
|
-
}
|
|
1337
|
-
if (endIndex === -1) {
|
|
1338
|
-
const lastSlashIndex = pattern.lastIndexOf("/");
|
|
1339
|
-
return lastSlashIndex !== -1 ? pattern.substring(0, lastSlashIndex) : ".";
|
|
1340
|
-
}
|
|
1341
|
-
const lastSeparatorIndex = pattern.lastIndexOf("/", endIndex);
|
|
1342
|
-
return lastSeparatorIndex === -1 ? "." : pattern.substring(0, lastSeparatorIndex);
|
|
1343
|
-
}
|
|
1344
|
-
function $LT_CreateChokidarWatcher(config) {
|
|
1345
|
-
const cwd = process.cwd();
|
|
1346
|
-
const baseDirsToWatch = [
|
|
1347
|
-
...new Set(config.includes.map((pattern) => getBasePath(pattern)))
|
|
1348
|
-
];
|
|
1349
|
-
const finalDirsToWatch = baseDirsToWatch.map((dir) => dir === "." ? cwd : dir);
|
|
1350
|
-
const ignored = [...config.excludes, "**/.git/**"];
|
|
1351
|
-
return chokidar.watch(finalDirsToWatch, {
|
|
1352
|
-
// Watch base directories
|
|
1353
|
-
cwd,
|
|
1354
|
-
ignored,
|
|
1355
|
-
persistent: true,
|
|
1356
|
-
ignoreInitial: true,
|
|
1357
|
-
awaitWriteFinish: {
|
|
1358
|
-
stabilityThreshold: 300,
|
|
1359
|
-
pollInterval: 100
|
|
1360
|
-
}
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1363
|
-
async function $LT_WatchTranslations() {
|
|
1364
|
-
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1365
|
-
await $LT_CMD_Collect();
|
|
1366
|
-
const watcher = $LT_CreateChokidarWatcher(config);
|
|
1367
|
-
logger.info("Starting watch mode for translations...");
|
|
1368
|
-
logger.info("Watching for changes...");
|
|
1369
|
-
logger.info("Press Ctrl+C to stop watching");
|
|
1370
|
-
watcher.on("change", async (filePath) => await handleFile(config, logger, filePath)).on("add", async (filePath) => await handleFile(config, logger, filePath)).on("error", (error) => {
|
|
1371
|
-
logger.error("Error in file watcher: {error}", { error });
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1374
|
-
async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
1375
|
-
if (!micromatch.isMatch(cwdRelativeFilePath, config.includes)) {
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
const cwd = process.cwd();
|
|
1379
|
-
const absoluteFilePath = path.join(cwd, cwdRelativeFilePath);
|
|
1380
|
-
await checkAndRegenerateFileLangTags(config, logger, absoluteFilePath, cwdRelativeFilePath);
|
|
1381
|
-
const files = await $LT_CollectCandidateFilesWithTags({ filesToScan: [cwdRelativeFilePath], config, logger });
|
|
1382
|
-
const namespaces = await $LT_GroupTagsToCollections({ logger, files, config });
|
|
1383
|
-
await $LT_WriteToCollections({ config, collections: namespaces, logger });
|
|
1384
|
-
}
|
|
1385
|
-
async function detectModuleSystem() {
|
|
1386
|
-
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
1387
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
1388
|
-
return "cjs";
|
|
1389
|
-
}
|
|
1390
|
-
try {
|
|
1391
|
-
const content = await promises.readFile(packageJsonPath, "utf-8");
|
|
1392
|
-
const packageJson = JSON.parse(content);
|
|
1393
|
-
if (packageJson.type === "module") {
|
|
1394
|
-
return "esm";
|
|
1395
|
-
}
|
|
1396
|
-
return "cjs";
|
|
1397
|
-
} catch (error) {
|
|
1398
|
-
return "cjs";
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
async function generateDefaultConfig() {
|
|
1402
|
-
const moduleSystem = await detectModuleSystem();
|
|
1403
|
-
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
|
|
1404
|
-
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1405
|
-
return `${importStatement}
|
|
1406
|
-
|
|
1407
|
-
const generationAlgorithm = pathBasedConfigGenerator({
|
|
1408
|
-
ignoreIncludesRootDirectories: true,
|
|
1409
|
-
removeBracketedDirectories: true,
|
|
1410
|
-
namespaceCase: 'kebab',
|
|
1411
|
-
pathCase: 'camel',
|
|
1412
|
-
clearOnDefaultNamespace: true,
|
|
1413
|
-
ignoreDirectories: ['core', 'utils', 'helpers'],
|
|
1414
|
-
// Advanced: Use pathRules for hierarchical transformations with ignore and rename
|
|
1415
|
-
// pathRules: {
|
|
1416
|
-
// app: {
|
|
1417
|
-
// dashboard: {
|
|
1418
|
-
// _: false, // ignore "dashboard" but continue with nested rules
|
|
1419
|
-
// modules: false // also ignore "modules"
|
|
1420
|
-
// },
|
|
1421
|
-
// admin: {
|
|
1422
|
-
// '>': 'management', // rename "admin" to "management"
|
|
1423
|
-
// users: false // ignore "users",
|
|
1424
|
-
// ui: {
|
|
1425
|
-
// '>>': { // 'redirect' - ignore everything, jump to 'ui' namespace and prefix all paths with 'admin'
|
|
1426
|
-
// namespace: 'ui',
|
|
1427
|
-
// pathPrefix: 'admin'
|
|
1428
|
-
// }
|
|
1429
|
-
// }
|
|
1430
|
-
// }
|
|
1431
|
-
// }
|
|
1432
|
-
// }
|
|
1433
|
-
});
|
|
1434
|
-
const keeper = configKeeper({ propertyName: 'keep' });
|
|
1435
|
-
|
|
1436
|
-
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1437
|
-
const config = {
|
|
1438
|
-
tagName: 'lang',
|
|
1439
|
-
isLibrary: false,
|
|
1440
|
-
includes: ['src/**/*.{js,ts,jsx,tsx}'],
|
|
1441
|
-
excludes: ['node_modules', 'dist', 'build', '**/*.test.ts'],
|
|
1442
|
-
localesDirectory: 'public/locales',
|
|
1443
|
-
baseLanguageCode: 'en',
|
|
1444
|
-
onConfigGeneration: async event => {
|
|
1445
|
-
// We do not modify imported configurations
|
|
1446
|
-
if (event.isImportedLibrary) return;
|
|
1447
|
-
|
|
1448
|
-
if (event.config?.keep === 'both') return;
|
|
1449
|
-
|
|
1450
|
-
await generationAlgorithm(event);
|
|
1451
|
-
|
|
1452
|
-
await keeper(event);
|
|
1453
|
-
},
|
|
1454
|
-
collect: {
|
|
1455
|
-
defaultNamespace: 'common',
|
|
1456
|
-
onConflictResolution: async event => {
|
|
1457
|
-
await event.logger.conflict(event.conflict, true);
|
|
1458
|
-
// By default, continue processing even if conflicts occur
|
|
1459
|
-
// Call event.exit(); to terminate the process upon the first conflict
|
|
1460
|
-
},
|
|
1461
|
-
onCollectFinish: event => {
|
|
1462
|
-
if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
|
|
1463
|
-
}
|
|
1464
|
-
},
|
|
1465
|
-
translationArgPosition: 1,
|
|
1466
|
-
debug: false,
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
${exportStatement}`;
|
|
1470
|
-
}
|
|
1471
|
-
async function $LT_CMD_InitConfig() {
|
|
1472
|
-
const logger = $LT_CreateDefaultLogger();
|
|
1473
|
-
if (fs.existsSync(CONFIG_FILE_NAME)) {
|
|
1474
|
-
logger.success("Configuration file already exists. Please remove the existing configuration file before creating a new default one");
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
try {
|
|
1478
|
-
const configContent = await generateDefaultConfig();
|
|
1479
|
-
await promises.writeFile(CONFIG_FILE_NAME, configContent, "utf-8");
|
|
1480
|
-
logger.success("Configuration file created successfully");
|
|
1481
|
-
} catch (error) {
|
|
1482
|
-
logger.error(error?.message);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
async function $LT_CollectExportFiles(logger) {
|
|
1486
|
-
const nodeModulesPath = path$1.join(process$1.cwd(), "node_modules");
|
|
1487
|
-
if (!fs.existsSync(nodeModulesPath)) {
|
|
1488
|
-
logger.error('"node_modules" directory not found');
|
|
1489
|
-
return [];
|
|
1490
|
-
}
|
|
1491
|
-
const results = [];
|
|
1492
|
-
try {
|
|
1493
|
-
const exportFiles = await globby.globby([
|
|
1494
|
-
`node_modules/**/${EXPORTS_FILE_NAME}`
|
|
1495
|
-
], {
|
|
1496
|
-
cwd: process$1.cwd(),
|
|
1497
|
-
onlyFiles: true,
|
|
1498
|
-
ignore: ["**/node_modules/**/node_modules/**"],
|
|
1499
|
-
deep: 4
|
|
1500
|
-
});
|
|
1501
|
-
for (const exportFile of exportFiles) {
|
|
1502
|
-
const fullExportPath = path$1.resolve(exportFile);
|
|
1503
|
-
const packageJsonPath = findPackageJsonForExport(fullExportPath, nodeModulesPath);
|
|
1504
|
-
if (packageJsonPath) {
|
|
1505
|
-
results.push({
|
|
1506
|
-
exportPath: fullExportPath,
|
|
1507
|
-
packageJsonPath
|
|
1508
|
-
});
|
|
1509
|
-
} else {
|
|
1510
|
-
logger.warn("Found export file but could not match package.json: {exportPath}", {
|
|
1511
|
-
exportPath: fullExportPath
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
logger.debug("Found {count} export files with matching package.json in node_modules", {
|
|
1516
|
-
count: results.length
|
|
1517
|
-
});
|
|
1518
|
-
} catch (error) {
|
|
1519
|
-
logger.error("Error scanning node_modules with globby: {error}", {
|
|
1520
|
-
error: String(error)
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
return results;
|
|
1524
|
-
}
|
|
1525
|
-
function findPackageJsonForExport(exportPath, nodeModulesPath) {
|
|
1526
|
-
const relativePath = path$1.relative(nodeModulesPath, exportPath);
|
|
1527
|
-
const pathParts = relativePath.split(path$1.sep);
|
|
1528
|
-
if (pathParts.length < 2) {
|
|
1529
|
-
return null;
|
|
1530
|
-
}
|
|
1531
|
-
if (pathParts[0].startsWith("@")) {
|
|
1532
|
-
if (pathParts.length >= 3) {
|
|
1533
|
-
const packageDir = path$1.join(nodeModulesPath, pathParts[0], pathParts[1]);
|
|
1534
|
-
const packageJsonPath = path$1.join(packageDir, "package.json");
|
|
1535
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
1536
|
-
return packageJsonPath;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
} else {
|
|
1540
|
-
const packageDir = path$1.join(nodeModulesPath, pathParts[0]);
|
|
1541
|
-
const packageJsonPath = path$1.join(packageDir, "package.json");
|
|
1542
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
1543
|
-
return packageJsonPath;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
return null;
|
|
1547
|
-
}
|
|
1548
|
-
const __filename$1 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
1549
|
-
const __dirname$1 = path.dirname(__filename$1);
|
|
1550
|
-
const templatePath = path.join(__dirname$1, "templates", "import", "imported-tag.mustache");
|
|
1551
|
-
const template = fs.readFileSync(templatePath, "utf-8");
|
|
1552
|
-
function renderTemplate$1(data) {
|
|
1553
|
-
return mustache.render(template, data, {}, { escape: (text) => text });
|
|
1554
|
-
}
|
|
1555
|
-
async function generateImportFiles(config, logger, importManager) {
|
|
1556
|
-
const importedFiles = importManager.getImportedFiles();
|
|
1557
|
-
for (const importedFile of importedFiles) {
|
|
1558
|
-
const filePath = path$1.resolve(
|
|
1559
|
-
process__namespace.cwd(),
|
|
1560
|
-
config.import.dir,
|
|
1561
|
-
importedFile.pathRelativeToImportDir
|
|
1562
|
-
);
|
|
1563
|
-
const processedExports = importedFile.tags.map((tag) => {
|
|
1564
|
-
const parameter1 = config.translationArgPosition === 1 ? tag.translations : tag.config;
|
|
1565
|
-
const parameter2 = config.translationArgPosition === 1 ? tag.config : tag.translations;
|
|
1566
|
-
const hasParameter2 = parameter2 !== null && parameter2 !== void 0 && (typeof parameter2 !== "object" || Object.keys(parameter2).length > 0);
|
|
1567
|
-
return {
|
|
1568
|
-
name: tag.variableName,
|
|
1569
|
-
parameter1: JSON5.stringify(parameter1, void 0, 4),
|
|
1570
|
-
parameter2: hasParameter2 ? JSON5.stringify(parameter2, void 0, 4) : null,
|
|
1571
|
-
hasParameter2,
|
|
1572
|
-
config: {
|
|
1573
|
-
tagName: config.tagName
|
|
1574
|
-
}
|
|
1575
|
-
};
|
|
1576
|
-
});
|
|
1577
|
-
const templateData = {
|
|
1578
|
-
tagImportPath: config.import.tagImportPath,
|
|
1579
|
-
exports: processedExports
|
|
1580
|
-
};
|
|
1581
|
-
const content = renderTemplate$1(templateData);
|
|
1582
|
-
await flexibleImportAlgorithm.$LT_EnsureDirectoryExists(path.dirname(filePath));
|
|
1583
|
-
await promises.writeFile(filePath, content, "utf-8");
|
|
1584
|
-
logger.success('Created tag file: "{file}"', { file: importedFile.pathRelativeToImportDir });
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
class ImportManager {
|
|
1588
|
-
importedFiles = [];
|
|
1589
|
-
constructor() {
|
|
1590
|
-
this.importedFiles = [];
|
|
1591
|
-
}
|
|
1592
|
-
importTag(pathRelativeToImportDir, tag) {
|
|
1593
|
-
if (!pathRelativeToImportDir) {
|
|
1594
|
-
throw new Error(`pathRelativeToImportDir required, got: ${pathRelativeToImportDir}`);
|
|
1595
|
-
}
|
|
1596
|
-
if (!tag?.variableName) {
|
|
1597
|
-
throw new Error(`tag.variableName required, got: ${tag?.variableName}`);
|
|
1600
|
+
if (!tag?.variableName) {
|
|
1601
|
+
throw new Error(
|
|
1602
|
+
`tag.variableName required, got: ${tag?.variableName}`
|
|
1603
|
+
);
|
|
1598
1604
|
}
|
|
1599
1605
|
if (!this.isValidJavaScriptIdentifier(tag.variableName)) {
|
|
1600
|
-
throw new Error(
|
|
1606
|
+
throw new Error(
|
|
1607
|
+
`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.`
|
|
1608
|
+
);
|
|
1601
1609
|
}
|
|
1602
1610
|
if (tag.translations == null) {
|
|
1603
1611
|
throw new Error(`tag.translations required`);
|
|
@@ -1610,7 +1618,9 @@ class ImportManager {
|
|
|
1610
1618
|
(existingTag) => existingTag.variableName === tag.variableName
|
|
1611
1619
|
);
|
|
1612
1620
|
if (duplicateTag) {
|
|
1613
|
-
throw new Error(
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Duplicate variable name "${tag.variableName}" in file "${pathRelativeToImportDir}". Variable names must be unique within the same file.`
|
|
1623
|
+
);
|
|
1614
1624
|
}
|
|
1615
1625
|
}
|
|
1616
1626
|
if (!importedFile) {
|
|
@@ -1637,11 +1647,16 @@ async function $LT_ImportLibraries(config, logger) {
|
|
|
1637
1647
|
const importManager = new ImportManager();
|
|
1638
1648
|
let exports2 = [];
|
|
1639
1649
|
for (const { exportPath, packageJsonPath } of exportFiles) {
|
|
1640
|
-
const exportData = await
|
|
1641
|
-
const packageJSON = await
|
|
1650
|
+
const exportData = await $LT_ReadJSON(exportPath);
|
|
1651
|
+
const packageJSON = await $LT_ReadJSON(packageJsonPath);
|
|
1642
1652
|
exports2.push({ packageJSON, exportData });
|
|
1643
1653
|
}
|
|
1644
|
-
config.import.onImport({
|
|
1654
|
+
config.import.onImport({
|
|
1655
|
+
exports: exports2,
|
|
1656
|
+
importManager,
|
|
1657
|
+
logger,
|
|
1658
|
+
langTagConfig: config
|
|
1659
|
+
});
|
|
1645
1660
|
if (!importManager.hasImportedFiles()) {
|
|
1646
1661
|
logger.warn("No tags were imported from any library files");
|
|
1647
1662
|
return;
|
|
@@ -1651,42 +1666,367 @@ async function $LT_ImportLibraries(config, logger) {
|
|
|
1651
1666
|
}
|
|
1652
1667
|
async function $LT_ImportTranslations() {
|
|
1653
1668
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1654
|
-
await
|
|
1669
|
+
await $LT_EnsureDirectoryExists(config.import.dir);
|
|
1655
1670
|
logger.info("Importing translations from libraries...");
|
|
1656
1671
|
await $LT_ImportLibraries(config, logger);
|
|
1657
1672
|
logger.success("Successfully imported translations from libraries.");
|
|
1658
1673
|
}
|
|
1659
|
-
function
|
|
1660
|
-
|
|
1674
|
+
function parseGitignore(cwd) {
|
|
1675
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
1676
|
+
try {
|
|
1677
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
1678
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter((line) => !line.startsWith("!")).map((line) => {
|
|
1679
|
+
if (line.endsWith("/")) line = line.slice(0, -1);
|
|
1680
|
+
if (line.startsWith("/")) line = line.slice(1);
|
|
1681
|
+
return line;
|
|
1682
|
+
}).filter((line) => {
|
|
1683
|
+
if (line.startsWith("*.")) return false;
|
|
1684
|
+
if (line.includes(".") && line.split(".")[1].length <= 4)
|
|
1685
|
+
return false;
|
|
1686
|
+
return true;
|
|
1687
|
+
});
|
|
1688
|
+
} catch {
|
|
1689
|
+
return [];
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
function isIgnored(entry, ignorePatterns) {
|
|
1693
|
+
if (entry.startsWith(".")) {
|
|
1694
|
+
return true;
|
|
1695
|
+
}
|
|
1696
|
+
if (ignorePatterns.length === 0) {
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
return micromatch.isMatch(entry, ignorePatterns);
|
|
1700
|
+
}
|
|
1701
|
+
function detectProjectDirectories() {
|
|
1702
|
+
const cwd = process.cwd();
|
|
1703
|
+
const ignorePatterns = parseGitignore(cwd);
|
|
1704
|
+
const detectedFolders = [];
|
|
1705
|
+
try {
|
|
1706
|
+
const entries = fs.readdirSync(cwd);
|
|
1707
|
+
for (const entry of entries) {
|
|
1708
|
+
if (isIgnored(entry, ignorePatterns)) {
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
try {
|
|
1712
|
+
const fullPath = path.join(cwd, entry);
|
|
1713
|
+
const stat = fs.statSync(fullPath);
|
|
1714
|
+
if (stat.isDirectory()) {
|
|
1715
|
+
detectedFolders.push(entry);
|
|
1716
|
+
}
|
|
1717
|
+
} catch {
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
} catch {
|
|
1721
|
+
return ["src", "app"];
|
|
1722
|
+
}
|
|
1723
|
+
return detectedFolders.length > 0 ? detectedFolders.sort() : ["src", "app"];
|
|
1724
|
+
}
|
|
1725
|
+
async function askProjectSetupQuestions() {
|
|
1726
|
+
const projectType = await select({
|
|
1727
|
+
message: "Is this a project or a library?",
|
|
1728
|
+
choices: [
|
|
1729
|
+
{
|
|
1730
|
+
name: "Project (application that consumes translations)",
|
|
1731
|
+
value: "project",
|
|
1732
|
+
description: "For applications that use translations"
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
name: "Library (exports translations for other projects)",
|
|
1736
|
+
value: "library",
|
|
1737
|
+
description: "For packages that provide translations"
|
|
1738
|
+
}
|
|
1739
|
+
]
|
|
1740
|
+
});
|
|
1741
|
+
const tagName = await input({
|
|
1742
|
+
message: "What name would you like for your translation tag function?",
|
|
1743
|
+
default: "lang"
|
|
1744
|
+
});
|
|
1745
|
+
let collectorType = "namespace";
|
|
1746
|
+
let namespaceOptions;
|
|
1747
|
+
let localesDirectory = "locales";
|
|
1748
|
+
const modifyNamespaceOptions = false;
|
|
1749
|
+
if (projectType === "project") {
|
|
1750
|
+
collectorType = await select({
|
|
1751
|
+
message: "How would you like to collect translations?",
|
|
1752
|
+
choices: [
|
|
1753
|
+
{
|
|
1754
|
+
name: "Namespace (organized by modules/features)",
|
|
1755
|
+
value: "namespace",
|
|
1756
|
+
description: "Organized structure with namespaces"
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
name: "Dictionary (flat structure, all translations in one file)",
|
|
1760
|
+
value: "dictionary",
|
|
1761
|
+
description: "Simple flat dictionary structure"
|
|
1762
|
+
}
|
|
1763
|
+
]
|
|
1764
|
+
});
|
|
1765
|
+
localesDirectory = await input({
|
|
1766
|
+
message: "Where should the translation files be stored?",
|
|
1767
|
+
default: "public/locales"
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
const defaultNamespace = await input({
|
|
1771
|
+
message: "Default namespace for tags without explicit namespace:",
|
|
1772
|
+
default: "common"
|
|
1773
|
+
});
|
|
1774
|
+
namespaceOptions = {
|
|
1775
|
+
modifyNamespaceOptions,
|
|
1776
|
+
defaultNamespace
|
|
1777
|
+
};
|
|
1778
|
+
const enableConfigGeneration = await confirm({
|
|
1779
|
+
message: "Do you want to script config generation for tags?",
|
|
1780
|
+
default: projectType === "project"
|
|
1781
|
+
});
|
|
1782
|
+
let configGeneration = {
|
|
1783
|
+
enabled: enableConfigGeneration
|
|
1784
|
+
};
|
|
1785
|
+
if (enableConfigGeneration) {
|
|
1786
|
+
const algorithmChoice = await select({
|
|
1787
|
+
message: "Which config generation approach would you like?",
|
|
1788
|
+
choices: [
|
|
1789
|
+
{
|
|
1790
|
+
name: "Path-based (automatic based on file structure)",
|
|
1791
|
+
value: "path-based",
|
|
1792
|
+
description: "Generates namespace and path from file location"
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
name: "Custom (write your own algorithm)",
|
|
1796
|
+
value: "custom",
|
|
1797
|
+
description: "Implement custom config generation logic"
|
|
1798
|
+
}
|
|
1799
|
+
]
|
|
1800
|
+
});
|
|
1801
|
+
const keepVariables = await confirm({
|
|
1802
|
+
message: "Add a keeper mechanism that locks parts of the configuration from being overwritten?",
|
|
1803
|
+
default: true
|
|
1804
|
+
});
|
|
1805
|
+
configGeneration = {
|
|
1806
|
+
enabled: true,
|
|
1807
|
+
useAlgorithm: algorithmChoice,
|
|
1808
|
+
keepVariables
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
const importLibraries = await confirm({
|
|
1812
|
+
message: "Do you plan to import translation tags from external libraries?",
|
|
1813
|
+
default: projectType === "project"
|
|
1814
|
+
});
|
|
1815
|
+
const interfereWithCollection = await confirm({
|
|
1816
|
+
message: "Do you want to interfere with collection mechanisms (conflict resolution, collection finish)?",
|
|
1817
|
+
default: false
|
|
1818
|
+
});
|
|
1819
|
+
const detectedDirectories = detectProjectDirectories();
|
|
1820
|
+
const includeDirectories = await checkbox({
|
|
1821
|
+
message: "Select directories where lang tags will be used (you can add more later):",
|
|
1822
|
+
choices: detectedDirectories.map((directory) => ({
|
|
1823
|
+
name: directory,
|
|
1824
|
+
value: directory,
|
|
1825
|
+
checked: directory === "src" || detectedDirectories.length === 1
|
|
1826
|
+
})),
|
|
1827
|
+
required: true
|
|
1828
|
+
});
|
|
1829
|
+
const baseLanguageCode = await input({
|
|
1830
|
+
message: "Base language code:",
|
|
1831
|
+
default: "en",
|
|
1832
|
+
validate: (value) => {
|
|
1833
|
+
if (!value || value.length < 2) {
|
|
1834
|
+
return "Please enter a valid language code (e.g., en, pl, fr, de, es)";
|
|
1835
|
+
}
|
|
1836
|
+
return true;
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
const addCommentGuides = await confirm({
|
|
1840
|
+
message: "Would you like guides in comments?",
|
|
1841
|
+
default: true
|
|
1842
|
+
});
|
|
1843
|
+
return {
|
|
1844
|
+
projectType,
|
|
1845
|
+
tagName,
|
|
1846
|
+
collectorType,
|
|
1847
|
+
namespaceOptions,
|
|
1848
|
+
localesDirectory,
|
|
1849
|
+
configGeneration,
|
|
1850
|
+
importLibraries,
|
|
1851
|
+
interfereWithCollection,
|
|
1852
|
+
includeDirectories,
|
|
1853
|
+
baseLanguageCode,
|
|
1854
|
+
addCommentGuides
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function renderTemplate$1(template2, data, partials) {
|
|
1858
|
+
return mustache.render(template2, data, partials, {
|
|
1859
|
+
escape: (text) => text
|
|
1860
|
+
});
|
|
1661
1861
|
}
|
|
1662
|
-
function
|
|
1862
|
+
function loadTemplateFile(filename, required = true) {
|
|
1663
1863
|
const __filename2 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename2).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
1664
1864
|
const __dirname = path.dirname(__filename2);
|
|
1665
|
-
|
|
1865
|
+
let templatePath2 = path.join(__dirname, "templates", "config", filename);
|
|
1666
1866
|
try {
|
|
1667
1867
|
return fs.readFileSync(templatePath2, "utf-8");
|
|
1668
|
-
} catch
|
|
1669
|
-
|
|
1868
|
+
} catch {
|
|
1869
|
+
templatePath2 = path.join(
|
|
1870
|
+
__dirname,
|
|
1871
|
+
"..",
|
|
1872
|
+
"..",
|
|
1873
|
+
"templates",
|
|
1874
|
+
"config",
|
|
1875
|
+
filename
|
|
1876
|
+
);
|
|
1877
|
+
try {
|
|
1878
|
+
return fs.readFileSync(templatePath2, "utf-8");
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
if (required) {
|
|
1881
|
+
throw new Error(
|
|
1882
|
+
`Failed to load template "${filename}": ${error}`
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
return null;
|
|
1886
|
+
}
|
|
1670
1887
|
}
|
|
1671
1888
|
}
|
|
1672
|
-
function
|
|
1889
|
+
function loadTemplate$1() {
|
|
1890
|
+
return loadTemplateFile("config.mustache", true);
|
|
1891
|
+
}
|
|
1892
|
+
function loadPartials() {
|
|
1893
|
+
const partials = {};
|
|
1894
|
+
const generationAlgorithm = loadTemplateFile(
|
|
1895
|
+
"generation-algorithm.mustache",
|
|
1896
|
+
false
|
|
1897
|
+
);
|
|
1898
|
+
if (generationAlgorithm) {
|
|
1899
|
+
partials["generation-algorithm"] = generationAlgorithm;
|
|
1900
|
+
}
|
|
1901
|
+
return partials;
|
|
1902
|
+
}
|
|
1903
|
+
function buildIncludesPattern(directories) {
|
|
1904
|
+
return directories.map((directory) => `'${directory}/**/*.{js,ts,jsx,tsx}'`).join(", ");
|
|
1905
|
+
}
|
|
1906
|
+
function buildExcludesPattern() {
|
|
1907
|
+
const excludes = [
|
|
1908
|
+
"node_modules",
|
|
1909
|
+
"dist",
|
|
1910
|
+
"build",
|
|
1911
|
+
"**/*.test.ts",
|
|
1912
|
+
"**/*.spec.ts"
|
|
1913
|
+
];
|
|
1914
|
+
return excludes.map((e) => `'${e}'`).join(", ");
|
|
1915
|
+
}
|
|
1916
|
+
function prepareTemplateData$1(options) {
|
|
1917
|
+
const { answers, moduleSystem } = options;
|
|
1918
|
+
const needsPathBasedImport = answers.configGeneration.enabled && answers.configGeneration.useAlgorithm === "path-based";
|
|
1919
|
+
const hasConfigGeneration = answers.configGeneration.enabled;
|
|
1920
|
+
const usePathBased = hasConfigGeneration && answers.configGeneration.useAlgorithm === "path-based";
|
|
1921
|
+
const useCustom = hasConfigGeneration && answers.configGeneration.useAlgorithm === "custom";
|
|
1922
|
+
const needsTagName = answers.tagName !== "lang";
|
|
1923
|
+
const useKeeper = answers.configGeneration.keepVariables || false;
|
|
1924
|
+
const isDictionaryCollector = answers.collectorType === "dictionary";
|
|
1925
|
+
const isModifiedNamespaceCollector = answers.collectorType === "namespace" && !!answers.namespaceOptions?.modifyNamespaceOptions;
|
|
1926
|
+
const importLibraries = answers.importLibraries;
|
|
1927
|
+
const defaultNamespace = answers.namespaceOptions?.defaultNamespace || "common";
|
|
1928
|
+
const isDefaultNamespace = defaultNamespace === "common";
|
|
1929
|
+
const hasCollectContent = isDictionaryCollector || isModifiedNamespaceCollector || answers.interfereWithCollection || !isDefaultNamespace;
|
|
1930
|
+
const needsAlgorithms = needsPathBasedImport || useKeeper || isDictionaryCollector || isModifiedNamespaceCollector || importLibraries;
|
|
1673
1931
|
return {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1932
|
+
isCJS: moduleSystem === "cjs",
|
|
1933
|
+
addComments: answers.addCommentGuides,
|
|
1934
|
+
needsAlgorithms,
|
|
1935
|
+
needsPathBasedImport,
|
|
1936
|
+
useKeeper,
|
|
1937
|
+
isDictionaryCollector,
|
|
1938
|
+
isModifiedNamespaceCollector,
|
|
1939
|
+
importLibraries,
|
|
1940
|
+
needsTagName,
|
|
1941
|
+
tagName: answers.tagName,
|
|
1942
|
+
isLibrary: answers.projectType === "library",
|
|
1943
|
+
includes: buildIncludesPattern(answers.includeDirectories),
|
|
1944
|
+
excludes: buildExcludesPattern(),
|
|
1945
|
+
localesDirectory: answers.localesDirectory,
|
|
1946
|
+
baseLanguageCode: answers.baseLanguageCode,
|
|
1947
|
+
hasConfigGeneration,
|
|
1948
|
+
usePathBased,
|
|
1949
|
+
useCustom,
|
|
1950
|
+
defaultNamespace,
|
|
1951
|
+
isDefaultNamespace,
|
|
1952
|
+
interfereWithCollection: answers.interfereWithCollection,
|
|
1953
|
+
hasCollectContent
|
|
1680
1954
|
};
|
|
1681
1955
|
}
|
|
1682
|
-
function
|
|
1683
|
-
const
|
|
1684
|
-
const
|
|
1685
|
-
const
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1956
|
+
function renderConfigTemplate(options) {
|
|
1957
|
+
const template2 = loadTemplate$1();
|
|
1958
|
+
const templateData = prepareTemplateData$1(options);
|
|
1959
|
+
const partials = loadPartials();
|
|
1960
|
+
return renderTemplate$1(template2, templateData, partials);
|
|
1961
|
+
}
|
|
1962
|
+
async function detectModuleSystem() {
|
|
1963
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
1964
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
1965
|
+
return "cjs";
|
|
1966
|
+
}
|
|
1967
|
+
try {
|
|
1968
|
+
const content = await promises.readFile(packageJsonPath, "utf-8");
|
|
1969
|
+
const packageJson = JSON.parse(content);
|
|
1970
|
+
if (packageJson.type === "module") {
|
|
1971
|
+
return "esm";
|
|
1972
|
+
}
|
|
1973
|
+
return "cjs";
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
return "cjs";
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
async function $LT_CMD_InitConfig() {
|
|
1979
|
+
const logger = $LT_CreateDefaultLogger();
|
|
1980
|
+
if (fs.existsSync(CONFIG_FILE_NAME)) {
|
|
1981
|
+
logger.error(
|
|
1982
|
+
"Configuration file already exists. Please remove the existing configuration file before creating a new one"
|
|
1983
|
+
);
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
console.log("");
|
|
1987
|
+
logger.info("Welcome to Lang Tag CLI Setup!");
|
|
1988
|
+
console.log("");
|
|
1989
|
+
try {
|
|
1990
|
+
const answers = await askProjectSetupQuestions();
|
|
1991
|
+
const moduleSystem = await detectModuleSystem();
|
|
1992
|
+
const configContent = renderConfigTemplate({
|
|
1993
|
+
answers,
|
|
1994
|
+
moduleSystem
|
|
1995
|
+
});
|
|
1996
|
+
await promises.writeFile(CONFIG_FILE_NAME, configContent, "utf-8");
|
|
1997
|
+
logger.success("Configuration file created successfully!");
|
|
1998
|
+
logger.success("Created {configFile}", {
|
|
1999
|
+
configFile: CONFIG_FILE_NAME
|
|
2000
|
+
});
|
|
2001
|
+
logger.info("Next steps:");
|
|
2002
|
+
logger.info(" 1. Review and customize {configFile}", {
|
|
2003
|
+
configFile: CONFIG_FILE_NAME
|
|
2004
|
+
});
|
|
2005
|
+
logger.info(
|
|
2006
|
+
" 2. Since you have installed all basic libraries (React, TypeScript, etc.)"
|
|
2007
|
+
);
|
|
2008
|
+
logger.info(
|
|
2009
|
+
" and the initialized basic tag is based on what you use in your project,"
|
|
2010
|
+
);
|
|
2011
|
+
logger.info(
|
|
2012
|
+
' we recommend using "npx lang-tag init-tag" to generate an initial version of the tag'
|
|
2013
|
+
);
|
|
2014
|
+
logger.info(
|
|
2015
|
+
" 3. Use your tag in the project under the include directories"
|
|
2016
|
+
);
|
|
2017
|
+
logger.info(' 4. Run "npx lang-tag collect" to collect translations');
|
|
2018
|
+
if (answers.projectType === "project") {
|
|
2019
|
+
logger.info(" 5. Your translations will be in {dir}", {
|
|
2020
|
+
dir: answers.localesDirectory
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
if (error.name === "ExitPromptError") {
|
|
2025
|
+
logger.warn("Setup cancelled");
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
logger.error(error?.message || "An error occurred during setup");
|
|
2029
|
+
}
|
|
1690
2030
|
}
|
|
1691
2031
|
async function readPackageJson() {
|
|
1692
2032
|
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
@@ -1729,51 +2069,312 @@ async function detectInitTagOptions(options, config) {
|
|
|
1729
2069
|
packageVersion: packageJson?.version || "1.0.0"
|
|
1730
2070
|
};
|
|
1731
2071
|
}
|
|
2072
|
+
function renderTemplate(template2, data) {
|
|
2073
|
+
return mustache.render(template2, data, {}, { escape: (text) => text });
|
|
2074
|
+
}
|
|
2075
|
+
function loadTemplate(templateName) {
|
|
2076
|
+
const __filename2 = url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename2).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
|
|
2077
|
+
const __dirname = path.dirname(__filename2);
|
|
2078
|
+
const templatePath2 = path.join(
|
|
2079
|
+
__dirname,
|
|
2080
|
+
"templates",
|
|
2081
|
+
"tag",
|
|
2082
|
+
`${templateName}.mustache`
|
|
2083
|
+
);
|
|
2084
|
+
try {
|
|
2085
|
+
return fs.readFileSync(templatePath2, "utf-8");
|
|
2086
|
+
} catch (error) {
|
|
2087
|
+
throw new Error(`Failed to load template ${templateName}: ${error}`);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
function prepareTemplateData(options) {
|
|
2091
|
+
return {
|
|
2092
|
+
...options,
|
|
2093
|
+
tmpVariables: {
|
|
2094
|
+
key: "{{key}}",
|
|
2095
|
+
username: "{{username}}",
|
|
2096
|
+
processRegex: "{{(.*?)}}"
|
|
2097
|
+
}
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
function renderInitTagTemplates(options) {
|
|
2101
|
+
const baseTemplateName = options.isLibrary ? "base-library" : "base-app";
|
|
2102
|
+
const baseTemplate = loadTemplate(baseTemplateName);
|
|
2103
|
+
const placeholderTemplate = loadTemplate("placeholder");
|
|
2104
|
+
const templateData = prepareTemplateData(options);
|
|
2105
|
+
const renderedBase = renderTemplate(baseTemplate, templateData);
|
|
2106
|
+
const renderedPlaceholders = renderTemplate(
|
|
2107
|
+
placeholderTemplate,
|
|
2108
|
+
templateData
|
|
2109
|
+
);
|
|
2110
|
+
return renderedBase + "\n\n" + renderedPlaceholders;
|
|
2111
|
+
}
|
|
1732
2112
|
async function $LT_CMD_InitTagFile(options = {}) {
|
|
1733
2113
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1734
2114
|
const renderOptions = await detectInitTagOptions(options, config);
|
|
1735
2115
|
const outputPath = options.output || `${renderOptions.tagName}.${renderOptions.fileExtension}`;
|
|
1736
2116
|
logger.info("Initializing lang-tag with the following options:");
|
|
1737
2117
|
logger.info(" Tag name: {tagName}", { tagName: renderOptions.tagName });
|
|
1738
|
-
logger.info(" Library mode: {isLibrary}", {
|
|
1739
|
-
|
|
1740
|
-
|
|
2118
|
+
logger.info(" Library mode: {isLibrary}", {
|
|
2119
|
+
isLibrary: renderOptions.isLibrary ? "Yes" : "No"
|
|
2120
|
+
});
|
|
2121
|
+
logger.info(" React: {isReact}", {
|
|
2122
|
+
isReact: renderOptions.isReact ? "Yes" : "No"
|
|
2123
|
+
});
|
|
2124
|
+
logger.info(" TypeScript: {isTypeScript}", {
|
|
2125
|
+
isTypeScript: renderOptions.isTypeScript ? "Yes" : "No"
|
|
2126
|
+
});
|
|
1741
2127
|
logger.info(" Output path: {outputPath}", { outputPath });
|
|
1742
2128
|
let renderedContent;
|
|
1743
2129
|
try {
|
|
1744
2130
|
renderedContent = renderInitTagTemplates(renderOptions);
|
|
1745
2131
|
} catch (error) {
|
|
1746
|
-
logger.error("Failed to render templates: {error}", {
|
|
2132
|
+
logger.error("Failed to render templates: {error}", {
|
|
2133
|
+
error: error?.message
|
|
2134
|
+
});
|
|
1747
2135
|
return;
|
|
1748
2136
|
}
|
|
1749
2137
|
if (fs.existsSync(outputPath)) {
|
|
1750
2138
|
logger.warn("File already exists: {outputPath}", { outputPath });
|
|
1751
|
-
logger.info(
|
|
2139
|
+
logger.info(
|
|
2140
|
+
"Use --output to specify a different path or remove the existing file"
|
|
2141
|
+
);
|
|
1752
2142
|
return;
|
|
1753
2143
|
}
|
|
1754
2144
|
try {
|
|
1755
|
-
await
|
|
1756
|
-
logger.success("Lang-tag file created successfully: {outputPath}", {
|
|
2145
|
+
await $LT_WriteFileWithDirs(outputPath, renderedContent);
|
|
2146
|
+
logger.success("Lang-tag file created successfully: {outputPath}", {
|
|
2147
|
+
outputPath
|
|
2148
|
+
});
|
|
1757
2149
|
logger.info("Next steps:");
|
|
1758
|
-
logger.info("1. Import the {tagName} function in your files:", {
|
|
2150
|
+
logger.info("1. Import the {tagName} function in your files:", {
|
|
2151
|
+
tagName: renderOptions.tagName
|
|
2152
|
+
});
|
|
1759
2153
|
logger.info(" import { {tagName} } from './{importPath}';", {
|
|
1760
2154
|
tagName: renderOptions.tagName,
|
|
1761
2155
|
importPath: outputPath.replace(/^src\//, "")
|
|
1762
2156
|
});
|
|
1763
|
-
logger.info(
|
|
2157
|
+
logger.info(
|
|
2158
|
+
"2. Create your translation objects and use the tag function"
|
|
2159
|
+
);
|
|
1764
2160
|
logger.info('3. Run "lang-tag collect" to extract translations');
|
|
1765
2161
|
} catch (error) {
|
|
1766
|
-
logger.error("Failed to write file: {error}", {
|
|
2162
|
+
logger.error("Failed to write file: {error}", {
|
|
2163
|
+
error: error?.message
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
function deepFreezeObject(obj) {
|
|
2168
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
2169
|
+
for (const name of propNames) {
|
|
2170
|
+
const value = obj[name];
|
|
2171
|
+
if (value && typeof value === "object") {
|
|
2172
|
+
deepFreezeObject(value);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return Object.freeze(obj);
|
|
2176
|
+
}
|
|
2177
|
+
async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
2178
|
+
let libraryImportsDir = config.import.dir;
|
|
2179
|
+
if (!libraryImportsDir.endsWith(path.sep)) libraryImportsDir += path.sep;
|
|
2180
|
+
const fileContent = fs.readFileSync(file, "utf-8");
|
|
2181
|
+
const processor = new $LT_TagProcessor(config);
|
|
2182
|
+
let tags = processor.extractTags(fileContent);
|
|
2183
|
+
tags = $LT_FilterInvalidTags(tags, config, logger);
|
|
2184
|
+
if (!tags.length) {
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
const replacements = [];
|
|
2188
|
+
let lastUpdatedLine = 0;
|
|
2189
|
+
for (let tag of tags) {
|
|
2190
|
+
let newConfig = void 0;
|
|
2191
|
+
let shouldUpdate = false;
|
|
2192
|
+
const frozenConfig = tag.parameterConfig ? deepFreezeObject(tag.parameterConfig) : tag.parameterConfig;
|
|
2193
|
+
const event = {
|
|
2194
|
+
langTagConfig: config,
|
|
2195
|
+
logger,
|
|
2196
|
+
config: frozenConfig,
|
|
2197
|
+
absolutePath: file,
|
|
2198
|
+
relativePath: path$12,
|
|
2199
|
+
isImportedLibrary: path$12.startsWith(libraryImportsDir),
|
|
2200
|
+
isSaved: false,
|
|
2201
|
+
savedConfig: void 0,
|
|
2202
|
+
save: (updatedConfig, triggerName) => {
|
|
2203
|
+
if (!updatedConfig && updatedConfig !== null)
|
|
2204
|
+
throw new Error("Wrong config data");
|
|
2205
|
+
newConfig = updatedConfig;
|
|
2206
|
+
shouldUpdate = true;
|
|
2207
|
+
event.isSaved = true;
|
|
2208
|
+
event.savedConfig = updatedConfig;
|
|
2209
|
+
logger.debug(
|
|
2210
|
+
'Called save for "{path}" with config "{config}" triggered by: ("{trigger}")',
|
|
2211
|
+
{
|
|
2212
|
+
path: path$12,
|
|
2213
|
+
config: JSON.stringify(updatedConfig),
|
|
2214
|
+
trigger: triggerName || "-"
|
|
2215
|
+
}
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
await config.onConfigGeneration(event);
|
|
2220
|
+
if (!shouldUpdate) {
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
lastUpdatedLine = tag.line;
|
|
2224
|
+
if (!isConfigSame(tag.parameterConfig, newConfig)) {
|
|
2225
|
+
replacements.push({ tag, config: newConfig });
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (replacements.length) {
|
|
2229
|
+
const newContent = processor.replaceTags(fileContent, replacements);
|
|
2230
|
+
await promises.writeFile(file, newContent, "utf-8");
|
|
2231
|
+
const encodedFile = encodeURI(file);
|
|
2232
|
+
logger.info(
|
|
2233
|
+
'Lang tag configurations written for file "{path}" (file://{file}:{line})',
|
|
2234
|
+
{ path: path$12, file: encodedFile, line: lastUpdatedLine }
|
|
2235
|
+
);
|
|
2236
|
+
return true;
|
|
2237
|
+
}
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
function isConfigSame(c1, c2) {
|
|
2241
|
+
if (!c1 && !c2) return true;
|
|
2242
|
+
if (c1 && typeof c1 === "object" && c2 && typeof c2 === "object" && JSON5.stringify(c1) === JSON5.stringify(c2))
|
|
2243
|
+
return true;
|
|
2244
|
+
return false;
|
|
2245
|
+
}
|
|
2246
|
+
async function $LT_CMD_RegenerateTags() {
|
|
2247
|
+
const { config, logger } = await $LT_GetCommandEssentials();
|
|
2248
|
+
const files = await globby.globby(config.includes, {
|
|
2249
|
+
cwd: process.cwd(),
|
|
2250
|
+
ignore: config.excludes,
|
|
2251
|
+
absolute: true
|
|
2252
|
+
});
|
|
2253
|
+
const charactersToSkip = process.cwd().length + 1;
|
|
2254
|
+
let dirty = false;
|
|
2255
|
+
for (const file of files) {
|
|
2256
|
+
const path2 = file.substring(charactersToSkip);
|
|
2257
|
+
const localDirty = await checkAndRegenerateFileLangTags(
|
|
2258
|
+
config,
|
|
2259
|
+
logger,
|
|
2260
|
+
file,
|
|
2261
|
+
path2
|
|
2262
|
+
);
|
|
2263
|
+
if (localDirty) {
|
|
2264
|
+
dirty = true;
|
|
2265
|
+
}
|
|
1767
2266
|
}
|
|
2267
|
+
if (!dirty) {
|
|
2268
|
+
logger.info(
|
|
2269
|
+
"No changes were made based on the current configuration and files"
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
function getBasePath(pattern) {
|
|
2274
|
+
const globStartIndex = pattern.indexOf("*");
|
|
2275
|
+
const braceStartIndex = pattern.indexOf("{");
|
|
2276
|
+
let endIndex = -1;
|
|
2277
|
+
if (globStartIndex !== -1 && braceStartIndex !== -1) {
|
|
2278
|
+
endIndex = Math.min(globStartIndex, braceStartIndex);
|
|
2279
|
+
} else if (globStartIndex !== -1) {
|
|
2280
|
+
endIndex = globStartIndex;
|
|
2281
|
+
} else if (braceStartIndex !== -1) {
|
|
2282
|
+
endIndex = braceStartIndex;
|
|
2283
|
+
}
|
|
2284
|
+
if (endIndex === -1) {
|
|
2285
|
+
const lastSlashIndex = pattern.lastIndexOf("/");
|
|
2286
|
+
return lastSlashIndex !== -1 ? pattern.substring(0, lastSlashIndex) : ".";
|
|
2287
|
+
}
|
|
2288
|
+
const lastSeparatorIndex = pattern.lastIndexOf("/", endIndex);
|
|
2289
|
+
return lastSeparatorIndex === -1 ? "." : pattern.substring(0, lastSeparatorIndex);
|
|
2290
|
+
}
|
|
2291
|
+
function $LT_CreateChokidarWatcher(config) {
|
|
2292
|
+
const cwd = process.cwd();
|
|
2293
|
+
const baseDirsToWatch = [
|
|
2294
|
+
...new Set(config.includes.map((pattern) => getBasePath(pattern)))
|
|
2295
|
+
];
|
|
2296
|
+
const finalDirsToWatch = baseDirsToWatch.map(
|
|
2297
|
+
(dir) => dir === "." ? cwd : dir
|
|
2298
|
+
);
|
|
2299
|
+
const ignored = [...config.excludes, "**/.git/**"];
|
|
2300
|
+
return chokidar.watch(finalDirsToWatch, {
|
|
2301
|
+
// Watch base directories
|
|
2302
|
+
cwd,
|
|
2303
|
+
ignored,
|
|
2304
|
+
persistent: true,
|
|
2305
|
+
ignoreInitial: true,
|
|
2306
|
+
awaitWriteFinish: {
|
|
2307
|
+
stabilityThreshold: 300,
|
|
2308
|
+
pollInterval: 100
|
|
2309
|
+
}
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
async function $LT_WatchTranslations() {
|
|
2313
|
+
const { config, logger } = await $LT_GetCommandEssentials();
|
|
2314
|
+
await $LT_CMD_Collect();
|
|
2315
|
+
const watcher = $LT_CreateChokidarWatcher(config);
|
|
2316
|
+
logger.info("Starting watch mode for translations...");
|
|
2317
|
+
logger.info("Watching for changes...");
|
|
2318
|
+
logger.info("Press Ctrl+C to stop watching");
|
|
2319
|
+
watcher.on(
|
|
2320
|
+
"change",
|
|
2321
|
+
async (filePath) => await handleFile(config, logger, filePath)
|
|
2322
|
+
).on(
|
|
2323
|
+
"add",
|
|
2324
|
+
async (filePath) => await handleFile(config, logger, filePath)
|
|
2325
|
+
).on("error", (error) => {
|
|
2326
|
+
logger.error("Error in file watcher: {error}", { error });
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
2330
|
+
if (!micromatch.isMatch(cwdRelativeFilePath, config.includes)) {
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
const cwd = process.cwd();
|
|
2334
|
+
const absoluteFilePath = path.join(cwd, cwdRelativeFilePath);
|
|
2335
|
+
await checkAndRegenerateFileLangTags(
|
|
2336
|
+
config,
|
|
2337
|
+
logger,
|
|
2338
|
+
absoluteFilePath,
|
|
2339
|
+
cwdRelativeFilePath
|
|
2340
|
+
);
|
|
2341
|
+
const files = await $LT_CollectCandidateFilesWithTags({
|
|
2342
|
+
filesToScan: [cwdRelativeFilePath],
|
|
2343
|
+
config,
|
|
2344
|
+
logger
|
|
2345
|
+
});
|
|
2346
|
+
const namespaces = await $LT_GroupTagsToCollections({
|
|
2347
|
+
logger,
|
|
2348
|
+
files,
|
|
2349
|
+
config
|
|
2350
|
+
});
|
|
2351
|
+
await $LT_WriteToCollections({ config, collections: namespaces, logger });
|
|
1768
2352
|
}
|
|
1769
2353
|
function createCli() {
|
|
1770
2354
|
commander.program.name("lang-tag").description("CLI to manage language translations").version("0.1.0");
|
|
1771
2355
|
commander.program.command("collect").alias("c").description("Collect translations from source files").option("-c, --clean", "Remove output directory before collecting").action($LT_CMD_Collect);
|
|
1772
2356
|
commander.program.command("import").alias("i").description("Import translations from libraries in node_modules").action($LT_ImportTranslations);
|
|
1773
2357
|
commander.program.command("regenerate-tags").alias("rt").description("Regenerate configuration for language tags").action($LT_CMD_RegenerateTags);
|
|
1774
|
-
commander.program.command("watch").alias("w").description(
|
|
2358
|
+
commander.program.command("watch").alias("w").description(
|
|
2359
|
+
"Watch for changes in source files and automatically collect translations"
|
|
2360
|
+
).action($LT_WatchTranslations);
|
|
1775
2361
|
commander.program.command("init").description("Initialize project with default configuration").action($LT_CMD_InitConfig);
|
|
1776
|
-
commander.program.command("init-tag").description("Initialize a new lang-tag function file").option(
|
|
2362
|
+
commander.program.command("init-tag").description("Initialize a new lang-tag function file").option(
|
|
2363
|
+
"-n, --name <name>",
|
|
2364
|
+
"Name of the tag function (default: from config)"
|
|
2365
|
+
).option(
|
|
2366
|
+
"-l, --library",
|
|
2367
|
+
"Generate library-style tag (default: from config)"
|
|
2368
|
+
).option(
|
|
2369
|
+
"-r, --react",
|
|
2370
|
+
"Include React-specific optimizations (default: auto-detect)"
|
|
2371
|
+
).option(
|
|
2372
|
+
"-t, --typescript",
|
|
2373
|
+
"Force TypeScript output (default: auto-detect)"
|
|
2374
|
+
).option(
|
|
2375
|
+
"-o, --output <path>",
|
|
2376
|
+
"Output file path (default: auto-generated)"
|
|
2377
|
+
).action(async (options) => {
|
|
1777
2378
|
await $LT_CMD_InitTagFile(options);
|
|
1778
2379
|
});
|
|
1779
2380
|
return commander.program;
|