i18next-cli 1.38.2 → 1.39.0

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/README.md CHANGED
@@ -33,7 +33,7 @@ A unified, high-performance i18next CLI toolchain, powered by SWC.
33
33
 
34
34
  ## Features
35
35
 
36
- - **Key Extraction**: Extract translation keys from JavaScript/TypeScript files with advanced AST analysis.
36
+ - **Key Extraction**: Extraction means automatically finding and collecting all translation keys used in your source code (JavaScript/TypeScript, etc.) by analyzing the code's structure (AST). This ensures every string that needs translation is identified and included in your translation files, reducing manual work and preventing missing keys.
37
37
  - **Type Safety**: Generate TypeScript definitions for full autocomplete and type safety.
38
38
  - **Locale Synchronization**: Keep all language files in sync with your primary language.
39
39
  - **Accurate Code Linting**: Detect hardcoded strings with high precision and configurable rules.
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.38.2'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.39.0'); // 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
@@ -307,28 +307,56 @@ async function updateTranslationFiles(oldParts, newParts, config, dryRun, logger
307
307
  const results = [];
308
308
  const keySeparator = config.extract.keySeparator ?? '.';
309
309
  for (const locale of config.locales) {
310
- const outputPath = fileUtils.getOutputPath(config.extract.output, locale, oldParts.namespace);
311
- const fullPath = node_path.resolve(process.cwd(), outputPath);
310
+ const oldOutputPath = fileUtils.getOutputPath(config.extract.output, locale, oldParts.namespace);
311
+ const oldFullPath = node_path.resolve(process.cwd(), oldOutputPath);
312
+ const newOutputPath = fileUtils.getOutputPath(config.extract.output, locale, newParts.namespace);
313
+ const newFullPath = node_path.resolve(process.cwd(), newOutputPath);
314
+ let oldTranslations;
315
+ let newTranslations;
312
316
  try {
313
- const translations = await fileUtils.loadTranslationFile(fullPath);
314
- if (!translations)
315
- continue;
316
- const oldValue = nestedObject.getNestedValue(translations, oldParts.key, keySeparator);
317
- if (oldValue === undefined)
318
- continue;
319
- // Remove old key
320
- deleteNestedValue(translations, oldParts.key, keySeparator);
321
- // Add new key with same value
322
- nestedObject.setNestedValue(translations, newParts.key, oldValue, keySeparator);
317
+ oldTranslations = await fileUtils.loadTranslationFile(oldFullPath);
318
+ }
319
+ catch { }
320
+ if (!oldTranslations)
321
+ continue;
322
+ const oldValue = nestedObject.getNestedValue(oldTranslations, oldParts.key, keySeparator);
323
+ if (oldValue === undefined)
324
+ continue;
325
+ if (oldParts.namespace === newParts.namespace) {
326
+ // Rename within the same namespace
327
+ deleteNestedValue(oldTranslations, oldParts.key, keySeparator);
328
+ nestedObject.setNestedValue(oldTranslations, newParts.key, oldValue, keySeparator);
323
329
  if (!dryRun) {
324
- const content = fileUtils.serializeTranslationFile(translations, config.extract.outputFormat, config.extract.indentation);
325
- await promises.writeFile(fullPath, content, 'utf-8');
330
+ const content = fileUtils.serializeTranslationFile(oldTranslations, config.extract.outputFormat, config.extract.indentation);
331
+ await promises.writeFile(oldFullPath, content, 'utf-8');
326
332
  }
327
- results.push({ path: fullPath, updated: true });
328
- logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${fullPath}`);
333
+ results.push({ path: oldFullPath, updated: true });
334
+ logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${oldFullPath}`);
329
335
  }
330
- catch (error) {
331
- // File doesn't exist or couldn't be processed
336
+ else {
337
+ // Move across namespaces
338
+ // Remove from old namespace
339
+ deleteNestedValue(oldTranslations, oldParts.key, keySeparator);
340
+ if (!dryRun) {
341
+ const content = fileUtils.serializeTranslationFile(oldTranslations, config.extract.outputFormat, config.extract.indentation);
342
+ await promises.writeFile(oldFullPath, content, 'utf-8');
343
+ }
344
+ results.push({ path: oldFullPath, updated: true });
345
+ logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${oldFullPath}`);
346
+ // Add to new namespace
347
+ try {
348
+ newTranslations = await fileUtils.loadTranslationFile(newFullPath);
349
+ }
350
+ catch { }
351
+ if (!newTranslations)
352
+ newTranslations = {};
353
+ nestedObject.setNestedValue(newTranslations, newParts.key, oldValue, keySeparator);
354
+ if (!dryRun) {
355
+ const content = fileUtils.serializeTranslationFile(newTranslations, config.extract.outputFormat, config.extract.indentation);
356
+ await promises.writeFile(newFullPath, content, 'utf-8');
357
+ }
358
+ results.push({ path: newFullPath, updated: true });
359
+ logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${newFullPath}`);
332
360
  }
333
361
  }
