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 +27 -0
- package/dist/cjs/cli.js +11 -6
- package/dist/cjs/extractor/core/extractor.js +29 -14
- package/dist/cjs/linter.js +17 -6
- package/dist/cjs/syncer.js +14 -6
- package/dist/cjs/types-generator.js +10 -5
- package/dist/cjs/utils/wrap-ora.js +112 -0
- package/dist/esm/cli.js +11 -6
- package/dist/esm/extractor/core/extractor.js +29 -14
- package/dist/esm/linter.js +17 -6
- package/dist/esm/syncer.js +14 -6
- package/dist/esm/types-generator.js +10 -5
- package/dist/esm/utils/wrap-ora.js +110 -0
- package/package.json +1 -1
- package/types/cli.d.ts.map +1 -1
- package/types/extractor/core/extractor.d.ts +2 -1
- package/types/extractor/core/extractor.d.ts.map +1 -1
- package/types/linter.d.ts +3 -1
- package/types/linter.d.ts.map +1 -1
- package/types/syncer.d.ts +3 -1
- package/types/syncer.d.ts.map +1 -1
- package/types/types-generator.d.ts +3 -1
- package/types/types-generator.d.ts.map +1 -1
- package/types/utils/wrap-ora.d.ts +22 -0
- package/types/utils/wrap-ora.d.ts.map +1 -0
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.
|
|
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
|
-
.
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
package/dist/cjs/linter.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
316
|
+
if (internalLogger.error)
|
|
317
|
+
internalLogger.error(wrappedError);
|
|
318
|
+
else
|
|
319
|
+
console.error(wrappedError);
|
|
309
320
|
process.exit(1);
|
|
310
321
|
}
|
|
311
322
|
}
|
package/dist/cjs/syncer.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
.
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
package/dist/esm/linter.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
+
if (internalLogger.error)
|
|
315
|
+
internalLogger.error(wrappedError);
|
|
316
|
+
else
|
|
317
|
+
console.error(wrappedError);
|
|
307
318
|
process.exit(1);
|
|
308
319
|
}
|
|
309
320
|
}
|
package/dist/esm/syncer.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
package/types/cli.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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,
|
|
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
|
|
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
|
*/
|
package/types/linter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
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
|
|
32
|
+
export declare function runSyncer(config: I18nextToolkitConfig, options?: {
|
|
33
|
+
quiet?: boolean;
|
|
34
|
+
}, logger?: any): Promise<void>;
|
|
33
35
|
//# sourceMappingURL=syncer.d.ts.map
|
package/types/syncer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../src/syncer.ts"],"names":[],"mappings":"
|
|
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
|
|
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":"
|
|
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"}
|