i18next-cli 1.42.6 → 1.42.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
@@ -5,7 +5,7 @@ var commander = require('commander');
5
5
  var chokidar = require('chokidar');
6
6
  var glob = require('glob');
7
7
  var minimatch = require('minimatch');
8
- var chalk = require('chalk');
8
+ var node_util = require('node:util');
9
9
  var config = require('./config.js');
10
10
  var heuristicConfig = require('./heuristic-config.js');
11
11
  var extractor = require('./extractor/core/extractor.js');
@@ -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.42.6'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.42.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
@@ -100,14 +100,14 @@ program
100
100
  const cfgPath = program.opts().config;
101
101
  let config$1 = await config.loadConfig(cfgPath);
102
102
  if (!config$1) {
103
- console.log(chalk.blue('No config file found. Attempting to detect project structure...'));
103
+ console.log(node_util.styleText('blue', 'No config file found. Attempting to detect project structure...'));
104
104
  const detected = await heuristicConfig.detectConfig();
105
105
  if (!detected) {
106
- console.error(chalk.red('Could not automatically detect your project structure.'));
107
- console.log(`Please create a config file first by running: ${chalk.cyan('npx i18next-cli init')}`);
106
+ console.error(node_util.styleText('red', 'Could not automatically detect your project structure.'));
107
+ console.log(`Please create a config file first by running: ${node_util.styleText('cyan', 'npx i18next-cli init')}`);
108
108
  process.exit(1);
109
109
  }
110
- console.log(chalk.green('Project structure detected successfully!'));
110
+ console.log(node_util.styleText('green', 'Project structure detected successfully!'));
111
111
  config$1 = detected;
112
112
  }
113
113
  await status.runStatus(config$1, { detail: locale, namespace: options.namespace });
@@ -164,14 +164,14 @@ program
164
164
  // The existing logic for loading the config or detecting it is now inside this function
165
165
  let config$1 = await config.loadConfig(cfgPath);
166
166
  if (!config$1) {
167
- console.log(chalk.blue('No config file found. Attempting to detect project structure...'));
167
+ console.log(node_util.styleText('blue', 'No config file found. Attempting to detect project structure...'));
168
168
  const detected = await heuristicConfig.detectConfig();
169
169
  if (!detected) {
170
- console.error(chalk.red('Could not automatically detect your project structure.'));
171
- console.log(`Please create a config file first by running: ${chalk.cyan('npx i18next-cli init')}`);
170
+ console.error(node_util.styleText('red', 'Could not automatically detect your project structure.'));
171
+ console.log(`Please create a config file first by running: ${node_util.styleText('cyan', 'npx i18next-cli init')}`);
172
172
  process.exit(1);
173
173
  }
174
- console.log(chalk.green('Project structure detected successfully!'));
174
+ console.log(node_util.styleText('green', 'Project structure detected successfully!'));
175
175
  config$1 = detected;
176
176
  }
177
177
  await linter.runLinterCli(config$1, { quiet: !!options.quiet });
@@ -242,21 +242,21 @@ program
242
242
  const result = await renameKey.runRenameKey(config$1, oldKey, newKey, options);
243
243
  if (!result.success) {
244
244
  if (result.conflicts) {
245
- console.error(chalk.red('\n❌ Conflicts detected:'));
245
+ console.error(node_util.styleText('red', '\n❌ Conflicts detected:'));
246
246
  result.conflicts.forEach(c => console.error(` - ${c}`));
247
247
  }
248
248
  if (result.error) {
249
- console.error(chalk.red(`\n❌ ${result.error}`));
249
+ console.error(node_util.styleText('red', `\n❌ ${result.error}`));
250
250
  }
251
251
  process.exit(1);
252
252
  }
253
253
  const totalChanges = result.sourceFiles.reduce((sum, f) => sum + f.changes, 0);
254
254
  if (totalChanges === 0) {
255
- console.log(chalk.yellow(`\n⚠️ No usages found for "${oldKey}"`));
255
+ console.log(node_util.styleText('yellow', `\n⚠️ No usages found for "${oldKey}"`));
256
256
  }
257
257
  }
258
258
  catch (error) {
259
- console.error(chalk.red('Error renaming key:'), error);
259
+ console.error(node_util.styleText('red', 'Error renaming key:'), error);
260
260
  process.exit(1);
261
261
  }
262
262
  });
@@ -6,7 +6,7 @@ var promises = require('node:fs/promises');
6
6
  var jiti = require('jiti');
7
7
  var jsoncParser = require('jsonc-parser');
8
8
  var inquirer = require('inquirer');
9
- var chalk = require('chalk');
9
+ var node_util = require('node:util');
10
10
  var init = require('./init.js');
11
11
  var logger = require('./utils/logger.js');
12
12
 
@@ -130,18 +130,18 @@ async function ensureConfig(configPath, logger$1 = new logger.ConsoleLogger()) {
130
130
  const { shouldInit } = await inquirer.prompt([{
131
131
  type: 'confirm',
132
132
  name: 'shouldInit',
133
- message: chalk.yellow('Configuration file not found. Would you like to create one now?'),
133
+ message: node_util.styleText('yellow', 'Configuration file not found. Would you like to create one now?'),
134
134
  default: true,
135
135
  }]);
136
136
  if (shouldInit) {
137
137
  await init.runInit(); // Run the interactive setup wizard (keeps existing behavior)
138
- logger$1.info(chalk.green('Configuration created. Resuming command...'));
138
+ logger$1.info(node_util.styleText('green', 'Configuration created. Resuming command...'));
139
139
  config = await loadConfig(configPath, logger$1); // Try loading the newly created config
140
140
  if (config) {
141
141
  return config;
142
142
  }
143
143
  else {
144
- logger$1.error(chalk.red('Error: Failed to load configuration after creation. Please try running the command again.'));
144
+ logger$1.error(node_util.styleText('red', 'Error: Failed to load configuration after creation. Please try running the command again.'));
145
145
  process.exit(1);
146
146
  }
147
147
  }
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var wrapOra = require('../../utils/wrap-ora.js');
4
- var chalk = require('chalk');
4
+ var node_util = require('node:util');
5
5
  var core = require('@swc/core');
6
6
  var promises = require('node:fs/promises');
7
7
  var node_path = require('node:path');
@@ -71,7 +71,7 @@ async function runExtractor(config, options = {}) {
71
71
  const fileContent = fileUtils.serializeTranslationFile(result.newTranslations, effectiveFormat, config.extract.indentation, rawContent);
72
72
  await promises.mkdir(node_path.dirname(result.path), { recursive: true });
73
73
  await promises.writeFile(result.path, fileContent);
74
- internalLogger.info(chalk.green(`Updated: ${result.path}`));
74
+ internalLogger.info(node_util.styleText('green', `Updated: ${result.path}`));
75
75
  }
76
76
  }
77
77
  }
@@ -82,14 +82,14 @@ async function runExtractor(config, options = {}) {
82
82
  await plugin.afterSync?.(results, config);
83
83
  }
84
84
  }
85
- spinner.succeed(chalk.bold('Extraction complete!'));
85
+ spinner.succeed(node_util.styleText('bold', 'Extraction complete!'));
86
86
  // Show the funnel message only if files were actually changed.
87
87
  if (anyFileUpdated)
88
88
  await printLocizeFunnel(options.logger);
89
89
  return anyFileUpdated;
90
90
  }
91
91
  catch (error) {
92
- spinner.fail(chalk.red('Extraction failed.'));
92
+ spinner.fail(node_util.styleText('red', 'Extraction failed.'));
93
93
  // Re-throw or handle error
94
94
  throw error;
95
95
  }
@@ -204,7 +204,7 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
204
204
  }
205
205
  }
