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