@lang-tag/cli 0.12.3 → 0.13.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 +1 -0
- package/algorithms/config-generation/path-based-config-generator.d.ts +28 -1
- package/algorithms/index.cjs +62 -1
- package/algorithms/index.js +62 -1
- package/index.cjs +18 -3
- package/index.js +18 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Lang-tag CLI
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
A professional solution for managing translations in modern JavaScript/TypeScript projects, especially those using component-based architectures. `lang-tag` simplifies internationalization by allowing you to define translation keys directly within the components where they are used. Translations become local, callable function objects with full TypeScript support, IntelliSense, and compile-time safety.
|
|
4
5
|
|
|
5
6
|
## Key Benefits
|
|
@@ -47,16 +47,43 @@ export interface PathBasedConfigGeneratorOptions {
|
|
|
47
47
|
/**
|
|
48
48
|
* Hierarchical structure for ignoring specific directory patterns.
|
|
49
49
|
* Keys represent path segments to match, values indicate what to ignore at that level.
|
|
50
|
+
* Supports special key `_` when set to `true` to ignore current segment but continue hierarchy.
|
|
50
51
|
*
|
|
51
52
|
* @example
|
|
52
53
|
* {
|
|
53
54
|
* 'src': {
|
|
54
55
|
* 'app': true, // ignore 'app' when under 'src'
|
|
55
|
-
* 'features': ['auth', 'admin'] // ignore 'auth' and 'admin' under 'src/features'
|
|
56
|
+
* 'features': ['auth', 'admin'], // ignore 'auth' and 'admin' under 'src/features'
|
|
57
|
+
* 'dashboard': {
|
|
58
|
+
* _: true, // ignore 'dashboard' but continue with nested rules
|
|
59
|
+
* modules: true // also ignore 'modules' under 'dashboard'
|
|
60
|
+
* }
|
|
56
61
|
* }
|
|
57
62
|
* }
|
|
58
63
|
*/
|
|
59
64
|
ignoreStructured?: Record<string, any>;
|
|
65
|
+
/**
|
|
66
|
+
* Advanced hierarchical rules for transforming path segments.
|
|
67
|
+
* Supports ignoring and renaming segments with special keys:
|
|
68
|
+
* - `_`: when `false`, ignores the current segment but continues hierarchy
|
|
69
|
+
* - `>`: renames the current segment to the specified value
|
|
70
|
+
* - Regular keys: nested rules or boolean/string for child segments
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* {
|
|
74
|
+
* app: {
|
|
75
|
+
* dashboard: {
|
|
76
|
+
* _: false, // ignore "dashboard" segment
|
|
77
|
+
* modules: false // ignore "modules" when under "dashboard"
|
|
78
|
+
* },
|
|
79
|
+
* admin: {
|
|
80
|
+
* '>': 'management', // rename "admin" to "management"
|
|
81
|
+
* users: true // keep "users" as is (does nothing)
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
*/
|
|
86
|
+
pathRules?: Record<string, any>;
|
|
60
87
|
/**
|
|
61
88
|
* Convert the final namespace to lowercase.
|
|
62
89
|
* @default false
|
package/algorithms/index.cjs
CHANGED
|
@@ -27,12 +27,20 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
27
27
|
ignoreDirectories = [],
|
|
28
28
|
ignoreIncludesRootDirectories = false,
|
|
29
29
|
ignoreStructured = {},
|
|
30
|
+
pathRules = {},
|
|
30
31
|
lowercaseNamespace = false,
|
|
31
32
|
namespaceCase,
|
|
32
33
|
pathCase,
|
|
33
34
|
fallbackNamespace,
|
|
34
35
|
clearOnDefaultNamespace = true
|
|
35
36
|
} = options;
|
|
37
|
+
const hasPathRules = Object.keys(pathRules).length > 0;
|
|
38
|
+
const hasIgnoreStructured = Object.keys(ignoreStructured).length > 0;
|
|
39
|
+
if (hasPathRules && hasIgnoreStructured) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'pathBasedConfigGenerator: Cannot use both "pathRules" and "ignoreStructured" options simultaneously. Please use "pathRules" (recommended) or "ignoreStructured" (legacy), but not both.'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
36
44
|
return async (event) => {
|
|
37
45
|
const { relativePath, langTagConfig } = event;
|
|
38
46
|
const actualFallbackNamespace = fallbackNamespace ?? langTagConfig.collect?.defaultNamespace;
|
|
@@ -54,7 +62,11 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
54
62
|
}
|
|
55
63
|
return segment;
|
|
56
64
|
}).filter((seg) => seg !== null);
|
|
57
|
-
|
|
65
|
+
if (hasPathRules) {
|
|
66
|
+
pathSegments = applyPathRules(pathSegments, pathRules);
|
|
67
|
+
} else {
|
|
68
|
+
pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
|
|
69
|
+
}
|
|
58
70
|
if (ignoreIncludesRootDirectories && langTagConfig.includes && pathSegments.length > 0) {
|
|
59
71
|
const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
|
|
60
72
|
if (extractedDirectories.includes(pathSegments[0])) {
|
|
@@ -135,7 +147,56 @@ function applyStructuredIgnore(segments, structure) {
|
|
|
135
147
|
currentStructure = structure;
|
|
136
148
|
continue;
|
|
137
149
|
} else if (typeof rule === "object" && rule !== null) {
|
|
150
|
+
const ignoreSelf = rule["_"] === true;
|
|
151
|
+
if (ignoreSelf) {
|
|
152
|
+
currentStructure = rule;
|
|
153
|
+
continue;
|
|
154
|
+
} else {
|
|
155
|
+
result.push(segment);
|
|
156
|
+
currentStructure = rule;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
result.push(segment);
|
|
162
|
+
currentStructure = structure;
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
function applyPathRules(segments, structure) {
|
|
167
|
+
const result = [];
|
|
168
|
+
let currentStructure = structure;
|
|
169
|
+
for (let i = 0; i < segments.length; i++) {
|
|
170
|
+
const segment = segments[i];
|
|
171
|
+
if (segment in currentStructure) {
|
|
172
|
+
const rule = currentStructure[segment];
|
|
173
|
+
if (rule === true) {
|
|
174
|
+
currentStructure = structure;
|
|
175
|
+
continue;
|
|
176
|
+
} else if (rule === false) {
|
|
177
|
+
currentStructure = structure;
|
|
178
|
+
continue;
|
|
179
|
+
} else if (typeof rule === "string") {
|
|
180
|
+
result.push(rule);
|
|
181
|
+
currentStructure = structure;
|
|
182
|
+
continue;
|
|
183
|
+
} else if (Array.isArray(rule)) {
|
|
138
184
|
result.push(segment);
|
|
185
|
+
if (i + 1 < segments.length && rule.includes(segments[i + 1])) {
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
currentStructure = structure;
|
|
189
|
+
continue;
|
|
190
|
+
} else if (typeof rule === "object" && rule !== null) {
|
|
191
|
+
const ignoreSelf = rule["_"] === false;
|
|
192
|
+
const renameTo = rule[">"];
|
|
193
|
+
if (!ignoreSelf) {
|
|
194
|
+
if (typeof renameTo === "string") {
|
|
195
|
+
result.push(renameTo);
|
|
196
|
+
} else {
|
|
197
|
+
result.push(segment);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
139
200
|
currentStructure = rule;
|
|
140
201
|
continue;
|
|
141
202
|
}
|
package/algorithms/index.js
CHANGED
|
@@ -8,12 +8,20 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
8
8
|
ignoreDirectories = [],
|
|
9
9
|
ignoreIncludesRootDirectories = false,
|
|
10
10
|
ignoreStructured = {},
|
|
11
|
+
pathRules = {},
|
|
11
12
|
lowercaseNamespace = false,
|
|
12
13
|
namespaceCase,
|
|
13
14
|
pathCase,
|
|
14
15
|
fallbackNamespace,
|
|
15
16
|
clearOnDefaultNamespace = true
|
|
16
17
|
} = options;
|
|
18
|
+
const hasPathRules = Object.keys(pathRules).length > 0;
|
|
19
|
+
const hasIgnoreStructured = Object.keys(ignoreStructured).length > 0;
|
|
20
|
+
if (hasPathRules && hasIgnoreStructured) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'pathBasedConfigGenerator: Cannot use both "pathRules" and "ignoreStructured" options simultaneously. Please use "pathRules" (recommended) or "ignoreStructured" (legacy), but not both.'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
17
25
|
return async (event) => {
|
|
18
26
|
const { relativePath, langTagConfig } = event;
|
|
19
27
|
const actualFallbackNamespace = fallbackNamespace ?? langTagConfig.collect?.defaultNamespace;
|
|
@@ -35,7 +43,11 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
35
43
|
}
|
|
36
44
|
return segment;
|
|
37
45
|
}).filter((seg) => seg !== null);
|
|
38
|
-
|
|
46
|
+
if (hasPathRules) {
|
|
47
|
+
pathSegments = applyPathRules(pathSegments, pathRules);
|
|
48
|
+
} else {
|
|
49
|
+
pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
|
|
50
|
+
}
|
|
39
51
|
if (ignoreIncludesRootDirectories && langTagConfig.includes && pathSegments.length > 0) {
|
|
40
52
|
const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
|
|
41
53
|
if (extractedDirectories.includes(pathSegments[0])) {
|
|
@@ -116,7 +128,56 @@ function applyStructuredIgnore(segments, structure) {
|
|
|
116
128
|
currentStructure = structure;
|
|
117
129
|
continue;
|
|
118
130
|
} else if (typeof rule === "object" && rule !== null) {
|
|
131
|
+
const ignoreSelf = rule["_"] === true;
|
|
132
|
+
if (ignoreSelf) {
|
|
133
|
+
currentStructure = rule;
|
|
134
|
+
continue;
|
|
135
|
+
} else {
|
|
136
|
+
result.push(segment);
|
|
137
|
+
currentStructure = rule;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
result.push(segment);
|
|
143
|
+
currentStructure = structure;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function applyPathRules(segments, structure) {
|
|
148
|
+
const result = [];
|
|
149
|
+
let currentStructure = structure;
|
|
150
|
+
for (let i = 0; i < segments.length; i++) {
|
|
151
|
+
const segment = segments[i];
|
|
152
|
+
if (segment in currentStructure) {
|
|
153
|
+
const rule = currentStructure[segment];
|
|
154
|
+
if (rule === true) {
|
|
155
|
+
currentStructure = structure;
|
|
156
|
+
continue;
|
|
157
|
+
} else if (rule === false) {
|
|
158
|
+
currentStructure = structure;
|
|
159
|
+
continue;
|
|
160
|
+
} else if (typeof rule === "string") {
|
|
161
|
+
result.push(rule);
|
|
162
|
+
currentStructure = structure;
|
|
163
|
+
continue;
|
|
164
|
+
} else if (Array.isArray(rule)) {
|
|
119
165
|
result.push(segment);
|
|
166
|
+
if (i + 1 < segments.length && rule.includes(segments[i + 1])) {
|
|
167
|
+
i++;
|
|
168
|
+
}
|
|
169
|
+
currentStructure = structure;
|
|
170
|
+
continue;
|
|
171
|
+
} else if (typeof rule === "object" && rule !== null) {
|
|
172
|
+
const ignoreSelf = rule["_"] === false;
|
|
173
|
+
const renameTo = rule[">"];
|
|
174
|
+
if (!ignoreSelf) {
|
|
175
|
+
if (typeof renameTo === "string") {
|
|
176
|
+
result.push(renameTo);
|
|
177
|
+
} else {
|
|
178
|
+
result.push(segment);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
120
181
|
currentStructure = rule;
|
|
121
182
|
continue;
|
|
122
183
|
}
|
package/index.cjs
CHANGED
|
@@ -484,7 +484,8 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
484
484
|
if (replacements.length) {
|
|
485
485
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
486
486
|
await promises.writeFile(file, newContent, "utf-8");
|
|
487
|
-
|
|
487
|
+
const encodedFile = encodeURI(file);
|
|
488
|
+
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path$12, file: encodedFile, line: lastUpdatedLine });
|
|
488
489
|
return true;
|
|
489
490
|
}
|
|
490
491
|
return false;
|
|
@@ -904,7 +905,8 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
904
905
|
console.error("Failed to colorize config:", error);
|
|
905
906
|
}
|
|
906
907
|
}
|
|
907
|
-
|
|
908
|
+
const encodedPath = encodeURI(filePath);
|
|
909
|
+
console.log(`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}file://${encodedPath}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`);
|
|
908
910
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
909
911
|
} catch (error) {
|
|
910
912
|
console.error("Error displaying conflict:", error);
|
|
@@ -1452,7 +1454,20 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1452
1454
|
namespaceCase: 'kebab',
|
|
1453
1455
|
pathCase: 'camel',
|
|
1454
1456
|
clearOnDefaultNamespace: true,
|
|
1455
|
-
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1457
|
+
ignoreDirectories: ['core', 'utils', 'helpers'],
|
|
1458
|
+
// Advanced: Use pathRules for hierarchical transformations with ignore and rename
|
|
1459
|
+
// pathRules: {
|
|
1460
|
+
// app: {
|
|
1461
|
+
// dashboard: {
|
|
1462
|
+
// _: false, // ignore "dashboard" but continue with nested rules
|
|
1463
|
+
// modules: false // also ignore "modules"
|
|
1464
|
+
// },
|
|
1465
|
+
// admin: {
|
|
1466
|
+
// '>': 'management', // rename "admin" to "management"
|
|
1467
|
+
// users: false // ignore "users"
|
|
1468
|
+
// }
|
|
1469
|
+
// }
|
|
1470
|
+
// }
|
|
1456
1471
|
});
|
|
1457
1472
|
const keeper = configKeeper();
|
|
1458
1473
|
|
package/index.js
CHANGED
|
@@ -464,7 +464,8 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
464
464
|
if (replacements.length) {
|
|
465
465
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
466
466
|
await writeFile(file, newContent, "utf-8");
|
|
467
|
-
|
|
467
|
+
const encodedFile = encodeURI(file);
|
|
468
|
+
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path2, file: encodedFile, line: lastUpdatedLine });
|
|
468
469
|
return true;
|
|
469
470
|
}
|
|
470
471
|
return false;
|
|
@@ -884,7 +885,8 @@ async function logTagConflictInfo(tagInfo, prefix, conflictPath, translationArgP
|
|
|
884
885
|
console.error("Failed to colorize config:", error);
|
|
885
886
|
}
|
|
886
887
|
}
|
|
887
|
-
|
|
888
|
+
const encodedPath = encodeURI(filePath);
|
|
889
|
+
console.log(`${ANSI.gray}${prefix}${ANSI.reset} ${ANSI.cyan}file://${encodedPath}${ANSI.reset}${ANSI.gray}:${lineNum}${ANSI.reset}`);
|
|
888
890
|
printLines(colorizedWhole.split("\n"), startLine, errorLines, condense);
|
|
889
891
|
} catch (error) {
|
|
890
892
|
console.error("Error displaying conflict:", error);
|
|
@@ -1432,7 +1434,20 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1432
1434
|
namespaceCase: 'kebab',
|
|
1433
1435
|
pathCase: 'camel',
|
|
1434
1436
|
clearOnDefaultNamespace: true,
|
|
1435
|
-
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1437
|
+
ignoreDirectories: ['core', 'utils', 'helpers'],
|
|
1438
|
+
// Advanced: Use pathRules for hierarchical transformations with ignore and rename
|
|
1439
|
+
// pathRules: {
|
|
1440
|
+
// app: {
|
|
1441
|
+
// dashboard: {
|
|
1442
|
+
// _: false, // ignore "dashboard" but continue with nested rules
|
|
1443
|
+
// modules: false // also ignore "modules"
|
|
1444
|
+
// },
|
|
1445
|
+
// admin: {
|
|
1446
|
+
// '>': 'management', // rename "admin" to "management"
|
|
1447
|
+
// users: false // ignore "users"
|
|
1448
|
+
// }
|
|
1449
|
+
// }
|
|
1450
|
+
// }
|
|
1436
1451
|
});
|
|
1437
1452
|
const keeper = configKeeper();
|
|
1438
1453
|
|