i18next-cli 1.43.0 → 1.45.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
@@ -40,7 +40,7 @@ A unified, high-performance i18next CLI toolchain, powered by SWC.
40
40
  - **Translation Status**: Get a high-level overview or a detailed, key-by-key report of your project's translation completeness.
41
41
  - **Plugin System**: Extensible architecture for custom extraction patterns and file types (e.g., HTML, Handlebars).
42
42
  - **Legacy Migration**: Automatic migration from `i18next-parser` configurations.
43
- - **Cloud Integration**: Seamless integration with the [locize](https://locize.com) translation management platform.
43
+ - **Cloud Integration**: Seamless integration with the [Locize](https://locize.com) translation management platform.
44
44
 
45
45
  ## Installation
46
46
 
@@ -573,7 +573,7 @@ export default defineConfig({
573
573
 
574
574
  ### Plugin System
575
575
 
576
- Create custom plugins to extend the capabilities of `i18next-cli`. The plugin system provides several hooks that allow you to tap into different stages of the extraction process, with full access to the AST parsing context and variable scope information.
576
+ Create custom plugins to extend the capabilities of `i18next-cli`. The plugin system provides hooks for both extraction and linting, with a single unified `plugins` array.
577
577
 
578
578
  **Available Hooks:**
579
579
 
@@ -585,6 +585,63 @@ Create custom plugins to extend the capabilities of `i18next-cli`. The plugin sy
585
585
  - `onEnd`: Runs after all JS/TS files have been parsed but *before* the final keys are compared with existing translation files. This is the ideal hook for parsing non-JavaScript files (like `.html`, `.vue`, or `.svelte`) and adding their keys to the collection.
586
586
  - `afterSync`: Runs after the extractor has compared the found keys with your translation files and generated the final results. This is perfect for post-processing tasks, like generating a report of newly added keys.
587
587
 
588
+ **Lint Plugin Hooks:**
589
+
590
+ - `lintSetup(context)`: Runs once before linting starts. Receives `LintPluginContext` with `config` and `logger`.
591
+ - `lintExtensions`: Optional extension hint (for example `['.vue']`). Used as a skip hint/optimization.
592
+ - `lintOnLoad(code, filePath)`: Runs before lint parsing for each file.
593
+ - Return `string` to replace source code for linting.
594
+ - Return `undefined` to pass through unchanged.
595
+ - Return `null` to skip linting the file entirely.
596
+ - `lintOnResult(filePath, issues)`: Runs after each file is linted. Return a new issues array to filter/augment results, or `undefined` to keep as-is.
597
+
598
+ ### Lint Plugin API
599
+
600
+ ```typescript
601
+ import type {
602
+ Plugin,
603
+ LinterPlugin,
604
+ LintPluginContext,
605
+ LintIssue,
606
+ } from 'i18next-cli';
607
+
608
+ // You can type your plugin as Plugin (full surface) or LinterPlugin (lint-focused)
609
+ export const vueLintPlugin = (): LinterPlugin => ({
610
+ name: 'vue-lint-plugin',
611
+ lintExtensions: ['.vue'],
612
+ lintSetup: async (context: LintPluginContext) => {
613
+ context.logger.info('vue lint plugin initialized');
614
+ },
615
+ lintOnLoad: async (code, filePath) => {
616
+ if (!filePath.endsWith('.vue')) return undefined;
617
+ // preprocess SFC/template to lintable JS/TS/JSX text
618
+ return code;
619
+ },
620
+ lintOnResult: async (_filePath, issues: LintIssue[]) => {
621
+ // Example: keep only interpolation issues
622
+ return issues.filter(issue => issue.type === 'interpolation');
623
+ }
624
+ });
625
+ ```
626
+
627
+ **Config usage (same plugins list for extract + lint):**
628
+
629
+ ```typescript
630
+ import { defineConfig } from 'i18next-cli';
631
+ import { vueLintPlugin } from './plugins/vue-lint-plugin.mjs';
632
+
633
+ export default defineConfig({
634
+ locales: ['en', 'de'],
635
+ extract: {
636
+ input: ['src/**/*.{ts,tsx,js,jsx,vue}'],
637
+ output: 'locales/{{language}}/{{namespace}}.json'
638
+ },
639
+ plugins: [
640
+ vueLintPlugin()
641
+ ]
642
+ });
643
+ ```
644
+
588
645
  **Basic Plugin Example:**
589
646
 
590
647
  ```typescript
@@ -1081,8 +1138,8 @@ const config: I18nextToolkitConfig = {
1081
1138
  };
1082
1139
 
1083
1140
  // Run the complete extraction process
1084
- const wasUpdated = await runExtractor(config);
1085
- console.log('Files updated:', wasUpdated);
1141
+ const { anyFileUpdated, hasErrors } = await runExtractor(config);
1142
+ console.log('Files updated:', anyFileUpdated);
1086
1143
 
1087
1144
  // Check translation status programmatically
1088
1145
  await runStatus(config);
@@ -1190,12 +1247,14 @@ This programmatic API gives you the same power as the CLI but with full control
1190
1247
 
1191
1248
  ---
1192
1249
 
1193
- **From the creators of i18next: localization as a service - locize.com**
1250
+ **From the creators of i18next: localization as a service - [Locize](https://www.locize.com)**
1251
+
1252
+ A translation management system built around the i18next ecosystem - [Locize](https://locize.com).
1194
1253
 
1195
- A translation management system built around the i18next ecosystem - [locize.com](https://www.locize.com).
1254
+ **Now with a [Free plan](https://locize.com/pricing) for small projects!** Perfect for hobbyists or getting started.
1196
1255
 
1197
- ![locize](https://www.locize.com/img/ads/github_locize.png)
1256
+ ![Locize](https://www.locize.com/img/ads/github_locize.png)
1198
1257
 
1199
- With using [locize](https://locize.com/?utm_source=i18next_readme&utm_medium=github) you directly support the future of i18next.
1258
+ With using [Locize](https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github) you directly support the future of i18next.
1200
1259
 
1201
1260
  ---
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.43.0'); // This string is replaced with the actual version at build time by rollup
31
+ .version('1.45.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
@@ -47,22 +47,25 @@ program
47
47
  const runExtract = async () => {
48
48
  // --sync-all implies sync-primary behavior
49
49
  const syncPrimary = !!options.syncPrimary || !!options.syncAll;
50
- const success = await extractor.runExtractor(config$1, {
50
+ const { anyFileUpdated, hasErrors } = await extractor.runExtractor(config$1, {
51
51
  isWatchMode: !!options.watch,
52
52
  isDryRun: !!options.dryRun,
53
53
  syncPrimaryWithDefaults: syncPrimary,
54
54
  syncAll: !!options.syncAll,
55
55
  quiet: !!options.quiet
56
56
  });
57
- if (options.ci && !success) {
57
+ if (options.ci && !anyFileUpdated) {
58
58
  console.log('✅ No files were updated.');
59
- process.exit(0);
59
+ process.exit(hasErrors ? 1 : 0);
60
60
  }
61
- else if (options.ci && success) {
61
+ else if (options.ci && anyFileUpdated) {
62
62
  console.error('❌ Some files were updated. This should not happen in CI mode.');
63
63
  process.exit(1);
64
64
  }
65
- return success;
65
+ if (hasErrors && !options.watch) {
66
+ process.exit(1);
67
+ }
68
+ return anyFileUpdated;
66
69
  };
67
70
  // Run the extractor once initially
68
71
  await runExtract();
@@ -52,7 +52,8 @@ async function runExtractor(config, options = {}) {
52
52
  // Only pass logger to spinner if explicitly provided
53
53
  const spinner = wrapOra.createSpinnerLike('Running i18next key extractor...\n', { quiet: !!options.quiet, logger: options.logger });
54
54
  try {
55
- const { allKeys, objectKeys } = await keyFinder.findKeys(config, internalLogger);
55
+ const fileErrors = [];
56
+ const { allKeys, objectKeys } = await keyFinder.findKeys(config, internalLogger, fileErrors);
56
57
  spinner.text = `Found ${allKeys.size} unique keys. Updating translation files...`;
57
58
  const results = await translationManager.getTranslations(allKeys, objectKeys, config, {
58
59
  syncPrimaryWithDefaults: options.syncPrimaryWithDefaults,
@@ -82,11 +83,14 @@ async function runExtractor(config, options = {}) {
82
83
  await plugin.afterSync?.(results, config);
83
84
  }
84
85
  }
85
- spinner.succeed(node_util.styleText('bold', 'Extraction complete!'));
86
+ const completionMessage = fileErrors.length > 0
87
+ ? node_util.styleText('bold', `Extraction complete, but ignored ${fileErrors.length} file${fileErrors.length === 1 ? '' : 's'}!`)
88
+ : node_util.styleText('bold', 'Extraction complete!');
89
+ spinner.succeed(completionMessage);
86
90
  // Show the funnel message only if files were actually changed.
87
91
  if (anyFileUpdated)
88
92
  await printLocizeFunnel(options.logger);
89
- return anyFileUpdated;
93
+ return { anyFileUpdated, hasErrors: fileErrors.length > 0 };
90
94
  }
91
95
  catch (error) {
92
96
  spinner.fail(node_util.styleText('red', 'Extraction failed.'));
@@ -114,7 +118,7 @@ async function runExtractor(config, options = {}) {
114
118
  *
115
119
  * @internal
116
120
  */
117
- async function processFile(file, plugins, astVisitors, pluginContext, config, logger$1 = new logger.ConsoleLogger()) {
121
+ async function processFile(file, plugins, astVisitors, pluginContext, config, logger$1 = new logger.ConsoleLogger(), fileErrors) {
118
122
  try {
119
123
  let code = await promises.readFile(file, 'utf-8');
120
124
  // Run onLoad hooks from plugins with error handling
@@ -215,6 +219,8 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
215
219
  if ((!err?.message || String(err.message).trim() === '') && err?.stack) {
216
220
  logger$1.warn(` ${String(err.stack)}`);
217
221
  }
222
+ // Record the failure so callers can exit non-zero even though we continue extraction
223
+ fileErrors?.push(file);
218
224
  }
219
225
  }
220
226
  /**
@@ -35,7 +35,7 @@ var expressionResolver = require('../parsers/expression-resolver.js');
35
35
  * console.log(`Found ${keys.size} unique translation keys`)
36
36
  * ```
37
37
  */
38
- async function findKeys(config, logger$1 = new logger.ConsoleLogger()) {
38
+ async function findKeys(config, logger$1 = new logger.ConsoleLogger(), fileErrors) {
39
39
  const { plugins: pluginsOrUndefined, ...otherConfig } = config;
40
40
  const plugins = pluginsOrUndefined || [];
41
41
  const sourceFiles = await processSourceFiles(config);
@@ -88,7 +88,7 @@ async function findKeys(config, logger$1 = new logger.ConsoleLogger()) {
88
88
  await pluginManager.initializePlugins(plugins);
89
89
  // 6. Process each file
90
90
  for (const file of sourceFiles) {
91
- await extractor.processFile(file, plugins, astVisitors$1, pluginContext, otherConfig, logger$1);
91
+ await extractor.processFile(file, plugins, astVisitors$1, pluginContext, otherConfig, logger$1, fileErrors);
92
92
  }
93
93
  // 7. Run onEnd hooks
94
94
  for (const plugin of plugins) {
@@ -254,9 +254,11 @@ const defaultIgnoredAttributes = ['className', 'key', 'id', 'style', 'href', 'i1
254
254
  const defaultIgnoredTags = ['script', 'style', 'code'];
255
255
  class Linter extends node_events.EventEmitter {
256
256
  config;
257
- constructor(config) {
257
+ logger;
258
+ constructor(config, logger$1 = new logger.ConsoleLogger()) {
258
259
  super({ captureRejections: true });
259
260
  this.config = config;
261
+ this.logger = logger$1;
260
262
  }
261
263
  wrapError(error) {
262
264
  const prefix = 'Linter failed to run: ';
@@ -288,10 +290,19 @@ class Linter extends node_events.EventEmitter {
288
290
  // Load translation values once so the interpolation linter can resolve lookup keys
289
291
  // to their translated strings (fixes: key != value interpolation not detected)
290
292
  const translationValues = await loadPrimaryTranslationValues(config);
293
+ const plugins = config.plugins || [];
294
+ const lintPluginContext = this.createLintPluginContext(config);
295
+ await this.initializeLintPlugins(plugins, lintPluginContext);
291
296
  let totalIssues = 0;
292
297
  const issuesByFile = new Map();
293
298
  for (const file of sourceFiles) {
294
- const code = await promises.readFile(file, 'utf-8');
299
+ const sourceCode = await promises.readFile(file, 'utf-8');
300
+ const lintPrepared = await this.runLintOnLoadPipeline(sourceCode, file, plugins);
301
+ if (lintPrepared === null) {
302
+ this.emit('progress', { message: `Skipped ${file} by plugin` });
303
+ continue;
304
+ }
305
+ const code = lintPrepared;
295
306
  // Determine parser options from file extension so .ts is not parsed as TSX
296
307
  const fileExt = node_path.extname(file).toLowerCase();
297
308
  const isTypeScriptFile = fileExt === '.ts' || fileExt === '.tsx' || fileExt === '.mts' || fileExt === '.cts';
@@ -349,7 +360,8 @@ class Linter extends node_events.EventEmitter {
349
360
  const hardcodedStrings = findHardcodedStrings(ast, code, config);
350
361
  // Collect interpolation parameter issues
351
362
  const interpolationIssues = lintInterpolationParams(ast, code, config, translationValues);
352
- const allIssues = [...hardcodedStrings, ...interpolationIssues];
363
+ let allIssues = [...hardcodedStrings, ...interpolationIssues];
364
+ allIssues = await this.runLintOnResultPipeline(file, allIssues, plugins);
353
365
  if (allIssues.length > 0) {
354
366
  totalIssues += allIssues.length;
355
367
  issuesByFile.set(file, allIssues);
@@ -366,6 +378,78 @@ class Linter extends node_events.EventEmitter {
366
378
  throw wrappedError;
367
379
  }
368
380
  }
381
+ createLintPluginContext(config) {
382
+ return {
383
+ config,
384
+ logger: this.logger,
385
+ };
386
+ }
387
+ async initializeLintPlugins(plugins, context) {
388
+ for (const plugin of plugins) {
389
+ try {
390
+ await plugin.lintSetup?.(context);
391
+ }
392
+ catch (err) {
393
+ const wrapped = this.wrapError(err);
394
+ this.emit('error', wrapped);
395
+ }
396
+ }
397
+ }
398
+ normalizeExtension(ext) {
399
+ const trimmed = ext.trim().toLowerCase();
400
+ if (!trimmed)
401
+ return '';
402
+ return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
403
+ }
404
+ shouldRunLintPluginForFile(plugin, filePath) {
405
+ const hints = plugin.lintExtensions;
406
+ if (!hints || hints.length === 0)
407
+ return true;
408
+ const fileExt = this.normalizeExtension(node_path.extname(filePath));
409
+ if (!fileExt)
410
+ return false;
411
+ const normalizedHints = hints.map(hint => this.normalizeExtension(hint)).filter(Boolean);
412
+ if (normalizedHints.length === 0)
413
+ return true;
414
+ return normalizedHints.includes(fileExt);
415
+ }
416
+ async runLintOnLoadPipeline(initialCode, filePath, plugins) {
417
+ let code = initialCode;
418
+ for (const plugin of plugins) {
419
+ if (!this.shouldRunLintPluginForFile(plugin, filePath))
420
+ continue;
421
+ try {
422
+ const result = await plugin.lintOnLoad?.(code, filePath);
423
+ if (result === null)
424
+ return null;
425
+ if (typeof result === 'string')
426
+ code = result;
427
+ }
428
+ catch (err) {
429
+ const wrapped = this.wrapError(err);
430
+ this.emit('error', wrapped);
431
+ }
432
+ }
433
+ return code;
434
+ }
435
+ async runLintOnResultPipeline(filePath, initialIssues, plugins) {
436
+ let issues = initialIssues;
437
+ for (const plugin of plugins) {
438
+ if (!this.shouldRunLintPluginForFile(plugin, filePath))
439
+ continue;
440
+ try {
441
+ const result = await plugin.lintOnResult?.(filePath, issues);
442
+ if (Array.isArray(result)) {
443
+ issues = result;
444
+ }
445
+ }
446
+ catch (err) {
447
+ const wrapped = this.wrapError(err);
448
+ this.emit('error', wrapped);
449
+ }
450
+ }
451
+ return issues;
452
+ }
369
453
  }
370
454
  /**
371
455
  * Runs the i18next linter to detect hardcoded strings and other potential issues.
@@ -401,7 +485,7 @@ async function runLinter(config) {
401
485
  }
402
486
  async function runLinterCli(config, options = {}) {
403
487
  const internalLogger = options.logger ?? new logger.ConsoleLogger();
404
- const linter = new Linter(config);
488
+ const linter = new Linter(config, internalLogger);
405
489
  const spinner = wrapOra.createSpinnerLike('', { quiet: !!options.quiet, logger: options.logger });
406
490
  linter.on('progress', (event) => {
407
491
  spinner.text = event.message;
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.43.0'); // This string is replaced with the actual version at build time by rollup
29
+ .version('1.45.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
@@ -45,22 +45,25 @@ program
45
45
  const runExtract = async () => {
46
46
  // --sync-all implies sync-primary behavior
47
47
  const syncPrimary = !!options.syncPrimary || !!options.syncAll;
48
- const success = await runExtractor(config, {
48
+ const { anyFileUpdated, hasErrors } = await runExtractor(config, {
49
49
  isWatchMode: !!options.watch,
50
50
  isDryRun: !!options.dryRun,
51
51
  syncPrimaryWithDefaults: syncPrimary,
52
52
  syncAll: !!options.syncAll,
53
53
  quiet: !!options.quiet
54
54
  });
55
- if (options.ci && !success) {
55
+ if (options.ci && !anyFileUpdated) {
56
56
  console.log('✅ No files were updated.');
57
- process.exit(0);
57
+ process.exit(hasErrors ? 1 : 0);
58
58
  }
59
- else if (options.ci && success) {
59
+ else if (options.ci && anyFileUpdated) {
60
60
  console.error('❌ Some files were updated. This should not happen in CI mode.');
61
61
  process.exit(1);
62
62
  }
63
- return success;
63
+ if (hasErrors && !options.watch) {
64
+ process.exit(1);
65
+ }
66
+ return anyFileUpdated;
64
67
  };
65
68
  // Run the extractor once initially
66
69
  await runExtract();
@@ -50,7 +50,8 @@ async function runExtractor(config, options = {}) {
50
50
  // Only pass logger to spinner if explicitly provided
51
51
  const spinner = createSpinnerLike('Running i18next key extractor...\n', { quiet: !!options.quiet, logger: options.logger });
52
52
  try {
53
- const { allKeys, objectKeys } = await findKeys(config, internalLogger);
53
+ const fileErrors = [];
54
+ const { allKeys, objectKeys } = await findKeys(config, internalLogger, fileErrors);
54
55
  spinner.text = `Found ${allKeys.size} unique keys. Updating translation files...`;
55
56
  const results = await getTranslations(allKeys, objectKeys, config, {
56
57
  syncPrimaryWithDefaults: options.syncPrimaryWithDefaults,
@@ -80,11 +81,14 @@ async function runExtractor(config, options = {}) {
80
81
  await plugin.afterSync?.(results, config);
81
82
  }
82
83
  }
83
- spinner.succeed(styleText('bold', 'Extraction complete!'));
84
+ const completionMessage = fileErrors.length > 0
85
+ ? styleText('bold', `Extraction complete, but ignored ${fileErrors.length} file${fileErrors.length === 1 ? '' : 's'}!`)
86
+ : styleText('bold', 'Extraction complete!');
87
+ spinner.succeed(completionMessage);
84
88
  // Show the funnel message only if files were actually changed.
85
89
  if (anyFileUpdated)
86
90
  await printLocizeFunnel(options.logger);
87
- return anyFileUpdated;
91
+ return { anyFileUpdated, hasErrors: fileErrors.length > 0 };
88
92
  }
89
93
  catch (error) {
90
94
  spinner.fail(styleText('red', 'Extraction failed.'));
@@ -112,7 +116,7 @@ async function runExtractor(config, options = {}) {
112
116
  *
113
117
  * @internal
114
118
  */
115
- async function processFile(file, plugins, astVisitors, pluginContext, config, logger = new ConsoleLogger()) {
119
+ async function processFile(file, plugins, astVisitors, pluginContext, config, logger = new ConsoleLogger(), fileErrors) {
116
120
  try {
117
121
  let code = await readFile(file, 'utf-8');
118
122
  // Run onLoad hooks from plugins with error handling
@@ -213,6 +217,8 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
213
217
  if ((!err?.message || String(err.message).trim() === '') && err?.stack) {
214
218
  logger.warn(` ${String(err.stack)}`);
215
219
  }
220
+ // Record the failure so callers can exit non-zero even though we continue extraction
221
+ fileErrors?.push(file);
216
222
  }
217
223
  }
218
224
  /**
@@ -33,7 +33,7 @@ import { ExpressionResolver } from '../parsers/expression-resolver.js';
33
33
  * console.log(`Found ${keys.size} unique translation keys`)
34
34
  * ```
35
35
  */
36
- async function findKeys(config, logger = new ConsoleLogger()) {
36
+ async function findKeys(config, logger = new ConsoleLogger(), fileErrors) {
37
37
  const { plugins: pluginsOrUndefined, ...otherConfig } = config;
38
38
  const plugins = pluginsOrUndefined || [];
39
39
  const sourceFiles = await processSourceFiles(config);
@@ -86,7 +86,7 @@ async function findKeys(config, logger = new ConsoleLogger()) {
86
86
  await initializePlugins(plugins);
87
87
  // 6. Process each file
88
88
  for (const file of sourceFiles) {
89
- await processFile(file, plugins, astVisitors, pluginContext, otherConfig, logger);
89
+ await processFile(file, plugins, astVisitors, pluginContext, otherConfig, logger, fileErrors);
90
90
  }
91
91
  // 7. Run onEnd hooks
92
92
  for (const plugin of plugins) {
@@ -252,9 +252,11 @@ const defaultIgnoredAttributes = ['className', 'key', 'id', 'style', 'href', 'i1
252
252
  const defaultIgnoredTags = ['script', 'style', 'code'];
253
253
  class Linter extends EventEmitter {
254
254
  config;
255
- constructor(config) {
255
+ logger;
256
+ constructor(config, logger = new ConsoleLogger()) {
256
257
  super({ captureRejections: true });
257
258
  this.config = config;
259
+ this.logger = logger;
258
260
  }
259
261
  wrapError(error) {
260
262
  const prefix = 'Linter failed to run: ';
@@ -286,10 +288,19 @@ class Linter extends EventEmitter {
286
288
  // Load translation values once so the interpolation linter can resolve lookup keys
287
289
  // to their translated strings (fixes: key != value interpolation not detected)
288
290
  const translationValues = await loadPrimaryTranslationValues(config);
291
+ const plugins = config.plugins || [];
292
+ const lintPluginContext = this.createLintPluginContext(config);
293
+ await this.initializeLintPlugins(plugins, lintPluginContext);
289
294
  let totalIssues = 0;
290
295
  const issuesByFile = new Map();
291
296
  for (const file of sourceFiles) {
292
- const code = await readFile(file, 'utf-8');
297
+ const sourceCode = await readFile(file, 'utf-8');
298
+ const lintPrepared = await this.runLintOnLoadPipeline(sourceCode, file, plugins);
299
+ if (lintPrepared === null) {
300
+ this.emit('progress', { message: `Skipped ${file} by plugin` });
301
+ continue;
302
+ }
303
+ const code = lintPrepared;
293
304
  // Determine parser options from file extension so .ts is not parsed as TSX
294
305
  const fileExt = extname(file).toLowerCase();
295
306
  const isTypeScriptFile = fileExt === '.ts' || fileExt === '.tsx' || fileExt === '.mts' || fileExt === '.cts';
@@ -347,7 +358,8 @@ class Linter extends EventEmitter {
347
358
  const hardcodedStrings = findHardcodedStrings(ast, code, config);
348
359
  // Collect interpolation parameter issues
349
360
  const interpolationIssues = lintInterpolationParams(ast, code, config, translationValues);
350
- const allIssues = [...hardcodedStrings, ...interpolationIssues];
361
+ let allIssues = [...hardcodedStrings, ...interpolationIssues];
362
+ allIssues = await this.runLintOnResultPipeline(file, allIssues, plugins);
351
363
  if (allIssues.length > 0) {
352
364
  totalIssues += allIssues.length;
353
365
  issuesByFile.set(file, allIssues);
@@ -364,6 +376,78 @@ class Linter extends EventEmitter {
364
376
  throw wrappedError;
365
377
  }
366
378
  }
379
+ createLintPluginContext(config) {
380
+ return {
381
+ config,
382
+ logger: this.logger,
383
+ };
384
+ }
385
+ async initializeLintPlugins(plugins, context) {
386
+ for (const plugin of plugins) {
387
+ try {
388
+ await plugin.lintSetup?.(context);
389
+ }
390
+ catch (err) {
391
+ const wrapped = this.wrapError(err);
392
+ this.emit('error', wrapped);
393
+ }
394
+ }
395
+ }
396
+ normalizeExtension(ext) {
397
+ const trimmed = ext.trim().toLowerCase();
398
+ if (!trimmed)
399
+ return '';
400
+ return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
401
+ }
402
+ shouldRunLintPluginForFile(plugin, filePath) {
403
+ const hints = plugin.lintExtensions;
404
+ if (!hints || hints.length === 0)
405
+ return true;
406
+ const fileExt = this.normalizeExtension(extname(filePath));
407
+ if (!fileExt)
408
+ return false;
409
+ const normalizedHints = hints.map(hint => this.normalizeExtension(hint)).filter(Boolean);
410
+ if (normalizedHints.length === 0)
411
+ return true;
412
+ return normalizedHints.includes(fileExt);
413
+ }
414
+ async runLintOnLoadPipeline(initialCode, filePath, plugins) {
415
+ let code = initialCode;
416
+ for (const plugin of plugins) {
417
+ if (!this.shouldRunLintPluginForFile(plugin, filePath))
418
+ continue;
419
+ try {
420
+ const result = await plugin.lintOnLoad?.(code, filePath);
421
+ if (result === null)
422
+ return null;
423
+ if (typeof result === 'string')
424
+ code = result;
425
+ }
426
+ catch (err) {
427
+ const wrapped = this.wrapError(err);
428
+ this.emit('error', wrapped);
429
+ }
430
+ }
431
+ return code;
432
+ }
433
+ async runLintOnResultPipeline(filePath, initialIssues, plugins) {
434
+ let issues = initialIssues;
435
+ for (const plugin of plugins) {
436
+ if (!this.shouldRunLintPluginForFile(plugin, filePath))
437
+ continue;
438
+ try {
439
+ const result = await plugin.lintOnResult?.(filePath, issues);
440
+ if (Array.isArray(result)) {
441
+ issues = result;
442
+ }
443
+ }
444
+ catch (err) {
445
+ const wrapped = this.wrapError(err);
446
+ this.emit('error', wrapped);
447
+ }
448
+ }
449
+ return issues;
450
+ }
367
451
  }
368
452
  /**
369
453
  * Runs the i18next linter to detect hardcoded strings and other potential issues.
@@ -399,7 +483,7 @@ async function runLinter(config) {
399
483
  }
400
484
  async function runLinterCli(config, options = {}) {
401
485
  const internalLogger = options.logger ?? new ConsoleLogger();
402
- const linter = new Linter(config);
486
+ const linter = new Linter(config, internalLogger);
403
487
  const spinner = createSpinnerLike('', { quiet: !!options.quiet, logger: options.logger });
404
488
  linter.on('progress', (event) => {
405
489
  spinner.text = event.message;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.43.0",
3
+ "version": "1.45.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;AAqR7B,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;AAyR7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
@@ -33,7 +33,10 @@ export declare function runExtractor(config: I18nextToolkitConfig, options?: {
33
33
  syncAll?: boolean;
34
34
  quiet?: boolean;
35
35
  logger?: Logger;
36
- }): Promise<boolean>;
36
+ }): Promise<{
37
+ anyFileUpdated: boolean;
38
+ hasErrors: boolean;
39
+ }>;
37
40
  /**
38
41
  * Processes an individual source file for translation key extraction.
39
42
  *
@@ -54,7 +57,7 @@ export declare function runExtractor(config: I18nextToolkitConfig, options?: {
54
57
  *
55
58
  * @internal
56
59
  */
57
- export declare function processFile(file: string, plugins: Plugin[], astVisitors: ASTVisitors, pluginContext: PluginContext, config: Omit<I18nextToolkitConfig, 'plugins'>, logger?: Logger): Promise<void>;
60
+ export declare function processFile(file: string, plugins: Plugin[], astVisitors: ASTVisitors, pluginContext: PluginContext, config: Omit<I18nextToolkitConfig, 'plugins'>, logger?: Logger, fileErrors?: string[]): Promise<void>;
58
61
  /**
59
62
  * Simplified extraction function that returns translation results without file writing.
60
63
  * Used primarily for testing and programmatic access.
@@ -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;AAMtF,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,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,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,CA2Gf;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;AAMtF,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,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAsE1D;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,EACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CA8Gf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,uBAA+B,EAAE,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,sDAO3I"}
@@ -27,7 +27,7 @@ import type { ExtractedKey, Logger, I18nextToolkitConfig } from '../../types';
27
27
  * console.log(`Found ${keys.size} unique translation keys`)
28
28
  * ```
29
29
  */
30
- export declare function findKeys(config: I18nextToolkitConfig, logger?: Logger): Promise<{
30
+ export declare function findKeys(config: I18nextToolkitConfig, logger?: Logger, fileErrors?: string[]): Promise<{
31
31
  allKeys: Map<string, ExtractedKey>;
32
32
  objectKeys: Set<string>;
33
33
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"key-finder.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/key-finder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAmB,MAAM,aAAa,CAAA;AAO9F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CA6E1E"}
1
+ {"version":3,"file":"key-finder.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/key-finder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAmB,MAAM,aAAa,CAAA;AAO9F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,EACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CA6E1E"}
package/types/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { I18nextToolkitConfig, Plugin, PluginContext, ExtractedKey, TranslationResult, ExtractedKeysMap, RenameKeyResult, Logger, } from './types';
1
+ export type { I18nextToolkitConfig, Plugin, LinterPlugin, LintPluginContext, LintIssue, PluginContext, ExtractedKey, TranslationResult, ExtractedKeysMap, RenameKeyResult, Logger, } from './types';
2
2
  export { defineConfig } from './config';
3
3
  export { extract, findKeys, getTranslations, runExtractor } from './extractor';
4
4
  export { runLinter, recommendedAcceptedTags, recommendedAcceptedAttributes } from './linter';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,oBAAoB,EACpB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,MAAM,GACP,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EACL,OAAO,EACP,QAAQ,EACR,eAAe,EACf,YAAY,EACb,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAC5F,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,oBAAoB,EACpB,MAAM,EACN,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,MAAM,GACP,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EACL,OAAO,EACP,QAAQ,EACR,eAAe,EACf,YAAY,EACb,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAC5F,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
package/types/linter.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import type { I18nextToolkitConfig, Logger } from './types';
2
+ import type { I18nextToolkitConfig, Logger, LintIssue } from './types';
3
3
  type LinterEventMap = {
4
4
  progress: [
5
5
  {
@@ -10,7 +10,7 @@ type LinterEventMap = {
10
10
  {
11
11
  success: boolean;
12
12
  message: string;
13
- files: Record<string, HardcodedString[]>;
13
+ files: Record<string, LintIssue[]>;
14
14
  }
15
15
  ];
16
16
  error: [error: Error];
@@ -19,15 +19,22 @@ export declare const recommendedAcceptedTags: string[];
19
19
  export declare const recommendedAcceptedAttributes: string[];
20
20
  export declare class Linter extends EventEmitter<LinterEventMap> {
21
21
  private config;
22
- constructor(config: I18nextToolkitConfig);
22
+ private logger;
23
+ constructor(config: I18nextToolkitConfig, logger?: Logger);
23
24
  wrapError(error: unknown): Error;
24
25
  run(): Promise<{
25
26
  success: boolean;
26
27
  message: string;
27
28
  files: {
28
- [k: string]: HardcodedString[];
29
+ [k: string]: LintIssue[];
29
30
  };
30
31
  }>;
32
+ private createLintPluginContext;
33
+ private initializeLintPlugins;
34
+ private normalizeExtension;
35
+ private shouldRunLintPluginForFile;
36
+ private runLintOnLoadPipeline;
37
+ private runLintOnResultPipeline;
31
38
  }
32
39
  /**
33
40
  * Runs the i18next linter to detect hardcoded strings and other potential issues.
@@ -62,23 +69,12 @@ export declare function runLinter(config: I18nextToolkitConfig): Promise<{
62
69
  success: boolean;
63
70
  message: string;
64
71
  files: {
65
- [k: string]: HardcodedString[];
72
+ [k: string]: LintIssue[];
66
73
  };
67
74
  }>;
68
75
  export declare function runLinterCli(config: I18nextToolkitConfig, options?: {
69
76
  quiet?: boolean;
70
77
  logger?: Logger;
71
78
  }): Promise<void>;
72
- /**
73
- * Represents a found hardcoded string or interpolation parameter error with its location information.
74
- */
75
- interface HardcodedString {
76
- /** The hardcoded text content or error message */
77
- text: string;
78
- /** Line number where the string or error was found */
79
- line: number;
80
- /** The type of issue: 'hardcoded' for hardcoded strings, 'interpolation' for interpolation parameter errors */
81
- type?: 'hardcoded' | 'interpolation';
82
- }
83
79
  export {};
84
80
  //# sourceMappingURL=linter.d.ts.map
@@ -1 +1 @@
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,EAAE,MAAM,SAAS,CAAA;AAwP3D,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;;;;;;;CAgGV;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,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBAkCnD;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,+GAA+G;IAC/G,IAAI,CAAC,EAAE,WAAW,GAAG,eAAe,CAAC;CACtC"}
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,EAAE,SAAS,EAA6B,MAAM,SAAS,CAAA;AAwPjG,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,SAAS,EAAE,CAAC,CAAC;SACpC;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;IACpC,OAAO,CAAC,MAAM,CAAQ;gBAET,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAE,MAA4B;IAM/E,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;IA2GT,OAAO,CAAC,uBAAuB;YAOjB,qBAAqB;IAWnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;YAUpB,qBAAqB;YAgBrB,uBAAuB;CAgBtC;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,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBAkCnD"}
package/types/types.d.ts CHANGED
@@ -223,6 +223,65 @@ export interface I18nextToolkitConfig {
223
223
  dryRun?: boolean;
224
224
  };
225
225
  }
226
+ type MaybePromise<T> = T | Promise<T>;
227
+ /**
228
+ * Represents a linter issue reported for a source file.
229
+ */
230
+ export interface LintIssue {
231
+ /** The hardcoded text content or interpolation error message */
232
+ text: string;
233
+ /** Line number where the issue was found */
234
+ line: number;
235
+ /** Issue category */
236
+ type?: 'hardcoded' | 'interpolation';
237
+ }
238
+ /**
239
+ * Context object provided to linter plugin hooks.
240
+ */
241
+ export interface LintPluginContext {
242
+ /** The fully resolved i18next-cli configuration. */
243
+ config: I18nextToolkitConfig;
244
+ /** The logger instance used by the linter run. */
245
+ logger: Logger;
246
+ }
247
+ /**
248
+ * Linter-specific plugin hooks.
249
+ *
250
+ * This interface is kept separate so lint capabilities can evolve
251
+ * without coupling extractor hook definitions.
252
+ */
253
+ export interface LinterPlugin {
254
+ /** Unique name for the plugin */
255
+ name: string;
256
+ /**
257
+ * Optional file-extension hint for lint-only hooks (optimization only).
258
+ *
259
+ * Examples: ['.vue'], ['vue', 'svelte']
260
+ */
261
+ lintExtensions?: string[];
262
+ /**
263
+ * Hook called once at the beginning of the linting process.
264
+ * Use for initialization required by lint hooks.
265
+ */
266
+ lintSetup?: (context: LintPluginContext) => MaybePromise<void>;
267
+ /**
268
+ * Hook called for each source file before lint parsing.
269
+ *
270
+ * Return semantics in lint pipeline:
271
+ * - string: use transformed code
272
+ * - undefined: pass through unchanged
273
+ * - null: skip linting this file entirely
274
+ */
275
+ lintOnLoad?: (code: string, filePath: string) => MaybePromise<string | null | undefined>;
276
+ /**
277
+ * Hook called after linting one file, allowing issue post-processing.
278
+ *
279
+ * Return semantics:
280
+ * - LintIssue[]: replace issues for this file
281
+ * - undefined: keep issues unchanged
282
+ */
283
+ lintOnResult?: (filePath: string, issues: LintIssue[]) => MaybePromise<LintIssue[] | undefined>;
284
+ }
226
285
  /**
227
286
  * Plugin interface for extending the i18next toolkit functionality.
228
287
  * Plugins can hook into various stages of the extraction process.
@@ -254,7 +313,7 @@ export interface I18nextToolkitConfig {
254
313
  * })
255
314
  * ```
256
315
  */
257
- export interface Plugin {
316
+ export interface Plugin extends LinterPlugin {
258
317
  /** Unique name for the plugin */
259
318
  name: string;
260
319
  /**
@@ -285,7 +344,7 @@ export interface Plugin {
285
344
  * Hook called once at the beginning of the extraction process.
286
345
  * Use for initialization tasks like setting up resources or validating configuration.
287
346
  */
288
- setup?: () => void | Promise<void>;
347
+ setup?: () => MaybePromise<void>;
289
348
  /**
290
349
  * Hook called for each source file before it's parsed.
291
350
  * Allows transformation of source code before AST generation.
@@ -294,7 +353,7 @@ export interface Plugin {
294
353
  * @param path - The file path being processed
295
354
  * @returns The transformed code (or undefined to keep original)
296
355
  */
297
- onLoad?: (code: string, path: string) => string | Promise<string>;
356
+ onLoad?: (code: string, path: string) => MaybePromise<string>;
298
357
  /**
299
358
  * Hook called for each AST node during traversal.
300
359
  * Enables custom extraction logic by examining syntax nodes.
@@ -309,7 +368,7 @@ export interface Plugin {
309
368
  *
310
369
  * @param keys - Final map of all extracted keys
311
370
  */
312
- onEnd?: (keys: ExtractedKeysMap) => void | Promise<void>;
371
+ onEnd?: (keys: ExtractedKeysMap) => MaybePromise<void>;
313
372
  /**
314
373
  * Hook called after all files have been processed and translation files have been generated.
315
374
  * Useful for post-processing, validation, or reporting based on the final results.
@@ -317,7 +376,7 @@ export interface Plugin {
317
376
  * @param results - Array of translation results with update status and content.
318
377
  * @param config - The i18next toolkit configuration object.
319
378
  */
320
- afterSync?: (results: TranslationResult[], config: I18nextToolkitConfig) => void | Promise<void>;
379
+ afterSync?: (results: TranslationResult[], config: I18nextToolkitConfig) => MaybePromise<void>;
321
380
  }
322
381
  /**
323
382
  * Represents an extracted translation key with its metadata.
@@ -611,4 +670,5 @@ export interface RenameKeyResult {
611
670
  conflicts?: string[];
612
671
  error?: string;
613
672
  }
673
+ export {};
614
674
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}