i18next-cli 1.39.4 → 1.39.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 +1 -1
- package/dist/cjs/rename-key.js +151 -104
- package/dist/esm/cli.js +1 -1
- package/dist/esm/rename-key.js +151 -104
- 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.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
|
package/dist/cjs/rename-key.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var glob = require('glob');
|
|
4
4
|
var promises = require('node:fs/promises');
|
|
5
|
+
var node_path = require('node:path');
|
|
5
6
|
var logger = require('./utils/logger.js');
|
|
6
7
|
var fileUtils = require('./utils/file-utils.js');
|
|
7
|
-
var node_path = require('node:path');
|
|
8
8
|
var nestedObject = require('./utils/nested-object.js');
|
|
9
9
|
var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
|
|
10
10
|
var chalk = require('chalk');
|
|
@@ -64,9 +64,13 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger$1 = new
|
|
|
64
64
|
error: 'Target key already exists in translation files'
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
+
// Build a quick map of which namespaces contain which keys (union across locales).
|
|
68
|
+
// This allows us to decide, per-call, whether an explicit `{ ns: 'x' }` refers to
|
|
69
|
+
// the namespace we're renaming, and whether that namespace actually contains the key.
|
|
70
|
+
const namespaceKeyMap = await buildNamespaceKeyMap(config);
|
|
67
71
|
logger$1.info(`🔍 Scanning for usages of "${oldKey}"...`);
|
|
68
72
|
// Find and update source files
|
|
69
|
-
const sourceResults = await updateSourceFiles(oldParts, newParts, config, dryRun, logger$1);
|
|
73
|
+
const sourceResults = await updateSourceFiles(oldParts, newParts, config, dryRun, logger$1, namespaceKeyMap);
|
|
70
74
|
// Update translation files
|
|
71
75
|
const translationResults = await updateTranslationFiles(oldParts, newParts, config, dryRun, logger$1);
|
|
72
76
|
const totalChanges = sourceResults.reduce((sum, r) => sum + r.changes, 0);
|
|
@@ -107,13 +111,15 @@ function parseKeyWithNamespace(key, config) {
|
|
|
107
111
|
return {
|
|
108
112
|
namespace: ns,
|
|
109
113
|
key: rest.join(nsSeparator),
|
|
110
|
-
fullKey: key
|
|
114
|
+
fullKey: key,
|
|
115
|
+
explicitNamespace: true
|
|
111
116
|
};
|
|
112
117
|
}
|
|
113
118
|
return {
|
|
114
119
|
namespace: config.extract.defaultNS || 'translation',
|
|
115
120
|
key,
|
|
116
|
-
fullKey: key
|
|
121
|
+
fullKey: key,
|
|
122
|
+
explicitNamespace: false
|
|
117
123
|
};
|
|
118
124
|
}
|
|
119
125
|
function validateKeys(oldKey, newKey, config) {
|
|
@@ -149,7 +155,54 @@ async function checkConflicts(newParts, config) {
|
|
|
149
155
|
}
|
|
150
156
|
return conflicts;
|
|
151
157
|
}
|
|
152
|
-
async function
|
|
158
|
+
async function buildNamespaceKeyMap(config) {
|
|
159
|
+
// Map namespace -> set of flattened keys present in that namespace (union across locales)
|
|
160
|
+
const map = new Map();
|
|
161
|
+
// config.extract.output may be either a string template or a function(language, namespace) => string.
|
|
162
|
+
// Produce a string we can turn into a glob pattern. For functions, call with wildcard values.
|
|
163
|
+
const rawOutput = config.extract.output;
|
|
164
|
+
const outputTemplate = typeof rawOutput === 'function'
|
|
165
|
+
? rawOutput('*', '*') // produce a path with wildcards we can glob
|
|
166
|
+
: String(rawOutput);
|
|
167
|
+
// make a glob pattern by replacing placeholders with *
|
|
168
|
+
const pat = outputTemplate
|
|
169
|
+
.replace(/\{\{language\}\}/g, '*')
|
|
170
|
+
.replace(/\{\{namespace\}\}/g, '*');
|
|
171
|
+
// glob expects unix-style
|
|
172
|
+
const files = await glob.glob([pat.replace(/\\/g, '/')], { nodir: true });
|
|
173
|
+
const keySeparator = config.extract.keySeparator ?? '.';
|
|
174
|
+
for (const f of files) {
|
|
175
|
+
try {
|
|
176
|
+
const translations = await fileUtils.loadTranslationFile(node_path.resolve(process.cwd(), f));
|
|
177
|
+
if (!translations)
|
|
178
|
+
continue;
|
|
179
|
+
// derive namespace name from filename: basename without extension (platform-safe)
|
|
180
|
+
const base = node_path.basename(f);
|
|
181
|
+
const ns = base.replace(/\.[^.]+$/, ''); // remove extension
|
|
182
|
+
const set = map.get(ns) ?? new Set();
|
|
183
|
+
// flatten keys recursively
|
|
184
|
+
const collect = (obj, prefix = '') => {
|
|
185
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
186
|
+
// only add non-empty prefix (avoid adding '')
|
|
187
|
+
if (prefix)
|
|
188
|
+
set.add(prefix);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const k of Object.keys(obj)) {
|
|
192
|
+
const next = prefix ? `${prefix}${keySeparator}${k}` : k;
|
|
193
|
+
collect(obj[k], next);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
collect(translations, '');
|
|
197
|
+
map.set(ns, set);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// ignore unreadable files
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return map;
|
|
204
|
+
}
|
|
205
|
+
async function updateSourceFiles(oldParts, newParts, config, dryRun, logger, namespaceKeyMap) {
|
|
153
206
|
const defaultIgnore = ['node_modules/**'];
|
|
154
207
|
const userIgnore = Array.isArray(config.extract.ignore)
|
|
155
208
|
? config.extract.ignore
|
|
@@ -167,7 +220,7 @@ async function updateSourceFiles(oldParts, newParts, config, dryRun, logger) {
|
|
|
167
220
|
const results = [];
|
|
168
221
|
for (const file of sourceFiles) {
|
|
169
222
|
const code = await promises.readFile(file, 'utf-8');
|
|
170
|
-
const { newCode, changes } = await replaceKeyInSource(code, oldParts, newParts, config);
|
|
223
|
+
const { newCode, changes } = await replaceKeyInSource(code, oldParts, newParts, config, namespaceKeyMap);
|
|
171
224
|
if (changes > 0) {
|
|
172
225
|
if (!dryRun) {
|
|
173
226
|
await promises.writeFile(file, newCode, 'utf-8');
|
|
@@ -181,11 +234,11 @@ async function updateSourceFiles(oldParts, newParts, config, dryRun, logger) {
|
|
|
181
234
|
}
|
|
182
235
|
return results;
|
|
183
236
|
}
|
|
184
|
-
async function replaceKeyInSource(code, oldParts, newParts, config) {
|
|
237
|
+
async function replaceKeyInSource(code, oldParts, newParts, config, namespaceKeyMap) {
|
|
185
238
|
// Simpler and robust regex-based replacement that covers tests' patterns
|
|
186
|
-
return replaceKeyWithRegex(code, oldParts, newParts, config);
|
|
239
|
+
return replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap);
|
|
187
240
|
}
|
|
188
|
-
function replaceKeyWithRegex(code, oldParts, newParts, config) {
|
|
241
|
+
function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap) {
|
|
189
242
|
let changes = 0;
|
|
190
243
|
let newCode = code;
|
|
191
244
|
const nsSeparator = config.extract.nsSeparator ?? ':';
|
|
@@ -200,87 +253,54 @@ function replaceKeyWithRegex(code, oldParts, newParts, config) {
|
|
|
200
253
|
// exact function name (may include dot like 'i18n.t' or 'translate')
|
|
201
254
|
return `\\b${escapeRegex(fnPattern)}`;
|
|
202
255
|
};
|
|
256
|
+
// Helper: check whether the old key exists in a given namespace (from the prebuilt map)
|
|
257
|
+
const hasKeyInNamespace = (ns) => {
|
|
258
|
+
if (!ns)
|
|
259
|
+
return false;
|
|
260
|
+
const set = namespaceKeyMap.get(ns);
|
|
261
|
+
return !!(set && set.has(oldParts.key));
|
|
262
|
+
};
|
|
203
263
|
// Replace exact string-key usages inside function calls: fn('key') or fn(`key`) or fn("key")
|
|
204
264
|
for (const fnPattern of configuredFunctions) {
|
|
205
265
|
const prefix = fnPrefixToRegex(fnPattern);
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
newCode = newCode.replace(regexFull, (match, q) => {
|
|
210
|
-
changes++;
|
|
211
|
-
const replacementKey = (oldParts.fullKey.includes(nsSeparator || ':') ? newParts.fullKey : newParts.key);
|
|
212
|
-
// preserve surrounding characters up to the opening quote
|
|
213
|
-
return match.replace(oldParts.fullKey, replacementKey);
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
// Then match bare key (no namespace in source)
|
|
217
|
-
const regexKey = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g');
|
|
218
|
-
newCode = newCode.replace(regexKey, (match) => {
|
|
219
|
-
changes++;
|
|
220
|
-
const replacementKey = newParts.key;
|
|
221
|
-
return match.replace(new RegExp(escapeRegex(oldParts.key)), replacementKey);
|
|
222
|
-
});
|
|
223
|
-
// Then match bare key (no namespace in source)
|
|
224
|
-
// If moving from defaultNS to another, and call is t('key') with no options, add ns option
|
|
225
|
-
const regexKeyWithParen = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
|
|
226
|
-
newCode = newCode.replace(regexKeyWithParen, (match, quote) => {
|
|
227
|
-
if (oldParts.namespace && newParts.namespace &&
|
|
228
|
-
oldParts.namespace !== newParts.namespace &&
|
|
229
|
-
config.extract.defaultNS === oldParts.namespace) {
|
|
230
|
-
changes++;
|
|
231
|
-
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
|
|
232
|
-
}
|
|
233
|
-
else if (oldParts.namespace && newParts.namespace &&
|
|
234
|
-
oldParts.namespace !== newParts.namespace &&
|
|
235
|
-
config.extract.defaultNS === newParts.namespace) {
|
|
236
|
-
// If moving from a namespaced key to defaultNS, update t('key', { ns: 'oldNs' }) to t('key')
|
|
237
|
-
// Remove the ns option if it matches the defaultNS
|
|
238
|
-
// This is handled below in the ns option replacement
|
|
239
|
-
// But also handle t('key', { ns: 'defaultNS' }) -> t('key')
|
|
240
|
-
// See below for explicit ns option removal
|
|
241
|
-
// No action here, handled below
|
|
242
|
-
return match;
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
changes++;
|
|
246
|
-
const replacementKey = newParts.key;
|
|
247
|
-
return match.replace(new RegExp(escapeRegex(oldParts.key)), replacementKey);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
// Remove ns option if moving to defaultNS
|
|
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
|
|
251
269
|
if (oldParts.namespace && newParts.namespace &&
|
|
252
270
|
oldParts.namespace !== newParts.namespace &&
|
|
253
|
-
config.extract.defaultNS === newParts.namespace
|
|
271
|
+
config.extract.defaultNS === newParts.namespace &&
|
|
272
|
+
hasKeyInNamespace(oldParts.namespace)) {
|
|
254
273
|
// t('key', { ns: 'oldNs' }) -> t('key')
|
|
255
274
|
const nsRegexToDefault = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
256
275
|
newCode = newCode.replace(nsRegexToDefault, (match, keyQ, beforeNs, nsQ, afterNs) => {
|
|
257
276
|
changes++;
|
|
258
277
|
// Build remaining object props (everything except the ns property)
|
|
259
278
|
const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
|
|
260
|
-
//
|
|
279
|
+
// Replace the key string itself, preserving the original quote style
|
|
261
280
|
let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
|
|
262
281
|
if (obj) {
|
|
263
|
-
// If other properties remain,
|
|
282
|
+
// If other properties remain, keep them
|
|
264
283
|
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
265
284
|
}
|
|
266
285
|
else {
|
|
267
|
-
// No other props — remove the
|
|
286
|
+
// No other props — remove the options object entirely
|
|
268
287
|
updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
|
|
269
288
|
}
|
|
270
289
|
return updated;
|
|
271
290
|
});
|
|
272
291
|
}
|
|
273
|
-
//
|
|
274
|
-
if
|
|
275
|
-
|
|
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', ... }))
|
|
276
296
|
const nsRegexFullKey = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
277
|
-
newCode = newCode.replace(nsRegexFullKey, (match
|
|
297
|
+
newCode = newCode.replace(nsRegexFullKey, (match) => {
|
|
278
298
|
changes++;
|
|
279
299
|
// replace ns value
|
|
280
300
|
return match.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
|
|
281
301
|
});
|
|
282
|
-
//
|
|
283
|
-
if (oldParts.fullKey) {
|
|
302
|
+
// case where fullKey was used inside the string (e.g. t('ns:key', { ns: 'oldNs' }))
|
|
303
|
+
if (oldParts.fullKey && oldParts.explicitNamespace) {
|
|
284
304
|
const nsRegexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
285
305
|
newCode = newCode.replace(nsRegexFull, (match) => {
|
|
286
306
|
changes++;
|
|
@@ -288,46 +308,73 @@ function replaceKeyWithRegex(code, oldParts, newParts, config) {
|
|
|
288
308
|
});
|
|
289
309
|
}
|
|
290
310
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
311
|
+
// 3) Replace occurrences where the call uses the fullKey inside the string (e.g. t('ns:key'))
|
|
312
|
+
if (oldParts.fullKey && oldParts.explicitNamespace) {
|
|
313
|
+
const regexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g');
|
|
314
|
+
newCode = newCode.replace(regexFull, (match) => {
|
|
315
|
+
changes++;
|
|
316
|
+
const replacementKey = (oldParts.fullKey.includes(nsSeparator || ':') ? newParts.fullKey : newParts.key);
|
|
317
|
+
return match.replace(oldParts.fullKey, replacementKey);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
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)
|
|
322
|
+
{
|
|
323
|
+
const dotRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\.${escapeRegex(oldParts.key)}\\s*\\)`, 'g');
|
|
324
|
+
newCode = newCode.replace(dotRegex, (match) => {
|
|
325
|
+
changes++;
|
|
326
|
+
const replacementKey = newParts.key;
|
|
327
|
+
return match.replace(`.${oldParts.key}`, `.${replacementKey}`);
|
|
328
|
+
});
|
|
329
|
+
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
|
+
newCode = newCode.replace(bracketRegex, (match) => {
|
|
331
|
+
changes++;
|
|
332
|
+
const replacementKey = newParts.key;
|
|
333
|
+
if (/^[A-Za-z_$][\w$]*$/.test(replacementKey)) {
|
|
334
|
+
return match.replace(new RegExp(`\\[\\s*['"\`]${escapeRegex(oldParts.key)}['"\`]\\s*\\]`), `.${replacementKey}`);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `$1${replacementKey}$1`);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
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).
|
|
355
|
+
if (oldParts.namespace && newParts.namespace &&
|
|
356
|
+
oldParts.namespace !== newParts.namespace &&
|
|
357
|
+
config.extract.defaultNS === oldParts.namespace &&
|
|
358
|
+
hasKeyInNamespace(oldParts.namespace)) {
|
|
359
|
+
const regexKeyWithParen = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
|
|
360
|
+
newCode = newCode.replace(regexKeyWithParen, (match, quote) => {
|
|
361
|
+
changes++;
|
|
362
|
+
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
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
|
+
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}`;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
331
378
|
}
|
|
332
379
|
return { newCode, changes };
|
|
333
380
|
}
|
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.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
|
package/dist/esm/rename-key.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { glob } from 'glob';
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve, basename } from 'node:path';
|
|
3
4
|
import { ConsoleLogger } from './utils/logger.js';
|
|
4
5
|
import { getOutputPath, loadTranslationFile, serializeTranslationFile } from './utils/file-utils.js';
|
|
5
|
-
import { resolve } from 'node:path';
|
|
6
6
|
import { getNestedValue, setNestedValue } from './utils/nested-object.js';
|
|
7
7
|
import { shouldShowFunnel, recordFunnelShown } from './utils/funnel-msg-tracker.js';
|
|
8
8
|
import chalk from 'chalk';
|
|
@@ -62,9 +62,13 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger = new C
|
|
|
62
62
|
error: 'Target key already exists in translation files'
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
// Build a quick map of which namespaces contain which keys (union across locales).
|
|
66
|
+
// This allows us to decide, per-call, whether an explicit `{ ns: 'x' }` refers to
|
|
67
|
+
// the namespace we're renaming, and whether that namespace actually contains the key.
|
|
68
|
+
const namespaceKeyMap = await buildNamespaceKeyMap(config);
|
|
65
69
|
logger.info(`🔍 Scanning for usages of "${oldKey}"...`);
|
|
66
70
|
// Find and update source files
|
|
67
|
-
const sourceResults = await updateSourceFiles(oldParts, newParts, config, dryRun, logger);
|
|
71
|
+
const sourceResults = await updateSourceFiles(oldParts, newParts, config, dryRun, logger, namespaceKeyMap);
|
|
68
72
|
// Update translation files
|
|
69
73
|
const translationResults = await updateTranslationFiles(oldParts, newParts, config, dryRun, logger);
|
|
70
74
|
const totalChanges = sourceResults.reduce((sum, r) => sum + r.changes, 0);
|
|
@@ -105,13 +109,15 @@ function parseKeyWithNamespace(key, config) {
|
|
|
105
109
|
return {
|
|
106
110
|
namespace: ns,
|
|
107
111
|
key: rest.join(nsSeparator),
|
|
108
|
-
fullKey: key
|
|
112
|
+
fullKey: key,
|
|
113
|
+
explicitNamespace: true
|
|
109
114
|
};
|
|
110
115
|
}
|
|
111
116
|
return {
|
|
112
117
|
namespace: config.extract.defaultNS || 'translation',
|
|
113
118
|
key,
|
|
114
|
-
fullKey: key
|
|
119
|
+
fullKey: key,
|
|
120
|
+
explicitNamespace: false
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
123
|
function validateKeys(oldKey, newKey, config) {
|
|
@@ -147,7 +153,54 @@ async function checkConflicts(newParts, config) {
|
|
|
147
153
|
}
|
|
148
154
|
return conflicts;
|
|
149
155
|
}
|
|
150
|
-
async function
|
|
156
|
+
async function buildNamespaceKeyMap(config) {
|
|
157
|
+
// Map namespace -> set of flattened keys present in that namespace (union across locales)
|
|
158
|
+
const map = new Map();
|
|
159
|
+
// config.extract.output may be either a string template or a function(language, namespace) => string.
|
|
160
|
+
// Produce a string we can turn into a glob pattern. For functions, call with wildcard values.
|
|
161
|
+
const rawOutput = config.extract.output;
|
|
162
|
+
const outputTemplate = typeof rawOutput === 'function'
|
|
163
|
+
? rawOutput('*', '*') // produce a path with wildcards we can glob
|
|
164
|
+
: String(rawOutput);
|
|
165
|
+
// make a glob pattern by replacing placeholders with *
|
|
166
|
+
const pat = outputTemplate
|
|
167
|
+
.replace(/\{\{language\}\}/g, '*')
|
|
168
|
+
.replace(/\{\{namespace\}\}/g, '*');
|
|
169
|
+
// glob expects unix-style
|
|
170
|
+
const files = await glob([pat.replace(/\\/g, '/')], { nodir: true });
|
|
171
|
+
const keySeparator = config.extract.keySeparator ?? '.';
|
|
172
|
+
for (const f of files) {
|
|
173
|
+
try {
|
|
174
|
+
const translations = await loadTranslationFile(resolve(process.cwd(), f));
|
|
175
|
+
if (!translations)
|
|
176
|
+
continue;
|
|
177
|
+
// derive namespace name from filename: basename without extension (platform-safe)
|
|
178
|
+
const base = basename(f);
|
|
179
|
+
const ns = base.replace(/\.[^.]+$/, ''); // remove extension
|
|
180
|
+
const set = map.get(ns) ?? new Set();
|
|
181
|
+
// flatten keys recursively
|
|
182
|
+
const collect = (obj, prefix = '') => {
|
|
183
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
184
|
+
// only add non-empty prefix (avoid adding '')
|
|
185
|
+
if (prefix)
|
|
186
|
+
set.add(prefix);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const k of Object.keys(obj)) {
|
|
190
|
+
const next = prefix ? `${prefix}${keySeparator}${k}` : k;
|
|
191
|
+
collect(obj[k], next);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
collect(translations, '');
|
|
195
|
+
map.set(ns, set);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// ignore unreadable files
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return map;
|
|
202
|
+
}
|
|
203
|
+
async function updateSourceFiles(oldParts, newParts, config, dryRun, logger, namespaceKeyMap) {
|
|
151
204
|
const defaultIgnore = ['node_modules/**'];
|
|
152
205
|
const userIgnore = Array.isArray(config.extract.ignore)
|
|
153
206
|
? config.extract.ignore
|
|
@@ -165,7 +218,7 @@ async function updateSourceFiles(oldParts, newParts, config, dryRun, logger) {
|
|
|
165
218
|
const results = [];
|
|
166
219
|
for (const file of sourceFiles) {
|
|
167
220
|
const code = await readFile(file, 'utf-8');
|
|
168
|
-
const { newCode, changes } = await replaceKeyInSource(code, oldParts, newParts, config);
|
|
221
|
+
const { newCode, changes } = await replaceKeyInSource(code, oldParts, newParts, config, namespaceKeyMap);
|
|
169
222
|
if (changes > 0) {
|
|
170
223
|
if (!dryRun) {
|
|
171
224
|
await writeFile(file, newCode, 'utf-8');
|
|
@@ -179,11 +232,11 @@ async function updateSourceFiles(oldParts, newParts, config, dryRun, logger) {
|
|
|
179
232
|
}
|
|
180
233
|
return results;
|
|
181
234
|
}
|
|
182
|
-
async function replaceKeyInSource(code, oldParts, newParts, config) {
|
|
235
|
+
async function replaceKeyInSource(code, oldParts, newParts, config, namespaceKeyMap) {
|
|
183
236
|
// Simpler and robust regex-based replacement that covers tests' patterns
|
|
184
|
-
return replaceKeyWithRegex(code, oldParts, newParts, config);
|
|
237
|
+
return replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap);
|
|
185
238
|
}
|
|
186
|
-
function replaceKeyWithRegex(code, oldParts, newParts, config) {
|
|
239
|
+
function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap) {
|
|
187
240
|
let changes = 0;
|
|
188
241
|
let newCode = code;
|
|
189
242
|
const nsSeparator = config.extract.nsSeparator ?? ':';
|
|
@@ -198,87 +251,54 @@ function replaceKeyWithRegex(code, oldParts, newParts, config) {
|
|
|
198
251
|
// exact function name (may include dot like 'i18n.t' or 'translate')
|
|
199
252
|
return `\\b${escapeRegex(fnPattern)}`;
|
|
200
253
|
};
|
|
254
|
+
// Helper: check whether the old key exists in a given namespace (from the prebuilt map)
|
|
255
|
+
const hasKeyInNamespace = (ns) => {
|
|
256
|
+
if (!ns)
|
|
257
|
+
return false;
|
|
258
|
+
const set = namespaceKeyMap.get(ns);
|
|
259
|
+
return !!(set && set.has(oldParts.key));
|
|
260
|
+
};
|
|
201
261
|
// Replace exact string-key usages inside function calls: fn('key') or fn(`key`) or fn("key")
|
|
202
262
|
for (const fnPattern of configuredFunctions) {
|
|
203
263
|
const prefix = fnPrefixToRegex(fnPattern);
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
newCode = newCode.replace(regexFull, (match, q) => {
|
|
208
|
-
changes++;
|
|
209
|
-
const replacementKey = (oldParts.fullKey.includes(nsSeparator || ':') ? newParts.fullKey : newParts.key);
|
|
210
|
-
// preserve surrounding characters up to the opening quote
|
|
211
|
-
return match.replace(oldParts.fullKey, replacementKey);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
// Then match bare key (no namespace in source)
|
|
215
|
-
const regexKey = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1`, 'g');
|
|
216
|
-
newCode = newCode.replace(regexKey, (match) => {
|
|
217
|
-
changes++;
|
|
218
|
-
const replacementKey = newParts.key;
|
|
219
|
-
return match.replace(new RegExp(escapeRegex(oldParts.key)), replacementKey);
|
|
220
|
-
});
|
|
221
|
-
// Then match bare key (no namespace in source)
|
|
222
|
-
// If moving from defaultNS to another, and call is t('key') with no options, add ns option
|
|
223
|
-
const regexKeyWithParen = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
|
|
224
|
-
newCode = newCode.replace(regexKeyWithParen, (match, quote) => {
|
|
225
|
-
if (oldParts.namespace && newParts.namespace &&
|
|
226
|
-
oldParts.namespace !== newParts.namespace &&
|
|
227
|
-
config.extract.defaultNS === oldParts.namespace) {
|
|
228
|
-
changes++;
|
|
229
|
-
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
|
|
230
|
-
}
|
|
231
|
-
else if (oldParts.namespace && newParts.namespace &&
|
|
232
|
-
oldParts.namespace !== newParts.namespace &&
|
|
233
|
-
config.extract.defaultNS === newParts.namespace) {
|
|
234
|
-
// If moving from a namespaced key to defaultNS, update t('key', { ns: 'oldNs' }) to t('key')
|
|
235
|
-
// Remove the ns option if it matches the defaultNS
|
|
236
|
-
// This is handled below in the ns option replacement
|
|
237
|
-
// But also handle t('key', { ns: 'defaultNS' }) -> t('key')
|
|
238
|
-
// See below for explicit ns option removal
|
|
239
|
-
// No action here, handled below
|
|
240
|
-
return match;
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
changes++;
|
|
244
|
-
const replacementKey = newParts.key;
|
|
245
|
-
return match.replace(new RegExp(escapeRegex(oldParts.key)), replacementKey);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
// Remove ns option if moving to defaultNS
|
|
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
|
|
249
267
|
if (oldParts.namespace && newParts.namespace &&
|
|
250
268
|
oldParts.namespace !== newParts.namespace &&
|
|
251
|
-
config.extract.defaultNS === newParts.namespace
|
|
269
|
+
config.extract.defaultNS === newParts.namespace &&
|
|
270
|
+
hasKeyInNamespace(oldParts.namespace)) {
|
|
252
271
|
// t('key', { ns: 'oldNs' }) -> t('key')
|
|
253
272
|
const nsRegexToDefault = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
254
273
|
newCode = newCode.replace(nsRegexToDefault, (match, keyQ, beforeNs, nsQ, afterNs) => {
|
|
255
274
|
changes++;
|
|
256
275
|
// Build remaining object props (everything except the ns property)
|
|
257
276
|
const obj = (beforeNs + afterNs).replace(/,?\s*$/, '').replace(/^\s*,?/, '').trim();
|
|
258
|
-
//
|
|
277
|
+
// Replace the key string itself, preserving the original quote style
|
|
259
278
|
let updated = match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `${keyQ}${newParts.key}${keyQ}`);
|
|
260
279
|
if (obj) {
|
|
261
|
-
// If other properties remain,
|
|
280
|
+
// If other properties remain, keep them
|
|
262
281
|
updated = updated.replace(/\{\s*([^}]*)\s*\}/, `{${obj}}`);
|
|
263
282
|
}
|
|
264
283
|
else {
|
|
265
|
-
// No other props — remove the
|
|
284
|
+
// No other props — remove the options object entirely
|
|
266
285
|
updated = updated.replace(/\s*,\s*\{[^}]*\}\s*\)/, ')');
|
|
267
286
|
}
|
|
268
287
|
return updated;
|
|
269
288
|
});
|
|
270
289
|
}
|
|
271
|
-
//
|
|
272
|
-
if
|
|
273
|
-
|
|
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', ... }))
|
|
274
294
|
const nsRegexFullKey = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
275
|
-
newCode = newCode.replace(nsRegexFullKey, (match
|
|
295
|
+
newCode = newCode.replace(nsRegexFullKey, (match) => {
|
|
276
296
|
changes++;
|
|
277
297
|
// replace ns value
|
|
278
298
|
return match.replace(new RegExp(`(\\bns\\s*:\\s*['"\`])${escapeRegex(oldParts.namespace ?? '')}(['"\`])`), `$1${newParts.namespace ?? ''}$2`);
|
|
279
299
|
});
|
|
280
|
-
//
|
|
281
|
-
if (oldParts.fullKey) {
|
|
300
|
+
// case where fullKey was used inside the string (e.g. t('ns:key', { ns: 'oldNs' }))
|
|
301
|
+
if (oldParts.fullKey && oldParts.explicitNamespace) {
|
|
282
302
|
const nsRegexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1\\s*,\\s*\\{([^}]*)\\bns\\s*:\\s*(['"\`])${escapeRegex(oldParts.namespace)}\\3([^}]*)\\}\\s*\\)`, 'g');
|
|
283
303
|
newCode = newCode.replace(nsRegexFull, (match) => {
|
|
284
304
|
changes++;
|
|
@@ -286,46 +306,73 @@ function replaceKeyWithRegex(code, oldParts, newParts, config) {
|
|
|
286
306
|
});
|
|
287
307
|
}
|
|
288
308
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
309
|
+
// 3) Replace occurrences where the call uses the fullKey inside the string (e.g. t('ns:key'))
|
|
310
|
+
if (oldParts.fullKey && oldParts.explicitNamespace) {
|
|
311
|
+
const regexFull = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.fullKey)}\\1`, 'g');
|
|
312
|
+
newCode = newCode.replace(regexFull, (match) => {
|
|
313
|
+
changes++;
|
|
314
|
+
const replacementKey = (oldParts.fullKey.includes(nsSeparator || ':') ? newParts.fullKey : newParts.key);
|
|
315
|
+
return match.replace(oldParts.fullKey, replacementKey);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
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)
|
|
320
|
+
{
|
|
321
|
+
const dotRegex = new RegExp(`${prefix}\\s*\\(\\s*\\(?\\s*([a-zA-Z_$][\\w$]*)\\s*\\)?\\s*=>\\s*\\1\\.${escapeRegex(oldParts.key)}\\s*\\)`, 'g');
|
|
322
|
+
newCode = newCode.replace(dotRegex, (match) => {
|
|
323
|
+
changes++;
|
|
324
|
+
const replacementKey = newParts.key;
|
|
325
|
+
return match.replace(`.${oldParts.key}`, `.${replacementKey}`);
|
|
326
|
+
});
|
|
327
|
+
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
|
+
newCode = newCode.replace(bracketRegex, (match) => {
|
|
329
|
+
changes++;
|
|
330
|
+
const replacementKey = newParts.key;
|
|
331
|
+
if (/^[A-Za-z_$][\w$]*$/.test(replacementKey)) {
|
|
332
|
+
return match.replace(new RegExp(`\\[\\s*['"\`]${escapeRegex(oldParts.key)}['"\`]\\s*\\]`), `.${replacementKey}`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1`), `$1${replacementKey}$1`);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
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).
|
|
353
|
+
if (oldParts.namespace && newParts.namespace &&
|
|
354
|
+
oldParts.namespace !== newParts.namespace &&
|
|
355
|
+
config.extract.defaultNS === oldParts.namespace &&
|
|
356
|
+
hasKeyInNamespace(oldParts.namespace)) {
|
|
357
|
+
const regexKeyWithParen = new RegExp(`${prefix}\\s*\\(\\s*(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`, 'g');
|
|
358
|
+
newCode = newCode.replace(regexKeyWithParen, (match, quote) => {
|
|
359
|
+
changes++;
|
|
360
|
+
return match.replace(new RegExp(`(['"\`])${escapeRegex(oldParts.key)}\\1\\s*\\)`), `${quote}${newParts.key}${quote}, { ns: '${newParts.namespace}' })`);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
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
|
+
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}`;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
329
376
|
}
|
|
330
377
|
return { newCode, changes };
|
|
331
378
|
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rename-key.d.ts","sourceRoot":"","sources":["../src/rename-key.ts"],"names":[],"mappings":"
|
|
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,CA6D1B"}
|