i18next-cli 1.39.6 → 1.39.7

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 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.39.6'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.39.7'); // 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
@@ -246,11 +246,9 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
246
246
  // Helper to create function-prefix regex fragment
247
247
  const fnPrefixToRegex = (fnPattern) => {
248
248
  if (fnPattern.startsWith('*.')) {
249
- // '*.t' -> match anyIdentifier.t
250
249
  const suffix = fnPattern.slice(2);
251
- return `\\b[\\w$]+\\.${escapeRegex(suffix)}`; // e.g. \b[\w$]+\.t
250
+ return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
252
251
  }
253
- // exact function name (may include dot like 'i18n.t' or 'translate')
254
252
  return `\\b${escapeRegex(fnPattern)}`;
255
253
  };
256
254
  // Helper: check whether the old key exists in a given namespace (from the prebuilt map)
@@ -260,55 +258,73 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
260
258
  const set = namespaceKeyMap.get(ns);
261
259
  return !!(set && set.has(oldParts.key));
262
260
  };
263
- // Replace exact string-key usages inside function calls: fn('key') or fn(`key`) or fn("key")
264
261
  for (const fnPattern of configuredFunctions) {
265
262
  const prefix = fnPrefixToRegex(fnPattern);
266
- // 1) If moving TO the defaultNS, remove the explicit ns option and update key in one go:
267
- // t('key', { ns: 'oldNs', ... }) -> t('newKey') (or t('newKey', { otherProps }) if other props exist)
268
- // Only do this if the old key actually exists in the old namespace
263
+ //
264
+ // 1) If moving TO the defaultNS, remove the explicit ns option and update key in one go.
265
+ // Only if the old key exists in the old namespace.
266
+ //
269
267
  if (oldParts.namespace && newParts.namespace &&
270
268
  oldParts.namespace !== newParts.namespace &&
271
269
  config.extract.defaultNS === newParts.namespace &&
272
270
  hasKeyInNamespace(oldParts.namespace)) {
273
- // t('key', { ns: 'oldNs' }) -> t('key')
274
271
  const nsRegexToDefault = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
275
272
  newCode = newCode.replace(nsRegexToDefault, (match, keyQ, beforeNs, nsQ, afterNs) => {
276
273
  changes++;
277
- // Build remaining object props (everything except the ns property)
278
274
  const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
279
- // Replace the key string itself, preserving the original quote style
280
275
  let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
281
276
  if (obj) {
282
- // If other properties remain, keep them
283
277
  updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
284
278
  }
285
279
  else {
286
- // No other props — remove the options object entirely
287
280
  updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
288
281
  }
289
282
  return updated;
290
283
  });
291
284
  }
292
- // 2) Update ns option value when moving across namespaces (when options are present)
293
- // Only attempt to update the ns option if the old namespace actually contains the key.
294
- if (oldParts.namespace && newParts.namespace && oldParts.namespace !== newParts.namespace && hasKeyInNamespace(oldParts.namespace)) {
295
- // case where key is bare (e.g. t('key', { ns: 'oldNs', ... }))
285
+ //
286
+ // 2) Handle calls that include an options object with ns: 'oldNs'.
287
+ // This covers both:
288
+ // - renames *inside the same namespace* (ns stays the same, key changes),
289
+ // - renames *across namespaces* (ns changes to new namespace OR removed if new default).
290
+ // Only run if old namespace actually contains the key (to avoid touching unrelated ns calls).
291
+ //
292
+ if (oldParts.namespace && newParts.namespace && hasKeyInNamespace(oldParts.namespace)) {
296
293
  const nsRegexFullKey = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
297
- newCode = newCode.replace(nsRegexFullKey, (match) => {
294
+ newCode = newCode.replace(nsRegexFullKey, (match, keyQ, beforeNs, nsQ, afterNs) => {
298
295
  changes++;
299
- // replace ns value
300
- return match.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
296
+ // remaining props except ns
297
+ const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
298
+ // start by replacing the key (preserve original quote style)
299
+ let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
300
+ if (oldParts.namespace === newParts.namespace) {
301
+ // same namespace: keep ns value untouched, but keep other props
302
+ if (obj) {
303
+ updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
304
+ }
305
+ }
306
+ else {
307
+ // moving across namespaces
308
+ if (config.extract.defaultNS === newParts.namespace) {
309
+ // moving INTO the default namespace -> remove the ns property
310
+ if (obj) {
311
+ updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
312
+ }
313
+ else {
314
+ updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
315
+ }
316
+ }
317
+ else {
318
+ // replace ns value to new namespace
319
+ updated = updated.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
320
+ }
321
+ }
322
+ return updated;
301
323
  });
302
- // case where fullKey was used inside the string (e.g. t('ns:key', { ns: 'oldNs' }))
303
- if (oldParts.fullKey && oldParts.explicitNamespace) {
304
- const nsRegexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
305
- newCode = newCode.replace(nsRegexFull, (match) => {
306
- changes++;
307
- return match.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
308
- });
309
- }
310
324
  }
311
- // 3) Replace occurrences where the call uses the fullKey inside the string (e.g. t('ns:key'))
325
+ //
326
+ // 3) fullKey (explicitly namespaced string in call): only when user supplied a namespaced target
327
+ //
312
328
  if (oldParts.fullKey && oldParts.explicitNamespace) {
313
329
  const regexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g');
314
330
  newCode = newCode.replace(regexFull, (match) => {
@@ -317,14 +333,14 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
317
333
  return match.replace(oldParts.fullKey, replacementKey);
318
334
  });
319
335
  }
320
- // 4) Handle selector / arrow and bracket forms (these are always "key form" so safe to replace)
321
- // Selector API: dot-notation: fn(($) => $.old.key)
336
+ //
337
+ // 4) Selector / bracket forms
338
+ //
322
339
  {
323
340
  const dotRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\.${escapeRegex(oldParts.key)}\\s*\\)`, 'g');
324
341
  newCode = newCode.replace(dotRegex, (match) => {
325
342
  changes++;
326
- const replacementKey = newParts.key;
327
- return match.replace(`.${oldParts.key}`, `.${replacementKey}`);
343
+ return match.replace(`.${oldParts.key}`, `.${newParts.key}`);
328
344
  });
329
345
  const bracketRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\s*\\[\\s*(['"\`])${escapeRegex(oldParts.key)}\\2\\s*\\]\\s*\\)`, 'g');
330
346
  newCode = newCode.replace(bracketRegex, (match) => {
@@ -338,20 +354,11 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
338
354
  }
339
355
  });
340
356
  }
341
- // 5) Replace bare calls WITHOUT an options object: fn('key') -> fn('newKey')
342
- // We purposely only match when the string is directly followed by the closing paren (no comma/options).
343
- {
344
- const regexKeyNoOptions = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
345
- newCode = newCode.replace(regexKeyNoOptions, (match, q) => {
346
- changes++;
347
- const replacementKey = newParts.key;
348
- return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${q}${replacementKey}${q}`);
349
- });
350
- }
351
- // 6) Handle the case where we have fn('key', /*no ns*/ { otherProps }) and we are moving
352
- // from defaultNS to another namespace: add ns when appropriate.
353
- // This block is only relevant when moving FROM defaultNS (add ns option). Only perform it
354
- // if the old key exists in the old namespace (if we tracked one).
357
+ //
358
+ // 5) Special-case: moving FROM defaultNS to another namespace for bare calls.
359
+ // Add ns option for bare calls. This must happen *before* the plain bare-call replacement
360
+ // so the final call includes the ns option.
361
+ //
355
362
  if (oldParts.namespace && newParts.namespace &&
356
363
  oldParts.namespace !== newParts.namespace &&
357
364
  config.extract.defaultNS === oldParts.namespace &&
@@ -362,19 +369,34 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
362
369
  return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
363
370
  });
364
371
  }
365
- // 7) JSX i18nKey attribute (handles both fullKey and key)
366
- const jsxPatterns = [
367
- { orig: oldParts.fullKey, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g') },
368
- { orig: oldParts.key, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g') }
369
- ];
370
- for (const p of jsxPatterns) {
371
- newCode = newCode.replace(p.regex, (match, q) => {
372
+ //
373
+ // 6) Bare calls without options: fn('key') -> fn('newKey')
374
+ //
375
+ {
376
+ const regexKeyNoOptions = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
377
+ newCode = newCode.replace(regexKeyNoOptions, (match, q) => {
372
378
  changes++;
373
- const nsSepStr = nsSeparator === false ? ':' : nsSeparator;
374
- const replacement = (p.orig === oldParts.fullKey && oldParts.fullKey.includes(nsSepStr)) ? newParts.fullKey : newParts.key;
375
- return `i18nKey=${q}${replacement}${q}`;
379
+ const replacementKey = newParts.key;
380
+ return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${q}${replacementKey}${q}`);
376
381
  });
377
382
  }
