i18next-cli 1.47.10 → 1.47.12

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/dist/cjs/cli.js CHANGED
@@ -31,7 +31,7 @@ const program = new commander.Command();
31
31
  program
32
32
  .name('i18next-cli')
33
33
  .description('A unified, high-performance i18next CLI.')
34
- .version('1.47.10'); // This string is replaced with the actual version at build time by rollup
34
+ .version('1.47.12'); // This string is replaced with the actual version at build time by rollup
35
35
  // new: global config override option
36
36
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
37
37
  program
@@ -8,9 +8,45 @@ class ScopeManager {
8
8
  scope = new Map();
9
9
  // Track simple local constants with string literal values to resolve identifier args
10
10
  simpleConstants = new Map();
11
+ // Track simple local constant objects with string literal property values
12
+ simpleConstantObjects = new Map();
11
13
  constructor(config) {
12
14
  this.config = config;
13
15
  }
16
+ /**
17
+ * Recursively unwraps TypeScript type assertion wrappers to reach the
18
+ * underlying expression node.
19
+ */
20
+ static unwrapTsExpression(node) {
21
+ if (!node)
22
+ return node;
23
+ switch (node.type) {
24
+ case 'TsConstAssertion':
25
+ case 'TsAsExpression':
26
+ case 'TsSatisfiesExpression':
27
+ return ScopeManager.unwrapTsExpression(node.expression);
28
+ default:
29
+ return node;
30
+ }
31
+ }
32
+ /**
33
+ * Extracts a string value from a variable declarator's TypeScript type
34
+ * annotation when it is a literal string type (e.g., `const ns: 'users'`).
35
+ */
36
+ static extractStringFromTypeAnnotation(node) {
37
+ if (!node?.id || node.id.type !== 'Identifier')
38
+ return undefined;
39
+ if ('typeAnnotation' in node.id) {
40
+ const rawTypeAnn = node.id.typeAnnotation;
41
+ if (!rawTypeAnn)
42
+ return undefined;
43
+ const tsType = rawTypeAnn.typeAnnotation;
44
+ if (tsType?.type === 'TsLiteralType' && tsType.literal?.type === 'StringLiteral') {
45
+ return tsType.literal.value;
46
+ }
47
+ }
48
+ return undefined;
49
+ }
14
50
  /**
15
51
  * Reset per-file scope state.
16
52
  *
@@ -22,6 +58,7 @@ class ScopeManager {
22
58
  this.scopeStack = [];
23
59
  this.scope = new Map();
24
60
  this.simpleConstants.clear();
61
+ this.simpleConstantObjects.clear();
25
62
  }
26
63
  /**
27
64
  * Enters a new variable scope by pushing a new scope map onto the stack.
@@ -100,6 +137,27 @@ class ScopeManager {
100
137
  resolveSimpleStringIdentifier(name) {
101
138
  return this.simpleConstants.get(name);
102
139
  }
140
+ /**
141
+ * Resolve a MemberExpression node (e.g., `ns.custom`) to its string value
142
+ * by looking up the object in `simpleConstantObjects`.
143
+ */
144
+ resolveSimpleMemberExpression(node) {
145
+ if (node.object.type !== 'Identifier') {
146
+ return undefined;
147
+ }
148
+ const map = this.simpleConstantObjects.get(node.object.value);
149
+ if (!map) {
150
+ return undefined;
151
+ }
152
+ const prop = node.property;
153
+ if (prop.type === 'Identifier') {
154
+ return map[prop.value];
155
+ }
156
+ if (prop.type === 'Computed' && prop.expression.type === 'StringLiteral') {
157
+ return map[prop.expression.value];
158
+ }
159
+ return undefined;
160
+ }
103
161
  /**
104
162
  * Handles variable declarations that might define translation functions.
105
163
  *
@@ -115,9 +173,43 @@ class ScopeManager {
115
173
  const init = node.init;
116
174
  if (!init)
117
175
  return;
118
- // Record simple const/let string initializers for later resolution
119
- if (node.id.type === 'Identifier' && init.type === 'StringLiteral') {
120
- this.simpleConstants.set(node.id.value, init.value);
176
+ // Record simple const/let string initializers for later resolution.
177
+ // Unwrap TS type assertion wrappers (as const, satisfies, as 'x') and fall
178
+ // back to the variable's type annotation when the init is not a string literal.
179
+ if (node.id.type === 'Identifier') {
180
+ const unwrapped = ScopeManager.unwrapTsExpression(init);
181
+ if (unwrapped?.type === 'StringLiteral') {
182
+ this.simpleConstants.set(node.id.value, unwrapped.value);
183
+ }
184
+ else if (unwrapped?.type === 'ObjectExpression' && Array.isArray(unwrapped.properties)) {
185
+ const map = {};
186
+ for (const p of unwrapped.properties) {
187
+ if (p.type !== 'KeyValueProperty') {
188
+ continue;
189
+ }
190
+ const keyName = p.key.type === 'Identifier'
191
+ ? p.key.value
192
+ : p.key.type === 'StringLiteral'
193
+ ? p.key.value
194
+ : undefined;
195
+ if (!keyName) {
196
+ continue;
197
+ }
198
+ const val = ScopeManager.unwrapTsExpression(p.value);
199
+ if (val?.type === 'StringLiteral') {
200
+ map[keyName] = val.value;
201
+ }
202
+ }
203
+ if (Object.keys(map).length > 0) {
204
+ this.simpleConstantObjects.set(node.id.value, map);
205
+ }
206
+ }
207
+ else {
208
+ const fromType = ScopeManager.extractStringFromTypeAnnotation(node);
209
+ if (fromType !== undefined) {
210
+ this.simpleConstants.set(node.id.value, fromType);
211
+ }
212
+ }
121
213
  // continue processing; still may be a useTranslation/getFixedT call below
122
214
  }
123
215
  // Determine the actual call expression, looking inside AwaitExpressions.
@@ -227,15 +319,19 @@ class ScopeManager {
227
319
  defaultNs = nsNode.value;
228
320
  }
229
321
  else if (nsNode?.type === 'Identifier') {
230
- // ← FIX A: resolve const I18N_NS = 'users'
231
322
  defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
232
323
  }
324
+ else if (nsNode?.type === 'MemberExpression') {
325
+ defaultNs = this.resolveSimpleMemberExpression(nsNode);
326
+ }
233
327
  else if (nsNode?.type === 'ArrayExpression') {
234
328
  const first = nsNode.elements[0]?.expression;
235
- if (first?.type === 'StringLiteral')
329
+ if (first?.type === 'StringLiteral') {
236
330
  defaultNs = first.value;
237
- else if (first?.type === 'Identifier')
331
+ }
332
+ else if (first?.type === 'Identifier') {
238
333
  defaultNs = this.resolveSimpleStringIdentifier(first.value);
334
+ }
239
335
  }
240
336
  }
241
337
  kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
@@ -308,15 +404,19 @@ class ScopeManager {
308
404
  defaultNs = nsNode.value;
309
405
  }
310
406
  else if (nsNode?.type === 'Identifier') {
311
- // ← FIX A: resolve const I18N_NS = 'users'
312
407
  defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
313
408
  }
409
+ else if (nsNode?.type === 'MemberExpression') {
410
+ defaultNs = this.resolveSimpleMemberExpression(nsNode);
411
+ }
314
412
  else if (nsNode?.type === 'ArrayExpression') {
315
413
  const first = nsNode.elements[0]?.expression;
316
- if (first?.type === 'StringLiteral')
414
+ if (first?.type === 'StringLiteral') {
317
415
  defaultNs = first.value;
318
- else if (first?.type === 'Identifier')
416
+ }
417
+ else if (first?.type === 'Identifier') {
319
418
  defaultNs = this.resolveSimpleStringIdentifier(first.value);
419
+ }
320
420
  }
321
421
  }
322
422
  kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
package/dist/esm/cli.js CHANGED
@@ -29,7 +29,7 @@ const program = new Command();
29
29
  program
30
30
  .name('i18next-cli')
31
31
  .description('A unified, high-performance i18next CLI.')
32
- .version('1.47.10'); // This string is replaced with the actual version at build time by rollup
32
+ .version('1.47.12'); // This string is replaced with the actual version at build time by rollup
33
33
  // new: global config override option
34
34
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
35
35
  program
@@ -6,9 +6,45 @@ class ScopeManager {
6
6
  scope = new Map();
7
7
  // Track simple local constants with string literal values to resolve identifier args
8
8
  simpleConstants = new Map();
9
+ // Track simple local constant objects with string literal property values
10
+ simpleConstantObjects = new Map();
9
11
  constructor(config) {
10
12
  this.config = config;
11
13
  }
14
+ /**
15
+ * Recursively unwraps TypeScript type assertion wrappers to reach the
16
+ * underlying expression node.
17
+ */
18
+ static unwrapTsExpression(node) {
19
+ if (!node)
20
+ return node;
21
+ switch (node.type) {
22
+ case 'TsConstAssertion':
23
+ case 'TsAsExpression':
24
+ case 'TsSatisfiesExpression':
25
+ return ScopeManager.unwrapTsExpression(node.expression);
26
+ default:
27
+ return node;
28
+ }
29
+ }
30
+ /**
31
+ * Extracts a string value from a variable declarator's TypeScript type
32
+ * annotation when it is a literal string type (e.g., `const ns: 'users'`).
33
+ */
34
+ static extractStringFromTypeAnnotation(node) {
35
+ if (!node?.id || node.id.type !== 'Identifier')
36
+ return undefined;
37
+ if ('typeAnnotation' in node.id) {
38
+ const rawTypeAnn = node.id.typeAnnotation;
39
+ if (!rawTypeAnn)
40
+ return undefined;
41
+ const tsType = rawTypeAnn.typeAnnotation;
42
+ if (tsType?.type === 'TsLiteralType' && tsType.literal?.type === 'StringLiteral') {
43
+ return tsType.literal.value;
44
+ }
45
+ }
46
+ return undefined;
47
+ }
12
48
  /**
13
49
  * Reset per-file scope state.
14
50
  *
@@ -20,6 +56,7 @@ class ScopeManager {
20
56
  this.scopeStack = [];
21
57
  this.scope = new Map();
22
58
  this.simpleConstants.clear();
59
+ this.simpleConstantObjects.clear();
23
60
  }
24
61
  /**
25
62
  * Enters a new variable scope by pushing a new scope map onto the stack.
@@ -98,6 +135,27 @@ class ScopeManager {
98
135
  resolveSimpleStringIdentifier(name) {
99
136
  return this.simpleConstants.get(name);
100
137
  }
138
+ /**
139
+ * Resolve a MemberExpression node (e.g., `ns.custom`) to its string value
140
+ * by looking up the object in `simpleConstantObjects`.
141
+ */
142
+ resolveSimpleMemberExpression(node) {
143
+ if (node.object.type !== 'Identifier') {
144
+ return undefined;
145
+ }
146
+ const map = this.simpleConstantObjects.get(node.object.value);
147
+ if (!map) {
148
+ return undefined;
149
+ }
150
+ const prop = node.property;
151
+ if (prop.type === 'Identifier') {
152
+ return map[prop.value];
153
+ }
154
+ if (prop.type === 'Computed' && prop.expression.type === 'StringLiteral') {
155
+ return map[prop.expression.value];
156
+ }
157
+ return undefined;
158
+ }
101
159
  /**
102
160
  * Handles variable declarations that might define translation functions.
103
161
  *
@@ -113,9 +171,43 @@ class ScopeManager {
113
171
  const init = node.init;
114
172
  if (!init)
115
173
  return;
116
- // Record simple const/let string initializers for later resolution
117
- if (node.id.type === 'Identifier' && init.type === 'StringLiteral') {
118
- this.simpleConstants.set(node.id.value, init.value);
174
+ // Record simple const/let string initializers for later resolution.
175
+ // Unwrap TS type assertion wrappers (as const, satisfies, as 'x') and fall
176
+ // back to the variable's type annotation when the init is not a string literal.
177
+ if (node.id.type === 'Identifier') {
178
+ const unwrapped = ScopeManager.unwrapTsExpression(init);
179
+ if (unwrapped?.type === 'StringLiteral') {
180
+ this.simpleConstants.set(node.id.value, unwrapped.value);
181
+ }
182
+ else if (unwrapped?.type === 'ObjectExpression' && Array.isArray(unwrapped.properties)) {
183
+ const map = {};
184
+ for (const p of unwrapped.properties) {
185
+ if (p.type !== 'KeyValueProperty') {
186
+ continue;
187
+ }
188
+ const keyName = p.key.type === 'Identifier'
189
+ ? p.key.value
190
+ : p.key.type === 'StringLiteral'
191
+ ? p.key.value
192
+ : undefined;
193
+ if (!keyName) {
194
+ continue;
195
+ }
196
+ const val = ScopeManager.unwrapTsExpression(p.value);
197
+ if (val?.type === 'StringLiteral') {
198
+ map[keyName] = val.value;
199
+ }
200
+ }
201
+ if (Object.keys(map).length > 0) {
202
+ this.simpleConstantObjects.set(node.id.value, map);
203
+ }
204
+ }
205
+ else {
206
+ const fromType = ScopeManager.extractStringFromTypeAnnotation(node);
207
+ if (fromType !== undefined) {
208
+ this.simpleConstants.set(node.id.value, fromType);
209
+ }
210
+ }
119
211
  // continue processing; still may be a useTranslation/getFixedT call below
120
212
  }
121
213
  // Determine the actual call expression, looking inside AwaitExpressions.
@@ -225,15 +317,19 @@ class ScopeManager {
225
317
  defaultNs = nsNode.value;
226
318
  }
227
319
  else if (nsNode?.type === 'Identifier') {
228
- // ← FIX A: resolve const I18N_NS = 'users'
229
320
  defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
230
321
  }
322
+ else if (nsNode?.type === 'MemberExpression') {
323
+ defaultNs = this.resolveSimpleMemberExpression(nsNode);
324
+ }
231
325
  else if (nsNode?.type === 'ArrayExpression') {
232
326
  const first = nsNode.elements[0]?.expression;
233
- if (first?.type === 'StringLiteral')
327
+ if (first?.type === 'StringLiteral') {
234
328
  defaultNs = first.value;
235
- else if (first?.type === 'Identifier')
329
+ }
330
+ else if (first?.type === 'Identifier') {
236
331
  defaultNs = this.resolveSimpleStringIdentifier(first.value);
332
+ }
237
333
  }
238
334
  }
239
335
  kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
@@ -306,15 +402,19 @@ class ScopeManager {
306
402
  defaultNs = nsNode.value;
307
403
  }
308
404
  else if (nsNode?.type === 'Identifier') {
309
- // ← FIX A: resolve const I18N_NS = 'users'
310
405
  defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
311
406
  }
407
+ else if (nsNode?.type === 'MemberExpression') {
408
+ defaultNs = this.resolveSimpleMemberExpression(nsNode);
409
+ }
312
410
  else if (nsNode?.type === 'ArrayExpression') {
313
411
  const first = nsNode.elements[0]?.expression;
314
- if (first?.type === 'StringLiteral')
412
+ if (first?.type === 'StringLiteral') {
315
413
  defaultNs = first.value;
316
- else if (first?.type === 'Identifier')
414
+ }
415
+ else if (first?.type === 'Identifier') {
317
416
  defaultNs = this.resolveSimpleStringIdentifier(first.value);
417
+ }
318
418
  }
319
419
  }
320
420
  kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.47.10",
3
+ "version": "1.47.12",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,7 +5,18 @@ export declare class ScopeManager {
5
5
  private config;
6
6
  private scope;
7
7
  private simpleConstants;
8
+ private simpleConstantObjects;
8
9
  constructor(config: Omit<I18nextToolkitConfig, 'plugins'>);
10
+ /**
11
+ * Recursively unwraps TypeScript type assertion wrappers to reach the
12
+ * underlying expression node.
13
+ */
14
+ private static unwrapTsExpression;
15
+ /**
16
+ * Extracts a string value from a variable declarator's TypeScript type
17
+ * annotation when it is a literal string type (e.g., `const ns: 'users'`).
18
+ */
19
+ private static extractStringFromTypeAnnotation;
9
20
  /**
10
21
  * Reset per-file scope state.
11
22
  *
@@ -45,6 +56,11 @@ export declare class ScopeManager {
45
56
  * Resolve simple identifier declared in-file to its string literal value, if known.
46
57
  */
47
58
  resolveSimpleStringIdentifier(name: string): string | undefined;
59
+ /**
60
+ * Resolve a MemberExpression node (e.g., `ns.custom`) to its string value
61
+ * by looking up the object in `simpleConstantObjects`.
62
+ */
63
+ private resolveSimpleMemberExpression;
48
64
  /**
49
65
  * Handles variable declarations that might define translation functions.
50
66
  *
@@ -1 +1 @@
1
- {"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,WAAW,CAAA;AACpF,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG5F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;gBAE3C,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAMrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACI,6BAA6B,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvE;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAuDzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IAsGvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IA+EtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAuB9C"}
1
+ {"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,kBAAkB,EAMnB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG5F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;IAGxD,OAAO,CAAC,qBAAqB,CAAiD;gBAEjE,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAcjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,+BAA+B;IAgB9C;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAOrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACI,6BAA6B,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvE;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAqBrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAuFzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA0GvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAmFtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAuB9C"}