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.
@@ -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
- transformedComponents.add(candidate.insideComponent);
87
- hasComponentCandidates = true;
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 (parts.length === 0)
121
- parts.push('t'); // fallback
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
- return `<Trans i18nKey="${key}">${candidate.content}</Trans>`;
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
- if (needs.needsUseTranslation && !hasImport(content, 'react-i18next')) {
294
- importStatement += "import { useTranslation } from 'react-i18next'\n";
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.7'); // This string is replaced with the actual version at build time by rollup
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