383
+ //
384
+ // 7) JSX i18nKey attribute (handles both fullKey and key)
385
+ //
386
+ {
387
+ const jsxPatterns = [
388
+ { orig: oldParts.fullKey, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g') },
389
+ { orig: oldParts.key, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g') }
390
+ ];
391
+ for (const p of jsxPatterns) {
392
+ newCode = newCode.replace(p.regex, (match, q) => {
393
+ changes++;
394
+ const nsSepStr = nsSeparator === false ? ':' : nsSeparator;
395
+ const replacement = (p.orig === oldParts.fullKey && oldParts.fullKey.includes(nsSepStr)) ? newParts.fullKey : newParts.key;
396
+ return `i18nKey=${q}${replacement}${q}`;
397
+ });
398
+ }
399
+ }
378
400
  }
379
401
  return { newCode, changes };
380
402
  }
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.39.6'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.39.7'); // 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
@@ -244,11 +244,9 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
244
244
  // Helper to create function-prefix regex fragment
245
245
  const fnPrefixToRegex = (fnPattern) => {
246
246
  if (fnPattern.startsWith('*.')) {
247
- // '*.t' -> match anyIdentifier.t
248
247
  const suffix = fnPattern.slice(2);
249
- return `\\b[\\w$]+\\.${escapeRegex(suffix)}`; // e.g. \b[\w$]+\.t
248
+ return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
250
249
  }
251
- // exact function name (may include dot like 'i18n.t' or 'translate')
252
250
  return `\\b${escapeRegex(fnPattern)}`;
253
251
  };
