i18next-cli 1.42.4 → 1.42.6
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 +7 -7
- package/dist/cjs/extractor/core/ast-visitors.js +0 -4
- package/dist/cjs/extractor/core/extractor.js +8 -4
- package/dist/cjs/extractor/core/translation-manager.js +12 -3
- package/dist/cjs/extractor/parsers/ast-utils.js +69 -2
- package/dist/cjs/extractor/parsers/call-expression-handler.js +12 -42
- package/dist/cjs/extractor/parsers/jsx-handler.js +5 -28
- package/dist/cjs/locize.js +3 -3
- package/dist/cjs/rename-key.js +1 -1
- package/dist/cjs/status.js +1 -1
- package/dist/cjs/utils/file-utils.js +38 -0
- package/dist/esm/cli.js +7 -7
- package/dist/esm/extractor/core/ast-visitors.js +0 -4
- package/dist/esm/extractor/core/extractor.js +9 -5
- package/dist/esm/extractor/core/translation-manager.js +14 -5
- package/dist/esm/extractor/parsers/ast-utils.js +68 -3
- package/dist/esm/extractor/parsers/call-expression-handler.js +13 -43
- package/dist/esm/extractor/parsers/jsx-handler.js +6 -29
- package/dist/esm/locize.js +3 -3
- package/dist/esm/rename-key.js +1 -1
- package/dist/esm/status.js +1 -1
- package/dist/esm/utils/file-utils.js +38 -1
- package/package.json +1 -1
- package/types/extractor/core/ast-visitors.d.ts +0 -1
- package/types/extractor/core/ast-visitors.d.ts.map +1 -1
- package/types/extractor/core/extractor.d.ts.map +1 -1
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/ast-utils.d.ts +28 -2
- package/types/extractor/parsers/ast-utils.d.ts.map +1 -1
- package/types/extractor/parsers/call-expression-handler.d.ts +4 -8
- package/types/extractor/parsers/call-expression-handler.d.ts.map +1 -1
- package/types/extractor/parsers/jsx-handler.d.ts +3 -6
- package/types/extractor/parsers/jsx-handler.d.ts.map +1 -1
- package/types/utils/file-utils.d.ts +16 -0
- package/types/utils/file-utils.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.6'); // 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
|
|
@@ -202,8 +202,8 @@ program
|
|
|
202
202
|
});
|
|
203
203
|
program
|
|
204
204
|
.command('locize-sync')
|
|
205
|
-
.description('Synchronize local translations with your
|
|
206
|
-
.option('--update-values', 'Update values of existing translations on
|
|
205
|
+
.description('Synchronize local translations with your Locize project.')
|
|
206
|
+
.option('--update-values', 'Update values of existing translations on Locize.')
|
|
207
207
|
.option('--src-lng-only', 'Check for changes in source language only.')
|
|
208
208
|
.option('--compare-mtime', 'Compare modification times when syncing.')
|
|
209
209
|
.option('--dry-run', 'Run the command without making any changes.')
|
|
@@ -215,8 +215,8 @@ program
|
|
|
215
215
|
});
|
|
216
216
|
program
|
|
217
217
|
.command('locize-download')
|
|
218
|
-
.description('Download all translations from your
|
|
219
|
-
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your
|
|
218
|
+
.description('Download all translations from your Locize project.')
|
|
219
|
+
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your Locize project)')
|
|
220
220
|
.action(async (options) => {
|
|
221
221
|
const cfgPath = program.opts().config;
|
|
222
222
|
const config$1 = await config.ensureConfig(cfgPath);
|
|
@@ -224,8 +224,8 @@ program
|
|
|
224
224
|
});
|
|
225
225
|
program
|
|
226
226
|
.command('locize-migrate')
|
|
227
|
-
.description('Migrate local translation files to a new
|
|
228
|
-
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your
|
|
227
|
+
.description('Migrate local translation files to a new Locize project.')
|
|
228
|
+
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your Locize project)')
|
|
229
229
|
.action(async (options) => {
|
|
230
230
|
const cfgPath = program.opts().config;
|
|
231
231
|
const config$1 = await config.ensureConfig(cfgPath);
|
|
@@ -342,14 +342,10 @@ class ASTVisitors {
|
|
|
342
342
|
}
|
|
343
343
|
/**
|
|
344
344
|
* Sets the current file path and code used by the extractor.
|
|
345
|
-
* Also resets the search index for location tracking.
|
|
346
345
|
*/
|
|
347
346
|
setCurrentFile(file, code) {
|
|
348
347
|
this.currentFile = file;
|
|
349
348
|
this.currentCode = code;
|
|
350
|
-
// Reset search indexes when processing a new file
|
|
351
|
-
this.callExpressionHandler.resetSearchIndex();
|
|
352
|
-
this.jsxHandler.resetSearchIndex();
|
|
353
349
|
}
|
|
354
350
|
/**
|
|
355
351
|
* Returns the currently set file path.
|
|
@@ -183,10 +183,14 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
|
|
|
183
183
|
throw new validation.ExtractorError('Failed to process file', file, err);
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
// Normalize SWC span offsets so every span is file-relative.
|
|
187
|
-
// SWC accumulates byte offsets across successive parse() calls
|
|
188
|
-
//
|
|
189
|
-
|
|
186
|
+
// Normalize SWC span offsets so every span is file-relative (0-based).
|
|
187
|
+
// SWC accumulates byte offsets across successive parse() calls and uses
|
|
188
|
+
// 1-based positions, so Module.span.start points to the first token,
|
|
189
|
+
// NOT to byte 0 of the source. We derive the true base by subtracting
|
|
190
|
+
// the 0-based index of that first token in the source string.
|
|
191
|
+
const firstTokenIdx = astUtils.findFirstTokenIndex(code);
|
|
192
|
+
const spanBase = ast.span.start - firstTokenIdx;
|
|
193
|
+
astUtils.normalizeASTSpans(ast, spanBase);
|
|
190
194
|
// "Wire up" the visitor's scope method to the context.
|
|
191
195
|
// This avoids a circular dependency while giving plugins access to the scope.
|
|
192
196
|
pluginContext.getVarFromScope = astVisitors.getVarFromScope.bind(astVisitors);
|
|
@@ -815,14 +815,23 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
|
|
|
815
815
|
// LOGIC PATH 2: Separate Namespace Files
|
|
816
816
|
}
|
|
817
817
|
else {
|
|
818
|
-
// Find all namespaces that exist on disk for this locale
|
|
818
|
+
// Find all namespaces that exist on disk for this locale.
|
|
819
|
+
// Use '**' so the glob crosses directory boundaries — namespaces
|
|
820
|
+
// can contain '/' and span multiple directory levels.
|
|
819
821
|
const namespacesToProcess = new Set(keysByNS.keys());
|
|
820
|
-
const existingNsPattern = fileUtils.getOutputPath(config.extract.output, locale, '
|
|
822
|
+
const existingNsPattern = fileUtils.getOutputPath(config.extract.output, locale, '**');
|
|
821
823
|
// Ensure glob receives POSIX-style separators so pattern matching works cross-platform (Windows -> backslashes)
|
|
822
824
|
const existingNsGlobPattern = existingNsPattern.replace(/\\/g, '/');
|
|
823
825
|
const existingNsFiles = await glob.glob(existingNsGlobPattern, { ignore: userIgnore });
|
|
824
826
|
for (const file of existingNsFiles) {
|
|
825
|
-
|
|
827
|
+
// Recover the full (possibly multi-segment) namespace from the file path
|
|
828
|
+
// by matching it against the output template.
|
|
829
|
+
const ns = typeof config.extract.output === 'string'
|
|
830
|
+
? fileUtils.extractNamespaceFromPath(config.extract.output, locale, file)
|
|
831
|
+
: undefined;
|
|
832
|
+
if (ns) {
|
|
833
|
+
namespacesToProcess.add(ns);
|
|
834
|
+
}
|
|
826
835
|
}
|
|
827
836
|
// Process each namespace individually and create a result for each one
|
|
828
837
|
for (const ns of namespacesToProcess) {
|
|
@@ -1,14 +1,62 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Returns the 0-based index of the first real token in the source code,
|
|
5
|
+
* skipping leading whitespace, single-line comments (`//`), multi-line
|
|
6
|
+
* comments, and hashbang lines (`#!`).
|
|
7
|
+
*
|
|
8
|
+
* This is needed because SWC's `Module.span.start` points to the first
|
|
9
|
+
* token, not to byte 0 of the source. Knowing the first token's index
|
|
10
|
+
* lets us compute the true base offset for span normalisation:
|
|
11
|
+
* `base = ast.span.start - findFirstTokenIndex(code)`.
|
|
12
|
+
*/
|
|
13
|
+
function findFirstTokenIndex(code) {
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < code.length) {
|
|
16
|
+
const ch = code[i];
|
|
17
|
+
// Skip whitespace
|
|
18
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
19
|
+
i++;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
// Skip hashbang (only at very start of file)
|
|
23
|
+
if (i === 0 && ch === '#' && code[1] === '!') {
|
|
24
|
+
while (i < code.length && code[i] !== '\n')
|
|
25
|
+
i++;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Skip single-line comment
|
|
29
|
+
if (ch === '/' && code[i + 1] === '/') {
|
|
30
|
+
i += 2;
|
|
31
|
+
while (i < code.length && code[i] !== '\n')
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Skip multi-line comment
|
|
36
|
+
if (ch === '/' && code[i + 1] === '*') {
|
|
37
|
+
i += 2;
|
|
38
|
+
while (i < code.length && !(code[i] === '*' && code[i + 1] === '/'))
|
|
39
|
+
i++;
|
|
40
|
+
i += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
return i;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
3
47
|
/**
|
|
4
48
|
* Recursively normalizes all SWC span offsets in an AST by subtracting a base
|
|
5
49
|
* offset. SWC's `parse()` accumulates byte offsets across successive calls in
|
|
6
50
|
* the same process, so `span.start`/`span.end` values can exceed the length of
|
|
7
51
|
* the source file. Call this once on the root `Module` node right after parsing
|
|
8
|
-
* to make every span file-relative.
|
|
52
|
+
* to make every span file-relative (0-based index into the source string).
|
|
53
|
+
*
|
|
54
|
+
* The correct base is `ast.span.start - findFirstTokenIndex(code)` because
|
|
55
|
+
* SWC uses 1-based byte positions and `Module.span.start` points to the first
|
|
56
|
+
* token, not to byte 0 of the source.
|
|
9
57
|
*
|
|
10
58
|
* @param node - Any AST node (or the root Module)
|
|
11
|
-
* @param base - The base offset to subtract
|
|
59
|
+
* @param base - The base offset to subtract
|
|
12
60
|
*/
|
|
13
61
|
function normalizeASTSpans(node, base) {
|
|
14
62
|
if (!node || typeof node !== 'object' || base === 0)
|
|
@@ -38,6 +86,23 @@ function normalizeASTSpans(node, base) {
|
|
|
38
86
|
}
|
|
39
87
|
}
|
|
40
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Computes 1-based line and 0-based column numbers from a byte offset in source code.
|
|
91
|
+
*
|
|
92
|
+
* @param code - The full source code string
|
|
93
|
+
* @param offset - A character offset (e.g. from a normalised `node.span.start`)
|
|
94
|
+
* @returns `{ line, column }` or `undefined` when the offset is out of range
|
|
95
|
+
*/
|
|
96
|
+
function lineColumnFromOffset(code, offset) {
|
|
97
|
+
if (offset < 0 || offset > code.length)
|
|
98
|
+
return undefined;
|
|
99
|
+
const upTo = code.substring(0, offset);
|
|
100
|
+
const lines = upTo.split('\n');
|
|
101
|
+
return {
|
|
102
|
+
line: lines.length,
|
|
103
|
+
column: lines[lines.length - 1].length
|
|
104
|
+
};
|
|
105
|
+
}
|
|
41
106
|
/**
|
|
42
107
|
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
43
108
|
* property name from an ObjectExpression.
|
|
@@ -117,8 +182,10 @@ function getObjectPropValue(object, propName) {
|
|
|
117
182
|
return undefined;
|
|
118
183
|
}
|
|
119
184
|
|
|
185
|
+
exports.findFirstTokenIndex = findFirstTokenIndex;
|
|
120
186
|
exports.getObjectPropValue = getObjectPropValue;
|
|
121
187
|
exports.getObjectPropValueExpression = getObjectPropValueExpression;
|
|
122
188
|
exports.getObjectProperty = getObjectProperty;
|
|
123
189
|
exports.isSimpleTemplateLiteral = isSimpleTemplateLiteral;
|
|
190
|
+
exports.lineColumnFromOffset = lineColumnFromOffset;
|
|
124
191
|
exports.normalizeASTSpans = normalizeASTSpans;
|
|
@@ -12,7 +12,6 @@ class CallExpressionHandler {
|
|
|
12
12
|
objectKeys = new Set();
|
|
13
13
|
getCurrentFile;
|
|
14
14
|
getCurrentCode;
|
|
15
|
-
lastSearchIndex = 0;
|
|
16
15
|
constructor(config, pluginContext, logger, expressionResolver, getCurrentFile, getCurrentCode) {
|
|
17
16
|
this.config = config;
|
|
18
17
|
this.pluginContext = pluginContext;
|
|
@@ -22,52 +21,23 @@ class CallExpressionHandler {
|
|
|
22
21
|
this.getCurrentCode = getCurrentCode;
|
|
23
22
|
}
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.lastSearchIndex = 0;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Helper method to calculate line and column from a position in the code.
|
|
33
|
-
* Uses string searching instead of SWC span offsets to avoid accumulation bugs.
|
|
24
|
+
* Computes line and column from a node's normalised span.
|
|
25
|
+
* For call / new expressions the location points to the first argument
|
|
26
|
+
* (the translation key) rather than the callee, matching the previous
|
|
27
|
+
* string-search behaviour.
|
|
34
28
|
*/
|
|
35
29
|
getLocationFromNode(node) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// For
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
if (!node?.span || typeof node.span.start !== 'number')
|
|
31
|
+
return undefined;
|
|
32
|
+
// For call / new expressions, prefer the first argument's span
|
|
33
|
+
if ((node.type === 'CallExpression' || node.type === 'NewExpression') &&
|
|
34
|
+
node.arguments?.length > 0) {
|
|
41
35
|
const firstArg = node.arguments[0].expression;
|
|
42
|
-
if (firstArg.
|
|
43
|
-
|
|
44
|
-
searchText = firstArg.raw ?? `'${firstArg.value}'`;
|
|
45
|
-
}
|
|
46
|
-
else if (firstArg.type === 'TemplateLiteral') {
|
|
47
|
-
// For template literals, search for the backtick
|
|
48
|
-
searchText = '`';
|
|
49
|
-
}
|
|
50
|
-
else if (firstArg.type === 'ArrowFunctionExpression') {
|
|
51
|
-
searchText = '=>';
|
|
36
|
+
if (firstArg?.span && typeof firstArg.span.start === 'number') {
|
|
37
|
+
return astUtils.lineColumnFromOffset(this.getCurrentCode(), firstArg.span.start);
|
|
52
38
|
}
|
|
53
39
|
}
|
|
54
|
-
|
|
55
|
-
return undefined;
|
|
56
|
-
// Search for the text starting from last known position
|
|
57
|
-
const position = code.indexOf(searchText, this.lastSearchIndex);
|
|
58
|
-
if (position === -1) {
|
|
59
|
-
// Not found - might be a parsing issue, skip location tracking
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
// Update last search position for next search
|
|
63
|
-
this.lastSearchIndex = position + searchText.length;
|
|
64
|
-
// Calculate line and column from the position
|
|
65
|
-
const upToPosition = code.substring(0, position);
|
|
66
|
-
const lines = upToPosition.split('\n');
|
|
67
|
-
return {
|
|
68
|
-
line: lines.length,
|
|
69
|
-
column: lines[lines.length - 1].length
|
|
70
|
-
};
|
|
40
|
+
return astUtils.lineColumnFromOffset(this.getCurrentCode(), node.span.start);
|
|
71
41
|
}
|
|
72
42
|
/**
|
|
73
43
|
* Processes function call expressions and new expressions to extract translation keys.
|
|
@@ -9,7 +9,6 @@ class JSXHandler {
|
|
|
9
9
|
expressionResolver;
|
|
10
10
|
getCurrentFile;
|
|
11
11
|
getCurrentCode;
|
|
12
|
-
lastSearchIndex = 0;
|
|
13
12
|
constructor(config, pluginContext, expressionResolver, getCurrentFile, getCurrentCode) {
|
|
14
13
|
this.config = config;
|
|
15
14
|
this.pluginContext = pluginContext;
|
|
@@ -18,36 +17,14 @@ class JSXHandler {
|
|
|
18
17
|
this.getCurrentCode = getCurrentCode;
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.lastSearchIndex = 0;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Helper method to calculate line and column by searching for the JSX element in the code.
|
|
20
|
+
* Computes line and column from a node's normalised span.
|
|
21
|
+
* SWC spans are normalised to file-relative offsets after parsing,
|
|
22
|
+
* so we can use them directly.
|
|
28
23
|
*/
|
|
29
24
|
getLocationFromNode(node) {
|
|
30
|
-
|
|
31
|
-
// For JSXElement, search for the opening tag
|
|
32
|
-
let searchText;
|
|
33
|
-
if (node.type === 'JSXElement' && node.opening) {
|
|
34
|
-
const tagName = node.opening.name?.value;
|
|
35
|
-
if (tagName) {
|
|
36
|
-
searchText = `<${tagName}`;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (!searchText)
|
|
40
|
-
return undefined;
|
|
41
|
-
const position = code.indexOf(searchText, this.lastSearchIndex);
|
|
42
|
-
if (position === -1)
|
|
25
|
+
if (!node?.span || typeof node.span.start !== 'number')
|
|
43
26
|
return undefined;
|
|
44
|
-
this.
|
|
45
|
-
const upToPosition = code.substring(0, position);
|
|
46
|
-
const lines = upToPosition.split('\n');
|
|
47
|
-
return {
|
|
48
|
-
line: lines.length,
|
|
49
|
-
column: lines[lines.length - 1].length
|
|
50
|
-
};
|
|
27
|
+
return astUtils.lineColumnFromOffset(this.getCurrentCode(), node.span.start);
|
|
51
28
|
}
|
|
52
29
|
/**
|
|
53
30
|
* Processes JSX elements to extract translation keys from Trans components.
|
package/dist/cjs/locize.js
CHANGED
|
@@ -25,7 +25,7 @@ async function checkLocizeCliExists() {
|
|
|
25
25
|
catch (error) {
|
|
26
26
|
if (error.code === 'ENOENT') {
|
|
27
27
|
console.error(chalk.red('Error: `locize-cli` command not found.'));
|
|
28
|
-
console.log(chalk.yellow('Please install it globally to use the
|
|
28
|
+
console.log(chalk.yellow('Please install it globally to use the Locize integration:'));
|
|
29
29
|
console.log(chalk.cyan('npm install -g locize-cli'));
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
@@ -59,13 +59,13 @@ async function interactiveCredentialSetup(config) {
|
|
|
59
59
|
{
|
|
60
60
|
type: 'input',
|
|
61
61
|
name: 'projectId',
|
|
62
|
-
message: 'What is your
|
|
62
|
+
message: 'What is your Locize Project ID? (Find this in your project settings on www.locize.app)',
|
|
63
63
|
validate: input => !!input || 'Project ID cannot be empty.',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
type: 'password',
|
|
67
67
|
name: 'apiKey',
|
|
68
|
-
message: 'What is your
|
|
68
|
+
message: 'What is your Locize API key? (Create or use one in your project settings > "API Keys")',
|
|
69
69
|
validate: input => !!input || 'API Key cannot be empty.',
|
|
70
70
|
},
|
|
71
71
|
{
|
package/dist/cjs/rename-key.js
CHANGED
|
@@ -107,7 +107,7 @@ async function printLocizeFunnel() {
|
|
|
107
107
|
if (!(await funnelMsgTracker.shouldShowFunnel('rename-key')))
|
|
108
108
|
return;
|
|
109
109
|
console.log(chalk.yellow.bold('\n💡 Tip: Managing translations across multiple projects?'));
|
|
110
|
-
console.log(' With
|
|
110
|
+
console.log(' With Locize, you can rename, move, and copy translation keys directly');
|
|
111
111
|
console.log(' in the web interface—no CLI needed. Perfect for collaboration with');
|
|
112
112
|
console.log(' translators and managing complex refactoring across namespaces.');
|
|
113
113
|
console.log(` Learn more: ${chalk.cyan('https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
|
package/dist/cjs/status.js
CHANGED
|
@@ -361,7 +361,7 @@ async function printLocizeFunnel() {
|
|
|
361
361
|
if (!(await funnelMsgTracker.shouldShowFunnel('status')))
|
|
362
362
|
return;
|
|
363
363
|
console.log(chalk.yellow.bold('\n✨ Take your localization to the next level!'));
|
|
364
|
-
console.log('Manage translations with your team in the cloud with
|
|
364
|
+
console.log('Manage translations with your team in the cloud with Locize => https://www.locize.com/docs/getting-started');
|
|
365
365
|
console.log(`Run ${chalk.cyan('npx i18next-cli locize-migrate')} to get started.`);
|
|
366
366
|
return funnelMsgTracker.recordFunnelShown('status');
|
|
367
367
|
}
|
|
@@ -45,6 +45,43 @@ function getOutputPath(outputTemplate, language, namespace) {
|
|
|
45
45
|
out = out.replace(/\/\/+/g, '/');
|
|
46
46
|
return node_path.normalize(out);
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Extracts the namespace value from a concrete file path by matching it against
|
|
50
|
+
* the output template.
|
|
51
|
+
*
|
|
52
|
+
* Given a template like `src/{{namespace}}/locales/{{language}}.json` and a file
|
|
53
|
+
* path like `src/widgets/component/locales/en.json`, this returns `widgets/component`.
|
|
54
|
+
*
|
|
55
|
+
* This handles multi-segment namespaces (namespaces containing `/`) which
|
|
56
|
+
* `basename()` cannot recover.
|
|
57
|
+
*
|
|
58
|
+
* @param outputTemplate - The output path template string (must contain `{{namespace}}`)
|
|
59
|
+
* @param language - The language value used when the file was generated
|
|
60
|
+
* @param filePath - The concrete file path to extract the namespace from
|
|
61
|
+
* @returns The namespace string, or `undefined` if the path doesn't match the template
|
|
62
|
+
*/
|
|
63
|
+
function extractNamespaceFromPath(outputTemplate, language, filePath) {
|
|
64
|
+
// Build a regex from the template by escaping everything except the placeholders.
|
|
65
|
+
// Replace {{language}}/{{lng}} with the literal language value and
|
|
66
|
+
// {{namespace}} with a named capture group that matches one or more path segments.
|
|
67
|
+
const pattern = outputTemplate
|
|
68
|
+
// Normalise to forward slashes for matching
|
|
69
|
+
.replace(/\\/g, '/');
|
|
70
|
+
// Escape regex-special characters (but keep our placeholders intact first)
|
|
71
|
+
const nsPlaceholder = '{{namespace}}';
|
|
72
|
+
const parts = pattern.split(nsPlaceholder);
|
|
73
|
+
// Escape each part individually then rejoin with the capture group
|
|
74
|
+
const escaped = parts.map(p => p
|
|
75
|
+
.replace(/\{\{language\}\}|\{\{lng\}\}/g, () => escapeForRegex(language))
|
|
76
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
77
|
+
// Don't anchor the start — the glob may return absolute or prefixed paths.
|
|
78
|
+
// Anchor only the end so that the namespace capture is unambiguous.
|
|
79
|
+
const regexStr = escaped.join('(.+)') + '$';
|
|
80
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
81
|
+
const m = new RegExp(regexStr).exec(normalized);
|
|
82
|
+
return m?.[1];
|
|
83
|
+
}
|
|
84
|
+
const escapeForRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
48
85
|
/**
|
|
49
86
|
* Dynamically loads a translation file, supporting .json, .js, and .ts formats.
|
|
50
87
|
* @param filePath - The path to the translation file.
|
|
@@ -156,6 +193,7 @@ function inferFormatFromPath(filePath, defaultFormat = 'json') {
|
|
|
156
193
|
return defaultFormat || 'json';
|
|
157
194
|
}
|
|
158
195
|
|
|
196
|
+
exports.extractNamespaceFromPath = extractNamespaceFromPath;
|
|
159
197
|
exports.getOutputPath = getOutputPath;
|
|
160
198
|
exports.inferFormatFromPath = inferFormatFromPath;
|
|
161
199
|
exports.loadRawJson5Content = loadRawJson5Content;
|
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.6'); // 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
|
|
@@ -200,8 +200,8 @@ program
|
|
|
200
200
|
});
|
|
201
201
|
program
|
|
202
202
|
.command('locize-sync')
|
|
203
|
-
.description('Synchronize local translations with your
|
|
204
|
-
.option('--update-values', 'Update values of existing translations on
|
|
203
|
+
.description('Synchronize local translations with your Locize project.')
|
|
204
|
+
.option('--update-values', 'Update values of existing translations on Locize.')
|
|
205
205
|
.option('--src-lng-only', 'Check for changes in source language only.')
|
|
206
206
|
.option('--compare-mtime', 'Compare modification times when syncing.')
|
|
207
207
|
.option('--dry-run', 'Run the command without making any changes.')
|
|
@@ -213,8 +213,8 @@ program
|
|
|
213
213
|
});
|
|
214
214
|
program
|
|
215
215
|
.command('locize-download')
|
|
216
|
-
.description('Download all translations from your
|
|
217
|
-
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your
|
|
216
|
+
.description('Download all translations from your Locize project.')
|
|
217
|
+
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your Locize project)')
|
|
218
218
|
.action(async (options) => {
|
|
219
219
|
const cfgPath = program.opts().config;
|
|
220
220
|
const config = await ensureConfig(cfgPath);
|
|
@@ -222,8 +222,8 @@ program
|
|
|
222
222
|
});
|
|
223
223
|
program
|
|
224
224
|
.command('locize-migrate')
|
|
225
|
-
.description('Migrate local translation files to a new
|
|
226
|
-
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your
|
|
225
|
+
.description('Migrate local translation files to a new Locize project.')
|
|
226
|
+
.option('--cdn-type <standard|pro>', 'Specify the cdn endpoint that should be used (depends on which cdn type you\'ve in your Locize project)')
|
|
227
227
|
.action(async (options) => {
|
|
228
228
|
const cfgPath = program.opts().config;
|
|
229
229
|
const config = await ensureConfig(cfgPath);
|
|
@@ -340,14 +340,10 @@ class ASTVisitors {
|
|
|
340
340
|
}
|
|
341
341
|
/**
|
|
342
342
|
* Sets the current file path and code used by the extractor.
|
|
343
|
-
* Also resets the search index for location tracking.
|
|
344
343
|
*/
|
|
345
344
|
setCurrentFile(file, code) {
|
|
346
345
|
this.currentFile = file;
|
|
347
346
|
this.currentCode = code;
|
|
348
|
-
// Reset search indexes when processing a new file
|
|
349
|
-
this.callExpressionHandler.resetSearchIndex();
|
|
350
|
-
this.jsxHandler.resetSearchIndex();
|
|
351
347
|
}
|
|
352
348
|
/**
|
|
353
349
|
* Returns the currently set file path.
|
|
@@ -7,7 +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
|
+
import { findFirstTokenIndex, normalizeASTSpans } from '../parsers/ast-utils.js';
|
|
11
11
|
import { ConsoleLogger } from '../../utils/logger.js';
|
|
12
12
|
import { inferFormatFromPath, loadRawJson5Content, serializeTranslationFile } from '../../utils/file-utils.js';
|
|
13
13
|
import { shouldShowFunnel, recordFunnelShown } from '../../utils/funnel-msg-tracker.js';
|
|
@@ -181,10 +181,14 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
|
|
|
181
181
|
throw new ExtractorError('Failed to process file', file, err);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
// Normalize SWC span offsets so every span is file-relative.
|
|
185
|
-
// SWC accumulates byte offsets across successive parse() calls
|
|
186
|
-
//
|
|
187
|
-
|
|
184
|
+
// Normalize SWC span offsets so every span is file-relative (0-based).
|
|
185
|
+
// SWC accumulates byte offsets across successive parse() calls and uses
|
|
186
|
+
// 1-based positions, so Module.span.start points to the first token,
|
|
187
|
+
// NOT to byte 0 of the source. We derive the true base by subtracting
|
|
188
|
+
// the 0-based index of that first token in the source string.
|
|
189
|
+
const firstTokenIdx = findFirstTokenIndex(code);
|
|
190
|
+
const spanBase = ast.span.start - firstTokenIdx;
|
|
191
|
+
normalizeASTSpans(ast, spanBase);
|
|
188
192
|
// "Wire up" the visitor's scope method to the context.
|
|
189
193
|
// This avoids a circular dependency while giving plugins access to the scope.
|
|
190
194
|
pluginContext.getVarFromScope = astVisitors.getVarFromScope.bind(astVisitors);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { resolve
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
2
|
import { glob } from 'glob';
|
|
3
3
|
import { getNestedKeys, getNestedValue, setNestedValue } from '../../utils/nested-object.js';
|
|
4
|
-
import { getOutputPath, loadTranslationFile } from '../../utils/file-utils.js';
|
|
4
|
+
import { getOutputPath, loadTranslationFile, extractNamespaceFromPath } from '../../utils/file-utils.js';
|
|
5
5
|
import { resolveDefaultValue } from '../../utils/default-value.js';
|
|
6
6
|
|
|
7
7
|
// used for natural language check
|
|
@@ -813,14 +813,23 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
|
|
|
813
813
|
// LOGIC PATH 2: Separate Namespace Files
|
|
814
814
|
}
|
|
815
815
|
else {
|
|
816
|
-
// Find all namespaces that exist on disk for this locale
|
|
816
|
+
// Find all namespaces that exist on disk for this locale.
|
|
817
|
+
// Use '**' so the glob crosses directory boundaries — namespaces
|
|
818
|
+
// can contain '/' and span multiple directory levels.
|
|
817
819
|
const namespacesToProcess = new Set(keysByNS.keys());
|
|
818
|
-
const existingNsPattern = getOutputPath(config.extract.output, locale, '
|
|
820
|
+
const existingNsPattern = getOutputPath(config.extract.output, locale, '**');
|
|
819
821
|
// Ensure glob receives POSIX-style separators so pattern matching works cross-platform (Windows -> backslashes)
|
|
820
822
|
const existingNsGlobPattern = existingNsPattern.replace(/\\/g, '/');
|
|
821
823
|
const existingNsFiles = await glob(existingNsGlobPattern, { ignore: userIgnore });
|
|
822
824
|
for (const file of existingNsFiles) {
|
|
823
|
-
|
|
825
|
+
// Recover the full (possibly multi-segment) namespace from the file path
|
|
826
|
+
// by matching it against the output template.
|
|
827
|
+
const ns = typeof config.extract.output === 'string'
|
|
828
|
+
? extractNamespaceFromPath(config.extract.output, locale, file)
|
|
829
|
+
: undefined;
|
|
830
|
+
if (ns) {
|
|
831
|
+
namespacesToProcess.add(ns);
|
|
832
|
+
}
|
|
824
833
|
}
|
|
825
834
|
// Process each namespace individually and create a result for each one
|
|
826
835
|
for (const ns of namespacesToProcess) {
|
|
@@ -1,12 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the 0-based index of the first real token in the source code,
|
|
3
|
+
* skipping leading whitespace, single-line comments (`//`), multi-line
|
|
4
|
+
* comments, and hashbang lines (`#!`).
|
|
5
|
+
*
|
|
6
|
+
* This is needed because SWC's `Module.span.start` points to the first
|
|
7
|
+
* token, not to byte 0 of the source. Knowing the first token's index
|
|
8
|
+
* lets us compute the true base offset for span normalisation:
|
|
9
|
+
* `base = ast.span.start - findFirstTokenIndex(code)`.
|
|
10
|
+
*/
|
|
11
|
+
function findFirstTokenIndex(code) {
|
|
12
|
+
let i = 0;
|
|
13
|
+
while (i < code.length) {
|
|
14
|
+
const ch = code[i];
|
|
15
|
+
// Skip whitespace
|
|
16
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
17
|
+
i++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
// Skip hashbang (only at very start of file)
|
|
21
|
+
if (i === 0 && ch === '#' && code[1] === '!') {
|
|
22
|
+
while (i < code.length && code[i] !== '\n')
|
|
23
|
+
i++;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Skip single-line comment
|
|
27
|
+
if (ch === '/' && code[i + 1] === '/') {
|
|
28
|
+
i += 2;
|
|
29
|
+
while (i < code.length && code[i] !== '\n')
|
|
30
|
+
i++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Skip multi-line comment
|
|
34
|
+
if (ch === '/' && code[i + 1] === '*') {
|
|
35
|
+
i += 2;
|
|
36
|
+
while (i < code.length && !(code[i] === '*' && code[i + 1] === '/'))
|
|
37
|
+
i++;
|
|
38
|
+
i += 2;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
return i;
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
1
45
|
/**
|
|
2
46
|
* Recursively normalizes all SWC span offsets in an AST by subtracting a base
|
|
3
47
|
* offset. SWC's `parse()` accumulates byte offsets across successive calls in
|
|
4
48
|
* the same process, so `span.start`/`span.end` values can exceed the length of
|
|
5
49
|
* the source file. Call this once on the root `Module` node right after parsing
|
|
6
|
-
* to make every span file-relative.
|
|
50
|
+
* to make every span file-relative (0-based index into the source string).
|
|
51
|
+
*
|
|
52
|
+
* The correct base is `ast.span.start - findFirstTokenIndex(code)` because
|
|
53
|
+
* SWC uses 1-based byte positions and `Module.span.start` points to the first
|
|
54
|
+
* token, not to byte 0 of the source.
|
|
7
55
|
*
|
|
8
56
|
* @param node - Any AST node (or the root Module)
|
|
9
|
-
* @param base - The base offset to subtract
|
|
57
|
+
* @param base - The base offset to subtract
|
|
10
58
|
*/
|
|
11
59
|
function normalizeASTSpans(node, base) {
|
|
12
60
|
if (!node || typeof node !== 'object' || base === 0)
|
|
@@ -36,6 +84,23 @@ function normalizeASTSpans(node, base) {
|
|
|
36
84
|
}
|
|
37
85
|
}
|
|
38
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Computes 1-based line and 0-based column numbers from a byte offset in source code.
|
|
89
|
+
*
|
|
90
|
+
* @param code - The full source code string
|
|
91
|
+
* @param offset - A character offset (e.g. from a normalised `node.span.start`)
|
|
92
|
+
* @returns `{ line, column }` or `undefined` when the offset is out of range
|
|
93
|
+
*/
|
|
94
|
+
function lineColumnFromOffset(code, offset) {
|
|
95
|
+
if (offset < 0 || offset > code.length)
|
|
96
|
+
return undefined;
|
|
97
|
+
const upTo = code.substring(0, offset);
|
|
98
|
+
const lines = upTo.split('\n');
|
|
99
|
+
return {
|
|
100
|
+
line: lines.length,
|
|
101
|
+
column: lines[lines.length - 1].length
|
|
102
|
+
};
|
|
103
|
+
}
|
|
39
104
|
/**
|
|
40
105
|
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
41
106
|
* property name from an ObjectExpression.
|
|
@@ -115,4 +180,4 @@ function getObjectPropValue(object, propName) {
|
|
|
115
180
|
return undefined;
|
|
116
181
|
}
|
|
117
182
|
|
|
118
|
-
export { getObjectPropValue, getObjectPropValueExpression, getObjectProperty, isSimpleTemplateLiteral, normalizeASTSpans };
|
|
183
|
+
export { findFirstTokenIndex, getObjectPropValue, getObjectPropValueExpression, getObjectProperty, isSimpleTemplateLiteral, lineColumnFromOffset, normalizeASTSpans };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isSimpleTemplateLiteral, getObjectPropValue, getObjectPropValueExpression } from './ast-utils.js';
|
|
1
|
+
import { lineColumnFromOffset, isSimpleTemplateLiteral, getObjectPropValue, getObjectPropValueExpression } from './ast-utils.js';
|
|
2
2
|
|
|
3
3
|
// Helper to escape regex characters
|
|
4
4
|
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -10,7 +10,6 @@ class CallExpressionHandler {
|
|
|
10
10
|
objectKeys = new Set();
|
|
11
11
|
getCurrentFile;
|
|
12
12
|
getCurrentCode;
|
|
13
|
-
lastSearchIndex = 0;
|
|
14
13
|
constructor(config, pluginContext, logger, expressionResolver, getCurrentFile, getCurrentCode) {
|
|
15
14
|
this.config = config;
|
|
16
15
|
this.pluginContext = pluginContext;
|
|
@@ -20,52 +19,23 @@ class CallExpressionHandler {
|
|
|
20
19
|
this.getCurrentCode = getCurrentCode;
|
|
21
20
|
}
|
|
22
21
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.lastSearchIndex = 0;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Helper method to calculate line and column from a position in the code.
|
|
31
|
-
* Uses string searching instead of SWC span offsets to avoid accumulation bugs.
|
|
22
|
+
* Computes line and column from a node's normalised span.
|
|
23
|
+
* For call / new expressions the location points to the first argument
|
|
24
|
+
* (the translation key) rather than the callee, matching the previous
|
|
25
|
+
* string-search behaviour.
|
|
32
26
|
*/
|
|
33
27
|
getLocationFromNode(node) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// For
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
if (!node?.span || typeof node.span.start !== 'number')
|
|
29
|
+
return undefined;
|
|
30
|
+
// For call / new expressions, prefer the first argument's span
|
|
31
|
+
if ((node.type === 'CallExpression' || node.type === 'NewExpression') &&
|
|
32
|
+
node.arguments?.length > 0) {
|
|
39
33
|
const firstArg = node.arguments[0].expression;
|
|
40
|
-
if (firstArg.
|
|
41
|
-
|
|
42
|
-
searchText = firstArg.raw ?? `'${firstArg.value}'`;
|
|
43
|
-
}
|
|
44
|
-
else if (firstArg.type === 'TemplateLiteral') {
|
|
45
|
-
// For template literals, search for the backtick
|
|
46
|
-
searchText = '`';
|
|
47
|
-
}
|
|
48
|
-
else if (firstArg.type === 'ArrowFunctionExpression') {
|
|
49
|
-
searchText = '=>';
|
|
34
|
+
if (firstArg?.span && typeof firstArg.span.start === 'number') {
|
|
35
|
+
return lineColumnFromOffset(this.getCurrentCode(), firstArg.span.start);
|
|
50
36
|
}
|
|
51
37
|
}
|
|
52
|
-
|
|
53
|
-
return undefined;
|
|
54
|
-
// Search for the text starting from last known position
|
|
55
|
-
const position = code.indexOf(searchText, this.lastSearchIndex);
|
|
56
|
-
if (position === -1) {
|
|
57
|
-
// Not found - might be a parsing issue, skip location tracking
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
// Update last search position for next search
|
|
61
|
-
this.lastSearchIndex = position + searchText.length;
|
|
62
|
-
// Calculate line and column from the position
|
|
63
|
-
const upToPosition = code.substring(0, position);
|
|
64
|
-
const lines = upToPosition.split('\n');
|
|
65
|
-
return {
|
|
66
|
-
line: lines.length,
|
|
67
|
-
column: lines[lines.length - 1].length
|
|
68
|
-
};
|
|
38
|
+
return lineColumnFromOffset(this.getCurrentCode(), node.span.start);
|
|
69
39
|
}
|
|
70
40
|
/**
|
|
71
41
|
* Processes function call expressions and new expressions to extract translation keys.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extractFromTransComponent } from './jsx-parser.js';
|
|
2
|
-
import { getObjectPropValue } from './ast-utils.js';
|
|
2
|
+
import { lineColumnFromOffset, getObjectPropValue } from './ast-utils.js';
|
|
3
3
|
|
|
4
4
|
class JSXHandler {
|
|
5
5
|
config;
|
|
@@ -7,7 +7,6 @@ class JSXHandler {
|
|
|
7
7
|
expressionResolver;
|
|
8
8
|
getCurrentFile;
|
|
9
9
|
getCurrentCode;
|
|
10
|
-
lastSearchIndex = 0;
|
|
11
10
|
constructor(config, pluginContext, expressionResolver, getCurrentFile, getCurrentCode) {
|
|
12
11
|
this.config = config;
|
|
13
12
|
this.pluginContext = pluginContext;
|
|
@@ -16,36 +15,14 @@ class JSXHandler {
|
|
|
16
15
|
this.getCurrentCode = getCurrentCode;
|
|
17
16
|
}
|
|
18
17
|
/**
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.lastSearchIndex = 0;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Helper method to calculate line and column by searching for the JSX element in the code.
|
|
18
|
+
* Computes line and column from a node's normalised span.
|
|
19
|
+
* SWC spans are normalised to file-relative offsets after parsing,
|
|
20
|
+
* so we can use them directly.
|
|
26
21
|
*/
|
|
27
22
|
getLocationFromNode(node) {
|
|
28
|
-
|
|
29
|
-
// For JSXElement, search for the opening tag
|
|
30
|
-
let searchText;
|
|
31
|
-
if (node.type === 'JSXElement' && node.opening) {
|
|
32
|
-
const tagName = node.opening.name?.value;
|
|
33
|
-
if (tagName) {
|
|
34
|
-
searchText = `<${tagName}`;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (!searchText)
|
|
38
|
-
return undefined;
|
|
39
|
-
const position = code.indexOf(searchText, this.lastSearchIndex);
|
|
40
|
-
if (position === -1)
|
|
23
|
+
if (!node?.span || typeof node.span.start !== 'number')
|
|
41
24
|
return undefined;
|
|
42
|
-
this.
|
|
43
|
-
const upToPosition = code.substring(0, position);
|
|
44
|
-
const lines = upToPosition.split('\n');
|
|
45
|
-
return {
|
|
46
|
-
line: lines.length,
|
|
47
|
-
column: lines[lines.length - 1].length
|
|
48
|
-
};
|
|
25
|
+
return lineColumnFromOffset(this.getCurrentCode(), node.span.start);
|
|
49
26
|
}
|
|
50
27
|
/**
|
|
51
28
|
* Processes JSX elements to extract translation keys from Trans components.
|
package/dist/esm/locize.js
CHANGED
|
@@ -23,7 +23,7 @@ async function checkLocizeCliExists() {
|
|
|
23
23
|
catch (error) {
|
|
24
24
|
if (error.code === 'ENOENT') {
|
|
25
25
|
console.error(chalk.red('Error: `locize-cli` command not found.'));
|
|
26
|
-
console.log(chalk.yellow('Please install it globally to use the
|
|
26
|
+
console.log(chalk.yellow('Please install it globally to use the Locize integration:'));
|
|
27
27
|
console.log(chalk.cyan('npm install -g locize-cli'));
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
@@ -57,13 +57,13 @@ async function interactiveCredentialSetup(config) {
|
|
|
57
57
|
{
|
|
58
58
|
type: 'input',
|
|
59
59
|
name: 'projectId',
|
|
60
|
-
message: 'What is your
|
|
60
|
+
message: 'What is your Locize Project ID? (Find this in your project settings on www.locize.app)',
|
|
61
61
|
validate: input => !!input || 'Project ID cannot be empty.',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
type: 'password',
|
|
65
65
|
name: 'apiKey',
|
|
66
|
-
message: 'What is your
|
|
66
|
+
message: 'What is your Locize API key? (Create or use one in your project settings > "API Keys")',
|
|
67
67
|
validate: input => !!input || 'API Key cannot be empty.',
|
|
68
68
|
},
|
|
69
69
|
{
|
package/dist/esm/rename-key.js
CHANGED
|
@@ -105,7 +105,7 @@ async function printLocizeFunnel() {
|
|
|
105
105
|
if (!(await shouldShowFunnel('rename-key')))
|
|
106
106
|
return;
|
|
107
107
|
console.log(chalk.yellow.bold('\n💡 Tip: Managing translations across multiple projects?'));
|
|
108
|
-
console.log(' With
|
|
108
|
+
console.log(' With Locize, you can rename, move, and copy translation keys directly');
|
|
109
109
|
console.log(' in the web interface—no CLI needed. Perfect for collaboration with');
|
|
110
110
|
console.log(' translators and managing complex refactoring across namespaces.');
|
|
111
111
|
console.log(` Learn more: ${chalk.cyan('https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
|
package/dist/esm/status.js
CHANGED
|
@@ -359,7 +359,7 @@ async function printLocizeFunnel() {
|
|
|
359
359
|
if (!(await shouldShowFunnel('status')))
|
|
360
360
|
return;
|
|
361
361
|
console.log(chalk.yellow.bold('\n✨ Take your localization to the next level!'));
|
|
362
|
-
console.log('Manage translations with your team in the cloud with
|
|
362
|
+
console.log('Manage translations with your team in the cloud with Locize => https://www.locize.com/docs/getting-started');
|
|
363
363
|
console.log(`Run ${chalk.cyan('npx i18next-cli locize-migrate')} to get started.`);
|
|
364
364
|
return recordFunnelShown('status');
|
|
365
365
|
}
|
|
@@ -43,6 +43,43 @@ function getOutputPath(outputTemplate, language, namespace) {
|
|
|
43
43
|
out = out.replace(/\/\/+/g, '/');
|
|
44
44
|
return normalize(out);
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Extracts the namespace value from a concrete file path by matching it against
|
|
48
|
+
* the output template.
|
|
49
|
+
*
|
|
50
|
+
* Given a template like `src/{{namespace}}/locales/{{language}}.json` and a file
|
|
51
|
+
* path like `src/widgets/component/locales/en.json`, this returns `widgets/component`.
|
|
52
|
+
*
|
|
53
|
+
* This handles multi-segment namespaces (namespaces containing `/`) which
|
|
54
|
+
* `basename()` cannot recover.
|
|
55
|
+
*
|
|
56
|
+
* @param outputTemplate - The output path template string (must contain `{{namespace}}`)
|
|
57
|
+
* @param language - The language value used when the file was generated
|
|
58
|
+
* @param filePath - The concrete file path to extract the namespace from
|
|
59
|
+
* @returns The namespace string, or `undefined` if the path doesn't match the template
|
|
60
|
+
*/
|
|
61
|
+
function extractNamespaceFromPath(outputTemplate, language, filePath) {
|
|
62
|
+
// Build a regex from the template by escaping everything except the placeholders.
|
|
63
|
+
// Replace {{language}}/{{lng}} with the literal language value and
|
|
64
|
+
// {{namespace}} with a named capture group that matches one or more path segments.
|
|
65
|
+
const pattern = outputTemplate
|
|
66
|
+
// Normalise to forward slashes for matching
|
|
67
|
+
.replace(/\\/g, '/');
|
|
68
|
+
// Escape regex-special characters (but keep our placeholders intact first)
|
|
69
|
+
const nsPlaceholder = '{{namespace}}';
|
|
70
|
+
const parts = pattern.split(nsPlaceholder);
|
|
71
|
+
// Escape each part individually then rejoin with the capture group
|
|
72
|
+
const escaped = parts.map(p => p
|
|
73
|
+
.replace(/\{\{language\}\}|\{\{lng\}\}/g, () => escapeForRegex(language))
|
|
74
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
75
|
+
// Don't anchor the start — the glob may return absolute or prefixed paths.
|
|
76
|
+
// Anchor only the end so that the namespace capture is unambiguous.
|
|
77
|
+
const regexStr = escaped.join('(.+)') + '$';
|
|
78
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
79
|
+
const m = new RegExp(regexStr).exec(normalized);
|
|
80
|
+
return m?.[1];
|
|
81
|
+
}
|
|
82
|
+
const escapeForRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
46
83
|
/**
|
|
47
84
|
* Dynamically loads a translation file, supporting .json, .js, and .ts formats.
|
|
48
85
|
* @param filePath - The path to the translation file.
|
|
@@ -154,4 +191,4 @@ function inferFormatFromPath(filePath, defaultFormat = 'json') {
|
|
|
154
191
|
return defaultFormat || 'json';
|
|
155
192
|
}
|
|
156
193
|
|
|
157
|
-
export { getOutputPath, inferFormatFromPath, loadRawJson5Content, loadTranslationFile, serializeTranslationFile };
|
|
194
|
+
export { extractNamespaceFromPath, getOutputPath, inferFormatFromPath, loadRawJson5Content, loadTranslationFile, serializeTranslationFile };
|
package/package.json
CHANGED
|
@@ -76,7 +76,6 @@ export declare class ASTVisitors {
|
|
|
76
76
|
getVarFromScope(name: string): ScopeInfo | undefined;
|
|
77
77
|
/**
|
|
78
78
|
* Sets the current file path and code used by the extractor.
|
|
79
|
-
* Also resets the search index for location tracking.
|
|
80
79
|
*/
|
|
81
80
|
setCurrentFile(file: string, code: string): void;
|
|
82
81
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAInE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAgCzC;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IA8NZ;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D
|
|
1
|
+
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAInE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAgCzC;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IA8NZ;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D;;OAEG;IACI,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxD;;;;;;OAMG;IACI,cAAc,IAAK,MAAM;IAIhC;;OAEG;IACI,cAAc,IAAK,MAAM;CAGjC"}
|
|
@@ -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;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,
|
|
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,CA0Gf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,sDAO3I"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAgyBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EACE,uBAA+B,EAC/B,OAAe,EAChB,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAA;CACb,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAgyBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EACE,uBAA+B,EAC/B,OAAe,EAChB,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAA;CACb,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAmJ9B"}
|
|
@@ -1,15 +1,41 @@
|
|
|
1
1
|
import type { Expression, ObjectExpression, TemplateLiteral } from '@swc/core';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the 0-based index of the first real token in the source code,
|
|
4
|
+
* skipping leading whitespace, single-line comments (`//`), multi-line
|
|
5
|
+
* comments, and hashbang lines (`#!`).
|
|
6
|
+
*
|
|
7
|
+
* This is needed because SWC's `Module.span.start` points to the first
|
|
8
|
+
* token, not to byte 0 of the source. Knowing the first token's index
|
|
9
|
+
* lets us compute the true base offset for span normalisation:
|
|
10
|
+
* `base = ast.span.start - findFirstTokenIndex(code)`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findFirstTokenIndex(code: string): number;
|
|
2
13
|
/**
|
|
3
14
|
* Recursively normalizes all SWC span offsets in an AST by subtracting a base
|
|
4
15
|
* offset. SWC's `parse()` accumulates byte offsets across successive calls in
|
|
5
16
|
* the same process, so `span.start`/`span.end` values can exceed the length of
|
|
6
17
|
* the source file. Call this once on the root `Module` node right after parsing
|
|
7
|
-
* to make every span file-relative.
|
|
18
|
+
* to make every span file-relative (0-based index into the source string).
|
|
19
|
+
*
|
|
20
|
+
* The correct base is `ast.span.start - findFirstTokenIndex(code)` because
|
|
21
|
+
* SWC uses 1-based byte positions and `Module.span.start` points to the first
|
|
22
|
+
* token, not to byte 0 of the source.
|
|
8
23
|
*
|
|
9
24
|
* @param node - Any AST node (or the root Module)
|
|
10
|
-
* @param base - The base offset to subtract
|
|
25
|
+
* @param base - The base offset to subtract
|
|
11
26
|
*/
|
|
12
27
|
export declare function normalizeASTSpans(node: any, base: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Computes 1-based line and 0-based column numbers from a byte offset in source code.
|
|
30
|
+
*
|
|
31
|
+
* @param code - The full source code string
|
|
32
|
+
* @param offset - A character offset (e.g. from a normalised `node.span.start`)
|
|
33
|
+
* @returns `{ line, column }` or `undefined` when the offset is out of range
|
|
34
|
+
*/
|
|
35
|
+
export declare function lineColumnFromOffset(code: string, offset: number): {
|
|
36
|
+
line: number;
|
|
37
|
+
column: number;
|
|
38
|
+
} | undefined;
|
|
13
39
|
/**
|
|
14
40
|
* Finds and returns the full property node (KeyValueProperty) for the given
|
|
15
41
|
* 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;;;;;;;;;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"}
|
|
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,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA2BzD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA0BhE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQhH;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"}
|
|
@@ -9,16 +9,12 @@ export declare class CallExpressionHandler {
|
|
|
9
9
|
objectKeys: Set<string>;
|
|
10
10
|
private getCurrentFile;
|
|
11
11
|
private getCurrentCode;
|
|
12
|
-
private lastSearchIndex;
|
|
13
12
|
constructor(config: Omit<I18nextToolkitConfig, 'plugins'>, pluginContext: PluginContext, logger: Logger, expressionResolver: ExpressionResolver, getCurrentFile: () => string, getCurrentCode: () => string);
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Helper method to calculate line and column from a position in the code.
|
|
21
|
-
* Uses string searching instead of SWC span offsets to avoid accumulation bugs.
|
|
14
|
+
* Computes line and column from a node's normalised span.
|
|
15
|
+
* For call / new expressions the location points to the first argument
|
|
16
|
+
* (the translation key) rather than the callee, matching the previous
|
|
17
|
+
* string-search behaviour.
|
|
22
18
|
*/
|
|
23
19
|
private getLocationFromNode;
|
|
24
20
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAM1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;
|
|
1
|
+
{"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAM1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAU9B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IAgYxG;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,oBAAoB;IA6E5B,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IA6LxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
|
|
@@ -7,14 +7,11 @@ export declare class JSXHandler {
|
|
|
7
7
|
private expressionResolver;
|
|
8
8
|
private getCurrentFile;
|
|
9
9
|
private getCurrentCode;
|
|
10
|
-
private lastSearchIndex;
|
|
11
10
|
constructor(config: Omit<I18nextToolkitConfig, 'plugins'>, pluginContext: PluginContext, expressionResolver: ExpressionResolver, getCurrentFile: () => string, getCurrentCode: () => string);
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Helper method to calculate line and column by searching for the JSX element in the code.
|
|
12
|
+
* Computes line and column from a node's normalised span.
|
|
13
|
+
* SWC spans are normalised to file-relative offsets after parsing,
|
|
14
|
+
* so we can use them directly.
|
|
18
15
|
*/
|
|
19
16
|
private getLocationFromNode;
|
|
20
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI1D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;
|
|
1
|
+
{"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI1D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;IAwUjI;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IAuHlC;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;CAevB"}
|
|
@@ -48,6 +48,22 @@ export declare function writeFileAsync(filePath: string, data: string): Promise<
|
|
|
48
48
|
* - Normalizes duplicate slashes and returns a platform-correct path.
|
|
49
49
|
*/
|
|
50
50
|
export declare function getOutputPath(outputTemplate: string | ((language: string, namespace?: string) => string) | undefined, language: string, namespace?: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the namespace value from a concrete file path by matching it against
|
|
53
|
+
* the output template.
|
|
54
|
+
*
|
|
55
|
+
* Given a template like `src/{{namespace}}/locales/{{language}}.json` and a file
|
|
56
|
+
* path like `src/widgets/component/locales/en.json`, this returns `widgets/component`.
|
|
57
|
+
*
|
|
58
|
+
* This handles multi-segment namespaces (namespaces containing `/`) which
|
|
59
|
+
* `basename()` cannot recover.
|
|
60
|
+
*
|
|
61
|
+
* @param outputTemplate - The output path template string (must contain `{{namespace}}`)
|
|
62
|
+
* @param language - The language value used when the file was generated
|
|
63
|
+
* @param filePath - The concrete file path to extract the namespace from
|
|
64
|
+
* @returns The namespace string, or `undefined` if the path doesn't match the template
|
|
65
|
+
*/
|
|
66
|
+
export declare function extractNamespaceFromPath(outputTemplate: string, language: string, filePath: string): string | undefined;
|
|
51
67
|
/**
|
|
52
68
|
* Dynamically loads a translation file, supporting .json, .js, and .ts formats.
|
|
53
69
|
* @param filePath - The path to the translation file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAKpD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EACvF,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA8BR;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAwChG;AAGD,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQnF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,EAChE,WAAW,GAAE,MAAM,GAAG,MAAU,EAChC,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CA6BR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,aAAa,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,GACtE,WAAW,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,CAAC,CAO9D"}
|
|
1
|
+
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAKpD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EACvF,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA8BR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAwBpB;AAID;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAwChG;AAGD,wBAAsB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQnF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,EAChE,WAAW,GAAE,MAAM,GAAG,MAAU,EAChC,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CA6BR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,aAAa,GAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAU,GACtE,WAAW,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,CAAC,CAO9D"}
|