i18next-cli 1.58.2 → 1.59.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
@@ -263,6 +263,8 @@ npx i18next-cli types [options]
263
263
 
264
264
  **Options:**
265
265
  - `--watch, -w`: Re-run automatically when translation files change
266
+ - `--ci`: Exit with a non-zero status if the generated TypeScript definitions are out of date (check-only, writes nothing). Cannot be combined with `--watch`.
267
+ - `--quiet, -q`: Suppress spinner and non-essential output (for CI or scripting)
266
268
 
267
269
  ### `sync`
268
270
  Synchronizes secondary language files against your primary language file, adding missing keys and removing extraneous ones.
@@ -1325,6 +1327,14 @@ Use the `--ci` flag to fail builds when translations are outdated:
1325
1327
  run: npx i18next-cli extract --ci
1326
1328
  ```
1327
1329
 
1330
+ Likewise, fail the build when the generated TypeScript definitions are out of date:
1331
+
1332
+ ```yaml
1333
+ # Fail the build if generated TypeScript definitions are out of date
1334
+ - name: Check i18n types
1335
+ run: npx i18next-cli types --ci
1336
+ ```
1337
+
1328
1338
  ## Watch Mode
1329
1339
 
1330
1340
  For development, use watch mode to automatically update translations:
package/dist/cjs/cli.js CHANGED
@@ -9,7 +9,7 @@ var node_util = require('node:util');
9
9
  var config = require('./config.js');
10
10
  var heuristicConfig = require('./heuristic-config.js');
11
11
  var extractor = require('./extractor/core/extractor.js');
12
- require('./extractor/parsers/jsx-parser.js');
12
+ require('node:module');
13
13
  require('node:path');
14
14
  var nestedObject = require('./utils/nested-object.js');
15
15
  require('node:fs/promises');
@@ -32,7 +32,7 @@ const program = new commander.Command();
32
32
  program
33
33
  .name('i18next-cli')
34
34
  .description('A unified, high-performance i18next CLI.')
35
- .version('1.58.2'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.59.0'); // This string is replaced with the actual version at build time by rollup
36
36
  // new: global config override option
37
37
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
38
38
  program
@@ -133,10 +133,27 @@ program
133
133
  .command('types')
134
134
  .description('Generate TypeScript definitions from translation resource files.')
135
135
  .option('-w, --watch', 'Watch for file changes and re-run the type generator.')
136
+ .option('--ci', 'Exit with a non-zero status code if type definitions are not up to date (check-only, no writes).')
136
137
  .option('-q, --quiet', 'Suppress spinner and output')
137
138
  .action(async (options) => {
138
139
  const cfgPath = program.opts().config;
139
140
  const config$1 = await config.ensureConfig(cfgPath);
141
+ if (options.ci && options.watch) {
142
+ console.error(node_util.styleText('red', '--ci and --watch cannot be used together'));
143
+ process.exit(1);
144
+ }
145
+ if (options.ci) {
146
+ const { changed, changedFiles } = await typesGenerator.runTypesGenerator(config$1, { checkOnly: true });
147
+ if (changed) {
148
+ console.error(node_util.styleText('red', "❌ TypeScript definitions are out of date. Run 'i18next-cli types' and commit the changes."));
149
+ for (const file of changedFiles) {
150
+ console.error(` ${file}`);
151
+ }
152
+ process.exit(1);
153
+ }
154
+ console.log(node_util.styleText('green', '✅ TypeScript definitions are up to date.'));
155
+ return;
156
+ }
140
157
  const run = () => typesGenerator.runTypesGenerator(config$1, { quiet: !!options.quiet });
141
158
  await run();
142
159
  if (options.watch) {
@@ -1,8 +1,31 @@
1
1
  'use strict';
2
2
 
3
3
  var astUtils = require('./ast-utils.js');
4
- var reactI18next = require('react-i18next');
4
+ var node_module = require('node:module');
5
5
 
6
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
+ let cachedReactI18next;
8
+ function loadReactI18next() {
9
+ if (cachedReactI18next !== undefined)
10
+ return cachedReactI18next;
11
+ try {
12
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('extractor/parsers/jsx-parser.js', document.baseURI).href)));
13
+ cachedReactI18next = require$1('react-i18next');
14
+ }
15
+ catch {
16
+ cachedReactI18next = null;
17
+ }
18
+ return cachedReactI18next;
19
+ }
20
+ let warnedReactI18nextUnavailable = false;
21
+ function warnReactI18nextUnavailable() {
22
+ if (warnedReactI18nextUnavailable)
23
+ return;
24
+ warnedReactI18nextUnavailable = true;
25
+ console.warn("i18next-cli: could not load 'react-i18next' to serialize <Trans> children — " +
26
+ 'skipping their default-value extraction. This usually means the installed ' +
27
+ "'react-i18next' and 'i18next' versions are incompatible. Other functionality is unaffected.");
28
+ }
6
29
  /**
7
30
  * Detects which `$$typeof` symbol react-i18next's `nodesToString` expects
8
31
  * from React elements. This matters because npm hoisting can cause
@@ -11,11 +34,14 @@ var reactI18next = require('react-i18next');
11
34
  *
12
35
  * React 18 uses `Symbol.for('react.element')`, while React 19 uses
13
36
  * `Symbol.for('react.transitional.element')` for its element `$$typeof`.
14
- * By probing `nodesToString` at load time we ensure the fake elements we
15
- * build match its `isValidElement` check, regardless of which React it
16
- * resolved to.
37
+ * By probing `nodesToString` we ensure the fake elements we build match its
38
+ * `isValidElement` check, regardless of which React it resolved to. Resolved
39
+ * lazily (and cached) the first time we serialize a `<Trans>`.
17
40
  */
18
- const REACT_ELEMENT_TYPE = (() => {
41
+ let cachedReactElementType;
42
+ function resolveReactElementType(rt) {
43
+ if (cachedReactElementType !== undefined)
44
+ return cachedReactElementType;
19
45
  const candidates = [
20
46
  Symbol.for('react.element'), // React ≤ 18
21
47
  Symbol.for('react.transitional.element') // React 19
@@ -31,16 +57,19 @@ const REACT_ELEMENT_TYPE = (() => {
31
57
  key: null,
32
58
  ref: null
33
59
  };
34
- const result = reactI18next.nodesToString([testEl], { ...reactI18next.getDefaults() });
35
- if (result === '<0>x</0>')
60
+ const result = rt.nodesToString([testEl], { ...rt.getDefaults() });
61
+ if (result === '<0>x</0>') {
62
+ cachedReactElementType = sym;
36
63
  return sym;
64
+ }
37
65
  }
38
66
  }
39
67
  finally {
40
68
  console.warn = savedWarn;
41
69
  }
70
+ cachedReactElementType = candidates[0];
42
71
  return candidates[0];
43
- })();
72
+ }
44
73
  /** `React.Fragment` equivalent – same symbol across all React versions. */
45
74
  const REACT_FRAGMENT = Symbol.for('react.fragment');
46
75
  /**
@@ -57,7 +86,9 @@ function createElement(type, props, ...children) {
57
86
  finalProps.children = children;
58
87
  }
59
88
  return {
60
- $$typeof: REACT_ELEMENT_TYPE,
89
+ // serializeJSXChildren() resolves and caches this before any element is
90
+ // built; the fallback only applies if createElement is ever reached first.
91
+ $$typeof: cachedReactElementType ?? Symbol.for('react.transitional.element'),
61
92
  type,
62
93
  props: finalProps,
63
94
  key: null,
@@ -460,11 +491,19 @@ function childrenHaveInlineCount(children) {
460
491
  return false;
461
492
  }
462
493
  function serializeJSXChildren(children, config) {
463
- const i18nextOptions = { ...reactI18next.getDefaults() };
494
+ const rt = loadReactI18next();
495
+ if (!rt) {
496
+ warnReactI18nextUnavailable();
497
+ return '';
498
+ }
499
+ // Resolve the element `$$typeof` before swcChildrenToReactNodes() builds any
500
+ // elements via createElement().
501
+ resolveReactElementType(rt);
502
+ const i18nextOptions = { ...rt.getDefaults() };
464
503
  if (config.extract.transKeepBasicHtmlNodesFor) {
465
504
  i18nextOptions.transKeepBasicHtmlNodesFor = config.extract.transKeepBasicHtmlNodesFor;
466
505
  }
467
- return reactI18next.nodesToString(swcChildrenToReactNodes(children), i18nextOptions);
506
+ return rt.nodesToString(swcChildrenToReactNodes(children), i18nextOptions);
468
507
  }
469
508
 
470
509
  exports.extractFromTransComponent = extractFromTransComponent;
package/dist/cjs/index.js CHANGED
@@ -4,7 +4,7 @@ var config = require('./config.js');
4
4
  var extractor = require('./extractor/core/extractor.js');
5
5
  var keyFinder = require('./extractor/core/key-finder.js');
6
6
  var translationManager = require('./extractor/core/translation-manager.js');
7
- require('./extractor/parsers/jsx-parser.js');
7
+ require('node:module');
8
8
  var linter = require('./linter.js');
9
9
  var syncer = require('./syncer.js');
10
10
  var status = require('./status.js');
@@ -13,7 +13,7 @@ var pluralRules = require('./utils/plural-rules.js');
13
13
  var nesting = require('./utils/nesting.js');
14
14
  var contextVariants = require('./utils/context-variants.js');
15
15
  var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
16
- require('./extractor/parsers/jsx-parser.js');
16
+ require('node:module');
17
17
 
18
18
  function classifyValue(value) {
19
19
  if (value === undefined || value === null)
@@ -94,7 +94,7 @@ function getNamespaceFromPath(filePath, basePath) {
94
94
  */
95
95
  async function runTypesGenerator(config, options = {}) {
96
96
  const internalLogger = options.logger ?? new logger.ConsoleLogger();
97
- const spinner = wrapOra.createSpinnerLike('Generating TypeScript types for translations...\n', { quiet: !!options.quiet, logger: options.logger });
97
+ const spinner = wrapOra.createSpinnerLike('Generating TypeScript types for translations...\n', { quiet: !!options.quiet || !!options.checkOnly, logger: options.logger });
98
98
  try {
99
99
  config.extract.primaryLanguage ||= config.locales[0] || 'en';
100
100
  let defaultTypesInputPath = config.extract.output || `locales/${config.extract.primaryLanguage}/*.json`;
@@ -109,7 +109,7 @@ async function runTypesGenerator(config, options = {}) {
109
109
  config.types.resourcesFile = node_path.join(node_path.dirname(config.types?.output), 'resources.d.ts');
110
110
  if (!config.types?.input || config.types?.input.length < 0) {
111
111
  console.log('No input defined!');
112
- return;
112
+ return { changed: false, changedFiles: [] };
113
113
  }
114
114
  const resourceFiles = await glob.glob(config.types.input, { cwd: process.cwd() });
115
115
  const basePath = config.types.basePath ? fileUtils.getOutputPath(config.types.basePath, config.extract.primaryLanguage) : undefined;
@@ -150,6 +150,26 @@ async function runTypesGenerator(config, options = {}) {
150
150
  ${i18nextResourcesForTs.mergeResourcesAsInterface(resources, { optimize: !!enableSelector, indentation })}`;
151
151
  const outputPath = node_path.resolve(process.cwd(), config.types?.output || '');
152
152
  const resourcesOutputPath = node_path.resolve(process.cwd(), config.types.resourcesFile);
153
+ if (options.checkOnly) {
154
+ const changedFiles = [];
155
+ let existingResources = null;
156
+ try {
157
+ existingResources = await promises.readFile(resourcesOutputPath, 'utf-8');
158
+ }
159
+ catch {
160
+ existingResources = null;
161
+ }
162
+ if (existingResources !== interfaceDefinition) {
163
+ changedFiles.push(config.types.resourcesFile);
164
+ }
165
+ try {
166
+ await promises.access(outputPath);
167
+ }
168
+ catch {
169
+ changedFiles.push(config.types.output || '');
170
+ }
171
+ return { changed: changedFiles.length > 0, changedFiles };
172
+ }
153
173
  await promises.mkdir(node_path.dirname(resourcesOutputPath), { recursive: true });
154
174
  await promises.writeFile(resourcesOutputPath, interfaceDefinition);
155
175
  logMessages.push(` ${node_util.styleText('green', '✓')} Resources interface written to ${config.types.resourcesFile}`);
@@ -183,6 +203,7 @@ declare module 'i18next' {
183
203
  }
184
204
  spinner.succeed(node_util.styleText('bold', 'TypeScript definitions generated successfully.'));
185
205
  logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
206
+ return { changed: false, changedFiles: [] };
186
207
  }
187
208
  catch (error) {
188
209
  spinner.fail(node_util.styleText('red', 'Failed to generate TypeScript definitions.'));
@@ -190,6 +211,7 @@ declare module 'i18next' {
190
211
  internalLogger.error(error);
191
212
  else
192
213
  console.error(error);
214
+ return { changed: false, changedFiles: [] };
193
215
  }
194
216
  }
195
217
 
package/dist/esm/cli.js CHANGED
@@ -7,7 +7,7 @@ import { styleText } from 'node:util';
7
7
  import { ensureConfig, loadConfig } from './config.js';
8
8
  import { detectConfig } from './heuristic-config.js';
9
9
  import { runExtractor } from './extractor/core/extractor.js';
10
- import './extractor/parsers/jsx-parser.js';
10
+ import 'node:module';
11
11
  import 'node:path';
12
12
  import { getNestedKeys, getNestedValue } from './utils/nested-object.js';
13
13
  import 'node:fs/promises';
@@ -30,7 +30,7 @@ const program = new Command();
30
30
  program
31
31
  .name('i18next-cli')
32
32
  .description('A unified, high-performance i18next CLI.')
33
- .version('1.58.2'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.59.0'); // This string is replaced with the actual version at build time by rollup
34
34
  // new: global config override option
35
35
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
36
36
  program
@@ -131,10 +131,27 @@ program
131
131
  .command('types')
132
132
  .description('Generate TypeScript definitions from translation resource files.')
133
133
  .option('-w, --watch', 'Watch for file changes and re-run the type generator.')
134
+ .option('--ci', 'Exit with a non-zero status code if type definitions are not up to date (check-only, no writes).')
134
135
  .option('-q, --quiet', 'Suppress spinner and output')
135
136
  .action(async (options) => {
136
137
  const cfgPath = program.opts().config;
137
138
  const config = await ensureConfig(cfgPath);
139
+ if (options.ci && options.watch) {
140
+ console.error(styleText('red', '--ci and --watch cannot be used together'));
141
+ process.exit(1);
142
+ }
143
+ if (options.ci) {
144
+ const { changed, changedFiles } = await runTypesGenerator(config, { checkOnly: true });
145
+ if (changed) {
146
+ console.error(styleText('red', "❌ TypeScript definitions are out of date. Run 'i18next-cli types' and commit the changes."));
147
+ for (const file of changedFiles) {
148
+ console.error(` ${file}`);
149
+ }
150
+ process.exit(1);
151
+ }
152
+ console.log(styleText('green', '✅ TypeScript definitions are up to date.'));
153
+ return;
154
+ }
138
155
  const run = () => runTypesGenerator(config, { quiet: !!options.quiet });
139
156
  await run();
140
157
  if (options.watch) {
@@ -1,6 +1,28 @@
1
1
  import { getObjectPropValueExpression, getObjectPropValue, isSimpleTemplateLiteral } from './ast-utils.js';
2
- import { nodesToString, getDefaults } from 'react-i18next';
2
+ import { createRequire } from 'node:module';
3
3
 
4
+ let cachedReactI18next;
5
+ function loadReactI18next() {
6
+ if (cachedReactI18next !== undefined)
7
+ return cachedReactI18next;
8
+ try {
9
+ const require = createRequire(import.meta.url);
10
+ cachedReactI18next = require('react-i18next');
11
+ }
12
+ catch {
13
+ cachedReactI18next = null;
14
+ }
15
+ return cachedReactI18next;
16
+ }
17
+ let warnedReactI18nextUnavailable = false;
18
+ function warnReactI18nextUnavailable() {
19
+ if (warnedReactI18nextUnavailable)
20
+ return;
21
+ warnedReactI18nextUnavailable = true;
22
+ console.warn("i18next-cli: could not load 'react-i18next' to serialize <Trans> children — " +
23
+ 'skipping their default-value extraction. This usually means the installed ' +
24
+ "'react-i18next' and 'i18next' versions are incompatible. Other functionality is unaffected.");
25
+ }
4
26
  /**
5
27
  * Detects which `$$typeof` symbol react-i18next's `nodesToString` expects
6
28
  * from React elements. This matters because npm hoisting can cause
@@ -9,11 +31,14 @@ import { nodesToString, getDefaults } from 'react-i18next';
9
31
  *
10
32
  * React 18 uses `Symbol.for('react.element')`, while React 19 uses
11
33
  * `Symbol.for('react.transitional.element')` for its element `$$typeof`.
12
- * By probing `nodesToString` at load time we ensure the fake elements we
13
- * build match its `isValidElement` check, regardless of which React it
14
- * resolved to.
34
+ * By probing `nodesToString` we ensure the fake elements we build match its
35
+ * `isValidElement` check, regardless of which React it resolved to. Resolved
36
+ * lazily (and cached) the first time we serialize a `<Trans>`.
15
37
  */