334
362
  if (results.length > 0) {
@@ -342,13 +370,21 @@ function deleteNestedValue(obj, path, separator) {
342
370
  return;
343
371
  }
344
372
  const keys = path.split(String(separator));
345
- let current = obj;
346
- for (let i = 0; i < keys.length - 1; i++) {
347
- if (!current[keys[i]])
348
- return;
349
- current = current[keys[i]];
373
+ function _delete(current, idx) {
374
+ const key = keys[idx];
375
+ if (idx === keys.length - 1) {
376
+ delete current[key];
377
+ }
378
+ else if (current[key]) {
379
+ const shouldDelete = _delete(current[key], idx + 1);
380
+ if (shouldDelete) {
381
+ delete current[key];
382
+ }
383
+ }
384
+ // Return true if current is now empty
385
+ return typeof current === 'object' && current !== null && Object.keys(current).length === 0;
350
386
  }
351
- delete current[keys[keys.length - 1]];
387
+ _delete(obj, 0);
352
388
  }
353
389
 
354
390
  exports.runRenameKey = runRenameKey;
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.38.2'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.39.0'); // 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
@@ -305,28 +305,56 @@ async function updateTranslationFiles(oldParts, newParts, config, dryRun, logger
305
305
  const results = [];
306
306
  const keySeparator = config.extract.keySeparator ?? '.';
307
307
  for (const locale of config.locales) {
308
- const outputPath = getOutputPath(config.extract.output, locale, oldParts.namespace);
309
- const fullPath = resolve(process.cwd(), outputPath);
308
+ const oldOutputPath = getOutputPath(config.extract.output, locale, oldParts.namespace);
309
+ const oldFullPath = resolve(process.cwd(), oldOutputPath);
310
+ const newOutputPath = getOutputPath(config.extract.output, locale, newParts.namespace);
311
+ const newFullPath = resolve(process.cwd(), newOutputPath);
312
+ let oldTranslations;
313
+ let newTranslations;
310
314
  try {
311
- const translations = await loadTranslationFile(fullPath);
312
- if (!translations)
313
- continue;
314
- const oldValue = getNestedValue(translations, oldParts.key, keySeparator);
315
- if (oldValue === undefined)
316
- continue;
317
- // Remove old key
318
- deleteNestedValue(translations, oldParts.key, keySeparator);
319
- // Add new key with same value
320
- setNestedValue(translations, newParts.key, oldValue, keySeparator);
315
+ oldTranslations = await loadTranslationFile(oldFullPath);
316
+ }
317
+ catch { }
318
+ if (!oldTranslations)
319
+ continue;
320
+ const oldValue = getNestedValue(oldTranslations, oldParts.key, keySeparator);
321
+ if (oldValue === undefined)
322
+ continue;
323
+ if (oldParts.namespace === newParts.namespace) {
324
+ // Rename within the same namespace
325
+ deleteNestedValue(oldTranslations, oldParts.key, keySeparator);
326
+ setNestedValue(oldTranslations, newParts.key, oldValue, keySeparator);
321
327
  if (!dryRun) {
322
- const content = serializeTranslationFile(translations, config.extract.outputFormat, config.extract.indentation);
323
- await writeFile(fullPath, content, 'utf-8');
328
+ const content = serializeTranslationFile(oldTranslations, config.extract.outputFormat, config.extract.indentation);
329
+ await writeFile(oldFullPath, content, 'utf-8');
324
330
  }
325
- results.push({ path: fullPath, updated: true });
326
- logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${fullPath}`);
331
+ results.push({ path: oldFullPath, updated: true });
332
+ logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${oldFullPath}`);
327
333
  }
328
- catch (error) {
329
- // File doesn't exist or couldn't be processed
334
+ else {
335
+ // Move across namespaces
336
+ // Remove from old namespace
337
+ deleteNestedValue(oldTranslations, oldParts.key, keySeparator);
338
+ if (!dryRun) {
339
+ const content = serializeTranslationFile(oldTranslations, config.extract.outputFormat, config.extract.indentation);
340
+ await writeFile(oldFullPath, content, 'utf-8');
341
+ }
342
+ results.push({ path: oldFullPath, updated: true });
343
+ logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${oldFullPath}`);
344
+ // Add to new namespace
345
+ try {
346
+ newTranslations = await loadTranslationFile(newFullPath);
347
+ }
348
+ catch { }
349
+ if (!newTranslations)
350
+ newTranslations = {};
351
+ setNestedValue(newTranslations, newParts.key, oldValue, keySeparator);
352
+ if (!dryRun) {
353
+ const content = serializeTranslationFile(newTranslations, config.extract.outputFormat, config.extract.indentation);
354
+ await writeFile(newFullPath, content, 'utf-8');
355
+ }
356
+ results.push({ path: newFullPath, updated: true });
357
+ logger.info(` ${dryRun ? '(dry-run) ' : ''}✓ ${newFullPath}`);
330
358
  }
331
359
  }
332
360
  if (results.length > 0) {
@@ -340,13 +368,21 @@ function deleteNestedValue(obj, path, separator) {
340
368
  return;
341
369
  }
342
370
  const keys = path.split(String(separator));
343
- let current = obj;
344
- for (let i = 0; i < keys.length - 1; i++) {
345
- if (!current[keys[i]])
346
- return;
347
- current = current[keys[i]];
371
+ function _delete(current, idx) {
372
+ const key = keys[idx];
373
+ if (idx === keys.length - 1) {
374
+ delete current[key];
375
+ }
376
+ else if (current[key]) {
377
+ const shouldDelete = _delete(current[key], idx + 1);
378
+ if (shouldDelete) {
379
+ delete current[key];
380
+ }
381
+ }
382
+ // Return true if current is now empty
383
+ return typeof current === 'object' && current !== null && Object.keys(current).length === 0;
348
384
  }
349
- delete current[keys[keys.length - 1]];
385
+ _delete(obj, 0);
350
386
  }
351
387
 
352
388
  export { runRenameKey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.38.2",
3
+ "version": "1.39.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {