@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 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
@@ -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
- pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
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
  }
@@ -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
- pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lang-tag/cli",
3
- "version": "0.12.4",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"