206
206
  catch (error) {
207
- logger$1.warn(`${chalk.yellow('Skipping file due to error:')} ${file}`);
207
+ logger$1.warn(`${node_util.styleText('yellow', 'Skipping file due to error:')} ${file}`);
208
208
  const err = error;
209
209
  const msg = typeof err?.message === 'string' && err.message.trim().length > 0
210
210
  ? err.message
@@ -248,18 +248,18 @@ async function printLocizeFunnel(logger$1) {
248
248
  return;
249
249
  const internalLogger = logger$1 ?? new logger.ConsoleLogger();
250
250
  if (typeof internalLogger.info === 'function') {
251
- internalLogger.info(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
251
+ internalLogger.info(node_util.styleText(['yellow', 'bold'], '\n💡 Tip: Tired of running the extractor manually?'));
252
252
  internalLogger.info(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
253
253
  internalLogger.info(' where keys are created and translated automatically as you code.');
254
- internalLogger.info(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
255
- internalLogger.info(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
254
+ internalLogger.info(` Learn more: ${node_util.styleText('cyan', 'https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
255
+ internalLogger.info(` Watch the video: ${node_util.styleText('cyan', 'https://youtu.be/joPsZghT3wM')}`);
256
256
  }
257
257
  else {
258
- console.log(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
258
+ console.log(node_util.styleText(['yellow', 'bold'], '\n💡 Tip: Tired of running the extractor manually?'));
259
259
  console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
260
260
  console.log(' where keys are created and translated automatically as you code.');
261
- console.log(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
262
- console.log(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
261
+ console.log(` Learn more: ${node_util.styleText('cyan', 'https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
262
+ console.log(` Watch the video: ${node_util.styleText('cyan', 'https://youtu.be/joPsZghT3wM')}`);
263
263
  }
264
264
  return funnelMsgTracker.recordFunnelShown('extract');
265
265
  }
@@ -5,7 +5,7 @@ var promises = require('node:fs/promises');
5
5
  var core = require('@swc/core');
6
6
  var node_path = require('node:path');
7
7
  var node_events = require('node:events');
8
- var chalk = require('chalk');
8
+ var node_util = require('node:util');
9
9
  var logger = require('./utils/logger.js');
10
10
  var wrapOra = require('./utils/wrap-ora.js');
11
11
 
@@ -313,25 +313,25 @@ async function runLinterCli(config, options = {}) {
313
313
  try {
314
314
  const { success, message, files } = await linter.run();
315
315
  if (!success) {
316
- spinner.fail(chalk.red.bold(message));
316
+ spinner.fail(node_util.styleText(['red', 'bold'], message));
317
317
  // Print detailed report after spinner fails
318
318
  for (const [file, issues] of Object.entries(files)) {
319
319
  if (internalLogger.info)
320
- internalLogger.info(chalk.yellow(`\n${file}`));
320
+ internalLogger.info(node_util.styleText('yellow', `\n${file}`));
321
321
  else
322
- console.log(chalk.yellow(`\n${file}`));
322
+ console.log(node_util.styleText('yellow', `\n${file}`));
323
323
  issues.forEach(({ text, line, type }) => {
324
324
  const label = type === 'interpolation' ? 'Interpolation issue' : 'Found hardcoded string';
325
325
  if (typeof internalLogger.info === 'function')
326
- internalLogger.info(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} ${label}: "${text}"`);
326
+ internalLogger.info(` ${node_util.styleText('gray', `${line}:`)} ${node_util.styleText('red', 'Error:')} ${label}: "${text}"`);
327
327
  else
328
- console.log(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} ${label}: "${text}"`);
328
+ console.log(` ${node_util.styleText('gray', `${line}:`)} ${node_util.styleText('red', 'Error:')} ${label}: "${text}"`);
329
329
  });
330
330
  }
331
331
  process.exit(1);
332
332
  }
333
333
  else {
334
- spinner.succeed(chalk.green.bold(message));
334
+ spinner.succeed(node_util.styleText(['green', 'bold'], message));
335
335
  }
336
336
  }
337
337
  catch (error) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var execa = require('execa');
4
- var chalk = require('chalk');
4
+ var node_util = require('node:util');
5
5
  var ora = require('ora');
6
6
  var inquirer = require('inquirer');
7
7
  var node_path = require('node:path');
@@ -24,9 +24,9 @@ async function checkLocizeCliExists() {
24
24
  }
25
25
  catch (error) {
26
26
  if (error.code === 'ENOENT') {
27
- console.error(chalk.red('Error: `locize-cli` command not found.'));
28
- console.log(chalk.yellow('Please install it globally to use the Locize integration:'));
29
- console.log(chalk.cyan('npm install -g locize-cli'));
27
+ console.error(node_util.styleText('red', 'Error: `locize-cli` command not found.'));
28
+ console.log(node_util.styleText('yellow', 'Please install it globally to use the Locize integration:'));
29
+ console.log(node_util.styleText('cyan', 'npm install -g locize-cli'));
30
30
  process.exit(1);
31
31
  }
32
32
  }
@@ -54,7 +54,7 @@ async function checkLocizeCliExists() {
54
54
  * ```
55
55
  */
56
56
  async function interactiveCredentialSetup(config) {
57
- console.log(chalk.yellow('\nLocize configuration is missing or invalid. Let\'s set it up!'));
57
+ console.log(node_util.styleText('yellow', '\nLocize configuration is missing or invalid. Let\'s set it up!'));
58
58
  const answers = await inquirer.prompt([
59
59
  {
60
60
  type: 'input',
@@ -76,7 +76,7 @@ async function interactiveCredentialSetup(config) {
76
76
  },
77
77
  ]);
78
78
  if (!answers.projectId) {
79
- console.error(chalk.red('Project ID is required to continue.'));
79
+ console.error(node_util.styleText('red', 'Project ID is required to continue.'));
80
80
  return undefined;
81
81
  }
82
82
  const { save } = await inquirer.prompt([{
@@ -98,11 +98,11 @@ LOCIZE_API_KEY=${answers.apiKey}
98
98
  apiKey: process.env.LOCIZE_API_KEY,
99
99
  version: '${answers.version}',
100
100
  },`;
101
- console.log(chalk.cyan('\nGreat! For the best security, we recommend using environment variables for your API key.'));
102
- console.log(chalk.bold('\nRecommended approach (.env file):'));
103
- console.log(chalk.green(envSnippet));
104
- console.log(chalk.bold('Then, in your i18next.config.ts:'));
105
- console.log(chalk.green(configSnippet));
101
+ console.log(node_util.styleText('cyan', '\nGreat! For the best security, we recommend using environment variables for your API key.'));
102
+ console.log(node_util.styleText('bold', '\nRecommended approach (.env file):'));
103
+ console.log(node_util.styleText('green', envSnippet));
104
+ console.log(node_util.styleText('bold', 'Then, in your i18next.config.ts:'));
105
+ console.log(node_util.styleText('green', configSnippet));
106
106
  }
107
107
  return {
108
108
  projectId: answers.projectId,
@@ -217,9 +217,9 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
217
217
  try {
218
218
  // 1. First attempt
219
219
  const initialArgs = buildArgs(command, effectiveConfig, cliOptions);
220
- console.log(chalk.cyan(`\nRunning 'locize ${initialArgs.join(' ')}'...`));
220
+ console.log(node_util.styleText('cyan', `\nRunning 'locize ${initialArgs.join(' ')}'...`));
221
221
  const result = await execa.execa('locize', initialArgs, { stdio: 'pipe' });
222
- spinner.succeed(chalk.green(`'locize ${command}' completed successfully.`));
222
+ spinner.succeed(node_util.styleText('green', `'locize ${command}' completed successfully.`));
223
223
  if (result?.stdout)
224
224
  console.log(result.stdout); // Print captured output on success
225
225
  }
@@ -234,14 +234,14 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
234
234
  try {
235
235
  // 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
236
236
  const retryArgs = buildArgs(command, effectiveConfig, cliOptions);
237
- console.log(chalk.cyan(`\nRunning 'locize ${retryArgs.join(' ')}'...`));
237
+ console.log(node_util.styleText('cyan', `\nRunning 'locize ${retryArgs.join(' ')}'...`));
238
238
  const result = await execa.execa('locize', retryArgs, { stdio: 'pipe' });
239
- spinner.succeed(chalk.green('Retry successful!'));
239
+ spinner.succeed(node_util.styleText('green', 'Retry successful!'));
240
240
  if (result?.stdout)
241
241
  console.log(result.stdout);
242
242
  }
243
243
  catch (retryError) {
244
- spinner.fail(chalk.red('Error during retry.'));
244
+ spinner.fail(node_util.styleText('red', 'Error during retry.'));
245
245
  console.error(retryError.stderr || retryError.message);
246
246
  process.exit(1);
247
247
  }
@@ -253,12 +253,12 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
253
253
  }
254
254
  else {
255
255
  // Handle other errors
256
- spinner.fail(chalk.red(`Error executing 'locize ${command}'.`));
256
+ spinner.fail(node_util.styleText('red', `Error executing 'locize ${command}'.`));
257
257
  console.error(stderr || error.message);
258
258
  process.exit(1);
259
259
  }
260
260
  }
261
- console.log(chalk.green(`\n✅ 'locize ${command}' completed successfully.`));
261
+ console.log(node_util.styleText('green', `\n✅ 'locize ${command}' completed successfully.`));
262
262
  }
263
263
  const runLocizeSync = (config, cliOptions) => runLocizeCommand('sync', config, cliOptions);
264
264
  const runLocizeDownload = (config, cliOptions) => runLocizeCommand('download', config, cliOptions);
@@ -7,7 +7,7 @@ var logger = require('./utils/logger.js');
7
7
  var fileUtils = require('./utils/file-utils.js');
8
8
  var nestedObject = require('./utils/nested-object.js');
9
9
  var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
10
- var chalk = require('chalk');
10
+ var node_util = require('node:util');
11
11
 
12
12
  const pluralSuffixes = ['zero', 'one', 'two', 'few', 'many', 'other'];
13
13
  /**
@@ -106,11 +106,11 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger$1 = new
106
106
  async function printLocizeFunnel() {
107
107
  if (!(await funnelMsgTracker.shouldShowFunnel('rename-key')))
108
108
  return;
109
- console.log(chalk.yellow.bold('\n💡 Tip: Managing translations across multiple projects?'));
109
+ console.log(node_util.styleText(['yellow', 'bold'], '\n💡 Tip: Managing translations across multiple projects?'));
110
110
  console.log(' With Locize, you can rename, move, and copy translation keys directly');
111
111
  console.log(' in the web interface—no CLI needed. Perfect for collaboration with');
112
112
  console.log(' translators and managing complex refactoring across namespaces.');
113
- console.log(` Learn more: ${chalk.cyan('https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
113
+ console.log(` Learn more: ${node_util.styleText('cyan', 'https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
114
114
  return funnelMsgTracker.recordFunnelShown('rename-key');
115
115
  }
116
116
  function parseKeyWithNamespace(key, config) {
@@ -1,12 +1,16 @@
1
1
  'use strict';
2
2
 
3
- var chalk = require('chalk');
3
+ var node_util = require('node:util');
4
4
  var ora = require('ora');
5
5
  var node_path = require('node:path');
6
+ require('@swc/core');
7
+ require('node:fs/promises');
6
8
  var keyFinder = require('./extractor/core/key-finder.js');
9
+ require('glob');
7
10
  var nestedObject = require('./utils/nested-object.js');
8
11
  var fileUtils = require('./utils/file-utils.js');
9
12
  var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
13
+ require('./extractor/parsers/jsx-parser.js');
10
14
 
11
15
  /**
12
16
  * Runs a health check on the project's i18next translations and displays a status report.
@@ -239,19 +243,19 @@ async function displayStatusReport(report, config, options) {
239
243
  */
240
244
  async function displayDetailedLocaleReport(report, config, locale, namespaceFilter) {
241
245
  if (locale === config.extract.primaryLanguage) {
242
- console.log(chalk.yellow(`Locale "${locale}" is the primary language. All keys are considered present.`));
246
+ console.log(node_util.styleText('yellow', `Locale "${locale}" is the primary language. All keys are considered present.`));
243
247
  return;
244
248
  }
245
249
  if (!config.locales.includes(locale)) {
246
- console.error(chalk.red(`Error: Locale "${locale}" is not defined in your configuration.`));
250
+ console.error(node_util.styleText('red', `Error: Locale "${locale}" is not defined in your configuration.`));
247
251
  return;
248
252
  }
249
253
  const localeData = report.locales.get(locale);
250
254
  if (!localeData) {
251
- console.error(chalk.red(`Error: Locale "${locale}" is not a valid secondary language.`));
255
+ console.error(node_util.styleText('red', `Error: Locale "${locale}" is not a valid secondary language.`));
252
256
  return;
253
257
  }
254
- console.log(chalk.bold(`\nKey Status for "${chalk.cyan(locale)}":`));
258
+ console.log(node_util.styleText('bold', `\nKey Status for "${node_util.styleText('cyan', locale)}":`));
255
259
  const totalKeysForLocale = localeData.totalKeys;
256
260
  printProgressBar('Overall', localeData.totalTranslated, totalKeysForLocale);
257
261
  const namespacesToDisplay = namespaceFilter ? [namespaceFilter] : Array.from(localeData.namespaces.keys()).sort();
@@ -259,19 +263,19 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
259
263
  const nsData = localeData.namespaces.get(ns);
260
264
  if (!nsData)
261
265
  continue;
262
- console.log(chalk.cyan.bold(`\nNamespace: ${ns}`));
266
+ console.log(node_util.styleText(['cyan', 'bold'], `\nNamespace: ${ns}`));
263
267
  printProgressBar('Namespace Progress', nsData.translatedKeys, nsData.totalKeys);
264
268
  nsData.keyDetails.forEach(({ key, isTranslated }) => {
265
- const icon = isTranslated ? chalk.green('✓') : chalk.red('✗');
269
+ const icon = isTranslated ? node_util.styleText('green', '✓') : node_util.styleText('red', '✗');
266
270
  console.log(` ${icon} ${key}`);
267
271
  });
268
272
  }
269
273
  const missingCount = totalKeysForLocale - localeData.totalTranslated;
270
274
  if (missingCount > 0) {
271
- console.log(chalk.yellow.bold(`\nSummary: Found ${missingCount} missing translations for "${locale}".`));
275
+ console.log(node_util.styleText(['yellow', 'bold'], `\nSummary: Found ${missingCount} missing translations for "${locale}".`));
272
276
  }
273
277
  else {
274
- console.log(chalk.green.bold(`\nSummary: 🎉 All keys are translated for "${locale}".`));
278
+ console.log(node_util.styleText(['green', 'bold'], `\nSummary: 🎉 All keys are translated for "${locale}".`));
275
279
  }
276
280
  await printLocizeFunnel();
277
281
  }
@@ -288,10 +292,10 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
288
292
  async function displayNamespaceSummaryReport(report, config, namespace) {
289
293
  const nsData = report.keysByNs.get(namespace);
290
294
  if (!nsData) {
291
- console.error(chalk.red(`Error: Namespace "${namespace}" was not found in your source code.`));
295
+ console.error(node_util.styleText('red', `Error: Namespace "${namespace}" was not found in your source code.`));
292
296
  return;
293
297
  }
294
- console.log(chalk.cyan.bold(`\nStatus for Namespace: "${namespace}"`));
298
+ console.log(node_util.styleText(['cyan', 'bold'], `\nStatus for Namespace: "${namespace}"`));
295
299
  console.log('------------------------');
296
300
  for (const [locale, localeData] of report.locales.entries()) {
297
301
  const nsLocaleData = localeData.namespaces.get(namespace);
@@ -316,12 +320,13 @@ async function displayNamespaceSummaryReport(report, config, namespace) {
316
320
  */
317
321
  async function displayOverallSummaryReport(report, config) {
318
322
  const { primaryLanguage } = config.extract;
319
- console.log(chalk.cyan.bold('\ni18next Project Status'));
323
+ console.log(node_util.styleText(['cyan', 'bold'], '\ni18next Project Status'));
320
324
  console.log('------------------------');
321
- console.log(`🔑 Keys Found: ${chalk.bold(report.totalBaseKeys)}`);
322
- console.log(`📚 Namespaces Found: ${chalk.bold(report.keysByNs.size)}`);
323
- console.log(`🌍 Locales: ${chalk.bold(config.locales.join(', '))}`);
324
- console.log(`✅ Primary Language: ${chalk.bold(primaryLanguage)}`);
325
+ console.log(`🔑 Keys Found: ${node_util.styleText('bold', `${report.totalBaseKeys}`)}`);
326
+ console.log(`📚 Namespaces Found: ${node_util.styleText('bold', `${report.keysByNs.size}`)}`);
327
+ console.log(`🌍 Locales: ${node_util.styleText('bold', config.locales.join(', '))}`);
328
+ if (primaryLanguage)
329
+ console.log(`✅ Primary Language: ${node_util.styleText('bold', primaryLanguage)}`);
325
330
  console.log('\nTranslation Progress:');
326
331
  for (const [locale, localeData] of report.locales.entries()) {
327
332
  const percentage = localeData.totalKeys > 0 ? Math.round((localeData.totalTranslated / localeData.totalKeys) * 100) : 100;
@@ -340,7 +345,7 @@ async function displayOverallSummaryReport(report, config) {
340
345
  function printProgressBar(label, current, total) {
341
346
  const percentage = total > 0 ? Math.round((current / total) * 100) : 100;
342
347
  const bar = generateProgressBarText(percentage);
343
- console.log(`${chalk.bold(label)}: ${bar} ${percentage}% (${current}/${total})`);
348
+ console.log(`${node_util.styleText('bold', label)}: ${bar} ${percentage}% (${current}/${total})`);
344
349
  }
345
350
  /**
346
351
  * Generates a visual progress bar string based on percentage completion.
@@ -355,14 +360,14 @@ function generateProgressBarText(percentage) {
355
360
  const totalBars = 20;
356
361
  const filledBars = Math.floor((percentage / 100) * totalBars);
357
362
  const emptyBars = totalBars - filledBars;
358
- return `[${chalk.green(''.padStart(filledBars, '■'))}${''.padStart(emptyBars, '□')}]`;
363
+ return `[${node_util.styleText('green', ''.padStart(filledBars, '■'))}${''.padStart(emptyBars, '□')}]`;
359
364
  }
360
365
  async function printLocizeFunnel() {
361
366
  if (!(await funnelMsgTracker.shouldShowFunnel('status')))
362
367
  return;
363
- console.log(chalk.yellow.bold('\n✨ Take your localization to the next level!'));
368
+ console.log(node_util.styleText(['yellow', 'bold'], '\n✨ Take your localization to the next level!'));
364
369
  console.log('Manage translations with your team in the cloud with Locize => https://www.locize.com/docs/getting-started');
365
- console.log(`Run ${chalk.cyan('npx i18next-cli locize-migrate')} to get started.`);
370
+ console.log(`Run ${node_util.styleText('cyan', 'npx i18next-cli locize-migrate')} to get started.`);
366
371
  return funnelMsgTracker.recordFunnelShown('status');
367
372
  }
368
373
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chalk = require('chalk');
3
+ var node_util = require('node:util');
4
4
  var glob = require('glob');
5
5
  var promises = require('node:fs/promises');
6
6
  var node_path = require('node:path');
@@ -75,7 +75,7 @@ async function runSyncer(config, options = {}) {
75
75
  continue;
76
76
  const primaryTranslations = await fileUtils.loadTranslationFile(primaryPath);
77
77
  if (!primaryTranslations) {
78
- logMessages.push(` ${chalk.yellow('-')} Could not read primary file: ${primaryPath}`);
78
+ logMessages.push(` ${node_util.styleText('yellow', '-')} Could not read primary file: ${primaryPath}`);
79
79
  continue;
80
80
  }
81
81
  const primaryKeys = nestedObject.getNestedKeys(primaryTranslations, keySeparator ?? '.');
@@ -102,27 +102,27 @@ async function runSyncer(config, options = {}) {
102
102
  const serializedContent = fileUtils.serializeTranslationFile(newSecondaryTranslations, perFileFormat, indentation, raw);
103
103
  await promises.mkdir(node_path.dirname(fullSecondaryPath), { recursive: true });
104
104
  await promises.writeFile(fullSecondaryPath, serializedContent);
105
- logMessages.push(` ${chalk.green('✓')} Synchronized: ${secondaryPath}`);
105
+ logMessages.push(` ${node_util.styleText('green', '✓')} Synchronized: ${secondaryPath}`);
106
106
  }
107
107
  else {
108
- logMessages.push(` ${chalk.gray('-')} Already in sync: ${secondaryPath}`);
108
+ logMessages.push(` ${node_util.styleText('gray', '-')} Already in sync: ${secondaryPath}`);
109
109
  }
110
110
  }
111
111
  }
112
- spinner.succeed(chalk.bold('Synchronization complete!'));
112
+ spinner.succeed(node_util.styleText('bold', 'Synchronization complete!'));
113
113
  logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
114
114
  if (wasAnythingSynced) {
115
115
  await printLocizeFunnel();
116
116
  }
117
117
  else {
118
118
  if (typeof internalLogger.info === 'function')
119
- internalLogger.info(chalk.green.bold('\n✅ All locales are already in sync.'));
119
+ internalLogger.info(node_util.styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
120
120
  else
121
- console.log(chalk.green.bold('\n✅ All locales are already in sync.'));
121
+ console.log(node_util.styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
122
122
  }
123
123
  }
124
124
  catch (error) {
125
- spinner.fail(chalk.red('Synchronization failed.'));
125
+ spinner.fail(node_util.styleText('red', 'Synchronization failed.'));
126
126
  if (typeof internalLogger.error === 'function')
127
127
  internalLogger.error(error);
128
128
  else
@@ -132,9 +132,9 @@ async function runSyncer(config, options = {}) {
132
132
  async function printLocizeFunnel() {
133
133
  if (!(await funnelMsgTracker.shouldShowFunnel('syncer')))
134
134
  return;
135
- console.log(chalk.green.bold('\n✅ Sync complete.'));
136
- console.log(chalk.yellow('🚀 Ready to collaborate with translators? Move your files to the cloud.'));
137
- console.log(` Get started with the official TMS for i18next: ${chalk.cyan('npx i18next-cli locize-migrate')}`);
135
+ console.log(node_util.styleText(['green', 'bold'], '\n✅ Sync complete.'));
136
+ console.log(node_util.styleText('yellow', '🚀 Ready to collaborate with translators? Move your files to the cloud.'));
137
+ console.log(` Get started with the official TMS for i18next: ${node_util.styleText('cyan', 'npx i18next-cli locize-migrate')}`);
138
138
  return funnelMsgTracker.recordFunnelShown('syncer');
139
139
  }
140
140
 
@@ -4,7 +4,7 @@ var logger = require('./utils/logger.js');
4
4
  var i18nextResourcesForTs = require('i18next-resources-for-ts');
5
5
  var glob = require('glob');
6
6
  var wrapOra = require('./utils/wrap-ora.js');
7
- var chalk = require('chalk');
7
+ var node_util = require('node:util');
8
8
  var promises = require('node:fs/promises');
9
9
  var node_path = require('node:path');
10
10
  var core = require('@swc/core');
@@ -111,7 +111,7 @@ async function runTypesGenerator(config, options = {}) {
111
111
  }
112
112
  const nonObjectKeys = keys.filter(k => !parsedContent[k] || typeof parsedContent[k] !== 'object');
113
113
  if (nonObjectKeys.length > 0) {
114
- console.warn(chalk.yellow(`Warning: The file ${file} contains top-level keys that are not objects (${nonObjectKeys.join(', ')}). When 'mergeNamespaces' is enabled, top-level keys are treated as namespaces. These keys will be ignored.`));
114
+ console.warn(node_util.styleText('yellow', `Warning: The file ${file} contains top-level keys that are not objects (${nonObjectKeys.join(', ')}). When 'mergeNamespaces' is enabled, top-level keys are treated as namespaces. These keys will be ignored.`));
115
115
  }
116
116
  continue;
117
117
  }
@@ -127,7 +127,7 @@ ${i18nextResourcesForTs.mergeResourcesAsInterface(resources, { optimize: !!enabl
127
127
  const resourcesOutputPath = node_path.resolve(process.cwd(), config.types.resourcesFile);
128
128
  await promises.mkdir(node_path.dirname(resourcesOutputPath), { recursive: true });
129
129
  await promises.writeFile(resourcesOutputPath, interfaceDefinition);
130
- logMessages.push(` ${chalk.green('✓')} Resources interface written to ${config.types.resourcesFile}`);
130
+ logMessages.push(` ${node_util.styleText('green', '✓')} Resources interface written to ${config.types.resourcesFile}`);
131
131
  let outputPathExists;
132
132
  try {
133
133
  await promises.access(outputPath);
@@ -154,13 +154,13 @@ declare module 'i18next' {
154
154
  }`;
155
155
  await promises.mkdir(node_path.dirname(outputPath), { recursive: true });
156
156
  await promises.writeFile(outputPath, fileContent);
157
- logMessages.push(` ${chalk.green('✓')} TypeScript definitions written to ${config.types.output || ''}`);
157
+ logMessages.push(` ${node_util.styleText('green', '✓')} TypeScript definitions written to ${config.types.output || ''}`);
158
158
  }
159
- spinner.succeed(chalk.bold('TypeScript definitions generated successfully.'));
159
+ spinner.succeed(node_util.styleText('bold', 'TypeScript definitions generated successfully.'));
160
160
  logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
161
161
  }
162
162
  catch (error) {
163
- spinner.fail(chalk.red('Failed to generate TypeScript definitions.'));
163
+ spinner.fail(node_util.styleText('red', 'Failed to generate TypeScript definitions.'));
164
164
  if (typeof internalLogger.error === 'function')
165
165
  internalLogger.error(error);
166
166
  else
package/dist/esm/cli.js CHANGED
@@ -3,7 +3,7 @@ import { Command } from 'commander';
3
3
  import chokidar from 'chokidar';
4
4
  import { glob } from 'glob';
5
5
  import { minimatch } from 'minimatch';
6
- import chalk from 'chalk';
6
+ import { styleText } from 'node:util';
7
7
  import { ensureConfig, loadConfig } from './config.js';
8
8
  import { detectConfig } from './heuristic-config.js';
9
9
  import { runExtractor } from './extractor/core/extractor.js';
@@ -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.42.6'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.42.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
@@ -98,14 +98,14 @@ program
98
98
  const cfgPath = program.opts().config;
99
99
  let config = await loadConfig(cfgPath);
100
100
  if (!config) {
101
- console.log(chalk.blue('No config file found. Attempting to detect project structure...'));
101
+ console.log(styleText('blue', 'No config file found. Attempting to detect project structure...'));
102
102
  const detected = await detectConfig();
103
103
  if (!detected) {
104
- console.error(chalk.red('Could not automatically detect your project structure.'));
105
- console.log(`Please create a config file first by running: ${chalk.cyan('npx i18next-cli init')}`);
104
+ console.error(styleText('red', 'Could not automatically detect your project structure.'));
105
+ console.log(`Please create a config file first by running: ${styleText('cyan', 'npx i18next-cli init')}`);
106
106
  process.exit(1);
107
107
  }
108
- console.log(chalk.green('Project structure detected successfully!'));
108
+ console.log(styleText('green', 'Project structure detected successfully!'));
109
109
  config = detected;
110
110
  }
111
111
  await runStatus(config, { detail: locale, namespace: options.namespace });
@@ -162,14 +162,14 @@ program
162
162
  // The existing logic for loading the config or detecting it is now inside this function
163
163
  let config = await loadConfig(cfgPath);
164
164
  if (!config) {
165
- console.log(chalk.blue('No config file found. Attempting to detect project structure...'));
165
+ console.log(styleText('blue', 'No config file found. Attempting to detect project structure...'));
166
166
  const detected = await detectConfig();
167
167
  if (!detected) {
168
- console.error(chalk.red('Could not automatically detect your project structure.'));
169
- console.log(`Please create a config file first by running: ${chalk.cyan('npx i18next-cli init')}`);
168
+ console.error(styleText('red', 'Could not automatically detect your project structure.'));
169
+ console.log(`Please create a config file first by running: ${styleText('cyan', 'npx i18next-cli init')}`);
170
170
  process.exit(1);
171
171
  }
172
- console.log(chalk.green('Project structure detected successfully!'));
172
+ console.log(styleText('green', 'Project structure detected successfully!'));
173
173
  config = detected;
174
174
  }
175
175
  await runLinterCli(config, { quiet: !!options.quiet });
@@ -240,21 +240,21 @@ program
240
240
  const result = await runRenameKey(config, oldKey, newKey, options);
241
241
  if (!result.success) {
242
242
  if (result.conflicts) {
243
- console.error(chalk.red('\n❌ Conflicts detected:'));
243
+ console.error(styleText('red', '\n❌ Conflicts detected:'));
244
244
  result.conflicts.forEach(c => console.error(` - ${c}`));
245
245
  }
246
246
  if (result.error) {
247
- console.error(chalk.red(`\n❌ ${result.error}`));
247
+ console.error(styleText('red', `\n❌ ${result.error}`));
248
248
  }
249
249
  process.exit(1);
250
250
  }
251
251
  const totalChanges = result.sourceFiles.reduce((sum, f) => sum + f.changes, 0);
252
252
  if (totalChanges === 0) {
253
- console.log(chalk.yellow(`\n⚠️ No usages found for "${oldKey}"`));
253
+ console.log(styleText('yellow', `\n⚠️ No usages found for "${oldKey}"`));
254
254
  }
255
255
  }
256
256
  catch (error) {
257
- console.error(chalk.red('Error renaming key:'), error);
257
+ console.error(styleText('red', 'Error renaming key:'), error);
258
258
  process.exit(1);
259
259
  }
260
260
  });
@@ -4,7 +4,7 @@ import { access, readFile } from 'node:fs/promises';
4
4
  import { createJiti } from 'jiti';
5
5
  import { parse } from 'jsonc-parser';
6
6
  import inquirer from 'inquirer';
7
- import chalk from 'chalk';
7
+ import { styleText } from 'node:util';
8
8
  import { runInit } from './init.js';
9
9
  import { ConsoleLogger } from './utils/logger.js';
10
10
 
@@ -128,18 +128,18 @@ async function ensureConfig(configPath, logger = new ConsoleLogger()) {
128
128
  const { shouldInit } = await inquirer.prompt([{
129
129
  type: 'confirm',
130
130
  name: 'shouldInit',
131
- message: chalk.yellow('Configuration file not found. Would you like to create one now?'),
131
+ message: styleText('yellow', 'Configuration file not found. Would you like to create one now?'),
132
132
  default: true,
133
133
  }]);
134
134
  if (shouldInit) {
135
135
  await runInit(); // Run the interactive setup wizard (keeps existing behavior)
136
- logger.info(chalk.green('Configuration created. Resuming command...'));
136
+ logger.info(styleText('green', 'Configuration created. Resuming command...'));
137
137
  config = await loadConfig(configPath, logger); // Try loading the newly created config
138
138
  if (config) {
139
139
  return config;
140
140
  }
141
141
  else {
142
- logger.error(chalk.red('Error: Failed to load configuration after creation. Please try running the command again.'));
142
+ logger.error(styleText('red', 'Error: Failed to load configuration after creation. Please try running the command again.'));
143
143
  process.exit(1);
144
144
  }
145
145
  }
@@ -1,5 +1,5 @@
1
1
  import { createSpinnerLike } from '../../utils/wrap-ora.js';
2
- import chalk from 'chalk';
2
+ import { styleText } from 'node:util';
3
3
  import { parse } from '@swc/core';
4
4
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
5
5
  import { dirname, extname } from 'node:path';
@@ -69,7 +69,7 @@ async function runExtractor(config, options = {}) {
69
69
  const fileContent = serializeTranslationFile(result.newTranslations, effectiveFormat, config.extract.indentation, rawContent);
70
70
  await mkdir(dirname(result.path), { recursive: true });
71
71
  await writeFile(result.path, fileContent);
72
- internalLogger.info(chalk.green(`Updated: ${result.path}`));
72
+ internalLogger.info(styleText('green', `Updated: ${result.path}`));
73
73
  }
74
74
  }
75
75
  }
@@ -80,14 +80,14 @@ async function runExtractor(config, options = {}) {
80
80
  await plugin.afterSync?.(results, config);
81
81
  }
82
82
  }
83
- spinner.succeed(chalk.bold('Extraction complete!'));
83
+ spinner.succeed(styleText('bold', 'Extraction complete!'));
84
84
  // Show the funnel message only if files were actually changed.
85
85
  if (anyFileUpdated)
86
86
  await printLocizeFunnel(options.logger);
87
87
  return anyFileUpdated;
88
88
  }
89
89
  catch (error) {
90
- spinner.fail(chalk.red('Extraction failed.'));
90
+ spinner.fail(styleText('red', 'Extraction failed.'));
91
91
  // Re-throw or handle error
92
92
  throw error;
93
93
  }
@@ -202,7 +202,7 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
202
202
  }
203
203
  }
204
204
  catch (error) {
205
- logger.warn(`${chalk.yellow('Skipping file due to error:')} ${file}`);
205
+ logger.warn(`${styleText('yellow', 'Skipping file due to error:')} ${file}`);
206
206
  const err = error;
207
207
  const msg = typeof err?.message === 'string' && err.message.trim().length > 0
208
208
  ? err.message
@@ -246,18 +246,18 @@ async function printLocizeFunnel(logger) {
246
246
  return;
247
247
  const internalLogger = logger ?? new ConsoleLogger();
248
248
  if (typeof internalLogger.info === 'function') {
249
- internalLogger.info(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
249
+ internalLogger.info(styleText(['yellow', 'bold'], '\n💡 Tip: Tired of running the extractor manually?'));
250
250
  internalLogger.info(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
251
251
  internalLogger.info(' where keys are created and translated automatically as you code.');
252
- internalLogger.info(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
253
- internalLogger.info(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
252
+ internalLogger.info(` Learn more: ${styleText('cyan', 'https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
253
+ internalLogger.info(` Watch the video: ${styleText('cyan', 'https://youtu.be/joPsZghT3wM')}`);
254
254
  }
255
255
  else {
256
- console.log(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
256
+ console.log(styleText(['yellow', 'bold'], '\n💡 Tip: Tired of running the extractor manually?'));
257
257
  console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
258
258
  console.log(' where keys are created and translated automatically as you code.');
259
- console.log(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
260
- console.log(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
259
+ console.log(` Learn more: ${styleText('cyan', 'https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
260
+ console.log(` Watch the video: ${styleText('cyan', 'https://youtu.be/joPsZghT3wM')}`);
261
261
  }
262
262
  return recordFunnelShown('extract');
263
263
  }
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises';
3
3
  import { parse } from '@swc/core';
4
4
  import { extname } from 'node:path';
5
5
  import { EventEmitter } from 'node:events';
6
- import chalk from 'chalk';
6
+ import { styleText } from 'node:util';
7
7
  import { ConsoleLogger } from './utils/logger.js';
8
8
  import { createSpinnerLike } from './utils/wrap-ora.js';
9
9
 
@@ -311,25 +311,25 @@ async function runLinterCli(config, options = {}) {
311
311
  try {
312
312
  const { success, message, files } = await linter.run();
313
313
  if (!success) {
314
- spinner.fail(chalk.red.bold(message));
314
+ spinner.fail(styleText(['red', 'bold'], message));
315
315
  // Print detailed report after spinner fails
316
316
  for (const [file, issues] of Object.entries(files)) {
317
317
  if (internalLogger.info)
318
- internalLogger.info(chalk.yellow(`\n${file}`));
318
+ internalLogger.info(styleText('yellow', `\n${file}`));
319
319
  else
320
- console.log(chalk.yellow(`\n${file}`));
320
+ console.log(styleText('yellow', `\n${file}`));
321
321
  issues.forEach(({ text, line, type }) => {
322
322
  const label = type === 'interpolation' ? 'Interpolation issue' : 'Found hardcoded string';
323
323
  if (typeof internalLogger.info === 'function')
324
- internalLogger.info(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} ${label}: "${text}"`);
324
+ internalLogger.info(` ${styleText('gray', `${line}:`)} ${styleText('red', 'Error:')} ${label}: "${text}"`);
325
325
  else
326
- console.log(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} ${label}: "${text}"`);
326
+ console.log(` ${styleText('gray', `${line}:`)} ${styleText('red', 'Error:')} ${label}: "${text}"`);
327
327
  });
328
328
  }
329
329
  process.exit(1);
330
330
  }
331
331
  else {
332
- spinner.succeed(chalk.green.bold(message));
332
+ spinner.succeed(styleText(['green', 'bold'], message));
333
333
  }
334
334
  }
335
335
  catch (error) {
@@ -1,5 +1,5 @@
1
1
  import { execa } from 'execa';
2
- import chalk from 'chalk';
2
+ import { styleText } from 'node:util';
3
3
  import ora from 'ora';
4
4
  import inquirer from 'inquirer';
5
5
  import { sep, resolve } from 'node:path';
@@ -22,9 +22,9 @@ async function checkLocizeCliExists() {
22
22
  }
23
23
  catch (error) {
24
24
  if (error.code === 'ENOENT') {
25
- console.error(chalk.red('Error: `locize-cli` command not found.'));
26
- console.log(chalk.yellow('Please install it globally to use the Locize integration:'));
27
- console.log(chalk.cyan('npm install -g locize-cli'));
25
+ console.error(styleText('red', 'Error: `locize-cli` command not found.'));
26
+ console.log(styleText('yellow', 'Please install it globally to use the Locize integration:'));
27
+ console.log(styleText('cyan', 'npm install -g locize-cli'));
28
28
  process.exit(1);
29
29
  }
30
30
  }
@@ -52,7 +52,7 @@ async function checkLocizeCliExists() {
52
52
  * ```
53
53
  */
54
54
  async function interactiveCredentialSetup(config) {
55
- console.log(chalk.yellow('\nLocize configuration is missing or invalid. Let\'s set it up!'));
55
+ console.log(styleText('yellow', '\nLocize configuration is missing or invalid. Let\'s set it up!'));
56
56
  const answers = await inquirer.prompt([
57
57
  {
58
58
  type: 'input',
@@ -74,7 +74,7 @@ async function interactiveCredentialSetup(config) {
74
74
  },
75
75
  ]);
76
76
  if (!answers.projectId) {
77
- console.error(chalk.red('Project ID is required to continue.'));
77
+ console.error(styleText('red', 'Project ID is required to continue.'));
78
78
  return undefined;
79
79
  }
80
80
  const { save } = await inquirer.prompt([{
@@ -96,11 +96,11 @@ LOCIZE_API_KEY=${answers.apiKey}
96
96
  apiKey: process.env.LOCIZE_API_KEY,
97
97
  version: '${answers.version}',
98
98
  },`;
99
- console.log(chalk.cyan('\nGreat! For the best security, we recommend using environment variables for your API key.'));
100
- console.log(chalk.bold('\nRecommended approach (.env file):'));
101
- console.log(chalk.green(envSnippet));
102
- console.log(chalk.bold('Then, in your i18next.config.ts:'));
103
- console.log(chalk.green(configSnippet));
99
+ console.log(styleText('cyan', '\nGreat! For the best security, we recommend using environment variables for your API key.'));
100
+ console.log(styleText('bold', '\nRecommended approach (.env file):'));
101
+ console.log(styleText('green', envSnippet));
102
+ console.log(styleText('bold', 'Then, in your i18next.config.ts:'));
103
+ console.log(styleText('green', configSnippet));
104
104
  }
105
105
  return {
106
106
  projectId: answers.projectId,
@@ -215,9 +215,9 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
215
215
  try {
216
216
  // 1. First attempt
217
217
  const initialArgs = buildArgs(command, effectiveConfig, cliOptions);
218
- console.log(chalk.cyan(`\nRunning 'locize ${initialArgs.join(' ')}'...`));
218
+ console.log(styleText('cyan', `\nRunning 'locize ${initialArgs.join(' ')}'...`));
219
219
  const result = await execa('locize', initialArgs, { stdio: 'pipe' });
220
- spinner.succeed(chalk.green(`'locize ${command}' completed successfully.`));
220
+ spinner.succeed(styleText('green', `'locize ${command}' completed successfully.`));
221
221
  if (result?.stdout)
222
222
  console.log(result.stdout); // Print captured output on success
223
223
  }
@@ -232,14 +232,14 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
232
232
  try {
233
233
  // 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
234
234
  const retryArgs = buildArgs(command, effectiveConfig, cliOptions);
235
- console.log(chalk.cyan(`\nRunning 'locize ${retryArgs.join(' ')}'...`));
235
+ console.log(styleText('cyan', `\nRunning 'locize ${retryArgs.join(' ')}'...`));
236
236
  const result = await execa('locize', retryArgs, { stdio: 'pipe' });
237
- spinner.succeed(chalk.green('Retry successful!'));
237
+ spinner.succeed(styleText('green', 'Retry successful!'));
238
238
  if (result?.stdout)
239
239
  console.log(result.stdout);
240
240
  }
241
241
  catch (retryError) {
242
- spinner.fail(chalk.red('Error during retry.'));
242
+ spinner.fail(styleText('red', 'Error during retry.'));
243
243
  console.error(retryError.stderr || retryError.message);
244
244
  process.exit(1);
245
245
  }
@@ -251,12 +251,12 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
251
251
  }
252
252
  else {
253
253
  // Handle other errors
254
- spinner.fail(chalk.red(`Error executing 'locize ${command}'.`));
254
+ spinner.fail(styleText('red', `Error executing 'locize ${command}'.`));
255
255
  console.error(stderr || error.message);
256
256
  process.exit(1);
257
257
  }
258
258
  }
259
- console.log(chalk.green(`\n✅ 'locize ${command}' completed successfully.`));
259
+ console.log(styleText('green', `\n✅ 'locize ${command}' completed successfully.`));
260
260
  }
261
261
  const runLocizeSync = (config, cliOptions) => runLocizeCommand('sync', config, cliOptions);
262
262
  const runLocizeDownload = (config, cliOptions) => runLocizeCommand('download', config, cliOptions);
@@ -5,7 +5,7 @@ import { ConsoleLogger } from './utils/logger.js';
5
5
  import { getOutputPath, loadTranslationFile, serializeTranslationFile } from './utils/file-utils.js';
6
6
  import { getNestedValue, setNestedValue } from './utils/nested-object.js';
7
7
  import { shouldShowFunnel, recordFunnelShown } from './utils/funnel-msg-tracker.js';
8
- import chalk from 'chalk';
8
+ import { styleText } from 'node:util';
9
9
 
10
10
  const pluralSuffixes = ['zero', 'one', 'two', 'few', 'many', 'other'];
11
11
  /**
@@ -104,11 +104,11 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger = new C
104
104
  async function printLocizeFunnel() {
105
105
  if (!(await shouldShowFunnel('rename-key')))
106
106
  return;
107
- console.log(chalk.yellow.bold('\n💡 Tip: Managing translations across multiple projects?'));
107
+ console.log(styleText(['yellow', 'bold'], '\n💡 Tip: Managing translations across multiple projects?'));
108
108
  console.log(' With Locize, you can rename, move, and copy translation keys directly');
109
109
  console.log(' in the web interface—no CLI needed. Perfect for collaboration with');
110
110
  console.log(' translators and managing complex refactoring across namespaces.');
111
- console.log(` Learn more: ${chalk.cyan('https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
111
+ console.log(` Learn more: ${styleText('cyan', 'https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
112
112
  return recordFunnelShown('rename-key');
113
113
  }
114
114
  function parseKeyWithNamespace(key, config) {
@@ -1,10 +1,14 @@
1
- import chalk from 'chalk';
1
+ import { styleText } from 'node:util';
2
2
  import ora from 'ora';
3
3
  import { resolve } from 'node:path';
4
+ import '@swc/core';
5
+ import 'node:fs/promises';
4
6
  import { findKeys } from './extractor/core/key-finder.js';
7
+ import 'glob';
5
8
  import { getNestedValue } from './utils/nested-object.js';
6
9
  import { loadTranslationFile, getOutputPath } from './utils/file-utils.js';
7
10
  import { shouldShowFunnel, recordFunnelShown } from './utils/funnel-msg-tracker.js';
11
+ import './extractor/parsers/jsx-parser.js';
8
12
 
9
13
  /**
10
14
  * Runs a health check on the project's i18next translations and displays a status report.
@@ -237,19 +241,19 @@ async function displayStatusReport(report, config, options) {
237
241
  */
238
242
  async function displayDetailedLocaleReport(report, config, locale, namespaceFilter) {
239
243
  if (locale === config.extract.primaryLanguage) {
240
- console.log(chalk.yellow(`Locale "${locale}" is the primary language. All keys are considered present.`));
244
+ console.log(styleText('yellow', `Locale "${locale}" is the primary language. All keys are considered present.`));
241
245
  return;
242
246
  }
243
247
  if (!config.locales.includes(locale)) {
244
- console.error(chalk.red(`Error: Locale "${locale}" is not defined in your configuration.`));
248
+ console.error(styleText('red', `Error: Locale "${locale}" is not defined in your configuration.`));
245
249
  return;
246
250
  }
247
251
  const localeData = report.locales.get(locale);
248
252
  if (!localeData) {
249
- console.error(chalk.red(`Error: Locale "${locale}" is not a valid secondary language.`));
253
+ console.error(styleText('red', `Error: Locale "${locale}" is not a valid secondary language.`));
250
254
  return;
251
255
  }
252
- console.log(chalk.bold(`\nKey Status for "${chalk.cyan(locale)}":`));
256
+ console.log(styleText('bold', `\nKey Status for "${styleText('cyan', locale)}":`));
253
257
  const totalKeysForLocale = localeData.totalKeys;
254
258
  printProgressBar('Overall', localeData.totalTranslated, totalKeysForLocale);
255
259
  const namespacesToDisplay = namespaceFilter ? [namespaceFilter] : Array.from(localeData.namespaces.keys()).sort();
@@ -257,19 +261,19 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
257
261
  const nsData = localeData.namespaces.get(ns);
258
262
  if (!nsData)
259
263
  continue;
260
- console.log(chalk.cyan.bold(`\nNamespace: ${ns}`));
264
+ console.log(styleText(['cyan', 'bold'], `\nNamespace: ${ns}`));
261
265
  printProgressBar('Namespace Progress', nsData.translatedKeys, nsData.totalKeys);
262
266
  nsData.keyDetails.forEach(({ key, isTranslated }) => {
263
- const icon = isTranslated ? chalk.green('✓') : chalk.red('✗');
267
+ const icon = isTranslated ? styleText('green', '✓') : styleText('red', '✗');
264
268
  console.log(` ${icon} ${key}`);
265
269
  });
266
270
  }
267
271
  const missingCount = totalKeysForLocale - localeData.totalTranslated;
268
272
  if (missingCount > 0) {
269
- console.log(chalk.yellow.bold(`\nSummary: Found ${missingCount} missing translations for "${locale}".`));
273
+ console.log(styleText(['yellow', 'bold'], `\nSummary: Found ${missingCount} missing translations for "${locale}".`));
270
274
  }
271
275
  else {
272
- console.log(chalk.green.bold(`\nSummary: 🎉 All keys are translated for "${locale}".`));
276
+ console.log(styleText(['green', 'bold'], `\nSummary: 🎉 All keys are translated for "${locale}".`));
273
277
  }
274
278
  await printLocizeFunnel();
275
279
  }
@@ -286,10 +290,10 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
286
290
  async function displayNamespaceSummaryReport(report, config, namespace) {
287
291
  const nsData = report.keysByNs.get(namespace);
288
292
  if (!nsData) {
289
- console.error(chalk.red(`Error: Namespace "${namespace}" was not found in your source code.`));
293
+ console.error(styleText('red', `Error: Namespace "${namespace}" was not found in your source code.`));
290
294
  return;
291
295
  }
292
- console.log(chalk.cyan.bold(`\nStatus for Namespace: "${namespace}"`));
296
+ console.log(styleText(['cyan', 'bold'], `\nStatus for Namespace: "${namespace}"`));
293
297
  console.log('------------------------');
294
298
  for (const [locale, localeData] of report.locales.entries()) {
295
299
  const nsLocaleData = localeData.namespaces.get(namespace);
@@ -314,12 +318,13 @@ async function displayNamespaceSummaryReport(report, config, namespace) {
314
318
  */
315
319
  async function displayOverallSummaryReport(report, config) {
316
320
  const { primaryLanguage } = config.extract;
317
- console.log(chalk.cyan.bold('\ni18next Project Status'));
321
+ console.log(styleText(['cyan', 'bold'], '\ni18next Project Status'));
318
322
  console.log('------------------------');
319
- console.log(`🔑 Keys Found: ${chalk.bold(report.totalBaseKeys)}`);
320
- console.log(`📚 Namespaces Found: ${chalk.bold(report.keysByNs.size)}`);
321
- console.log(`🌍 Locales: ${chalk.bold(config.locales.join(', '))}`);
322
- console.log(`✅ Primary Language: ${chalk.bold(primaryLanguage)}`);
323
+ console.log(`🔑 Keys Found: ${styleText('bold', `${report.totalBaseKeys}`)}`);
324
+ console.log(`📚 Namespaces Found: ${styleText('bold', `${report.keysByNs.size}`)}`);
325
+ console.log(`🌍 Locales: ${styleText('bold', config.locales.join(', '))}`);
326
+ if (primaryLanguage)
327
+ console.log(`✅ Primary Language: ${styleText('bold', primaryLanguage)}`);
323
328
  console.log('\nTranslation Progress:');
324
329
  for (const [locale, localeData] of report.locales.entries()) {
325
330
  const percentage = localeData.totalKeys > 0 ? Math.round((localeData.totalTranslated / localeData.totalKeys) * 100) : 100;
@@ -338,7 +343,7 @@ async function displayOverallSummaryReport(report, config) {
338
343
  function printProgressBar(label, current, total) {
339
344
  const percentage = total > 0 ? Math.round((current / total) * 100) : 100;
340
345
  const bar = generateProgressBarText(percentage);
341
- console.log(`${chalk.bold(label)}: ${bar} ${percentage}% (${current}/${total})`);
346
+ console.log(`${styleText('bold', label)}: ${bar} ${percentage}% (${current}/${total})`);
342
347
  }
343
348
  /**
344
349
  * Generates a visual progress bar string based on percentage completion.
@@ -353,14 +358,14 @@ function generateProgressBarText(percentage) {
353
358
  const totalBars = 20;
354
359
  const filledBars = Math.floor((percentage / 100) * totalBars);
355
360
  const emptyBars = totalBars - filledBars;
356
- return `[${chalk.green(''.padStart(filledBars, '■'))}${''.padStart(emptyBars, '□')}]`;
361
+ return `[${styleText('green', ''.padStart(filledBars, '■'))}${''.padStart(emptyBars, '□')}]`;
357
362
  }
358
363
  async function printLocizeFunnel() {
359
364
  if (!(await shouldShowFunnel('status')))
360
365
  return;
361
- console.log(chalk.yellow.bold('\n✨ Take your localization to the next level!'));
366
+ console.log(styleText(['yellow', 'bold'], '\n✨ Take your localization to the next level!'));
362
367
  console.log('Manage translations with your team in the cloud with Locize => https://www.locize.com/docs/getting-started');
363
- console.log(`Run ${chalk.cyan('npx i18next-cli locize-migrate')} to get started.`);
368
+ console.log(`Run ${styleText('cyan', 'npx i18next-cli locize-migrate')} to get started.`);
364
369
  return recordFunnelShown('status');
365
370
  }
366
371
 
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk';
1
+ import { styleText } from 'node:util';
2
2
  import { glob } from 'glob';
3
3
  import { mkdir, writeFile } from 'node:fs/promises';
4
4
  import { basename, resolve, dirname } from 'node:path';
@@ -73,7 +73,7 @@ async function runSyncer(config, options = {}) {
73
73
  continue;
74
74
  const primaryTranslations = await loadTranslationFile(primaryPath);
75
75
  if (!primaryTranslations) {
76
- logMessages.push(` ${chalk.yellow('-')} Could not read primary file: ${primaryPath}`);
76
+ logMessages.push(` ${styleText('yellow', '-')} Could not read primary file: ${primaryPath}`);
77
77
  continue;
78
78
  }
79
79
  const primaryKeys = getNestedKeys(primaryTranslations, keySeparator ?? '.');
@@ -100,27 +100,27 @@ async function runSyncer(config, options = {}) {
100
100
  const serializedContent = serializeTranslationFile(newSecondaryTranslations, perFileFormat, indentation, raw);
101
101
  await mkdir(dirname(fullSecondaryPath), { recursive: true });
102
102
  await writeFile(fullSecondaryPath, serializedContent);
103
- logMessages.push(` ${chalk.green('✓')} Synchronized: ${secondaryPath}`);
103
+ logMessages.push(` ${styleText('green', '✓')} Synchronized: ${secondaryPath}`);
104
104
  }
105
105
  else {
106
- logMessages.push(` ${chalk.gray('-')} Already in sync: ${secondaryPath}`);
106
+ logMessages.push(` ${styleText('gray', '-')} Already in sync: ${secondaryPath}`);
107
107
  }
108
108
  }
109
109
  }
110
- spinner.succeed(chalk.bold('Synchronization complete!'));
110
+ spinner.succeed(styleText('bold', 'Synchronization complete!'));
111
111
  logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
112
112
  if (wasAnythingSynced) {
113
113
  await printLocizeFunnel();
114
114
  }
115
115
  else {
116
116
  if (typeof internalLogger.info === 'function')
117
- internalLogger.info(chalk.green.bold('\n✅ All locales are already in sync.'));
117
+ internalLogger.info(styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
118
118
  else
119
- console.log(chalk.green.bold('\n✅ All locales are already in sync.'));
119
+ console.log(styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
120
120
  }
121
121
  }
122
122
  catch (error) {
123
- spinner.fail(chalk.red('Synchronization failed.'));
123
+ spinner.fail(styleText('red', 'Synchronization failed.'));
124
124
  if (typeof internalLogger.error === 'function')
125
125
  internalLogger.error(error);
126
126
  else
@@ -130,9 +130,9 @@ async function runSyncer(config, options = {}) {
130
130
  async function printLocizeFunnel() {
131
131
  if (!(await shouldShowFunnel('syncer')))
132
132
  return;
133
- console.log(chalk.green.bold('\n✅ Sync complete.'));
134
- console.log(chalk.yellow('🚀 Ready to collaborate with translators? Move your files to the cloud.'));
135
- console.log(` Get started with the official TMS for i18next: ${chalk.cyan('npx i18next-cli locize-migrate')}`);
133
+ console.log(styleText(['green', 'bold'], '\n✅ Sync complete.'));
134
+ console.log(styleText('yellow', '🚀 Ready to collaborate with translators? Move your files to the cloud.'));
135
+ console.log(` Get started with the official TMS for i18next: ${styleText('cyan', 'npx i18next-cli locize-migrate')}`);
136
136
  return recordFunnelShown('syncer');
137
137
  }
138
138
 
@@ -2,7 +2,7 @@ import { ConsoleLogger } from './utils/logger.js';
2
2
  import { mergeResourcesAsInterface } from 'i18next-resources-for-ts';
3
3
  import { glob } from 'glob';
4
4
  import { createSpinnerLike } from './utils/wrap-ora.js';
5
- import chalk from 'chalk';
5
+ import { styleText } from 'node:util';
6
6
  import { mkdir, writeFile, access, readFile } from 'node:fs/promises';
7
7
  import { join, dirname, basename, extname, resolve, relative } from 'node:path';
8
8
  import { transform } from '@swc/core';
@@ -109,7 +109,7 @@ async function runTypesGenerator(config, options = {}) {
109
109
  }
110
110
  const nonObjectKeys = keys.filter(k => !parsedContent[k] || typeof parsedContent[k] !== 'object');
111
111
  if (nonObjectKeys.length > 0) {
112
- console.warn(chalk.yellow(`Warning: The file ${file} contains top-level keys that are not objects (${nonObjectKeys.join(', ')}). When 'mergeNamespaces' is enabled, top-level keys are treated as namespaces. These keys will be ignored.`));
112
+ console.warn(styleText('yellow', `Warning: The file ${file} contains top-level keys that are not objects (${nonObjectKeys.join(', ')}). When 'mergeNamespaces' is enabled, top-level keys are treated as namespaces. These keys will be ignored.`));
113
113
  }
114
114
  continue;
115
115
  }
@@ -125,7 +125,7 @@ ${mergeResourcesAsInterface(resources, { optimize: !!enableSelector, indentation
125
125
  const resourcesOutputPath = resolve(process.cwd(), config.types.resourcesFile);
126
126
  await mkdir(dirname(resourcesOutputPath), { recursive: true });
127
127
  await writeFile(resourcesOutputPath, interfaceDefinition);
128
- logMessages.push(` ${chalk.green('✓')} Resources interface written to ${config.types.resourcesFile}`);
128
+ logMessages.push(` ${styleText('green', '✓')} Resources interface written to ${config.types.resourcesFile}`);
129
129
  let outputPathExists;
130
130
  try {
131
131
  await access(outputPath);
@@ -152,13 +152,13 @@ declare module 'i18next' {
152
152
  }`;
153
153
  await mkdir(dirname(outputPath), { recursive: true });
154
154
  await writeFile(outputPath, fileContent);
155
- logMessages.push(` ${chalk.green('✓')} TypeScript definitions written to ${config.types.output || ''}`);
155
+ logMessages.push(` ${styleText('green', '✓')} TypeScript definitions written to ${config.types.output || ''}`);
156
156
  }
157
- spinner.succeed(chalk.bold('TypeScript definitions generated successfully.'));
157
+ spinner.succeed(styleText('bold', 'TypeScript definitions generated successfully.'));
158
158
  logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
159
159
  }
160
160
  catch (error) {
161
- spinner.fail(chalk.red('Failed to generate TypeScript definitions.'));
161
+ spinner.fail(styleText('red', 'Failed to generate TypeScript definitions.'));
162
162
  if (typeof internalLogger.error === 'function')
163
163
  internalLogger.error(error);
164
164
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.42.6",
3
+ "version": "1.42.7",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -63,7 +63,6 @@
63
63
  "memfs": "4.56.10",
64
64
  "neostandard": "0.12.2",
65
65
  "rollup-plugin-typescript2": "0.36.0",
66
- "ts-node": "10.9.2",
67
66
  "typescript": "5.9.3",
68
67
  "unplugin-swc": "1.5.9",
69
68
  "vitest": "4.0.18"
@@ -72,7 +71,6 @@
72
71
  "@croct/json5-parser": "0.2.2",
73
72
  "@swc/core": "1.15.11",
74
73
  "yaml": "2.8.2",
75
- "chalk": "5.6.2",
76
74
  "chokidar": "5.0.0",
77
75
  "commander": "14.0.3",
78
76
  "execa": "9.6.1",
@@ -84,7 +82,6 @@
84
82
  "minimatch": "10.1.2",
85
83
  "ora": "9.3.0",
86
84
  "react": "^19.2.4",
87
- "react-i18next": "^16.5.4",
88
- "swc-walk": "1.0.1"
85
+ "react-i18next": "^16.5.4"
89
86
  }
90
87
  }