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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
79
|
+
"inquirer": "13.3.0",
|
|
80
80
|
"jiti": "2.6.1",
|
|
81
81
|
"jsonc-parser": "3.3.1",
|
|
82
|
-
"minimatch": "10.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,
|
|
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"}
|