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