i18next-cli 1.42.2 → 1.42.4
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 +1 -1
- package/dist/cjs/extractor/core/extractor.js +5 -0
- package/dist/cjs/extractor/parsers/ast-utils.js +39 -0
- package/dist/cjs/linter.js +7 -2
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/extractor.js +5 -0
- package/dist/esm/extractor/parsers/ast-utils.js +39 -1
- package/dist/esm/linter.js +7 -2
- package/package.json +1 -1
- package/types/extractor/core/extractor.d.ts.map +1 -1
- package/types/extractor/parsers/ast-utils.d.ts +11 -0
- package/types/extractor/parsers/ast-utils.d.ts.map +1 -1
- package/types/linter.d.ts.map +1 -1
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.42.
|
|
31
|
+
.version('1.42.4'); // 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
|
|
@@ -9,6 +9,7 @@ var keyFinder = require('./key-finder.js');
|
|
|
9
9
|
var translationManager = require('./translation-manager.js');
|
|
10
10
|
var validation = require('../../utils/validation.js');
|
|
11
11
|
var commentParser = require('../parsers/comment-parser.js');
|
|
12
|
+
var astUtils = require('../parsers/ast-utils.js');
|
|
12
13
|
var logger = require('../../utils/logger.js');
|
|
13
14
|
var fileUtils = require('../../utils/file-utils.js');
|
|
14
15
|
var funnelMsgTracker = require('../../utils/funnel-msg-tracker.js');
|
|
@@ -182,6 +183,10 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
|
|
|
182
183
|
throw new validation.ExtractorError('Failed to process file', file, err);
|
|
183
184
|
}
|
|
184
185
|
}
|
|
186
|
+
// Normalize SWC span offsets so every span is file-relative.
|
|
187
|
+
// SWC accumulates byte offsets across successive parse() calls,
|
|
188
|
+
// so without this, plugins would see positions beyond the file length.
|
|
189
|
+
astUtils.normalizeASTSpans(ast, ast.span.start);
|
|
185
190
|
// "Wire up" the visitor's scope method to the context.
|
|
186
191
|
// This avoids a circular dependency while giving plugins access to the scope.
|
|
187
192
|
pluginContext.getVarFromScope = astVisitors.getVarFromScope.bind(astVisitors);
|
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Recursively normalizes all SWC span offsets in an AST by subtracting a base
|
|
5
|
+
* offset. SWC's `parse()` accumulates byte offsets across successive calls in
|
|
6
|
+
* the same process, so `span.start`/`span.end` values can exceed the length of
|
|
7
|
+
* the source file. Call this once on the root `Module` node right after parsing
|
|
8
|
+
* to make every span file-relative.
|
|
9
|
+
*
|
|
10
|
+
* @param node - Any AST node (or the root Module)
|
|
11
|
+
* @param base - The base offset to subtract (`ast.span.start`)
|
|
12
|
+
*/
|
|
13
|
+
function normalizeASTSpans(node, base) {
|
|
14
|
+
if (!node || typeof node !== 'object' || base === 0)
|
|
15
|
+
return;
|
|
16
|
+
// Normalize this node's own span
|
|
17
|
+
if (node.span && typeof node.span.start === 'number') {
|
|
18
|
+
node.span = {
|
|
19
|
+
...node.span,
|
|
20
|
+
start: node.span.start - base,
|
|
21
|
+
end: node.span.end - base
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Recurse into every property (skip span itself to avoid double-processing)
|
|
25
|
+
for (const key of Object.keys(node)) {
|
|
26
|
+
if (key === 'span')
|
|
27
|
+
continue;
|
|
28
|
+
const child = node[key];
|
|
29
|
+
if (Array.isArray(child)) {
|
|
30
|
+
for (const item of child) {
|
|
31
|
+
if (item && typeof item === 'object') {
|
|
32
|
+
normalizeASTSpans(item, base);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (child && typeof child === 'object') {
|
|
37
|
+
normalizeASTSpans(child, base);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
3
41
|
/**
|
|
4
42
|
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
5
43
|
* property name from an ObjectExpression.
|
|
@@ -83,3 +121,4 @@ exports.getObjectPropValue = getObjectPropValue;
|
|
|
83
121
|
exports.getObjectPropValueExpression = getObjectPropValueExpression;
|
|
84
122
|
exports.getObjectProperty = getObjectProperty;
|
|
85
123
|
exports.isSimpleTemplateLiteral = isSimpleTemplateLiteral;
|
|
124
|
+
exports.normalizeASTSpans = normalizeASTSpans;
|
package/dist/cjs/linter.js
CHANGED
|
@@ -119,8 +119,11 @@ function lintInterpolationParams(ast, code, config) {
|
|
|
119
119
|
const issueLineNumber = position > -1 ? getLineNumber(position) : 1;
|
|
120
120
|
// Only check for unused parameters if there is at least one interpolation key in the string
|
|
121
121
|
if (keys.length > 0) {
|
|
122
|
+
// i18next supports nested object access via dot notation (e.g. {{author.name}} with { author }).
|
|
123
|
+
// For each interpolation key, check if the root (part before the first dot) matches a provided param.
|
|
122
124
|
for (const k of keys) {
|
|
123
|
-
|
|
125
|
+
const root = k.split('.')[0];
|
|
126
|
+
if (!paramKeys.includes(k) && !paramKeys.includes(root)) {
|
|
124
127
|
issues.push({
|
|
125
128
|
text: `Interpolation parameter "${k}" was not provided`,
|
|
126
129
|
line: issueLineNumber,
|
|
@@ -128,8 +131,10 @@ function lintInterpolationParams(ast, code, config) {
|
|
|
128
131
|
});
|
|
129
132
|
}
|
|
130
133
|
}
|
|
134
|
+
// For each provided param, check if it is used either directly or as the root of a dotted key.
|
|
131
135
|
for (const pk of paramKeys) {
|
|
132
|
-
|
|
136
|
+
const isUsed = keys.some(k => k === pk || k.split('.')[0] === pk);
|
|
137
|
+
if (!isUsed) {
|
|
133
138
|
issues.push({
|
|
134
139
|
text: `Parameter "${pk}" is not used in translation string`,
|
|
135
140
|
line: issueLineNumber,
|
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.42.
|
|
29
|
+
.version('1.42.4'); // 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
|
|
@@ -7,6 +7,7 @@ import { findKeys } from './key-finder.js';
|
|
|
7
7
|
import { getTranslations } from './translation-manager.js';
|
|
8
8
|
import { validateExtractorConfig, ExtractorError } from '../../utils/validation.js';
|
|
9
9
|
import { extractKeysFromComments } from '../parsers/comment-parser.js';
|
|
10
|
+
import { normalizeASTSpans } from '../parsers/ast-utils.js';
|
|
10
11
|
import { ConsoleLogger } from '../../utils/logger.js';
|
|
11
12
|
import { inferFormatFromPath, loadRawJson5Content, serializeTranslationFile } from '../../utils/file-utils.js';
|
|
12
13
|
import { shouldShowFunnel, recordFunnelShown } from '../../utils/funnel-msg-tracker.js';
|
|
@@ -180,6 +181,10 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
|
|
|
180
181
|
throw new ExtractorError('Failed to process file', file, err);
|
|
181
182
|
}
|
|
182
183
|
}
|
|
184
|
+
// Normalize SWC span offsets so every span is file-relative.
|
|
185
|
+
// SWC accumulates byte offsets across successive parse() calls,
|
|
186
|
+
// so without this, plugins would see positions beyond the file length.
|
|
187
|
+
normalizeASTSpans(ast, ast.span.start);
|
|
183
188
|
// "Wire up" the visitor's scope method to the context.
|
|
184
189
|
// This avoids a circular dependency while giving plugins access to the scope.
|
|
185
190
|
pluginContext.getVarFromScope = astVisitors.getVarFromScope.bind(astVisitors);
|
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively normalizes all SWC span offsets in an AST by subtracting a base
|
|
3
|
+
* offset. SWC's `parse()` accumulates byte offsets across successive calls in
|
|
4
|
+
* the same process, so `span.start`/`span.end` values can exceed the length of
|
|
5
|
+
* the source file. Call this once on the root `Module` node right after parsing
|
|
6
|
+
* to make every span file-relative.
|
|
7
|
+
*
|
|
8
|
+
* @param node - Any AST node (or the root Module)
|
|
9
|
+
* @param base - The base offset to subtract (`ast.span.start`)
|
|
10
|
+
*/
|
|
11
|
+
function normalizeASTSpans(node, base) {
|
|
12
|
+
if (!node || typeof node !== 'object' || base === 0)
|
|
13
|
+
return;
|
|
14
|
+
// Normalize this node's own span
|
|
15
|
+
if (node.span && typeof node.span.start === 'number') {
|
|
16
|
+
node.span = {
|
|
17
|
+
...node.span,
|
|
18
|
+
start: node.span.start - base,
|
|
19
|
+
end: node.span.end - base
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// Recurse into every property (skip span itself to avoid double-processing)
|
|
23
|
+
for (const key of Object.keys(node)) {
|
|
24
|
+
if (key === 'span')
|
|
25
|
+
continue;
|
|
26
|
+
const child = node[key];
|
|
27
|
+
if (Array.isArray(child)) {
|
|
28
|
+
for (const item of child) {
|
|
29
|
+
if (item && typeof item === 'object') {
|
|
30
|
+
normalizeASTSpans(item, base);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (child && typeof child === 'object') {
|
|
35
|
+
normalizeASTSpans(child, base);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
1
39
|
/**
|
|
2
40
|
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
3
41
|
* property name from an ObjectExpression.
|
|
@@ -77,4 +115,4 @@ function getObjectPropValue(object, propName) {
|
|
|
77
115
|
return undefined;
|
|
78
116
|
}
|
|
79
117
|
|
|
80
|
-
export { getObjectPropValue, getObjectPropValueExpression, getObjectProperty, isSimpleTemplateLiteral };
|
|
118
|
+
export { getObjectPropValue, getObjectPropValueExpression, getObjectProperty, isSimpleTemplateLiteral, normalizeASTSpans };
|
package/dist/esm/linter.js
CHANGED
|
@@ -117,8 +117,11 @@ function lintInterpolationParams(ast, code, config) {
|
|
|
117
117
|
const issueLineNumber = position > -1 ? getLineNumber(position) : 1;
|
|
118
118
|
// Only check for unused parameters if there is at least one interpolation key in the string
|
|
119
119
|
if (keys.length > 0) {
|
|
120
|
+
// i18next supports nested object access via dot notation (e.g. {{author.name}} with { author }).
|
|
121
|
+
// For each interpolation key, check if the root (part before the first dot) matches a provided param.
|
|
120
122
|
for (const k of keys) {
|
|
121
|
-
|
|
123
|
+
const root = k.split('.')[0];
|
|
124
|
+
if (!paramKeys.includes(k) && !paramKeys.includes(root)) {
|
|
122
125
|
issues.push({
|
|
123
126
|
text: `Interpolation parameter "${k}" was not provided`,
|
|
124
127
|
line: issueLineNumber,
|
|
@@ -126,8 +129,10 @@ function lintInterpolationParams(ast, code, config) {
|
|
|
126
129
|
});
|
|
127
130
|
}
|
|
128
131
|
}
|
|
132
|
+
// For each provided param, check if it is used either directly or as the root of a dotted key.
|
|
129
133
|
for (const pk of paramKeys) {
|
|
130
|
-
|
|
134
|
+
const isUsed = keys.some(k => k === pk || k.split('.')[0] === pk);
|
|
135
|
+
if (!isUsed) {
|
|
131
136
|
issues.push({
|
|
132
137
|
text: `Parameter "${pk}" is not used in translation string`,
|
|
133
138
|
line: issueLineNumber,
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAMtF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAK5C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,OAAO,CAAC,CAkElB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAsGf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,sDAO3I"}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { Expression, ObjectExpression, TemplateLiteral } from '@swc/core';
|
|
2
|
+
/**
|
|
3
|
+
* Recursively normalizes all SWC span offsets in an AST by subtracting a base
|
|
4
|
+
* offset. SWC's `parse()` accumulates byte offsets across successive calls in
|
|
5
|
+
* the same process, so `span.start`/`span.end` values can exceed the length of
|
|
6
|
+
* the source file. Call this once on the root `Module` node right after parsing
|
|
7
|
+
* to make every span file-relative.
|
|
8
|
+
*
|
|
9
|
+
* @param node - Any AST node (or the root Module)
|
|
10
|
+
* @param base - The base offset to subtract (`ast.span.start`)
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeASTSpans(node: any, base: number): void;
|
|
2
13
|
/**
|
|
3
14
|
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
4
15
|
* property name from an ObjectExpression.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE1F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAKhH;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAYrH"}
|
|
1
|
+
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAc,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE1F;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA0BhE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAKhH;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAYrH"}
|
package/types/linter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AA6I3D,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;SAC1C;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,UAET,CAAA;AAC3B,eAAO,MAAM,6BAA6B,UAAgN,CAAA;AAK1P,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;gBAEvB,MAAM,EAAE,oBAAoB;IAKzC,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;CA6FV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBAkCnD;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,+GAA+G;IAC/G,IAAI,CAAC,EAAE,WAAW,GAAG,eAAe,CAAC;CACtC"}
|