254
252
  // Helper: check whether the old key exists in a given namespace (from the prebuilt map)
@@ -258,55 +256,73 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
258
256
  const set = namespaceKeyMap.get(ns);
259
257
  return !!(set && set.has(oldParts.key));
260
258
  };
261
- // Replace exact string-key usages inside function calls: fn('key') or fn(`key`) or fn("key")
262
259
  for (const fnPattern of configuredFunctions) {
263
260
  const prefix = fnPrefixToRegex(fnPattern);
264
- // 1) If moving TO the defaultNS, remove the explicit ns option and update key in one go:
265
- // t('key', { ns: 'oldNs', ... }) -> t('newKey') (or t('newKey', { otherProps }) if other props exist)
266
- // Only do this if the old key actually exists in the old namespace
261
+ //
262
+ // 1) If moving TO the defaultNS, remove the explicit ns option and update key in one go.
263
+ // Only if the old key exists in the old namespace.
264
+ //
267
265
  if (oldParts.namespace && newParts.namespace &&
268
266
  oldParts.namespace !== newParts.namespace &&
269
267
  config.extract.defaultNS === newParts.namespace &&
270
268
  hasKeyInNamespace(oldParts.namespace)) {
271
- // t('key', { ns: 'oldNs' }) -> t('key')
272
269
  const nsRegexToDefault = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
273
270
  newCode = newCode.replace(nsRegexToDefault, (match, keyQ, beforeNs, nsQ, afterNs) => {
274
271
  changes++;
275
- // Build remaining object props (everything except the ns property)
276
272
  const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
277
- // Replace the key string itself, preserving the original quote style
278
273
  let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
279
274
  if (obj) {
280
- // If other properties remain, keep them
281
275
  updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
282
276
  }
283
277
  else {
284
- // No other props — remove the options object entirely
285
278
  updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
286
279
  }
287
280
  return updated;
288
281
  });
289
282
  }
290
- // 2) Update ns option value when moving across namespaces (when options are present)
291
- // Only attempt to update the ns option if the old namespace actually contains the key.
292
- if (oldParts.namespace && newParts.namespace && oldParts.namespace !== newParts.namespace && hasKeyInNamespace(oldParts.namespace)) {
293
- // case where key is bare (e.g. t('key', { ns: 'oldNs', ... }))
283
+ //
284
+ // 2) Handle calls that include an options object with ns: 'oldNs'.
285
+ // This covers both:
286
+ // - renames *inside the same namespace* (ns stays the same, key changes),
287
+ // - renames *across namespaces* (ns changes to new namespace OR removed if new default).
288
+ // Only run if old namespace actually contains the key (to avoid touching unrelated ns calls).
289
+ //
290
+ if (oldParts.namespace && newParts.namespace && hasKeyInNamespace(oldParts.namespace)) {
294
291
  const nsRegexFullKey = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
295
- newCode = newCode.replace(nsRegexFullKey, (match) => {
292
+ newCode = newCode.replace(nsRegexFullKey, (match, keyQ, beforeNs, nsQ, afterNs) => {
296
293
  changes++;
297
- // replace ns value
298
- return match.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
294
+ // remaining props except ns
295
+ const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
296
+ // start by replacing the key (preserve original quote style)
297
+ let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
298
+ if (oldParts.namespace === newParts.namespace) {
299
+ // same namespace: keep ns value untouched, but keep other props
300
+ if (obj) {
301
+ updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
302
+ }
303
+ }
304
+ else {
305
+ // moving across namespaces
306
+ if (config.extract.defaultNS === newParts.namespace) {
307
+ // moving INTO the default namespace -> remove the ns property
308
+ if (obj) {
309
+ updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
310
+ }
311
+ else {
312
+ updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
313
+ }
314
+ }
315
+ else {
316
+ // replace ns value to new namespace
317
+ updated = updated.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
318
+ }
319
+ }
320
+ return updated;
299
321
  });
300
- // case where fullKey was used inside the string (e.g. t('ns:key', { ns: 'oldNs' }))
301
- if (oldParts.fullKey && oldParts.explicitNamespace) {
302
- const nsRegexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
303
- newCode = newCode.replace(nsRegexFull, (match) => {
304
- changes++;
305
- return match.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
306
- });
307
- }
308
322
  }
309
- // 3) Replace occurrences where the call uses the fullKey inside the string (e.g. t('ns:key'))
323
+ //
324
+ // 3) fullKey (explicitly namespaced string in call): only when user supplied a namespaced target
325
+ //
310
326
  if (oldParts.fullKey && oldParts.explicitNamespace) {
311
327
  const regexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g');
312
328
  newCode = newCode.replace(regexFull, (match) => {
@@ -315,14 +331,14 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
315
331
  return match.replace(oldParts.fullKey, replacementKey);
316
332
  });
317
333
  }
318
- // 4) Handle selector / arrow and bracket forms (these are always "key form" so safe to replace)
319
- // Selector API: dot-notation: fn(($) => $.old.key)
334
+ //
335
+ // 4) Selector / bracket forms
336
+ //
320
337
  {
321
338
  const dotRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\.${escapeRegex(oldParts.key)}\\s*\\)`, 'g');
322
339
  newCode = newCode.replace(dotRegex, (match) => {
323
340
  changes++;
324
- const replacementKey = newParts.key;
325
- return match.replace(`.${oldParts.key}`, `.${replacementKey}`);
341
+ return match.replace(`.${oldParts.key}`, `.${newParts.key}`);
326
342
  });
327
343
  const bracketRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\s*\\[\\s*(['"\`])${escapeRegex(oldParts.key)}\\2\\s*\\]\\s*\\)`, 'g');
328
344
  newCode = newCode.replace(bracketRegex, (match) => {
@@ -336,20 +352,11 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
336
352
  }
337
353
  });
338
354
  }
339
- // 5) Replace bare calls WITHOUT an options object: fn('key') -> fn('newKey')
340
- // We purposely only match when the string is directly followed by the closing paren (no comma/options).
341
- {
342
- const regexKeyNoOptions = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
343
- newCode = newCode.replace(regexKeyNoOptions, (match, q) => {
344
- changes++;
345
- const replacementKey = newParts.key;
346
- return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${q}${replacementKey}${q}`);
347
- });
348
- }
349
- // 6) Handle the case where we have fn('key', /*no ns*/ { otherProps }) and we are moving
350
- // from defaultNS to another namespace: add ns when appropriate.
351
- // This block is only relevant when moving FROM defaultNS (add ns option). Only perform it
352
- // if the old key exists in the old namespace (if we tracked one).
355
+ //
356
+ // 5) Special-case: moving FROM defaultNS to another namespace for bare calls.
357
+ // Add ns option for bare calls. This must happen *before* the plain bare-call replacement
358
+ // so the final call includes the ns option.
359
+ //
353
360
  if (oldParts.namespace && newParts.namespace &&
354
361
  oldParts.namespace !== newParts.namespace &&
355
362
  config.extract.defaultNS === oldParts.namespace &&
@@ -360,19 +367,34 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
360
367
  return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
361
368
  });
362
369
  }
363
- // 7) JSX i18nKey attribute (handles both fullKey and key)
364
- const jsxPatterns = [
365
- { orig: oldParts.fullKey, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g') },
366
- { orig: oldParts.key, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g') }
367
- ];
368
- for (const p of jsxPatterns) {
369
- newCode = newCode.replace(p.regex, (match, q) => {
370
+ //
371
+ // 6) Bare calls without options: fn('key') -> fn('newKey')
372
+ //
373
+ {
374
+ const regexKeyNoOptions = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
375
+ newCode = newCode.replace(regexKeyNoOptions, (match, q) => {
370
376
  changes++;
371
- const nsSepStr = nsSeparator === false ? ':' : nsSeparator;
372
- const replacement = (p.orig === oldParts.fullKey && oldParts.fullKey.includes(nsSepStr)) ? newParts.fullKey : newParts.key;
373
- return `i18nKey=${q}${replacement}${q}`;
377
+ const replacementKey = newParts.key;
378
+ return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${q}${replacementKey}${q}`);
374
379
  });
375
380
  }
381
+ //
382
+ // 7) JSX i18nKey attribute (handles both fullKey and key)
383
+ //
384
+ {
385
+ const jsxPatterns = [
386
+ { orig: oldParts.fullKey, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g') },
387
+ { orig: oldParts.key, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g') }
388
+ ];
389
+ for (const p of jsxPatterns) {
390
+ newCode = newCode.replace(p.regex, (match, q) => {
391
+ changes++;
392
+ const nsSepStr = nsSeparator === false ? ':' : nsSeparator;
393
+ const replacement = (p.orig === oldParts.fullKey && oldParts.fullKey.includes(nsSepStr)) ? newParts.fullKey : newParts.key;
394
+ return `i18nKey=${q}${replacement}${q}`;
395
+ });
396
+ }
397
+ }
376
398
  }
377
399
  return { newCode, changes };
378
400
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.39.6",
3
+ "version": "1.39.7",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {