i18next-cli 1.37.1 → 1.38.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
@@ -114,6 +114,33 @@ npx i18next-cli extract [options]
114
114
  - `--dry-run`: Does not change any files - useful in combination with `--ci` (for CI/CD)
115
115
  - `--sync-primary`: Sync primary language values with default values from code
116
116
  - `--sync-all`: Sync primary language values with default values from code AND clear synced keys in all other locales (implies `--sync-primary`)
117
+ - `--quiet`: Suppress spinner and non-essential output (for CI or scripting)
118
+ ### Spinner and Logger Output Control
119
+
120
+ All commands that show progress spinners (extract, types, lint, sync) now support:
121
+
122
+ - `--quiet` flag to silence spinner and non-essential output (for CI, scripting, or log capture)
123
+ - Programmatic logger support: pass a custom logger object to capture output in your own format or stream
124
+
125
+ **CLI Example:**
126
+
127
+ ```bash
128
+ npx i18next-cli extract --quiet
129
+ ```
130
+
131
+ **Programmatic Example:**
132
+
133
+ ```typescript
134
+ import { runExtractor } from 'i18next-cli';
135
+ const logger = {
136
+ info: (msg) => myLogStream.write(msg + '\n'),
137
+ warn: (msg) => myWarnStream.write(msg + '\n'),
138
+ error: (msg) => myErrStream.write(msg + '\n'),
139
+ };
140
+ await runExtractor(config, { quiet: false }, logger);
141
+ ```
142
+
143
+ If you pass a logger, spinner output and all progress/info messages are routed to your logger instead of the interactive spinner.
117
144
 
118
145
  **Examples:**
119
146
  ```bash
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.37.1'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.38.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
@@ -39,6 +39,7 @@ program
39
39
  .option('--dry-run', 'Run the extractor without writing any files to disk.')
40
40
  .option('--sync-primary', 'Sync primary language values with default values from code.')
41
41
  .option('--sync-all', 'Sync primary language values with default values from code AND clear synced keys in all other locales.')
42
+ .option('-q, --quiet', 'Suppress spinner and output')
42
43
  .action(async (options) => {
43
44
  try {
44
45
  const cfgPath = program.opts().config;
@@ -50,7 +51,8 @@ program
50
51
  isWatchMode: !!options.watch,
51
52
  isDryRun: !!options.dryRun,
52
53
  syncPrimaryWithDefaults: syncPrimary,
53
- syncAll: !!options.syncAll
54
+ syncAll: !!options.syncAll,
55
+ quiet: !!options.quiet
54
56
  });
55
57
  if (options.ci && !success) {
56
58
  console.log('✅ No files were updated.');
@@ -114,10 +116,11 @@ program
114
116
  .command('types')
115
117
  .description('Generate TypeScript definitions from translation resource files.')
116
118
  .option('-w, --watch', 'Watch for file changes and re-run the type generator.')
119
+ .option('-q, --quiet', 'Suppress spinner and output')
117
120
  .action(async (options) => {
118
121
  const cfgPath = program.opts().config;
119
122
  const config$1 = await config.ensureConfig(cfgPath);
120
- const run = () => typesGenerator.runTypesGenerator(config$1);
123
+ const run = () => typesGenerator.runTypesGenerator(config$1, { quiet: !!options.quiet });
121
124
  await run();
122
125
  if (options.watch) {
123
126
  console.log('\nWatching for changes...');
@@ -134,10 +137,11 @@ program
134
137
  program
135
138
  .command('sync')
136
139
  .description('Synchronize secondary language files with the primary language file.')
137
- .action(async () => {
140
+ .option('-q, --quiet', 'Suppress spinner and output')
141
+ .action(async (options) => {
138
142
  const cfgPath = program.opts().config;
139
143
  const config$1 = await config.ensureConfig(cfgPath);
140
- await syncer.runSyncer(config$1);
144
+ await syncer.runSyncer(config$1, { quiet: !!options.quiet });
141
145
  });
142
146
  program
143
147
  .command('migrate-config [configPath]')
@@ -153,6 +157,7 @@ program
153
157
  .command('lint')
154
158
  .description('Find potential issues like hardcoded strings in your codebase.')
155
159
  .option('-w, --watch', 'Watch for file changes and re-run the linter.')
160
+ .option('-q, --quiet', 'Suppress spinner and output')
156
161
  .action(async (options) => {
157
162
  const cfgPath = program.opts().config;
158
163
  const loadAndRunLinter = async () => {
@@ -169,7 +174,7 @@ program
169
174
  console.log(chalk.green('Project structure detected successfully!'));
170
175
  config$1 = detected;
171
176
  }
172
- await linter.runLinterCli(config$1);
177
+ await linter.runLinterCli(config$1, { quiet: !!options.quiet });
173
178
  };
174
179
  // Run the linter once initially
175
180
  await loadAndRunLinter();
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var ora = require('ora');
3
+ var wrapOra = require('../../utils/wrap-ora.js');
4
4
  var chalk = require('chalk');
5
5
  var core = require('@swc/core');
6
6
  var promises = require('node:fs/promises');
@@ -39,7 +39,7 @@ var funnelMsgTracker = require('../../utils/funnel-msg-tracker.js');
39
39
  * }
40
40
  * ```
41
41
  */
42
- async function runExtractor(config, { isWatchMode = false, isDryRun = false, syncPrimaryWithDefaults = false, syncAll = false } = {}, logger$1 = new logger.ConsoleLogger()) {
42
+ async function runExtractor(config, options = {}, logger$1) {
43
43
  config.extract.primaryLanguage ||= config.locales[0] || 'en';
44
44
  config.extract.secondaryLanguages ||= config.locales.filter((l) => l !== config?.extract?.primaryLanguage);
45
45
  // Ensure default function and component names are set if not provided.
@@ -47,16 +47,21 @@ async function runExtractor(config, { isWatchMode = false, isDryRun = false, syn
47
47
  config.extract.transComponents ||= ['Trans'];
48
48
  validation.validateExtractorConfig(config);
49
49
  const plugins = config.plugins || [];
50
- const spinner = ora('Running i18next key extractor...\n').start();
50
+ const internalLogger = logger$1 ?? new logger.ConsoleLogger();
51
+ // Only pass logger to spinner if explicitly provided
52
+ const spinner = wrapOra.createSpinnerLike('Running i18next key extractor...\n', { quiet: !!options.quiet, logger: logger$1 });
51
53
  try {
52
- const { allKeys, objectKeys } = await keyFinder.findKeys(config, logger$1);
54
+ const { allKeys, objectKeys } = await keyFinder.findKeys(config, internalLogger);
53
55
  spinner.text = `Found ${allKeys.size} unique keys. Updating translation files...`;
54
- const results = await translationManager.getTranslations(allKeys, objectKeys, config, { syncPrimaryWithDefaults, syncAll });
56
+ const results = await translationManager.getTranslations(allKeys, objectKeys, config, {
57
+ syncPrimaryWithDefaults: options.syncPrimaryWithDefaults,
58
+ syncAll: options.syncAll
59
+ });
55
60
  let anyFileUpdated = false;
56
61
  for (const result of results) {
57
62
  if (result.updated) {
58
63
  anyFileUpdated = true;
59
- if (!isDryRun) {
64
+ if (!options.isDryRun) {
60
65
  // prefer explicit outputFormat; otherwise infer from file extension per-file
61
66
  const effectiveFormat = config.extract.outputFormat ?? (result.path.endsWith('.json5') ? 'json5' : 'json');
62
67
  const rawContent = effectiveFormat === 'json5'
@@ -65,7 +70,7 @@ async function runExtractor(config, { isWatchMode = false, isDryRun = false, syn
65
70
  const fileContent = fileUtils.serializeTranslationFile(result.newTranslations, effectiveFormat, config.extract.indentation, rawContent);
66
71
  await promises.mkdir(node_path.dirname(result.path), { recursive: true });
67
72
  await promises.writeFile(result.path, fileContent);
68
- logger$1.info(chalk.green(`Updated: ${result.path}`));
73
+ internalLogger.info(chalk.green(`Updated: ${result.path}`));
69
74
  }
70
75
  }
71
76
  }
@@ -79,7 +84,7 @@ async function runExtractor(config, { isWatchMode = false, isDryRun = false, syn
79
84
  spinner.succeed(chalk.bold('Extraction complete!'));
80
85
  // Show the funnel message only if files were actually changed.
81
86
  if (anyFileUpdated)
82
- await printLocizeFunnel();
87
+ await printLocizeFunnel(logger$1);
83
88
  return anyFileUpdated;
84
89
  }
85
90
  catch (error) {
@@ -229,14 +234,24 @@ async function extract(config, { syncPrimaryWithDefaults = false } = {}) {
229
234
  * Prints a promotional message for the locize saveMissing workflow.
230
235
  * This message is shown after a successful extraction that resulted in changes.
231
236
  */
232
- async function printLocizeFunnel() {
237
+ async function printLocizeFunnel(logger$1) {
233
238
  if (!(await funnelMsgTracker.shouldShowFunnel('extract')))
234
239
  return;
235
- console.log(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
236
- console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
237
- console.log(' where keys are created and translated automatically as you code.');
238
- console.log(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
239
- console.log(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
240
+ const internalLogger = logger$1 ?? new logger.ConsoleLogger();
241
+ if (typeof internalLogger.info === 'function') {
242
+ internalLogger.info(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
243
+ internalLogger.info(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
244
+ internalLogger.info(' where keys are created and translated automatically as you code.');
245
+ internalLogger.info(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
246
+ internalLogger.info(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
247
+ }
248
+ else {
249
+ console.log(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
250
+ console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
251
+ console.log(' where keys are created and translated automatically as you code.');
252
+ console.log(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
253
+ console.log(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
254
+ }
240
255
  return funnelMsgTracker.recordFunnelShown('extract');
241
256
  }
242
257
 
@@ -6,7 +6,8 @@ var core = require('@swc/core');
6
6
  var node_path = require('node:path');
7
7
  var node_events = require('node:events');
8
8
  var chalk = require('chalk');
9
- var ora = require('ora');
9
+ var logger = require('./utils/logger.js');
10
+ var wrapOra = require('./utils/wrap-ora.js');
10
11
 
11
12
  // Helper to extract interpolation keys from a translation string
12
13
  function extractInterpolationKeys(str, config) {
@@ -279,9 +280,10 @@ class Linter extends node_events.EventEmitter {
279
280
  async function runLinter(config) {
280
281
  return new Linter(config).run();
281
282
  }
282
- async function runLinterCli(config) {
283
+ async function runLinterCli(config, options = {}, logger$1) {
284
+ const internalLogger = new logger.ConsoleLogger();
283
285
  const linter = new Linter(config);
284
- const spinner = ora().start();
286
+ const spinner = wrapOra.createSpinnerLike('', { quiet: !!options.quiet, logger: logger$1 });
285
287
  linter.on('progress', (event) => {
286
288
  spinner.text = event.message;
287
289
  });
@@ -291,9 +293,15 @@ async function runLinterCli(config) {
291
293
  spinner.fail(chalk.red.bold(message));
292
294
  // Print detailed report after spinner fails
293
295
  for (const [file, issues] of Object.entries(files)) {
294
- console.log(chalk.yellow(`\n${file}`));
296
+ if (internalLogger.info)
297
+ internalLogger.info(chalk.yellow(`\n${file}`));
298
+ else
299
+ console.log(chalk.yellow(`\n${file}`));
295
300
  issues.forEach(({ text, line }) => {
296
- console.log(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} Found hardcoded string: "${text}"`);
301
+ if (typeof internalLogger.info === 'function')
302
+ internalLogger.info(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} Found hardcoded string: "${text}"`);
303
+ else
304
+ console.log(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} Found hardcoded string: "${text}"`);
297
305
  });
298
306
  }
299
307
  process.exit(1);
@@ -305,7 +313,10 @@ async function runLinterCli(config) {
305
313
  catch (error) {
306
314
  const wrappedError = linter.wrapError(error);
307
315
  spinner.fail(wrappedError.message);
308
- console.error(wrappedError);
316
+ if (internalLogger.error)
317
+ internalLogger.error(wrappedError);
318
+ else
319
+ console.error(wrappedError);
309
320
  process.exit(1);
310
321
  }
311
322
  }
@@ -4,7 +4,8 @@ var chalk = require('chalk');
4
4
  var glob = require('glob');
5
5
  var promises = require('node:fs/promises');
6
6
  var node_path = require('node:path');
7
- var ora = require('ora');
7
+ var wrapOra = require('./utils/wrap-ora.js');
8
+ var logger = require('./utils/logger.js');
8
9
  var defaultValue = require('./utils/default-value.js');
9
10
  var fileUtils = require('./utils/file-utils.js');
10
11
  var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
@@ -40,8 +41,9 @@ var nestedObject = require('./utils/nested-object.js');
40
41
  * await runSyncer(config)
41
42
  * ```
42
43
  */
43
- async function runSyncer(config) {
44
- const spinner = ora('Running i18next locale synchronizer...\n').start();
44
+ async function runSyncer(config, options = {}, logger$1) {
45
+ const internalLogger = logger$1 ?? new logger.ConsoleLogger();
46
+ const spinner = wrapOra.createSpinnerLike('Running i18next locale synchronizer...\n', { quiet: !!options.quiet, logger: logger$1 });
45
47
  try {
46
48
  const primaryLanguage = config.extract.primaryLanguage || config.locales[0] || 'en';
47
49
  const secondaryLanguages = config.locales.filter((l) => l !== primaryLanguage);
@@ -95,17 +97,23 @@ async function runSyncer(config) {
95
97
  }
96
98
  }
97
99
  spinner.succeed(chalk.bold('Synchronization complete!'));
98
- logMessages.forEach(msg => console.log(msg));
100
+ logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
99
101
  if (wasAnythingSynced) {
100
102
  await printLocizeFunnel();
101
103
  }
102
104
  else {
103
- console.log(chalk.green.bold('\n✅ All locales are already in sync.'));
105
+ if (typeof internalLogger.info === 'function')
106
+ internalLogger.info(chalk.green.bold('\n✅ All locales are already in sync.'));
107
+ else
108
+ console.log(chalk.green.bold('\n✅ All locales are already in sync.'));
104
109
  }
105
110
  }
106
111
  catch (error) {
107
112
  spinner.fail(chalk.red('Synchronization failed.'));
108
- console.error(error);
113
+ if (typeof internalLogger.error === 'function')
114
+ internalLogger.error(error);
115
+ else
116
+ console.error(error);
109
117
  }
110
118
  }
111
119
  async function printLocizeFunnel() {
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ var logger = require('./utils/logger.js');
3
4
  var i18nextResourcesForTs = require('i18next-resources-for-ts');
4
5
  var glob = require('glob');
5
- var ora = require('ora');
6
+ var wrapOra = require('./utils/wrap-ora.js');
6
7
  var chalk = require('chalk');
7
8
  var promises = require('node:fs/promises');
8
9
  var node_path = require('node:path');
@@ -70,8 +71,9 @@ async function loadFile(file) {
70
71
  * await runTypesGenerator(config)
71
72
  * ```
72
73
  */
73
- async function runTypesGenerator(config) {
74
- const spinner = ora('Generating TypeScript types for translations...\n').start();
74
+ async function runTypesGenerator(config, options = {}, logger$1) {
75
+ const internalLogger = logger$1 ?? new logger.ConsoleLogger();
76
+ const spinner = wrapOra.createSpinnerLike('Generating TypeScript types for translations...\n', { quiet: !!options.quiet, logger: logger$1 });
75
77
  try {
76
78
  config.extract.primaryLanguage ||= config.locales[0] || 'en';
77
79
  let defaultTypesInputPath = config.extract.output || `locales/${config.extract.primaryLanguage}/*.json`;
@@ -155,11 +157,14 @@ declare module 'i18next' {
155
157
  logMessages.push(` ${chalk.green('✓')} TypeScript definitions written to ${config.types.output || ''}`);
156
158
  }
157
159
  spinner.succeed(chalk.bold('TypeScript definitions generated successfully.'));
158
- logMessages.forEach(msg => console.log(msg));
160
+ logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
159
161
  }
160
162
  catch (error) {
161
163
  spinner.fail(chalk.red('Failed to generate TypeScript definitions.'));
162
- console.error(error);
164
+ if (typeof internalLogger.error === 'function')
165
+ internalLogger.error(error);
166
+ else
167
+ console.error(error);
163
168
  }
164
169
  }
165
170
 
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ var ora = require('ora');
4
+
5
+ /**
6
+ * Creates a spinner-like object that either:
7
+ * - is fully silent (quiet mode),
8
+ * - logs only start/succeed/fail events to a logger (no animation),
9
+ * - or falls back to ora's default spinner.
10
+ *
11
+ * This avoids flooding structured loggers with spinner frames and ensures clean output in CI, Vite, etc.
12
+ */
13
+ function createSpinnerLike(initialText, options = {}) {
14
+ const { quiet, logger } = options;
15
+ let text = initialText;
16
+ // If interactive (no logger and not quiet), create a single real ora spinner
17
+ let realSpinner = null;
18
+ if (!quiet && !logger) {
19
+ realSpinner = ora({ text }).start();
20
+ }
21
+ const self = {
22
+ get text() { return text; },
23
+ set text(v) {
24
+ text = v;
25
+ if (realSpinner)
26
+ realSpinner.text = v;
27
+ },
28
+ start() { return self; },
29
+ succeed(msg) {
30
+ const message = msg ?? text;
31
+ if (quiet)
32
+ return;
33
+ if (logger) {
34
+ if (typeof logger.info === 'function')
35
+ logger.info(message);
36
+ else if (typeof logger.log === 'function')
37
+ logger.log(message);
38
+ else
39
+ process.stderr.write(message + '\n');
40
+ }
41
+ else {
42
+ if (!realSpinner)
43
+ realSpinner = ora({ text }).start();
44
+ realSpinner.succeed(message);
45
+ }
46
+ },
47
+ fail(msg) {
48
+ const message = msg ?? text;
49
+ if (quiet)
50
+ return;
51
+ if (logger) {
52
+ if (typeof logger.error === 'function')
53
+ logger.error(message);
54
+ else if (typeof logger.log === 'function')
55
+ logger.log(message);
56
+ else
57
+ process.stderr.write(message + '\n');
58
+ }
59
+ else {
60
+ if (!realSpinner)
61
+ realSpinner = ora({ text }).start();
62
+ realSpinner.fail(message);
63
+ }
64
+ },
65
+ warn(msg) {
66
+ const message = msg ?? text;
67
+ if (quiet)
68
+ return;
69
+ if (logger) {
70
+ if (typeof logger.warn === 'function')
71
+ logger.warn(message);
72
+ else if (typeof logger.log === 'function')
73
+ logger.log(message);
74
+ else
75
+ process.stderr.write(message + '\n');
76
+ }
77
+ else {
78
+ if (!realSpinner)
79
+ realSpinner = ora({ text }).start();
80
+ try {
81
+ realSpinner.warn?.(message);
82
+ }
83
+ catch {
84
+ realSpinner.stop();
85
+ process.stderr.write(message + '\n');
86
+ }
87
+ }
88
+ },
89
+ stop() { if (realSpinner)
90
+ realSpinner.stop(); },
91
+ progress(msg) {
92
+ if (quiet)
93
+ return;
94
+ if (logger) {
95
+ if (typeof logger.info === 'function')
96
+ logger.info(msg);
97
+ else if (typeof logger.log === 'function')
98
+ logger.log(msg);
99
+ else
100
+ process.stderr.write(msg + '\n');
101
+ }
102
+ else {
103
+ if (!realSpinner)
104
+ realSpinner = ora({ text }).start();
105
+ realSpinner.text = String(msg);
106
+ }
107
+ }
108
+ };
109
+ return self;
110
+ }
111
+
112
+ exports.createSpinnerLike = createSpinnerLike;
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.37.1'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.38.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
@@ -37,6 +37,7 @@ program
37
37
  .option('--dry-run', 'Run the extractor without writing any files to disk.')
38
38
  .option('--sync-primary', 'Sync primary language values with default values from code.')
39
39
  .option('--sync-all', 'Sync primary language values with default values from code AND clear synced keys in all other locales.')
40
+ .option('-q, --quiet', 'Suppress spinner and output')
40
41
  .action(async (options) => {
41
42
  try {
42
43
  const cfgPath = program.opts().config;
@@ -48,7 +49,8 @@ program
48
49
  isWatchMode: !!options.watch,
49
50
  isDryRun: !!options.dryRun,
50
51
  syncPrimaryWithDefaults: syncPrimary,
51
- syncAll: !!options.syncAll
52
+ syncAll: !!options.syncAll,
53
+ quiet: !!options.quiet
52
54
  });
53
55
  if (options.ci && !success) {
54
56
  console.log('✅ No files were updated.');
@@ -112,10 +114,11 @@ program
112
114
  .command('types')
113
115
  .description('Generate TypeScript definitions from translation resource files.')
114
116
  .option('-w, --watch', 'Watch for file changes and re-run the type generator.')
117
+ .option('-q, --quiet', 'Suppress spinner and output')
115
118
  .action(async (options) => {
116
119
  const cfgPath = program.opts().config;
117
120
  const config = await ensureConfig(cfgPath);
118
- const run = () => runTypesGenerator(config);
121
+ const run = () => runTypesGenerator(config, { quiet: !!options.quiet });
119
122
  await run();
120
123
  if (options.watch) {
121
124
  console.log('\nWatching for changes...');
@@ -132,10 +135,11 @@ program
132
135
  program
133
136
  .command('sync')
134
137
  .description('Synchronize secondary language files with the primary language file.')
135
- .action(async () => {
138
+ .option('-q, --quiet', 'Suppress spinner and output')
139
+ .action(async (options) => {
136
140
  const cfgPath = program.opts().config;
137
141
  const config = await ensureConfig(cfgPath);
138
- await runSyncer(config);
142
+ await runSyncer(config, { quiet: !!options.quiet });
139
143
  });
140
144
  program
141
145
  .command('migrate-config [configPath]')
@@ -151,6 +155,7 @@ program
151
155
  .command('lint')
152
156
  .description('Find potential issues like hardcoded strings in your codebase.')
153
157
  .option('-w, --watch', 'Watch for file changes and re-run the linter.')
158
+ .option('-q, --quiet', 'Suppress spinner and output')
154
159
  .action(async (options) => {
155
160
  const cfgPath = program.opts().config;
156
161
  const loadAndRunLinter = async () => {
@@ -167,7 +172,7 @@ program
167
172
  console.log(chalk.green('Project structure detected successfully!'));
168
173
  config = detected;
169
174
  }
170
- await runLinterCli(config);
175
+ await runLinterCli(config, { quiet: !!options.quiet });
171
176
  };
172
177
  // Run the linter once initially
173
178
  await loadAndRunLinter();
@@ -1,4 +1,4 @@
1
- import ora from 'ora';
1
+ import { createSpinnerLike } from '../../utils/wrap-ora.js';
2
2
  import chalk from 'chalk';
3
3
  import { parse } from '@swc/core';
4
4
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
@@ -37,7 +37,7 @@ import { shouldShowFunnel, recordFunnelShown } from '../../utils/funnel-msg-trac
37
37
  * }
38
38
  * ```
39
39
  */
40
- async function runExtractor(config, { isWatchMode = false, isDryRun = false, syncPrimaryWithDefaults = false, syncAll = false } = {}, logger = new ConsoleLogger()) {
40
+ async function runExtractor(config, options = {}, logger) {
41
41
  config.extract.primaryLanguage ||= config.locales[0] || 'en';
42
42
  config.extract.secondaryLanguages ||= config.locales.filter((l) => l !== config?.extract?.primaryLanguage);
43
43
  // Ensure default function and component names are set if not provided.
@@ -45,16 +45,21 @@ async function runExtractor(config, { isWatchMode = false, isDryRun = false, syn
45
45
  config.extract.transComponents ||= ['Trans'];
46
46
  validateExtractorConfig(config);
47
47
  const plugins = config.plugins || [];
48
- const spinner = ora('Running i18next key extractor...\n').start();
48
+ const internalLogger = logger ?? new ConsoleLogger();
49
+ // Only pass logger to spinner if explicitly provided
50
+ const spinner = createSpinnerLike('Running i18next key extractor...\n', { quiet: !!options.quiet, logger });
49
51
  try {
50
- const { allKeys, objectKeys } = await findKeys(config, logger);
52
+ const { allKeys, objectKeys } = await findKeys(config, internalLogger);
51
53
  spinner.text = `Found ${allKeys.size} unique keys. Updating translation files...`;
52
- const results = await getTranslations(allKeys, objectKeys, config, { syncPrimaryWithDefaults, syncAll });
54
+ const results = await getTranslations(allKeys, objectKeys, config, {
55
+ syncPrimaryWithDefaults: options.syncPrimaryWithDefaults,
56
+ syncAll: options.syncAll
57
+ });
53
58
  let anyFileUpdated = false;
54
59
  for (const result of results) {
55
60
  if (result.updated) {
56
61
  anyFileUpdated = true;
57
- if (!isDryRun) {
62
+ if (!options.isDryRun) {
58
63
  // prefer explicit outputFormat; otherwise infer from file extension per-file
59
64
  const effectiveFormat = config.extract.outputFormat ?? (result.path.endsWith('.json5') ? 'json5' : 'json');
60
65
  const rawContent = effectiveFormat === 'json5'
@@ -63,7 +68,7 @@ async function runExtractor(config, { isWatchMode = false, isDryRun = false, syn
63
68
  const fileContent = serializeTranslationFile(result.newTranslations, effectiveFormat, config.extract.indentation, rawContent);
64
69
  await mkdir(dirname(result.path), { recursive: true });
65
70
  await writeFile(result.path, fileContent);
66
- logger.info(chalk.green(`Updated: ${result.path}`));
71
+ internalLogger.info(chalk.green(`Updated: ${result.path}`));
67
72
  }
68
73
  }
69
74
  }
@@ -77,7 +82,7 @@ async function runExtractor(config, { isWatchMode = false, isDryRun = false, syn
77
82
  spinner.succeed(chalk.bold('Extraction complete!'));
78
83
  // Show the funnel message only if files were actually changed.
79
84
  if (anyFileUpdated)
80
- await printLocizeFunnel();
85
+ await printLocizeFunnel(logger);
81
86
  return anyFileUpdated;
82
87
  }
83
88
  catch (error) {
@@ -227,14 +232,24 @@ async function extract(config, { syncPrimaryWithDefaults = false } = {}) {
227
232
  * Prints a promotional message for the locize saveMissing workflow.
228
233
  * This message is shown after a successful extraction that resulted in changes.
229
234
  */
230
- async function printLocizeFunnel() {
235
+ async function printLocizeFunnel(logger) {
231
236
  if (!(await shouldShowFunnel('extract')))
232
237
  return;
233
- console.log(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
234
- console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
235
- console.log(' where keys are created and translated automatically as you code.');
236
- console.log(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
237
- console.log(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
238
+ const internalLogger = logger ?? new ConsoleLogger();
239
+ if (typeof internalLogger.info === 'function') {
240
+ internalLogger.info(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
241
+ internalLogger.info(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
242
+ internalLogger.info(' where keys are created and translated automatically as you code.');
243
+ internalLogger.info(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
244
+ internalLogger.info(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
245
+ }
246
+ else {
247
+ console.log(chalk.yellow.bold('\n💡 Tip: Tired of running the extractor manually?'));
248
+ console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
249
+ console.log(' where keys are created and translated automatically as you code.');
250
+ console.log(` Learn more: ${chalk.cyan('https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
251
+ console.log(` Watch the video: ${chalk.cyan('https://youtu.be/joPsZghT3wM')}`);
252
+ }
238
253
  return recordFunnelShown('extract');
239
254
  }
240
255
 
@@ -4,7 +4,8 @@ import { parse } from '@swc/core';
4
4
  import { extname } from 'node:path';
5
5
  import { EventEmitter } from 'node:events';
6
6
  import chalk from 'chalk';
7
- import ora from 'ora';
7
+ import { ConsoleLogger } from './utils/logger.js';
8
+ import { createSpinnerLike } from './utils/wrap-ora.js';
8
9
 
9
10
  // Helper to extract interpolation keys from a translation string
10
11
  function extractInterpolationKeys(str, config) {
@@ -277,9 +278,10 @@ class Linter extends EventEmitter {
277
278
  async function runLinter(config) {
278
279
  return new Linter(config).run();
279
280
  }
280
- async function runLinterCli(config) {
281
+ async function runLinterCli(config, options = {}, logger) {
282
+ const internalLogger = new ConsoleLogger();
281
283
  const linter = new Linter(config);
282
- const spinner = ora().start();
284
+ const spinner = createSpinnerLike('', { quiet: !!options.quiet, logger });
283
285
  linter.on('progress', (event) => {
284
286
  spinner.text = event.message;
285
287
  });
@@ -289,9 +291,15 @@ async function runLinterCli(config) {
289
291
  spinner.fail(chalk.red.bold(message));
290
292
  // Print detailed report after spinner fails
291
293
  for (const [file, issues] of Object.entries(files)) {
292
- console.log(chalk.yellow(`\n${file}`));
294
+ if (internalLogger.info)
295
+ internalLogger.info(chalk.yellow(`\n${file}`));
296
+ else
297
+ console.log(chalk.yellow(`\n${file}`));
293
298
  issues.forEach(({ text, line }) => {
294
- console.log(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} Found hardcoded string: "${text}"`);
299
+ if (typeof internalLogger.info === 'function')
300
+ internalLogger.info(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} Found hardcoded string: "${text}"`);
301
+ else
302
+ console.log(` ${chalk.gray(`${line}:`)} ${chalk.red('Error:')} Found hardcoded string: "${text}"`);
295
303
  });
296
304
  }
297
305
  process.exit(1);
@@ -303,7 +311,10 @@ async function runLinterCli(config) {
303
311
  catch (error) {
304
312
  const wrappedError = linter.wrapError(error);
305
313
  spinner.fail(wrappedError.message);
306
- console.error(wrappedError);
314
+ if (internalLogger.error)
315
+ internalLogger.error(wrappedError);
316
+ else
317
+ console.error(wrappedError);
307
318
  process.exit(1);
308
319
  }
309
320
  }
@@ -2,7 +2,8 @@ import chalk from 'chalk';
2
2
  import { glob } from 'glob';
3
3
  import { mkdir, writeFile } from 'node:fs/promises';
4
4
  import { basename, resolve, dirname } from 'node:path';
5
- import ora from 'ora';
5
+ import { createSpinnerLike } from './utils/wrap-ora.js';
6
+ import { ConsoleLogger } from './utils/logger.js';
6
7
  import { resolveDefaultValue } from './utils/default-value.js';
7
8
  import { getOutputPath, loadTranslationFile, loadRawJson5Content, serializeTranslationFile } from './utils/file-utils.js';
8
9
  import { shouldShowFunnel, recordFunnelShown } from './utils/funnel-msg-tracker.js';
@@ -38,8 +39,9 @@ import { getNestedKeys, getNestedValue, setNestedValue } from './utils/nested-ob
38
39
  * await runSyncer(config)
39
40
  * ```
40
41
  */
41
- async function runSyncer(config) {
42
- const spinner = ora('Running i18next locale synchronizer...\n').start();
42
+ async function runSyncer(config, options = {}, logger) {
43
+ const internalLogger = logger ?? new ConsoleLogger();
44
+ const spinner = createSpinnerLike('Running i18next locale synchronizer...\n', { quiet: !!options.quiet, logger });
43
45
  try {
44
46
  const primaryLanguage = config.extract.primaryLanguage || config.locales[0] || 'en';
45
47
  const secondaryLanguages = config.locales.filter((l) => l !== primaryLanguage);
@@ -93,17 +95,23 @@ async function runSyncer(config) {
93
95
  }
94
96
  }
95
97
  spinner.succeed(chalk.bold('Synchronization complete!'));
96
- logMessages.forEach(msg => console.log(msg));
98
+ logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
97
99
  if (wasAnythingSynced) {
98
100
  await printLocizeFunnel();
99
101
  }
100
102
  else {
101
- console.log(chalk.green.bold('\n✅ All locales are already in sync.'));
103
+ if (typeof internalLogger.info === 'function')
104
+ internalLogger.info(chalk.green.bold('\n✅ All locales are already in sync.'));
105
+ else
106
+ console.log(chalk.green.bold('\n✅ All locales are already in sync.'));
102
107
  }
103
108
  }
104
109
  catch (error) {
105
110
  spinner.fail(chalk.red('Synchronization failed.'));
106
- console.error(error);
111
+ if (typeof internalLogger.error === 'function')
112
+ internalLogger.error(error);
113
+ else
114
+ console.error(error);
107
115
  }
108
116
  }
109
117
  async function printLocizeFunnel() {
@@ -1,6 +1,7 @@
1
+ import { ConsoleLogger } from './utils/logger.js';
1
2
  import { mergeResourcesAsInterface } from 'i18next-resources-for-ts';
2
3
  import { glob } from 'glob';
3
- import ora from 'ora';
4
+ import { createSpinnerLike } from './utils/wrap-ora.js';
4
5
  import chalk from 'chalk';
5
6
  import { mkdir, writeFile, access, readFile } from 'node:fs/promises';
6
7
  import { join, dirname, basename, extname, resolve, relative } from 'node:path';
@@ -68,8 +69,9 @@ async function loadFile(file) {
68
69
  * await runTypesGenerator(config)
69
70
  * ```
70
71
  */
71
- async function runTypesGenerator(config) {
72
- const spinner = ora('Generating TypeScript types for translations...\n').start();
72
+ async function runTypesGenerator(config, options = {}, logger) {
73
+ const internalLogger = logger ?? new ConsoleLogger();
74
+ const spinner = createSpinnerLike('Generating TypeScript types for translations...\n', { quiet: !!options.quiet, logger });
73
75
  try {
74
76
  config.extract.primaryLanguage ||= config.locales[0] || 'en';
75
77
  let defaultTypesInputPath = config.extract.output || `locales/${config.extract.primaryLanguage}/*.json`;
@@ -153,11 +155,14 @@ declare module 'i18next' {
153
155
  logMessages.push(` ${chalk.green('✓')} TypeScript definitions written to ${config.types.output || ''}`);
154
156
  }
155
157
  spinner.succeed(chalk.bold('TypeScript definitions generated successfully.'));
156
- logMessages.forEach(msg => console.log(msg));
158
+ logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
157
159
  }
158
160
  catch (error) {
159
161
  spinner.fail(chalk.red('Failed to generate TypeScript definitions.'));
160
- console.error(error);
162
+ if (typeof internalLogger.error === 'function')
163
+ internalLogger.error(error);
164
+ else
165
+ console.error(error);
161
166
  }
162
167
  }
163
168
 
@@ -0,0 +1,110 @@
1
+ import ora from 'ora';
2
+
3
+ /**
4
+ * Creates a spinner-like object that either:
5
+ * - is fully silent (quiet mode),
6
+ * - logs only start/succeed/fail events to a logger (no animation),
7
+ * - or falls back to ora's default spinner.
8
+ *
9
+ * This avoids flooding structured loggers with spinner frames and ensures clean output in CI, Vite, etc.
10
+ */
11
+ function createSpinnerLike(initialText, options = {}) {
12
+ const { quiet, logger } = options;
13
+ let text = initialText;
14
+ // If interactive (no logger and not quiet), create a single real ora spinner
15
+ let realSpinner = null;
16
+ if (!quiet && !logger) {
17
+ realSpinner = ora({ text }).start();
18
+ }
19
+ const self = {
20
+ get text() { return text; },
21
+ set text(v) {
22
+ text = v;
23
+ if (realSpinner)
24
+ realSpinner.text = v;
25
+ },
26
+ start() { return self; },
27
+ succeed(msg) {
28
+ const message = msg ?? text;
29
+ if (quiet)
30
+ return;
31
+ if (logger) {
32
+ if (typeof logger.info === 'function')
33
+ logger.info(message);
34
+ else if (typeof logger.log === 'function')
35
+ logger.log(message);
36
+ else
37
+ process.stderr.write(message + '\n');
38
+ }
39
+ else {
40
+ if (!realSpinner)
41
+ realSpinner = ora({ text }).start();
42
+ realSpinner.succeed(message);
43
+ }
44
+ },
45
+ fail(msg) {
46
+ const message = msg ?? text;
47
+ if (quiet)
48
+ return;
49
+ if (logger) {
50
+ if (typeof logger.error === 'function')
51
+ logger.error(message);
52
+ else if (typeof logger.log === 'function')
53
+ logger.log(message);
54
+ else
55
+ process.stderr.write(message + '\n');
56
+ }
57
+ else {
58
+ if (!realSpinner)
59
+ realSpinner = ora({ text }).start();
60
+ realSpinner.fail(message);
61
+ }
62
+ },
63
+ warn(msg) {
64
+ const message = msg ?? text;
65
+ if (quiet)
66
+ return;
67
+ if (logger) {
68
+ if (typeof logger.warn === 'function')
69
+ logger.warn(message);
70
+ else if (typeof logger.log === 'function')
71
+ logger.log(message);
72
+ else
73
+ process.stderr.write(message + '\n');
74
+ }
75
+ else {
76
+ if (!realSpinner)
77
+ realSpinner = ora({ text }).start();
78
+ try {
79
+ realSpinner.warn?.(message);
80
+ }
81
+ catch {
82
+ realSpinner.stop();
83
+ process.stderr.write(message + '\n');
84
+ }
85
+ }
86
+ },
87
+ stop() { if (realSpinner)
88
+ realSpinner.stop(); },
89
+ progress(msg) {
90
+ if (quiet)
91
+ return;
92
+ if (logger) {
93
+ if (typeof logger.info === 'function')
94
+ logger.info(msg);
95
+ else if (typeof logger.log === 'function')
96
+ logger.log(msg);
97
+ else
98
+ process.stderr.write(msg + '\n');
99
+ }
100
+ else {
101
+ if (!realSpinner)
102
+ realSpinner = ora({ text }).start();
103
+ realSpinner.text = String(msg);
104
+ }
105
+ }
106
+ };
107
+ return self;
108
+ }
109
+
110
+ export { createSpinnerLike };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.37.1",
3
+ "version": "1.38.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAkBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AA+Q7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAkBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AAoR7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
@@ -26,11 +26,12 @@ import { ASTVisitors } from './ast-visitors';
26
26
  * }
27
27
  * ```
28
28
  */
29
- export declare function runExtractor(config: I18nextToolkitConfig, { isWatchMode, isDryRun, syncPrimaryWithDefaults, syncAll }?: {
29
+ export declare function runExtractor(config: I18nextToolkitConfig, options?: {
30
30
  isWatchMode?: boolean;
31
31
  isDryRun?: boolean;
32
32
  syncPrimaryWithDefaults?: boolean;
33
33
  syncAll?: boolean;
34
+ quiet?: boolean;
34
35
  }, logger?: Logger): Promise<boolean>;
35
36
  /**
36
37
  * Processes an individual source file for translation key extraction.
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKtF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAK5C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,EACE,WAAmB,EACnB,QAAgB,EAChB,uBAA+B,EAC/B,OAAe,EAChB,GAAE;IACD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CA8DlB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAiGf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,sDAO3I"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKtF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAK5C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CACX,EACN,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAkElB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAiGf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,sDAO3I"}
package/types/linter.d.ts CHANGED
@@ -65,7 +65,9 @@ export declare function runLinter(config: I18nextToolkitConfig): Promise<{
65
65
  [k: string]: HardcodedString[];
66
66
  };
67
67
  }>;
68
- export declare function runLinterCli(config: I18nextToolkitConfig): Promise<void>;
68
+ export declare function runLinterCli(config: I18nextToolkitConfig, options?: {
69
+ quiet?: boolean;
70
+ }, logger?: any): Promise<void>;
69
71
  /**
70
72
  * Represents a found hardcoded string or interpolation parameter error with its location information.
71
73
  */
@@ -1 +1 @@
1
- {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAqHnD,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;SAC1C;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,UAET,CAAA;AAC3B,eAAO,MAAM,6BAA6B,UAAgN,CAAA;AAK1P,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;gBAEvB,MAAM,EAAE,oBAAoB;IAKzC,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;CA6FV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAAE,MAAM,EAAE,oBAAoB,iBA4B/D;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;CACd"}
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAqHnD,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;SAC1C;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,UAET,CAAA;AAC3B,eAAO,MAAM,6BAA6B,UAAgN,CAAA;AAK1P,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;gBAEvB,MAAM,EAAE,oBAAoB;IAKzC,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;CA6FV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,EACjC,MAAM,CAAC,EAAE,GAAG,iBAiCb;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;CACd"}
package/types/syncer.d.ts CHANGED
@@ -29,5 +29,7 @@ import type { I18nextToolkitConfig } from './types';
29
29
  * await runSyncer(config)
30
30
  * ```
31
31
  */
32
- export declare function runSyncer(config: I18nextToolkitConfig): Promise<void>;
32
+ export declare function runSyncer(config: I18nextToolkitConfig, options?: {
33
+ quiet?: boolean;
34
+ }, logger?: any): Promise<void>;
33
35
  //# sourceMappingURL=syncer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../src/syncer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAMnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,iBAmF5D"}
1
+ {"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../src/syncer.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAMnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,EACjC,MAAM,CAAC,EAAE,GAAG,iBAuFb"}
@@ -25,5 +25,7 @@ import type { I18nextToolkitConfig } from './types';
25
25
  * await runTypesGenerator(config)
26
26
  * ```
27
27
  */
28
- export declare function runTypesGenerator(config: I18nextToolkitConfig): Promise<void>;
28
+ export declare function runTypesGenerator(config: I18nextToolkitConfig, options?: {
29
+ quiet?: boolean;
30
+ }, logger?: import('./types').Logger): Promise<void>;
29
31
  //# sourceMappingURL=types-generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-generator.d.ts","sourceRoot":"","sources":["../src/types-generator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAsDnD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CAAE,MAAM,EAAE,oBAAoB,iBAsGpE"}
1
+ {"version":3,"file":"types-generator.d.ts","sourceRoot":"","sources":["../src/types-generator.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAsDnD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,EACjC,MAAM,CAAC,EAAE,OAAO,SAAS,EAAE,MAAM,iBAyGlC"}
@@ -0,0 +1,22 @@
1
+ export interface SpinnerLike {
2
+ text: string;
3
+ start(): SpinnerLike;
4
+ succeed(msg?: string): void;
5
+ fail(msg?: string): void;
6
+ warn(msg?: string): void;
7
+ stop(): void;
8
+ progress?(msg: string): void;
9
+ }
10
+ /**
11
+ * Creates a spinner-like object that either:
12
+ * - is fully silent (quiet mode),
13
+ * - logs only start/succeed/fail events to a logger (no animation),
14
+ * - or falls back to ora's default spinner.
15
+ *
16
+ * This avoids flooding structured loggers with spinner frames and ensures clean output in CI, Vite, etc.
17
+ */
18
+ export declare function createSpinnerLike(initialText: string, options?: {
19
+ quiet?: boolean;
20
+ logger?: any;
21
+ }): SpinnerLike;
22
+ //# sourceMappingURL=wrap-ora.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-ora.d.ts","sourceRoot":"","sources":["../../src/utils/wrap-ora.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,IAAI,WAAW,CAAA;IACpB,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,IAAI,IAAI,IAAI,CAAA;IACZ,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;;;;;;GAOG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAO,GAC9C,WAAW,CAoEb"}