16
- const REACT_ELEMENT_TYPE = (() => {
38
+ let cachedReactElementType;
39
+ function resolveReactElementType(rt) {
40
+ if (cachedReactElementType !== undefined)
41
+ return cachedReactElementType;
17
42
  const candidates = [
18
43
  Symbol.for('react.element'), // React ≤ 18
19
44
  Symbol.for('react.transitional.element') // React 19
@@ -29,16 +54,19 @@ const REACT_ELEMENT_TYPE = (() => {
29
54
  key: null,
30
55
  ref: null
31
56
  };
32
- const result = nodesToString([testEl], { ...getDefaults() });
33
- if (result === '<0>x</0>')
57
+ const result = rt.nodesToString([testEl], { ...rt.getDefaults() });
58
+ if (result === '<0>x</0>') {
59
+ cachedReactElementType = sym;
34
60
  return sym;
61
+ }
35
62
  }
36
63
  }
37
64
  finally {
38
65
  console.warn = savedWarn;
39
66
  }
67
+ cachedReactElementType = candidates[0];
40
68
  return candidates[0];
41
- })();
69
+ }
42
70
  /** `React.Fragment` equivalent – same symbol across all React versions. */
43
71
  const REACT_FRAGMENT = Symbol.for('react.fragment');
44
72
  /**
@@ -55,7 +83,9 @@ function createElement(type, props, ...children) {
55
83
  finalProps.children = children;
56
84
  }
57
85
  return {
58
- $$typeof: REACT_ELEMENT_TYPE,
86
+ // serializeJSXChildren() resolves and caches this before any element is
87
+ // built; the fallback only applies if createElement is ever reached first.
88
+ $$typeof: cachedReactElementType ?? Symbol.for('react.transitional.element'),
59
89
  type,
60
90
  props: finalProps,
61
91
  key: null,
@@ -458,11 +488,19 @@ function childrenHaveInlineCount(children) {
458
488
  return false;
459
489
  }
460
490
  function serializeJSXChildren(children, config) {
461
- const i18nextOptions = { ...getDefaults() };
491
+ const rt = loadReactI18next();
492
+ if (!rt) {
493
+ warnReactI18nextUnavailable();
494
+ return '';
495
+ }
496
+ // Resolve the element `$$typeof` before swcChildrenToReactNodes() builds any
497
+ // elements via createElement().
498
+ resolveReactElementType(rt);
499
+ const i18nextOptions = { ...rt.getDefaults() };
462
500
  if (config.extract.transKeepBasicHtmlNodesFor) {
463
501
  i18nextOptions.transKeepBasicHtmlNodesFor = config.extract.transKeepBasicHtmlNodesFor;
464
502
  }
465
- return nodesToString(swcChildrenToReactNodes(children), i18nextOptions);
503
+ return rt.nodesToString(swcChildrenToReactNodes(children), i18nextOptions);
466
504
  }
467
505
 
468
506
  export { extractFromTransComponent };
package/dist/esm/index.js CHANGED
@@ -2,7 +2,7 @@ export { defineConfig } from './config.js';
2
2
  export { extract, runExtractor } from './extractor/core/extractor.js';
3
3
  export { findKeys } from './extractor/core/key-finder.js';
4
4
  export { getTranslations } from './extractor/core/translation-manager.js';
5
- import './extractor/parsers/jsx-parser.js';
5
+ import 'node:module';
6
6
  export { recommendedAcceptedAttributes, recommendedAcceptedTags, runLinter } from './linter.js';
7
7
  export { runSyncer } from './syncer.js';
8
8
  export { runStatus } from './status.js';
@@ -11,7 +11,7 @@ import { safePluralRules } from './utils/plural-rules.js';
11
11
  import { parseNestedReferences } from './utils/nesting.js';
12
12
  import { isContextVariantOfAcceptingKey } from './utils/context-variants.js';
13
13
  import { shouldShowFunnel, recordFunnelShown } from './utils/funnel-msg-tracker.js';
14
- import './extractor/parsers/jsx-parser.js';
14
+ import 'node:module';
15
15
 
16
16
  function classifyValue(value) {
17
17
  if (value === undefined || value === null)
@@ -3,7 +3,7 @@ import { mergeResourcesAsInterface } from 'i18next-resources-for-ts';
3
3
  import { glob } from 'glob';
4
4
  import { createSpinnerLike } from './utils/wrap-ora.js';
5
5
  import { styleText } from 'node:util';
6
- import { mkdir, writeFile, access, readFile } from 'node:fs/promises';
6
+ import { readFile, access, mkdir, writeFile } from 'node:fs/promises';
7
7
  import { join, dirname, basename, extname, resolve, relative } from 'node:path';
8
8
  import { transform } from '@swc/core';
9
9
  import { getOutputPath } from './utils/file-utils.js';
@@ -92,7 +92,7 @@ function getNamespaceFromPath(filePath, basePath) {
92
92
  */
