i18next-cli 1.45.0 β†’ 1.46.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
@@ -7,6 +7,10 @@ A unified, high-performance i18next CLI toolchain, powered by SWC.
7
7
 
8
8
  ---
9
9
 
10
+
11
+ > By default, `i18next-cli` only extracts translation keys from JavaScript and TypeScript files (`.js`, `.jsx`, `.ts`, `.tsx`).
12
+ > To extract from other file types (such as `.pug`, `.vue`, `.svelte`, etc.), you must use or create a plugin. Specifying additional file extensions in the `extract.input` config is not sufficient on its ownβ€”plugins are required for non-JS/TS formats. See the [Plugin System](#plugin-system) section for details and examples.
13
+
10
14
  `i18next-cli` is a complete reimagining of the static analysis toolchain for the i18next ecosystem. It consolidates key extraction, type safety generation, locale syncing, linting, and cloud integrations into a single, cohesive, and blazing-fast CLI.
11
15
 
12
16
  > ### πŸš€ Try it Now - Zero Config!
@@ -80,6 +84,7 @@ Get an overview of your project's localization health:
80
84
  npx i18next-cli status
81
85
  ```
82
86
 
87
+
83
88
  ### 3. Extract Translation Keys
84
89
 
85
90
  ```bash
@@ -336,6 +341,7 @@ The configuration file supports both TypeScript (`.ts`) and JavaScript (`.js`) f
336
341
 
337
342
  > **πŸ’‘ No Installation Required?** If you don't want to install `i18next-cli` as a dependency, you can skip the `defineConfig` helper and return a plain JavaScript object or JSON instead. The `defineConfig` function is purely for TypeScript support and doesn't affect functionality.
338
343
 
344
+
339
345
  ### Basic Configuration
340
346
 
341
347
  ```typescript
@@ -351,6 +357,9 @@ export default defineConfig({
351
357
  });
352
358
  ```
353
359
 
360
+ > **❗ Important:**
361
+ > Only `.js`, `.jsx`, `.ts`, and `.tsx` files are extracted by default. If you want to extract from other file types (e.g., `.pug`, `.vue`), you must use or create a plugin. See the [Plugin System](#plugin-system) section for more information.
362
+
354
363
  **Alternative without local installation:**
355
364
 
356
365
  ```javascript
package/dist/cjs/cli.js CHANGED
@@ -28,7 +28,7 @@ const program = new commander.Command();
28
28
  program
29
29
  .name('i18next-cli')
30
30
  .description('A unified, high-performance i18next CLI.')
31
- .version('1.45.0'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.46.1'); // This string is replaced with the actual version at build time by rollup
32
32
  // new: global config override option
33
33
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
34
34
  program
@@ -135,7 +135,13 @@ function extractFromTransComponent(node, config) {
135
135
  valuesAttr.value.expression.type === 'ObjectExpression') {
136
136
  valuesCountProperty = astUtils.getObjectPropValueExpression(valuesAttr.value.expression, 'count');
137
137
  }
138
- const hasCount = !!countAttr || !!valuesCountProperty;
138
+ // Mirror react-i18next v16.4.0: infer count from inline {{ count }} children
139
+ // when no explicit `count` prop or `values={{ count }}` is present.
140
+ // The runtime check is `typeof valuesFromChildren.count === 'number'`; at
141
+ // extraction time we can only inspect the AST shape, so we look for any
142
+ // ObjectExpression interpolation that declares a `count` key.
143
+ const hasInlineCount = !countAttr && !valuesCountProperty && childrenHaveInlineCount(node.children);
144
+ const hasCount = !!countAttr || !!valuesCountProperty || hasInlineCount;
139
145
  const tOptionsAttr = node.opening.attributes?.find((attr) => attr.type === 'JSXAttribute' &&
140
146
  attr.name.type === 'Identifier' &&
141
147
  attr.name.value === 'tOptions');
@@ -385,6 +391,74 @@ function swcChildToReactNode(node) {
385
391
  function swcChildrenToReactNodes(children) {
386
392
  return children.map(swcChildToReactNode).filter(n => n !== null);
387
393
  }
394
+ /**
395
+ * Unwraps TypeScript type-assertion and parenthesis wrappers from a JSX
396
+ * expression so callers can inspect the underlying node type.
397
+ *
398
+ * Handles:
399
+ * `{{ count } as any}` β†’ TsAsExpression wrapping ObjectExpression
400
+ * `{{ count } as TransInterpolation}` β†’ same
401
+ * `({{ count }})` β†’ ParenthesisExpression wrapping ObjectExpression
402
+ */
403
+ function unwrapJSXExpression(expr) {
404
+ if (expr.type === 'TsAsExpression' || expr.type === 'TsSatisfiesExpression') {
405
+ return unwrapJSXExpression(expr.expression);
406
+ }
407
+ if (expr.type === 'ParenthesisExpression') {
408
+ return unwrapJSXExpression(expr.expression);
409
+ }
410
+ return expr;
411
+ }
412
+ /**
413
+ * Recursively walks JSX children to determine whether any interpolation
414
+ * object contains a `count` property β€” mirroring the runtime behaviour of
415
+ * react-i18next v16.4.0's `getValuesFromChildren`.
416
+ *
417
+ * This lets the extractor infer `hasCount = true` when a `{{ count }}`
418
+ * (or `{{ count: expr }}`) interpolation is present in children without an
419
+ * explicit `count` prop on the `<Trans>` component.
420
+ *
421
+ * Matches:
422
+ * `{{ count }}` β€” shorthand Identifier (prop.type === 'Identifier')
423
+ * `{{ count: someExpr }}` β€” KeyValueProperty with Identifier key
424
+ * `{{ count } as any}` β€” TsAsExpression-wrapped ObjectExpression
425
+ * Deeply nested in child JSX elements (e.g. `<strong>{{ count }}</strong>`)
426
+ *
427
+ * @param children - The JSX children array to search
428
+ * @returns `true` when a `count` interpolation is found anywhere in the tree
429
+ */
430
+ function childrenHaveInlineCount(children) {
431
+ for (const child of children) {
432
+ if (child.type === 'JSXExpressionContainer') {
433
+ const inner = unwrapJSXExpression(child.expression);
434
+ if (inner.type === 'ObjectExpression') {
435
+ const hasCount = inner.properties.some(prop => {
436
+ if (prop.type === 'KeyValueProperty') {
437
+ // { count: expr }
438
+ return ((prop.key.type === 'Identifier' || prop.key.type === 'StringLiteral') &&
439
+ prop.key.value === 'count');
440
+ }
441
+ if (prop.type === 'Identifier') {
442
+ // shorthand { count }
443
+ return prop.value === 'count';
444
+ }
445
+ return false;
446
+ });
447
+ if (hasCount)
448
+ return true;
449
+ }
450
+ }
451
+ else if (child.type === 'JSXElement') {
452
+ if (childrenHaveInlineCount(child.children))
453
+ return true;
454
+ }
455
+ else if (child.type === 'JSXFragment') {
456
+ if (childrenHaveInlineCount(child.children))
457
+ return true;
458
+ }
459
+ }
460
+ return false;
461
+ }
388
462
  function serializeJSXChildren(children, config) {
389
463
  const i18nextOptions = { ...reactI18next.getDefaults() };
390
464
  if (config.extract.transKeepBasicHtmlNodesFor) {
package/dist/esm/cli.js CHANGED
@@ -26,7 +26,7 @@ const program = new Command();
26
26
  program
27
27
  .name('i18next-cli')
28
28
  .description('A unified, high-performance i18next CLI.')
29
- .version('1.45.0'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.46.1'); // This string is replaced with the actual version at build time by rollup
30
30
  // new: global config override option
31
31
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
32
32
  program
@@ -133,7 +133,13 @@ function extractFromTransComponent(node, config) {
133
133
  valuesAttr.value.expression.type === 'ObjectExpression') {
134
134
  valuesCountProperty = getObjectPropValueExpression(valuesAttr.value.expression, 'count');
135
135
  }
136
- const hasCount = !!countAttr || !!valuesCountProperty;
136
+ // Mirror react-i18next v16.4.0: infer count from inline {{ count }} children
137
+ // when no explicit `count` prop or `values={{ count }}` is present.
138
+ // The runtime check is `typeof valuesFromChildren.count === 'number'`; at
139
+ // extraction time we can only inspect the AST shape, so we look for any
140
+ // ObjectExpression interpolation that declares a `count` key.
141
+ const hasInlineCount = !countAttr && !valuesCountProperty && childrenHaveInlineCount(node.children);
142
+ const hasCount = !!countAttr || !!valuesCountProperty || hasInlineCount;
137
143
  const tOptionsAttr = node.opening.attributes?.find((attr) => attr.type === 'JSXAttribute' &&
138
144
  attr.name.type === 'Identifier' &&
139
145
  attr.name.value === 'tOptions');
@@ -383,6 +389,74 @@ function swcChildToReactNode(node) {
383
389
  function swcChildrenToReactNodes(children) {
384
390
  return children.map(swcChildToReactNode).filter(n => n !== null);
385
391
  }
392
+ /**
393
+ * Unwraps TypeScript type-assertion and parenthesis wrappers from a JSX
394
+ * expression so callers can inspect the underlying node type.
395
+ *
396
+ * Handles:
397
+ * `{{ count } as any}` β†’ TsAsExpression wrapping ObjectExpression
398
+ * `{{ count } as TransInterpolation}` β†’ same
399
+ * `({{ count }})` β†’ ParenthesisExpression wrapping ObjectExpression
400
+ */
401
+ function unwrapJSXExpression(expr) {
402
+ if (expr.type === 'TsAsExpression' || expr.type === 'TsSatisfiesExpression') {
403
+ return unwrapJSXExpression(expr.expression);
404
+ }
405
+ if (expr.type === 'ParenthesisExpression') {
406
+ return unwrapJSXExpression(expr.expression);
407
+ }
408
+ return expr;
409
+ }
410
+ /**
411
+ * Recursively walks JSX children to determine whether any interpolation
412
+ * object contains a `count` property β€” mirroring the runtime behaviour of
413
+ * react-i18next v16.4.0's `getValuesFromChildren`.
414
+ *
415
+ * This lets the extractor infer `hasCount = true` when a `{{ count }}`
416
+ * (or `{{ count: expr }}`) interpolation is present in children without an
417
+ * explicit `count` prop on the `<Trans>` component.
418
+ *
419
+ * Matches:
420
+ * `{{ count }}` β€” shorthand Identifier (prop.type === 'Identifier')
421
+ * `{{ count: someExpr }}` β€” KeyValueProperty with Identifier key
422
+ * `{{ count } as any}` β€” TsAsExpression-wrapped ObjectExpression
423
+ * Deeply nested in child JSX elements (e.g. `<strong>{{ count }}</strong>`)
424
+ *
425
+ * @param children - The JSX children array to search
426
+ * @returns `true` when a `count` interpolation is found anywhere in the tree
427
+ */
428
+ function childrenHaveInlineCount(children) {
429
+ for (const child of children) {
430
+ if (child.type === 'JSXExpressionContainer') {
431
+ const inner = unwrapJSXExpression(child.expression);
432
+ if (inner.type === 'ObjectExpression') {
433
+ const hasCount = inner.properties.some(prop => {
434
+ if (prop.type === 'KeyValueProperty') {
435
+ // { count: expr }
436
+ return ((prop.key.type === 'Identifier' || prop.key.type === 'StringLiteral') &&
437
+ prop.key.value === 'count');
438
+ }
439
+ if (prop.type === 'Identifier') {
440
+ // shorthand { count }
441
+ return prop.value === 'count';
442
+ }
443
+ return false;
444
+ });
445
+ if (hasCount)
446
+ return true;
447
+ }
448
+ }
449
+ else if (child.type === 'JSXElement') {
450
+ if (childrenHaveInlineCount(child.children))
451
+ return true;
452
+ }
453
+ else if (child.type === 'JSXFragment') {
454
+ if (childrenHaveInlineCount(child.children))
455
+ return true;
456
+ }
457
+ }
458
+ return false;
459
+ }
386
460
  function serializeJSXChildren(children, config) {
387
461
  const i18nextOptions = { ...getDefaults() };
388
462
  if (config.extract.transKeepBasicHtmlNodesFor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.45.0",
3
+ "version": "1.46.1",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,7 +55,7 @@
55
55
  "@rollup/plugin-replace": "6.0.3",
56
56
  "@rollup/plugin-terser": "0.4.4",
57
57
  "@types/inquirer": "9.0.9",
58
- "@types/node": "25.3.0",
58
+ "@types/node": "25.3.2",
59
59
  "@types/react": "19.2.14",
60
60
  "@vitest/coverage-v8": "4.0.18",
61
61
  "eslint": "9.39.2",
@@ -69,17 +69,17 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "@croct/json5-parser": "0.2.2",
72
- "@swc/core": "1.15.11",
72
+ "@swc/core": "1.15.17",
73
73
  "yaml": "2.8.2",
74
74
  "chokidar": "5.0.0",
75
75
  "commander": "14.0.3",
76
76
  "execa": "9.6.1",
77
77
  "glob": "13.0.6",
78
78
  "i18next-resources-for-ts": "2.0.0",
79
- "inquirer": "13.2.5",
79
+ "inquirer": "13.3.0",
80
80
  "jiti": "2.6.1",
81
81
  "jsonc-parser": "3.3.1",
82
- "minimatch": "10.2.2",
82
+ "minimatch": "10.2.4",
83
83
  "ora": "9.3.0",
84
84
  "react": "^19.2.4",
85
85
  "react-i18next": "^16.5.4"
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAsC,UAAU,EAAkD,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC7J,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAsEvD,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAE/B,kHAAkH;IAClH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CA2LxH"}
1
+ {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAsC,UAAU,EAAkD,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC7J,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAsEvD,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAE/B,kHAAkH;IAClH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CAkMxH"}