i18next-cli 1.47.7 → 1.47.9
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/instrumenter/core/instrumenter.js +418 -131
- package/dist/cjs/instrumenter/core/transformer.js +53 -11
- package/dist/esm/cli.js +1 -1
- package/dist/esm/instrumenter/core/instrumenter.js +418 -131
- package/dist/esm/instrumenter/core/transformer.js +53 -11
- package/package.json +1 -1
- package/types/instrumenter/core/instrumenter.d.ts.map +1 -1
- package/types/instrumenter/core/transformer.d.ts.map +1 -1
- package/types/types.d.ts +6 -0
- package/types/types.d.ts.map +1 -1
|
@@ -29,6 +29,7 @@ function transformFile(content, file, candidates, options) {
|
|
|
29
29
|
const transformedComponents = new Set();
|
|
30
30
|
let hasComponentCandidates = false;
|
|
31
31
|
let hasNonComponentCandidates = false;
|
|
32
|
+
let hasTransCandidates = false;
|
|
32
33
|
// ── Language-change site injections ────────────────────────────────────
|
|
33
34
|
const languageChangeSites = options.languageChangeSites || [];
|
|
34
35
|
// Track components that need `i18n` from useTranslation()
|
|
@@ -82,9 +83,15 @@ function transformFile(content, file, candidates, options) {
|
|
|
82
83
|
if (replacement) {
|
|
83
84
|
s.overwrite(candidate.offset, candidate.endOffset, replacement);
|
|
84
85
|
transformCount++;
|
|
86
|
+
if (candidate.type === 'jsx-mixed') {
|
|
87
|
+
hasTransCandidates = true;
|
|
88
|
+
}
|
|
85
89
|
if (candidate.insideComponent) {
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
// jsx-mixed candidates use <Trans>, not t(), so they don't need useTranslation
|
|
91
|
+
if (candidate.type !== 'jsx-mixed') {
|
|
92
|
+
transformedComponents.add(candidate.insideComponent);
|
|
93
|
+
hasComponentCandidates = true;
|
|
94
|
+
}
|
|
88
95
|
}
|
|
89
96
|
else {
|
|
90
97
|
hasNonComponentCandidates = true;
|
|
@@ -108,17 +115,20 @@ function transformFile(content, file, candidates, options) {
|
|
|
108
115
|
const indent = detectIndent(content, comp.bodyStart);
|
|
109
116
|
const defaultNS = options.config.extract?.defaultNS ?? 'translation';
|
|
110
117
|
const nsArg = (options.namespace && options.namespace !== defaultNS) ? `'${options.namespace}'` : '';
|
|
111
|
-
// Build destructuring: include `t` if the component has string candidates
|
|
118
|
+
// Build destructuring: include `t` if the component has string candidates
|
|
119
|
+
// (but not jsx-mixed which use <Trans>),
|
|
112
120
|
// include `i18n` if the component has language-change sites.
|
|
113
|
-
const needsT = highConfidenceCandidates.some(c => c.insideComponent === comp.name);
|
|
121
|
+
const needsT = highConfidenceCandidates.some(c => c.insideComponent === comp.name && c.type !== 'jsx-mixed');
|
|
114
122
|
const needsI18n = componentsNeedingI18n.has(comp.name);
|
|
115
123
|
const parts = [];
|
|
116
124
|
if (needsT)
|
|
117
125
|
parts.push('t');
|
|
118
126
|
if (needsI18n)
|
|
119
127
|
parts.push('i18n');
|
|
120
|
-
if
|
|
121
|
-
|
|
128
|
+
// Skip if component needs neither t nor i18n
|
|
129
|
+
// (e.g. component only has jsx-mixed / <Trans> candidates)
|
|
130
|
+
if (!needsT && !needsI18n)
|
|
131
|
+
continue;
|
|
122
132
|
const destructured = `{ ${parts.join(', ')} }`;
|
|
123
133
|
s.appendRight(comp.bodyStart + 1, `\n${indent}const ${destructured} = useTranslation(${nsArg})`);
|
|
124
134
|
injections.hookInjected = true;
|
|
@@ -148,7 +158,8 @@ function transformFile(content, file, candidates, options) {
|
|
|
148
158
|
// Add import statements
|
|
149
159
|
addImportStatements(s, content, {
|
|
150
160
|
needsUseTranslation: hasComponentCandidates && options.hasReact,
|
|
151
|
-
needsI18next: hasNonComponentCandidates || !options.hasReact
|
|
161
|
+
needsI18next: hasNonComponentCandidates || !options.hasReact,
|
|
162
|
+
needsTrans: hasTransCandidates && options.hasReact
|
|
152
163
|
});
|
|
153
164
|
injections.importAdded = true;
|
|
154
165
|
}
|
|
@@ -232,7 +243,8 @@ function buildReplacement(candidate, key, useHookStyle, namespace) {
|
|
|
232
243
|
return `{${tCall}}`;
|
|
233
244
|
case 'jsx-mixed':
|
|
234
245
|
if (useHookStyle) {
|
|
235
|
-
|
|
246
|
+
const nsAttr = namespace ? ` ns="${namespace}"` : '';
|
|
247
|
+
return `<Trans i18nKey="${key}"${nsAttr}>${candidate.content}</Trans>`;
|
|
236
248
|
}
|
|
237
249
|
return candidate.content;
|
|
238
250
|
case 'template-literal':
|
|
@@ -286,12 +298,24 @@ function detectIndent(content, braceOffset) {
|
|
|
286
298
|
return ' ';
|
|
287
299
|
}
|
|
288
300
|
/**
|
|
289
|
-
* Adds necessary import statements (useTranslation and/or i18next).
|
|
301
|
+
* Adds necessary import statements (useTranslation, Trans, and/or i18next).
|
|
290
302
|
*/
|
|
291
303
|
function addImportStatements(s, content, needs) {
|
|
292
304
|
let importStatement = '';
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
// Build a combined react-i18next import
|
|
306
|
+
const reactI18nextImports = [];
|
|
307
|
+
if (needs.needsUseTranslation)
|
|
308
|
+
reactI18nextImports.push('useTranslation');
|
|
309
|
+
if (needs.needsTrans)
|
|
310
|
+
reactI18nextImports.push('Trans');
|
|
311
|
+
if (reactI18nextImports.length > 0) {
|
|
312
|
+
if (!hasImport(content, 'react-i18next')) {
|
|
313
|
+
importStatement += `import { ${reactI18nextImports.join(', ')} } from 'react-i18next'\n`;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// react-i18next is already imported — augment with any missing named exports
|
|
317
|
+
augmentReactI18nextImport(s, content, reactI18nextImports);
|
|
318
|
+
}
|
|
295
319
|
}
|
|
296
320
|
if (needs.needsI18next && !hasImport(content, 'i18next')) {
|
|
297
321
|
importStatement += "import i18next from 'i18next'\n";
|
|
@@ -316,6 +340,24 @@ function addImportStatements(s, content, needs) {
|
|
|
316
340
|
}
|
|
317
341
|
s.appendRight(insertPos, importStatement);
|
|
318
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Augments an existing `import { ... } from 'react-i18next'` with any missing
|
|
345
|
+
* named exports (e.g. adds `Trans` when only `useTranslation` is imported).
|
|
346
|
+
*/
|
|
347
|
+
function augmentReactI18nextImport(s, content, needed) {
|
|
348
|
+
const importMatch = /import\s*\{([^}]*)\}\s*from\s*['"]react-i18next['"]/.exec(content);
|
|
349
|
+
if (!importMatch)
|
|
350
|
+
return;
|
|
351
|
+
const existingImports = importMatch[1].split(',').map(x => x.trim()).filter(Boolean);
|
|
352
|
+
const toAdd = needed.filter(n => !existingImports.includes(n));
|
|
353
|
+
if (toAdd.length === 0)
|
|
354
|
+
return;
|
|
355
|
+
const newImports = [...existingImports, ...toAdd].join(', ');
|
|
356
|
+
const newImportStatement = `import { ${newImports} } from 'react-i18next'`;
|
|
357
|
+
const matchStart = importMatch.index;
|
|
358
|
+
const matchEnd = matchStart + importMatch[0].length;
|
|
359
|
+
s.overwrite(matchStart, matchEnd, newImportStatement);
|
|
360
|
+
}
|
|
319
361
|
/**
|
|
320
362
|
* Generates a unified diff showing what changed.
|
|
321
363
|
*/
|
package/dist/esm/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ const program = new Command();
|
|
|
29
29
|
program
|
|
30
30
|
.name('i18next-cli')
|
|
31
31
|
.description('A unified, high-performance i18next CLI.')
|
|
32
|
-
.version('1.47.
|
|
32
|
+
.version('1.47.9'); // This string is replaced with the actual version at build time by rollup
|
|
33
33
|
// new: global config override option
|
|
34
34
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
35
35
|
program
|