93
93
  async function runTypesGenerator(config, options = {}) {
94
94
  const internalLogger = options.logger ?? new ConsoleLogger();
95
- const spinner = createSpinnerLike('Generating TypeScript types for translations...\n', { quiet: !!options.quiet, logger: options.logger });
95
+ const spinner = createSpinnerLike('Generating TypeScript types for translations...\n', { quiet: !!options.quiet || !!options.checkOnly, logger: options.logger });
96
96
  try {
97
97
  config.extract.primaryLanguage ||= config.locales[0] || 'en';
98
98
  let defaultTypesInputPath = config.extract.output || `locales/${config.extract.primaryLanguage}/*.json`;
@@ -107,7 +107,7 @@ async function runTypesGenerator(config, options = {}) {
107
107
  config.types.resourcesFile = join(dirname(config.types?.output), 'resources.d.ts');
108
108
  if (!config.types?.input || config.types?.input.length < 0) {
109
109
  console.log('No input defined!');
110
- return;
110
+ return { changed: false, changedFiles: [] };
111
111
  }
112
112
  const resourceFiles = await glob(config.types.input, { cwd: process.cwd() });
113
113
  const basePath = config.types.basePath ? getOutputPath(config.types.basePath, config.extract.primaryLanguage) : undefined;
@@ -148,6 +148,26 @@ async function runTypesGenerator(config, options = {}) {
148
148
  ${mergeResourcesAsInterface(resources, { optimize: !!enableSelector, indentation })}`;
149
149
  const outputPath = resolve(process.cwd(), config.types?.output || '');
150
150
  const resourcesOutputPath = resolve(process.cwd(), config.types.resourcesFile);
151
+ if (options.checkOnly) {
152
+ const changedFiles = [];
153
+ let existingResources = null;
154
+ try {
155
+ existingResources = await readFile(resourcesOutputPath, 'utf-8');
156
+ }
157
+ catch {
158
+ existingResources = null;
159
+ }
160
+ if (existingResources !== interfaceDefinition) {
161
+ changedFiles.push(config.types.resourcesFile);
162
+ }
163
+ try {
164
+ await access(outputPath);
165
+ }
166
+ catch {
167
+ changedFiles.push(config.types.output || '');
168
+ }
169
+ return { changed: changedFiles.length > 0, changedFiles };
170
+ }
151
171
  await mkdir(dirname(resourcesOutputPath), { recursive: true });
152
172
  await writeFile(resourcesOutputPath, interfaceDefinition);
153
173
  logMessages.push(` ${styleText('green', '✓')} Resources interface written to ${config.types.resourcesFile}`);
@@ -181,6 +201,7 @@ declare module 'i18next' {
181
201
  }
182
202
  spinner.succeed(styleText('bold', 'TypeScript definitions generated successfully.'));
183
203
  logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
204
+ return { changed: false, changedFiles: [] };
184
205
  }
185
206
  catch (error) {
186
207
  spinner.fail(styleText('red', 'Failed to generate TypeScript definitions.'));
@@ -188,6 +209,7 @@ declare module 'i18next' {
188
209
  internalLogger.error(error);
189
210
  else
190
211
  console.error(error);
212
+ return { changed: false, changedFiles: [] };
191
213
  }
192
214
  }
193
215
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.58.2",
3
+ "version": "1.59.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -59,16 +59,16 @@
59
59
  "@types/node": "^25.9.1",
60
60
  "@types/react": "^19.2.15",
61
61
  "@typescript-eslint/parser": "^8.60.0",
62
- "@vitest/coverage-v8": "^4.1.7",
62
+ "@vitest/coverage-v8": "^4.1.8",
63
63
  "eslint": "^9.39.4",
64
- "eslint-import-resolver-typescript": "^4.4.4",
64
+ "eslint-import-resolver-typescript": "^4.4.5",
65
65
  "eslint-plugin-import": "^2.32.0",
66
66
  "memfs": "^4.57.3",
67
67
  "neostandard": "^0.13.0",
68
- "rollup": "^4.60.4",
68
+ "rollup": "^4.61.0",
69
69
  "typescript": "^6.0.3",
70
70
  "unplugin-swc": "^1.5.9",
71
- "vitest": "^4.1.7"
71
+ "vitest": "^4.1.8"
72
72
  },
73
73
  "dependencies": {
74
74
  "@croct/json5-parser": "^0.2.2",
@@ -77,6 +77,7 @@
77
77
  "commander": "^14.0.3",
78
78
  "execa": "^9.6.1",
79
79
  "glob": "^13.0.6",
80
+ "i18next": "^26.3.0",
80
81
  "i18next-resources-for-ts": "^2.1.0",
81
82
  "inquirer": "^13.4.3",
82
83
  "jiti": "^2.7.0",
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAoBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AA8Z7B,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;AAoBnC,QAAA,MAAM,OAAO,SAAgB,CAAA;AAib7B,OAAO,EAAE,OAAO,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAsC,UAAU,EAAkD,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC7J,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAsE1D,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAE/B,kHAAkH;IAClH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CAkMxH"}
1
+ {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAsC,UAAU,EAAkD,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC7J,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAkH1D,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAE/B,kHAAkH;IAClH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CAkMxH"}
@@ -28,5 +28,9 @@ import type { I18nextToolkitConfig, Logger } from './types.js';
28
28
  export declare function runTypesGenerator(config: I18nextToolkitConfig, options?: {
29
29
  quiet?: boolean;
30
30
  logger?: Logger;
31
- }): Promise<void>;
31
+ checkOnly?: boolean;
32
+ }): Promise<{
33
+ changed: boolean;
34
+ changedFiles: string[];
35
+ }>;
32
36
  //# sourceMappingURL=types-generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-generator.d.ts","sourceRoot":"","sources":["../src/types-generator.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AA6E9D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBA+GnD"}
1
+ {"version":3,"file":"types-generator.d.ts","sourceRoot":"","sources":["../src/types-generator.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AA6E9D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GACtE,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuIvD"}