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 +10 -0
- package/dist/cjs/cli.js +19 -2
- package/dist/cjs/extractor/parsers/jsx-parser.js +50 -11
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/status.js +1 -1
- package/dist/cjs/types-generator.js +24 -2
- package/dist/esm/cli.js +19 -2
- package/dist/esm/extractor/parsers/jsx-parser.js +49 -11
- package/dist/esm/index.js +1 -1
- package/dist/esm/status.js +1 -1
- package/dist/esm/types-generator.js +25 -3
- package/package.json +6 -5
- package/types/cli.d.ts.map +1 -1
- package/types/extractor/parsers/jsx-parser.d.ts.map +1 -1
- package/types/types-generator.d.ts +5 -1
- package/types/types-generator.d.ts.map +1 -1
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('
|
|
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.
|
|
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
|
|
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`
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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('
|
|
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');
|
package/dist/cjs/status.js
CHANGED
|
@@ -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('
|
|
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 '
|
|
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.
|
|
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 {
|
|
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`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 '
|
|
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';
|
package/dist/esm/status.js
CHANGED
|
@@ -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 '
|
|
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 {
|
|
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.
|
|
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.
|
|
62
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
63
63
|
"eslint": "^9.39.4",
|
|
64
|
-
"eslint-import-resolver-typescript": "^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.
|
|
68
|
+
"rollup": "^4.61.0",
|
|
69
69
|
"typescript": "^6.0.3",
|
|
70
70
|
"unplugin-swc": "^1.5.9",
|
|
71
|
-
"vitest": "^4.1.
|
|
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",
|
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;AAoBnC,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;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;
|
|
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
|
-
|
|
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,
|
|
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"}
|