@lang-tag/cli 0.12.4 → 0.13.1
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/config.d.ts +2 -0
- package/index.cjs +17 -2
- package/index.js +17 -2
- 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/config.d.ts
CHANGED
|
@@ -223,6 +223,8 @@ export interface LangTagCLIConflictResolutionEvent {
|
|
|
223
223
|
exit(): void;
|
|
224
224
|
}
|
|
225
225
|
export interface LangTagCLICollectFinishEvent {
|
|
226
|
+
totalTags: number;
|
|
227
|
+
namespaces: Record<string, Record<string, any>>;
|
|
226
228
|
conflicts: LangTagCLIConflict[];
|
|
227
229
|
logger: LangTagCLILogger;
|
|
228
230
|
/** Breaks translation collection process */
|
package/index.cjs
CHANGED
|
@@ -1224,6 +1224,8 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
|
|
|
1224
1224
|
if (config.collect?.onCollectFinish) {
|
|
1225
1225
|
let shouldContinue = true;
|
|
1226
1226
|
config.collect.onCollectFinish({
|
|
1227
|
+
totalTags,
|
|
1228
|
+
namespaces,
|
|
1227
1229
|
conflicts: allConflicts,
|
|
1228
1230
|
logger,
|
|
1229
1231
|
exit() {
|
|
@@ -1454,9 +1456,22 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1454
1456
|
namespaceCase: 'kebab',
|
|
1455
1457
|
pathCase: 'camel',
|
|
1456
1458
|
clearOnDefaultNamespace: true,
|
|
1457
|
-
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1459
|
+
ignoreDirectories: ['core', 'utils', 'helpers'],
|
|
1460
|
+
// Advanced: Use pathRules for hierarchical transformations with ignore and rename
|
|
1461
|
+
// pathRules: {
|
|
1462
|
+
// app: {
|
|
1463
|
+
// dashboard: {
|
|
1464
|
+
// _: false, // ignore "dashboard" but continue with nested rules
|
|
1465
|
+
// modules: false // also ignore "modules"
|
|
1466
|
+
// },
|
|
1467
|
+
// admin: {
|
|
1468
|
+
// '>': 'management', // rename "admin" to "management"
|
|
1469
|
+
// users: false // ignore "users"
|
|
1470
|
+
// }
|
|
1471
|
+
// }
|
|
1472
|
+
// }
|
|
1458
1473
|
});
|
|
1459
|
-
const keeper = configKeeper();
|
|
1474
|
+
const keeper = configKeeper({ propertyName: 'keep' });
|
|
1460
1475
|
|
|
1461
1476
|
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1462
1477
|
const config = {
|
package/index.js
CHANGED
|
@@ -1204,6 +1204,8 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
|
|
|
1204
1204
|
if (config.collect?.onCollectFinish) {
|
|
1205
1205
|
let shouldContinue = true;
|
|
1206
1206
|
config.collect.onCollectFinish({
|
|
1207
|
+
totalTags,
|
|
1208
|
+
namespaces,
|
|
1207
1209
|
conflicts: allConflicts,
|
|
1208
1210
|
logger,
|
|
1209
1211
|
exit() {
|
|
@@ -1434,9 +1436,22 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1434
1436
|
namespaceCase: 'kebab',
|
|
1435
1437
|
pathCase: 'camel',
|
|
1436
1438
|
clearOnDefaultNamespace: true,
|
|
1437
|
-
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1439
|
+
ignoreDirectories: ['core', 'utils', 'helpers'],
|
|
1440
|
+
// Advanced: Use pathRules for hierarchical transformations with ignore and rename
|
|
1441
|
+
// pathRules: {
|
|
1442
|
+
// app: {
|
|
1443
|
+
// dashboard: {
|
|
1444
|
+
// _: false, // ignore "dashboard" but continue with nested rules
|
|
1445
|
+
// modules: false // also ignore "modules"
|
|
1446
|
+
// },
|
|
1447
|
+
// admin: {
|
|
1448
|
+
// '>': 'management', // rename "admin" to "management"
|
|
1449
|
+
// users: false // ignore "users"
|
|
1450
|
+
// }
|
|
1451
|
+
// }
|
|
1452
|
+
// }
|
|
1438
1453
|
});
|
|
1439
|
-
const keeper = configKeeper();
|
|
1454
|
+
const keeper = configKeeper({ propertyName: 'keep' });
|
|
1440
1455
|
|
|
1441
1456
|
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1442
1457
|
const config = {
|