i18next-cli 1.39.6 → 1.39.8
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/rename-key.js +121 -64
- package/dist/esm/cli.js +1 -1
- package/dist/esm/rename-key.js +121 -64
- package/package.json +1 -1
- package/types/rename-key.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.39.
|
|
31
|
+
.version('1.39.8'); // 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
|
package/dist/cjs/rename-key.js
CHANGED
|
@@ -56,13 +56,21 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger$1 = new
|
|
|
56
56
|
// Check for conflicts in translation files
|
|
57
57
|
const conflicts = await checkConflicts(newParts, config);
|
|
58
58
|
if (conflicts.length > 0) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
// If the old key doesn't exist in any translation file, treat this as a
|
|
60
|
+
// no-op (allow the command to succeed). This mirrors previous behavior
|
|
61
|
+
// where renaming a missing key doesn't fail just because the target
|
|
62
|
+
// already exists (it avoids blocking repeated/no-op renames).
|
|
63
|
+
const oldExists = await checkOldKeyExists(parseKeyWithNamespace(oldKey, config), config);
|
|
64
|
+
if (oldExists) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
sourceFiles: [],
|
|
68
|
+
translationFiles: [],
|
|
69
|
+
conflicts,
|
|
70
|
+
error: 'Target key already exists in translation files'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// otherwise: old key not present -> continue (no-op)
|
|
66
74
|
}
|
|
67
75
|
// Build a quick map of which namespaces contain which keys (union across locales).
|
|
68
76
|
// This allows us to decide, per-call, whether an explicit `{ ns: 'x' }` refers to
|
|
@@ -155,6 +163,25 @@ async function checkConflicts(newParts, config) {
|
|
|
155
163
|
}
|
|
156
164
|
return conflicts;
|
|
157
165
|
}
|
|
166
|
+
async function checkOldKeyExists(oldParts, config) {
|
|
167
|
+
const keySeparator = config.extract.keySeparator ?? '.';
|
|
168
|
+
for (const locale of config.locales) {
|
|
169
|
+
const outputPath = fileUtils.getOutputPath(config.extract.output, locale, oldParts.namespace);
|
|
170
|
+
const fullPath = node_path.resolve(process.cwd(), outputPath);
|
|
171
|
+
try {
|
|
172
|
+
const translations = await fileUtils.loadTranslationFile(fullPath);
|
|
173
|
+
if (translations) {
|
|
174
|
+
const val = nestedObject.getNestedValue(translations, oldParts.key, keySeparator);
|
|
175
|
+
if (val !== undefined)
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// file missing — continue to next locale
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
158
185
|
async function buildNamespaceKeyMap(config) {
|
|
159
186
|
// Map namespace -> set of flattened keys present in that namespace (union across locales)
|
|
160
187
|
const map = new Map();
|
|
@@ -246,11 +273,9 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
246
273
|
// Helper to create function-prefix regex fragment
|
|
247
274
|
const fnPrefixToRegex = (fnPattern) => {
|
|
248
275
|
if (fnPattern.startsWith('*.')) {
|
|
249
|
-
// '*.t' -> match anyIdentifier.t
|
|
250
276
|
const suffix = fnPattern.slice(2);
|
|
251
|
-
return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
|
|
277
|
+
return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
|
|
252
278
|
}
|
|
253
|
-
// exact function name (may include dot like 'i18n.t' or 'translate')
|
|
254
279
|
return `\\b${escapeRegex(fnPattern)}`;
|
|
255
280
|
};
|
|
256
281
|
// Helper: check whether the old key exists in a given namespace (from the prebuilt map)
|
|
@@ -260,55 +285,73 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
260
285
|
const set = namespaceKeyMap.get(ns);
|
|
261
286
|
return !!(set && set.has(oldParts.key));
|
|
262
287
|
};
|
|
263
|
-
// Replace exact string-key usages inside function calls: fn('key') or fn(`key`) or fn("key")
|
|
264
288
|
for (const fnPattern of configuredFunctions) {
|
|
265
289
|
const prefix = fnPrefixToRegex(fnPattern);
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
290
|
+
//
|
|
291
|
+
// 1) If moving TO the defaultNS, remove the explicit ns option and update key in one go.
|
|
292
|
+
// Only if the old key exists in the old namespace.
|
|
293
|
+
//
|
|
269
294
|
if (oldParts.namespace && newParts.namespace &&
|
|
270
295
|
oldParts.namespace !== newParts.namespace &&
|
|
271
296
|
config.extract.defaultNS === newParts.namespace &&
|
|
272
297
|
hasKeyInNamespace(oldParts.namespace)) {
|
|
273
|
-
// t('key', { ns: 'oldNs' }) -> t('key')
|
|
274
298
|
const nsRegexToDefault = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
275
299
|
newCode = newCode.replace(nsRegexToDefault, (match, keyQ, beforeNs, nsQ, afterNs) => {
|
|
276
300
|
changes++;
|
|
277
|
-
// Build remaining object props (everything except the ns property)
|
|
278
301
|
const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
|
|
279
|
-
// Replace the key string itself, preserving the original quote style
|
|
280
302
|
let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
|
|
281
303
|
if (obj) {
|
|
282
|
-
// If other properties remain, keep them
|
|
283
304
|
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
284
305
|
}
|
|
285
306
|
else {
|
|
286
|
-
// No other props — remove the options object entirely
|
|
287
307
|
updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
|
|
288
308
|
}
|
|
289
309
|
return updated;
|
|
290
310
|
});
|
|
291
311
|
}
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
312
|
+
//
|
|
313
|
+
// 2) Handle calls that include an options object with ns: 'oldNs'.
|
|
314
|
+
// This covers both:
|
|
315
|
+
// - renames *inside the same namespace* (ns stays the same, key changes),
|
|
316
|
+
// - renames *across namespaces* (ns changes to new namespace OR removed if new default).
|
|
317
|
+
// Only run if old namespace actually contains the key (to avoid touching unrelated ns calls).
|
|
318
|
+
//
|
|
319
|
+
if (oldParts.namespace && newParts.namespace && hasKeyInNamespace(oldParts.namespace)) {
|
|
296
320
|
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) => {
|
|
321
|
+
newCode = newCode.replace(nsRegexFullKey, (match, keyQ, beforeNs, nsQ, afterNs) => {
|
|
298
322
|
changes++;
|
|
299
|
-
//
|
|
300
|
-
|
|
323
|
+
// remaining props except ns
|
|
324
|
+
const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
|
|
325
|
+
// start by replacing the key (preserve original quote style)
|
|
326
|
+
let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
|
|
327
|
+
if (oldParts.namespace === newParts.namespace) {
|
|
328
|
+
// same namespace: keep ns value untouched, but keep other props
|
|
329
|
+
if (obj) {
|
|
330
|
+
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// moving across namespaces
|
|
335
|
+
if (config.extract.defaultNS === newParts.namespace) {
|
|
336
|
+
// moving INTO the default namespace -> remove the ns property
|
|
337
|
+
if (obj) {
|
|
338
|
+
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
// replace ns value to new namespace
|
|
346
|
+
updated = updated.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return updated;
|
|
301
350
|
});
|
|
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
351
|
}
|
|
311
|
-
//
|
|
352
|
+
//
|
|
353
|
+
// 3) fullKey (explicitly namespaced string in call): only when user supplied a namespaced target
|
|
354
|
+
//
|
|
312
355
|
if (oldParts.fullKey && oldParts.explicitNamespace) {
|
|
313
356
|
const regexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g');
|
|
314
357
|
newCode = newCode.replace(regexFull, (match) => {
|
|
@@ -317,14 +360,14 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
317
360
|
return match.replace(oldParts.fullKey, replacementKey);
|
|
318
361
|
});
|
|
319
362
|
}
|
|
320
|
-
//
|
|
321
|
-
// Selector
|
|
363
|
+
//
|
|
364
|
+
// 4) Selector / bracket forms
|
|
365
|
+
//
|
|
322
366
|
{
|
|
323
367
|
const dotRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\.${escapeRegex(oldParts.key)}\\s*\\)`, 'g');
|
|
324
368
|
newCode = newCode.replace(dotRegex, (match) => {
|
|
325
369
|
changes++;
|
|
326
|
-
|
|
327
|
-
return match.replace(`.${oldParts.key}`, `.${replacementKey}`);
|
|
370
|
+
return match.replace(`.${oldParts.key}`, `.${newParts.key}`);
|
|
328
371
|
});
|
|
329
372
|
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
373
|
newCode = newCode.replace(bracketRegex, (match) => {
|
|
@@ -338,20 +381,11 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
338
381
|
}
|
|
339
382
|
});
|
|
340
383
|
}
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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).
|
|
384
|
+
//
|
|
385
|
+
// 5) Special-case: moving FROM defaultNS to another namespace for bare calls.
|
|
386
|
+
// Add ns option for bare calls. This must happen *before* the plain bare-call replacement
|
|
387
|
+
// so the final call includes the ns option.
|
|
388
|
+
//
|
|
355
389
|
if (oldParts.namespace && newParts.namespace &&
|
|
356
390
|
oldParts.namespace !== newParts.namespace &&
|
|
357
391
|
config.extract.defaultNS === oldParts.namespace &&
|
|
@@ -362,18 +396,41 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
362
396
|
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
|
|
363
397
|
});
|
|
364
398
|
}
|
|
399
|
+
//
|
|
400
|
+
// 6) Bare calls without options: fn('key') -> fn('newKey')
|
|
401
|
+
// Apply this replacement only when the old key's namespace is the
|
|
402
|
+
// *effective* default namespace (config.extract.defaultNS ?? 'translation').
|
|
403
|
+
// This preserves previous behaviour: default-namespace bare-calls are
|
|
404
|
+
// considered "key form" and should be rewritten even when the translation
|
|
405
|
+
// file exists but the specific key isn't present.
|
|
406
|
+
//
|
|
407
|
+
{
|
|
408
|
+
const effectiveDefaultNS = config.extract.defaultNS ?? 'translation';
|
|
409
|
+
if (oldParts.namespace === effectiveDefaultNS) {
|
|
410
|
+
const regexKeyNoOptions = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
|
|
411
|
+
newCode = newCode.replace(regexKeyNoOptions, (match, q) => {
|
|
412
|
+
changes++;
|
|
413
|
+
const replacementKey = newParts.key;
|
|
414
|
+
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${q}${replacementKey}${q}`);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
//
|
|
365
419
|
// 7) JSX i18nKey attribute (handles both fullKey and key)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
420
|
+
//
|
|
421
|
+
{
|
|
422
|
+
const jsxPatterns = [
|
|
423
|
+
{ orig: oldParts.fullKey, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g') },
|
|
424
|
+
{ orig: oldParts.key, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g') }
|
|
425
|
+
];
|
|
426
|
+
for (const p of jsxPatterns) {
|
|
427
|
+
newCode = newCode.replace(p.regex, (match, q) => {
|
|
428
|
+
changes++;
|
|
429
|
+
const nsSepStr = nsSeparator === false ? ':' : nsSeparator;
|
|
430
|
+
const replacement = (p.orig === oldParts.fullKey && oldParts.fullKey.includes(nsSepStr)) ? newParts.fullKey : newParts.key;
|
|
431
|
+
return `i18nKey=${q}${replacement}${q}`;
|
|
432
|
+
});
|
|
433
|
+
}
|
|
377
434
|
}
|
|
378
435
|
}
|
|
379
436
|
return { newCode, changes };
|
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.
|
|
29
|
+
.version('1.39.8'); // 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
|
package/dist/esm/rename-key.js
CHANGED
|
@@ -54,13 +54,21 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger = new C
|
|
|
54
54
|
// Check for conflicts in translation files
|
|
55
55
|
const conflicts = await checkConflicts(newParts, config);
|
|
56
56
|
if (conflicts.length > 0) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
// If the old key doesn't exist in any translation file, treat this as a
|
|
58
|
+
// no-op (allow the command to succeed). This mirrors previous behavior
|
|
59
|
+
// where renaming a missing key doesn't fail just because the target
|
|
60
|
+
// already exists (it avoids blocking repeated/no-op renames).
|
|
61
|
+
const oldExists = await checkOldKeyExists(parseKeyWithNamespace(oldKey, config), config);
|
|
62
|
+
if (oldExists) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
sourceFiles: [],
|
|
66
|
+
translationFiles: [],
|
|
67
|
+
conflicts,
|
|
68
|
+
error: 'Target key already exists in translation files'
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// otherwise: old key not present -> continue (no-op)
|
|
64
72
|
}
|
|
65
73
|
// Build a quick map of which namespaces contain which keys (union across locales).
|
|
66
74
|
// This allows us to decide, per-call, whether an explicit `{ ns: 'x' }` refers to
|
|
@@ -153,6 +161,25 @@ async function checkConflicts(newParts, config) {
|
|
|
153
161
|
}
|
|
154
162
|
return conflicts;
|
|
155
163
|
}
|
|
164
|
+
async function checkOldKeyExists(oldParts, config) {
|
|
165
|
+
const keySeparator = config.extract.keySeparator ?? '.';
|
|
166
|
+
for (const locale of config.locales) {
|
|
167
|
+
const outputPath = getOutputPath(config.extract.output, locale, oldParts.namespace);
|
|
168
|
+
const fullPath = resolve(process.cwd(), outputPath);
|
|
169
|
+
try {
|
|
170
|
+
const translations = await loadTranslationFile(fullPath);
|
|
171
|
+
if (translations) {
|
|
172
|
+
const val = getNestedValue(translations, oldParts.key, keySeparator);
|
|
173
|
+
if (val !== undefined)
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// file missing — continue to next locale
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
156
183
|
async function buildNamespaceKeyMap(config) {
|
|
157
184
|
// Map namespace -> set of flattened keys present in that namespace (union across locales)
|
|
158
185
|
const map = new Map();
|
|
@@ -244,11 +271,9 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
244
271
|
// Helper to create function-prefix regex fragment
|
|
245
272
|
const fnPrefixToRegex = (fnPattern) => {
|
|
246
273
|
if (fnPattern.startsWith('*.')) {
|
|
247
|
-
// '*.t' -> match anyIdentifier.t
|
|
248
274
|
const suffix = fnPattern.slice(2);
|
|
249
|
-
return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
|
|
275
|
+
return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
|
|
250
276
|
}
|
|
251
|
-
// exact function name (may include dot like 'i18n.t' or 'translate')
|
|
252
277
|
return `\\b${escapeRegex(fnPattern)}`;
|
|
253
278
|
};
|
|
254
279
|
// Helper: check whether the old key exists in a given namespace (from the prebuilt map)
|
|
@@ -258,55 +283,73 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
258
283
|
const set = namespaceKeyMap.get(ns);
|
|
259
284
|
return !!(set && set.has(oldParts.key));
|
|
260
285
|
};
|
|
261
|
-
// Replace exact string-key usages inside function calls: fn('key') or fn(`key`) or fn("key")
|
|
262
286
|
for (const fnPattern of configuredFunctions) {
|
|
263
287
|
const prefix = fnPrefixToRegex(fnPattern);
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
288
|
+
//
|
|
289
|
+
// 1) If moving TO the defaultNS, remove the explicit ns option and update key in one go.
|
|
290
|
+
// Only if the old key exists in the old namespace.
|
|
291
|
+
//
|
|
267
292
|
if (oldParts.namespace && newParts.namespace &&
|
|
268
293
|
oldParts.namespace !== newParts.namespace &&
|
|
269
294
|
config.extract.defaultNS === newParts.namespace &&
|
|
270
295
|
hasKeyInNamespace(oldParts.namespace)) {
|
|
271
|
-
// t('key', { ns: 'oldNs' }) -> t('key')
|
|
272
296
|
const nsRegexToDefault = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
273
297
|
newCode = newCode.replace(nsRegexToDefault, (match, keyQ, beforeNs, nsQ, afterNs) => {
|
|
274
298
|
changes++;
|
|
275
|
-
// Build remaining object props (everything except the ns property)
|
|
276
299
|
const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
|
|
277
|
-
// Replace the key string itself, preserving the original quote style
|
|
278
300
|
let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
|
|
279
301
|
if (obj) {
|
|
280
|
-
// If other properties remain, keep them
|
|
281
302
|
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
282
303
|
}
|
|
283
304
|
else {
|
|
284
|
-
// No other props — remove the options object entirely
|
|
285
305
|
updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
|
|
286
306
|
}
|
|
287
307
|
return updated;
|
|
288
308
|
});
|
|
289
309
|
}
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
310
|
+
//
|
|
311
|
+
// 2) Handle calls that include an options object with ns: 'oldNs'.
|
|
312
|
+
// This covers both:
|
|
313
|
+
// - renames *inside the same namespace* (ns stays the same, key changes),
|
|
314
|
+
// - renames *across namespaces* (ns changes to new namespace OR removed if new default).
|
|
315
|
+
// Only run if old namespace actually contains the key (to avoid touching unrelated ns calls).
|
|
316
|
+
//
|
|
317
|
+
if (oldParts.namespace && newParts.namespace && hasKeyInNamespace(oldParts.namespace)) {
|
|
294
318
|
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) => {
|
|
319
|
+
newCode = newCode.replace(nsRegexFullKey, (match, keyQ, beforeNs, nsQ, afterNs) => {
|
|
296
320
|
changes++;
|
|
297
|
-
//
|
|
298
|
-
|
|
321
|
+
// remaining props except ns
|
|
322
|
+
const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
|
|
323
|
+
// start by replacing the key (preserve original quote style)
|
|
324
|
+
let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
|
|
325
|
+
if (oldParts.namespace === newParts.namespace) {
|
|
326
|
+
// same namespace: keep ns value untouched, but keep other props
|
|
327
|
+
if (obj) {
|
|
328
|
+
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
// moving across namespaces
|
|
333
|
+
if (config.extract.defaultNS === newParts.namespace) {
|
|
334
|
+
// moving INTO the default namespace -> remove the ns property
|
|
335
|
+
if (obj) {
|
|
336
|
+
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// replace ns value to new namespace
|
|
344
|
+
updated = updated.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return updated;
|
|
299
348
|
});
|
|
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
349
|
}
|
|
309
|
-
//
|
|
350
|
+
//
|
|
351
|
+
// 3) fullKey (explicitly namespaced string in call): only when user supplied a namespaced target
|
|
352
|
+
//
|
|
310
353
|
if (oldParts.fullKey && oldParts.explicitNamespace) {
|
|
311
354
|
const regexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g');
|
|
312
355
|
newCode = newCode.replace(regexFull, (match) => {
|
|
@@ -315,14 +358,14 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
315
358
|
return match.replace(oldParts.fullKey, replacementKey);
|
|
316
359
|
});
|
|
317
360
|
}
|
|
318
|
-
//
|
|
319
|
-
// Selector
|
|
361
|
+
//
|
|
362
|
+
// 4) Selector / bracket forms
|
|
363
|
+
//
|
|
320
364
|
{
|
|
321
365
|
const dotRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\.${escapeRegex(oldParts.key)}\\s*\\)`, 'g');
|
|
322
366
|
newCode = newCode.replace(dotRegex, (match) => {
|
|
323
367
|
changes++;
|
|
324
|
-
|
|
325
|
-
return match.replace(`.${oldParts.key}`, `.${replacementKey}`);
|
|
368
|
+
return match.replace(`.${oldParts.key}`, `.${newParts.key}`);
|
|
326
369
|
});
|
|
327
370
|
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
371
|
newCode = newCode.replace(bracketRegex, (match) => {
|
|
@@ -336,20 +379,11 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
336
379
|
}
|
|
337
380
|
});
|
|
338
381
|
}
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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).
|
|
382
|
+
//
|
|
383
|
+
// 5) Special-case: moving FROM defaultNS to another namespace for bare calls.
|
|
384
|
+
// Add ns option for bare calls. This must happen *before* the plain bare-call replacement
|
|
385
|
+
// so the final call includes the ns option.
|
|
386
|
+
//
|
|
353
387
|
if (oldParts.namespace && newParts.namespace &&
|
|
354
388
|
oldParts.namespace !== newParts.namespace &&
|
|
355
389
|
config.extract.defaultNS === oldParts.namespace &&
|
|
@@ -360,18 +394,41 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
360
394
|
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
|
|
361
395
|
});
|
|
362
396
|
}
|
|
397
|
+
//
|
|
398
|
+
// 6) Bare calls without options: fn('key') -> fn('newKey')
|
|
399
|
+
// Apply this replacement only when the old key's namespace is the
|
|
400
|
+
// *effective* default namespace (config.extract.defaultNS ?? 'translation').
|
|
401
|
+
// This preserves previous behaviour: default-namespace bare-calls are
|
|
402
|
+
// considered "key form" and should be rewritten even when the translation
|
|
403
|
+
// file exists but the specific key isn't present.
|
|
404
|
+
//
|
|
405
|
+
{
|
|
406
|
+
const effectiveDefaultNS = config.extract.defaultNS ?? 'translation';
|
|
407
|
+
if (oldParts.namespace === effectiveDefaultNS) {
|
|
408
|
+
const regexKeyNoOptions = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
|
|
409
|
+
newCode = newCode.replace(regexKeyNoOptions, (match, q) => {
|
|
410
|
+
changes++;
|
|
411
|
+
const replacementKey = newParts.key;
|
|
412
|
+
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${q}${replacementKey}${q}`);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
//
|
|
363
417
|
// 7) JSX i18nKey attribute (handles both fullKey and key)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
418
|
+
//
|
|
419
|
+
{
|
|
420
|
+
const jsxPatterns = [
|
|
421
|
+
{ orig: oldParts.fullKey, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g') },
|
|
422
|
+
{ orig: oldParts.key, regex: new RegExp(`i18nKey=(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g') }
|
|
423
|
+
];
|
|
424
|
+
for (const p of jsxPatterns) {
|
|
425
|
+
newCode = newCode.replace(p.regex, (match, q) => {
|
|
426
|
+
changes++;
|
|
427
|
+
const nsSepStr = nsSeparator === false ? ':' : nsSeparator;
|
|
428
|
+
const replacement = (p.orig === oldParts.fullKey && oldParts.fullKey.includes(nsSepStr)) ? newParts.fullKey : newParts.key;
|
|
429
|
+
return `i18nKey=${q}${replacement}${q}`;
|
|
430
|
+
});
|
|
431
|
+
}
|
|
375
432
|
}
|
|
376
433
|
}
|
|
377
434
|
return { newCode, changes };
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rename-key.d.ts","sourceRoot":"","sources":["../src/rename-key.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAO5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,OAAO,CAAA;CACZ,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"rename-key.d.ts","sourceRoot":"","sources":["../src/rename-key.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAO5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,OAAO,CAAA;CACZ,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,eAAe,CAAC,CAqE1B"}
|