i18next-cli 1.42.5 → 1.42.7
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/dist/cjs/cli.js +14 -14
- package/dist/cjs/config.js +4 -4
- package/dist/cjs/extractor/core/extractor.js +11 -11
- package/dist/cjs/extractor/core/translation-manager.js +12 -3
- package/dist/cjs/linter.js +7 -7
- package/dist/cjs/locize.js +18 -18
- package/dist/cjs/rename-key.js +3 -3
- package/dist/cjs/status.js +25 -20
- package/dist/cjs/syncer.js +11 -11
- package/dist/cjs/types-generator.js +6 -6
- package/dist/cjs/utils/file-utils.js +38 -0
- package/dist/esm/cli.js +14 -14
- package/dist/esm/config.js +4 -4
- package/dist/esm/extractor/core/extractor.js +11 -11
- package/dist/esm/extractor/core/translation-manager.js +14 -5
- package/dist/esm/linter.js +7 -7
- package/dist/esm/locize.js +18 -18
- package/dist/esm/rename-key.js +3 -3
- package/dist/esm/status.js +25 -20
- package/dist/esm/syncer.js +11 -11
- package/dist/esm/types-generator.js +6 -6
- package/dist/esm/utils/file-utils.js +38 -1
- package/package.json +2 -5
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/utils/file-utils.d.ts +16 -0
- package/types/utils/file-utils.d.ts.map +1 -1
package/dist/cjs/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ var commander = require('commander');
|
|
|
5
5
|
var chokidar = require('chokidar');
|
|
6
6
|
var glob = require('glob');
|
|
7
7
|
var minimatch = require('minimatch');
|
|
8
|
-
var
|
|
8
|
+
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');
|
|
@@ -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.42.
|
|
31
|
+
.version('1.42.7'); // 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
|
|
@@ -100,14 +100,14 @@ program
|
|
|
100
100
|
const cfgPath = program.opts().config;
|
|
101
101
|
let config$1 = await config.loadConfig(cfgPath);
|
|
102
102
|
if (!config$1) {
|
|
103
|
-
console.log(
|
|
103
|
+
console.log(node_util.styleText('blue', 'No config file found. Attempting to detect project structure...'));
|
|
104
104
|
const detected = await heuristicConfig.detectConfig();
|
|
105
105
|
if (!detected) {
|
|
106
|
-
console.error(
|
|
107
|
-
console.log(`Please create a config file first by running: ${
|
|
106
|
+
console.error(node_util.styleText('red', 'Could not automatically detect your project structure.'));
|
|
107
|
+
console.log(`Please create a config file first by running: ${node_util.styleText('cyan', 'npx i18next-cli init')}`);
|
|
108
108
|
process.exit(1);
|
|
109
109
|
}
|
|
110
|
-
console.log(
|
|
110
|
+
console.log(node_util.styleText('green', 'Project structure detected successfully!'));
|
|
111
111
|
config$1 = detected;
|
|
112
112
|
}
|
|
113
113
|
await status.runStatus(config$1, { detail: locale, namespace: options.namespace });
|
|
@@ -164,14 +164,14 @@ program
|
|
|
164
164
|
// The existing logic for loading the config or detecting it is now inside this function
|
|
165
165
|
let config$1 = await config.loadConfig(cfgPath);
|
|
166
166
|
if (!config$1) {
|
|
167
|
-
console.log(
|
|
167
|
+
console.log(node_util.styleText('blue', 'No config file found. Attempting to detect project structure...'));
|
|
168
168
|
const detected = await heuristicConfig.detectConfig();
|
|
169
169
|
if (!detected) {
|
|
170
|
-
console.error(
|
|
171
|
-
console.log(`Please create a config file first by running: ${
|
|
170
|
+
console.error(node_util.styleText('red', 'Could not automatically detect your project structure.'));
|
|
171
|
+
console.log(`Please create a config file first by running: ${node_util.styleText('cyan', 'npx i18next-cli init')}`);
|
|
172
172
|
process.exit(1);
|
|
173
173
|
}
|
|
174
|
-
console.log(
|
|
174
|
+
console.log(node_util.styleText('green', 'Project structure detected successfully!'));
|
|
175
175
|
config$1 = detected;
|
|
176
176
|
}
|
|
177
177
|
await linter.runLinterCli(config$1, { quiet: !!options.quiet });
|
|
@@ -242,21 +242,21 @@ program
|
|
|
242
242
|
const result = await renameKey.runRenameKey(config$1, oldKey, newKey, options);
|
|
243
243
|
if (!result.success) {
|
|
244
244
|
if (result.conflicts) {
|
|
245
|
-
console.error(
|
|
245
|
+
console.error(node_util.styleText('red', '\n❌ Conflicts detected:'));
|
|
246
246
|
result.conflicts.forEach(c => console.error(` - ${c}`));
|
|
247
247
|
}
|
|
248
248
|
if (result.error) {
|
|
249
|
-
console.error(
|
|
249
|
+
console.error(node_util.styleText('red', `\n❌ ${result.error}`));
|
|
250
250
|
}
|
|
251
251
|
process.exit(1);
|
|
252
252
|
}
|
|
253
253
|
const totalChanges = result.sourceFiles.reduce((sum, f) => sum + f.changes, 0);
|
|
254
254
|
if (totalChanges === 0) {
|
|
255
|
-
console.log(
|
|
255
|
+
console.log(node_util.styleText('yellow', `\n⚠️ No usages found for "${oldKey}"`));
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
catch (error) {
|
|
259
|
-
console.error(
|
|
259
|
+
console.error(node_util.styleText('red', 'Error renaming key:'), error);
|
|
260
260
|
process.exit(1);
|
|
261
261
|
}
|
|
262
262
|
});
|
package/dist/cjs/config.js
CHANGED
|
@@ -6,7 +6,7 @@ var promises = require('node:fs/promises');
|
|
|
6
6
|
var jiti = require('jiti');
|
|
7
7
|
var jsoncParser = require('jsonc-parser');
|
|
8
8
|
var inquirer = require('inquirer');
|
|
9
|
-
var
|
|
9
|
+
var node_util = require('node:util');
|
|
10
10
|
var init = require('./init.js');
|
|
11
11
|
var logger = require('./utils/logger.js');
|
|
12
12
|
|
|
@@ -130,18 +130,18 @@ async function ensureConfig(configPath, logger$1 = new logger.ConsoleLogger()) {
|
|
|
130
130
|
const { shouldInit } = await inquirer.prompt([{
|
|
131
131
|
type: 'confirm',
|
|
132
132
|
name: 'shouldInit',
|
|
133
|
-
message:
|
|
133
|
+
message: node_util.styleText('yellow', 'Configuration file not found. Would you like to create one now?'),
|
|
134
134
|
default: true,
|
|
135
135
|
}]);
|
|
136
136
|
if (shouldInit) {
|
|
137
137
|
await init.runInit(); // Run the interactive setup wizard (keeps existing behavior)
|
|
138
|
-
logger$1.info(
|
|
138
|
+
logger$1.info(node_util.styleText('green', 'Configuration created. Resuming command...'));
|
|
139
139
|
config = await loadConfig(configPath, logger$1); // Try loading the newly created config
|
|
140
140
|
if (config) {
|
|
141
141
|
return config;
|
|
142
142
|
}
|
|
143
143
|
else {
|
|
144
|
-
logger$1.error(
|
|
144
|
+
logger$1.error(node_util.styleText('red', 'Error: Failed to load configuration after creation. Please try running the command again.'));
|
|
145
145
|
process.exit(1);
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var wrapOra = require('../../utils/wrap-ora.js');
|
|
4
|
-
var
|
|
4
|
+
var node_util = require('node:util');
|
|
5
5
|
var core = require('@swc/core');
|
|
6
6
|
var promises = require('node:fs/promises');
|
|
7
7
|
var node_path = require('node:path');
|
|
@@ -71,7 +71,7 @@ async function runExtractor(config, options = {}) {
|
|
|
71
71
|
const fileContent = fileUtils.serializeTranslationFile(result.newTranslations, effectiveFormat, config.extract.indentation, rawContent);
|
|
72
72
|
await promises.mkdir(node_path.dirname(result.path), { recursive: true });
|
|
73
73
|
await promises.writeFile(result.path, fileContent);
|
|
74
|
-
internalLogger.info(
|
|
74
|
+
internalLogger.info(node_util.styleText('green', `Updated: ${result.path}`));
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -82,14 +82,14 @@ async function runExtractor(config, options = {}) {
|
|
|
82
82
|
await plugin.afterSync?.(results, config);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
spinner.succeed(
|
|
85
|
+
spinner.succeed(node_util.styleText('bold', 'Extraction complete!'));
|
|
86
86
|
// Show the funnel message only if files were actually changed.
|
|
87
87
|
if (anyFileUpdated)
|
|
88
88
|
await printLocizeFunnel(options.logger);
|
|
89
89
|
return anyFileUpdated;
|
|
90
90
|
}
|
|
91
91
|
catch (error) {
|
|
92
|
-
spinner.fail(
|
|
92
|
+
spinner.fail(node_util.styleText('red', 'Extraction failed.'));
|
|
93
93
|
// Re-throw or handle error
|
|
94
94
|
throw error;
|
|
95
95
|
}
|
|
@@ -204,7 +204,7 @@ async function processFile(file, plugins, astVisitors, pluginContext, config, lo
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
catch (error) {
|
|
207
|
-
logger$1.warn(`${
|
|
207
|
+
logger$1.warn(`${node_util.styleText('yellow', 'Skipping file due to error:')} ${file}`);
|
|
208
208
|
const err = error;
|
|
209
209
|
const msg = typeof err?.message === 'string' && err.message.trim().length > 0
|
|
210
210
|
? err.message
|
|
@@ -248,18 +248,18 @@ async function printLocizeFunnel(logger$1) {
|
|
|
248
248
|
return;
|
|
249
249
|
const internalLogger = logger$1 ?? new logger.ConsoleLogger();
|
|
250
250
|
if (typeof internalLogger.info === 'function') {
|
|
251
|
-
internalLogger.info(
|
|
251
|
+
internalLogger.info(node_util.styleText(['yellow', 'bold'], '\n💡 Tip: Tired of running the extractor manually?'));
|
|
252
252
|
internalLogger.info(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
|
|
253
253
|
internalLogger.info(' where keys are created and translated automatically as you code.');
|
|
254
|
-
internalLogger.info(` Learn more: ${
|
|
255
|
-
internalLogger.info(` Watch the video: ${
|
|
254
|
+
internalLogger.info(` Learn more: ${node_util.styleText('cyan', 'https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
|
|
255
|
+
internalLogger.info(` Watch the video: ${node_util.styleText('cyan', 'https://youtu.be/joPsZghT3wM')}`);
|
|
256
256
|
}
|
|
257
257
|
else {
|
|
258
|
-
console.log(
|
|
258
|
+
console.log(node_util.styleText(['yellow', 'bold'], '\n💡 Tip: Tired of running the extractor manually?'));
|
|
259
259
|
console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,');
|
|
260
260
|
console.log(' where keys are created and translated automatically as you code.');
|
|
261
|
-
console.log(` Learn more: ${
|
|
262
|
-
console.log(` Watch the video: ${
|
|
261
|
+
console.log(` Learn more: ${node_util.styleText('cyan', 'https://www.locize.com/blog/i18next-savemissing-ai-automation')}`);
|
|
262
|
+
console.log(` Watch the video: ${node_util.styleText('cyan', 'https://youtu.be/joPsZghT3wM')}`);
|
|
263
263
|
}
|
|
264
264
|
return funnelMsgTracker.recordFunnelShown('extract');
|
|
265
265
|
}
|
|
@@ -815,14 +815,23 @@ async function getTranslations(keys, objectKeys, config, { syncPrimaryWithDefaul
|
|
|
815
815
|
// LOGIC PATH 2: Separate Namespace Files
|
|
816
816
|
}
|
|
817
817
|
else {
|
|
818
|
-
// Find all namespaces that exist on disk for this locale
|
|
818
|
+
// Find all namespaces that exist on disk for this locale.
|
|
819
|
+
// Use '**' so the glob crosses directory boundaries — namespaces
|
|
820
|
+
// can contain '/' and span multiple directory levels.
|
|
819
821
|
const namespacesToProcess = new Set(keysByNS.keys());
|
|
820
|
-
const existingNsPattern = fileUtils.getOutputPath(config.extract.output, locale, '
|
|
822
|
+
const existingNsPattern = fileUtils.getOutputPath(config.extract.output, locale, '**');
|
|
821
823
|
// Ensure glob receives POSIX-style separators so pattern matching works cross-platform (Windows -> backslashes)
|
|
822
824
|
const existingNsGlobPattern = existingNsPattern.replace(/\\/g, '/');
|
|
823
825
|
const existingNsFiles = await glob.glob(existingNsGlobPattern, { ignore: userIgnore });
|
|
824
826
|
for (const file of existingNsFiles) {
|
|
825
|
-
|
|
827
|
+
// Recover the full (possibly multi-segment) namespace from the file path
|
|
828
|
+
// by matching it against the output template.
|
|
829
|
+
const ns = typeof config.extract.output === 'string'
|
|
830
|
+
? fileUtils.extractNamespaceFromPath(config.extract.output, locale, file)
|
|
831
|
+
: undefined;
|
|
832
|
+
if (ns) {
|
|
833
|
+
namespacesToProcess.add(ns);
|
|
834
|
+
}
|
|
826
835
|
}
|
|
827
836
|
// Process each namespace individually and create a result for each one
|
|
828
837
|
for (const ns of namespacesToProcess) {
|
package/dist/cjs/linter.js
CHANGED
|
@@ -5,7 +5,7 @@ var promises = require('node:fs/promises');
|
|
|
5
5
|
var core = require('@swc/core');
|
|
6
6
|
var node_path = require('node:path');
|
|
7
7
|
var node_events = require('node:events');
|
|
8
|
-
var
|
|
8
|
+
var node_util = require('node:util');
|
|
9
9
|
var logger = require('./utils/logger.js');
|
|
10
10
|
var wrapOra = require('./utils/wrap-ora.js');
|
|
11
11
|
|
|
@@ -313,25 +313,25 @@ async function runLinterCli(config, options = {}) {
|
|
|
313
313
|
try {
|
|
314
314
|
const { success, message, files } = await linter.run();
|
|
315
315
|
if (!success) {
|
|
316
|
-
spinner.fail(
|
|
316
|
+
spinner.fail(node_util.styleText(['red', 'bold'], message));
|
|
317
317
|
// Print detailed report after spinner fails
|
|
318
318
|
for (const [file, issues] of Object.entries(files)) {
|
|
319
319
|
if (internalLogger.info)
|
|
320
|
-
internalLogger.info(
|
|
320
|
+
internalLogger.info(node_util.styleText('yellow', `\n${file}`));
|
|
321
321
|
else
|
|
322
|
-
console.log(
|
|
322
|
+
console.log(node_util.styleText('yellow', `\n${file}`));
|
|
323
323
|
issues.forEach(({ text, line, type }) => {
|
|
324
324
|
const label = type === 'interpolation' ? 'Interpolation issue' : 'Found hardcoded string';
|
|
325
325
|
if (typeof internalLogger.info === 'function')
|
|
326
|
-
internalLogger.info(` ${
|
|
326
|
+
internalLogger.info(` ${node_util.styleText('gray', `${line}:`)} ${node_util.styleText('red', 'Error:')} ${label}: "${text}"`);
|
|
327
327
|
else
|
|
328
|
-
console.log(` ${
|
|
328
|
+
console.log(` ${node_util.styleText('gray', `${line}:`)} ${node_util.styleText('red', 'Error:')} ${label}: "${text}"`);
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
process.exit(1);
|
|
332
332
|
}
|
|
333
333
|
else {
|
|
334
|
-
spinner.succeed(
|
|
334
|
+
spinner.succeed(node_util.styleText(['green', 'bold'], message));
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
catch (error) {
|
package/dist/cjs/locize.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var execa = require('execa');
|
|
4
|
-
var
|
|
4
|
+
var node_util = require('node:util');
|
|
5
5
|
var ora = require('ora');
|
|
6
6
|
var inquirer = require('inquirer');
|
|
7
7
|
var node_path = require('node:path');
|
|
@@ -24,9 +24,9 @@ async function checkLocizeCliExists() {
|
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
if (error.code === 'ENOENT') {
|
|
27
|
-
console.error(
|
|
28
|
-
console.log(
|
|
29
|
-
console.log(
|
|
27
|
+
console.error(node_util.styleText('red', 'Error: `locize-cli` command not found.'));
|
|
28
|
+
console.log(node_util.styleText('yellow', 'Please install it globally to use the Locize integration:'));
|
|
29
|
+
console.log(node_util.styleText('cyan', 'npm install -g locize-cli'));
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -54,7 +54,7 @@ async function checkLocizeCliExists() {
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
async function interactiveCredentialSetup(config) {
|
|
57
|
-
console.log(
|
|
57
|
+
console.log(node_util.styleText('yellow', '\nLocize configuration is missing or invalid. Let\'s set it up!'));
|
|
58
58
|
const answers = await inquirer.prompt([
|
|
59
59
|
{
|
|
60
60
|
type: 'input',
|
|
@@ -76,7 +76,7 @@ async function interactiveCredentialSetup(config) {
|
|
|
76
76
|
},
|
|
77
77
|
]);
|
|
78
78
|
if (!answers.projectId) {
|
|
79
|
-
console.error(
|
|
79
|
+
console.error(node_util.styleText('red', 'Project ID is required to continue.'));
|
|
80
80
|
return undefined;
|
|
81
81
|
}
|
|
82
82
|
const { save } = await inquirer.prompt([{
|
|
@@ -98,11 +98,11 @@ LOCIZE_API_KEY=${answers.apiKey}
|
|
|
98
98
|
apiKey: process.env.LOCIZE_API_KEY,
|
|
99
99
|
version: '${answers.version}',
|
|
100
100
|
},`;
|
|
101
|
-
console.log(
|
|
102
|
-
console.log(
|
|
103
|
-
console.log(
|
|
104
|
-
console.log(
|
|
105
|
-
console.log(
|
|
101
|
+
console.log(node_util.styleText('cyan', '\nGreat! For the best security, we recommend using environment variables for your API key.'));
|
|
102
|
+
console.log(node_util.styleText('bold', '\nRecommended approach (.env file):'));
|
|
103
|
+
console.log(node_util.styleText('green', envSnippet));
|
|
104
|
+
console.log(node_util.styleText('bold', 'Then, in your i18next.config.ts:'));
|
|
105
|
+
console.log(node_util.styleText('green', configSnippet));
|
|
106
106
|
}
|
|
107
107
|
return {
|
|
108
108
|
projectId: answers.projectId,
|
|
@@ -217,9 +217,9 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
|
217
217
|
try {
|
|
218
218
|
// 1. First attempt
|
|
219
219
|
const initialArgs = buildArgs(command, effectiveConfig, cliOptions);
|
|
220
|
-
console.log(
|
|
220
|
+
console.log(node_util.styleText('cyan', `\nRunning 'locize ${initialArgs.join(' ')}'...`));
|
|
221
221
|
const result = await execa.execa('locize', initialArgs, { stdio: 'pipe' });
|
|
222
|
-
spinner.succeed(
|
|
222
|
+
spinner.succeed(node_util.styleText('green', `'locize ${command}' completed successfully.`));
|
|
223
223
|
if (result?.stdout)
|
|
224
224
|
console.log(result.stdout); // Print captured output on success
|
|
225
225
|
}
|
|
@@ -234,14 +234,14 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
|
234
234
|
try {
|
|
235
235
|
// 3. Retry attempt, rebuilding args with the NOW-UPDATED currentConfig object
|
|
236
236
|
const retryArgs = buildArgs(command, effectiveConfig, cliOptions);
|
|
237
|
-
console.log(
|
|
237
|
+
console.log(node_util.styleText('cyan', `\nRunning 'locize ${retryArgs.join(' ')}'...`));
|
|
238
238
|
const result = await execa.execa('locize', retryArgs, { stdio: 'pipe' });
|
|
239
|
-
spinner.succeed(
|
|
239
|
+
spinner.succeed(node_util.styleText('green', 'Retry successful!'));
|
|
240
240
|
if (result?.stdout)
|
|
241
241
|
console.log(result.stdout);
|
|
242
242
|
}
|
|
243
243
|
catch (retryError) {
|
|
244
|
-
spinner.fail(
|
|
244
|
+
spinner.fail(node_util.styleText('red', 'Error during retry.'));
|
|
245
245
|
console.error(retryError.stderr || retryError.message);
|
|
246
246
|
process.exit(1);
|
|
247
247
|
}
|
|
@@ -253,12 +253,12 @@ async function runLocizeCommand(command, config, cliOptions = {}) {
|
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
255
|
// Handle other errors
|
|
256
|
-
spinner.fail(
|
|
256
|
+
spinner.fail(node_util.styleText('red', `Error executing 'locize ${command}'.`));
|
|
257
257
|
console.error(stderr || error.message);
|
|
258
258
|
process.exit(1);
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
-
console.log(
|
|
261
|
+
console.log(node_util.styleText('green', `\n✅ 'locize ${command}' completed successfully.`));
|
|
262
262
|
}
|
|
263
263
|
const runLocizeSync = (config, cliOptions) => runLocizeCommand('sync', config, cliOptions);
|
|
264
264
|
const runLocizeDownload = (config, cliOptions) => runLocizeCommand('download', config, cliOptions);
|
package/dist/cjs/rename-key.js
CHANGED
|
@@ -7,7 +7,7 @@ var logger = require('./utils/logger.js');
|
|
|
7
7
|
var fileUtils = require('./utils/file-utils.js');
|
|
8
8
|
var nestedObject = require('./utils/nested-object.js');
|
|
9
9
|
var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
|
|
10
|
-
var
|
|
10
|
+
var node_util = require('node:util');
|
|
11
11
|
|
|
12
12
|
const pluralSuffixes = ['zero', 'one', 'two', 'few', 'many', 'other'];
|
|
13
13
|
/**
|
|
@@ -106,11 +106,11 @@ async function runRenameKey(config, oldKey, newKey, options = {}, logger$1 = new
|
|
|
106
106
|
async function printLocizeFunnel() {
|
|
107
107
|
if (!(await funnelMsgTracker.shouldShowFunnel('rename-key')))
|
|
108
108
|
return;
|
|
109
|
-
console.log(
|
|
109
|
+
console.log(node_util.styleText(['yellow', 'bold'], '\n💡 Tip: Managing translations across multiple projects?'));
|
|
110
110
|
console.log(' With Locize, you can rename, move, and copy translation keys directly');
|
|
111
111
|
console.log(' in the web interface—no CLI needed. Perfect for collaboration with');
|
|
112
112
|
console.log(' translators and managing complex refactoring across namespaces.');
|
|
113
|
-
console.log(` Learn more: ${
|
|
113
|
+
console.log(` Learn more: ${node_util.styleText('cyan', 'https://www.locize.com/docs/how-can-a-segment-key-be-copied-moved-or-renamed')}`);
|
|
114
114
|
return funnelMsgTracker.recordFunnelShown('rename-key');
|
|
115
115
|
}
|
|
116
116
|
function parseKeyWithNamespace(key, config) {
|
package/dist/cjs/status.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var node_util = require('node:util');
|
|
4
4
|
var ora = require('ora');
|
|
5
5
|
var node_path = require('node:path');
|
|
6
|
+
require('@swc/core');
|
|
7
|
+
require('node:fs/promises');
|
|
6
8
|
var keyFinder = require('./extractor/core/key-finder.js');
|
|
9
|
+
require('glob');
|
|
7
10
|
var nestedObject = require('./utils/nested-object.js');
|
|
8
11
|
var fileUtils = require('./utils/file-utils.js');
|
|
9
12
|
var funnelMsgTracker = require('./utils/funnel-msg-tracker.js');
|
|
13
|
+
require('./extractor/parsers/jsx-parser.js');
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* Runs a health check on the project's i18next translations and displays a status report.
|
|
@@ -239,19 +243,19 @@ async function displayStatusReport(report, config, options) {
|
|
|
239
243
|
*/
|
|
240
244
|
async function displayDetailedLocaleReport(report, config, locale, namespaceFilter) {
|
|
241
245
|
if (locale === config.extract.primaryLanguage) {
|
|
242
|
-
console.log(
|
|
246
|
+
console.log(node_util.styleText('yellow', `Locale "${locale}" is the primary language. All keys are considered present.`));
|
|
243
247
|
return;
|
|
244
248
|
}
|
|
245
249
|
if (!config.locales.includes(locale)) {
|
|
246
|
-
console.error(
|
|
250
|
+
console.error(node_util.styleText('red', `Error: Locale "${locale}" is not defined in your configuration.`));
|
|
247
251
|
return;
|
|
248
252
|
}
|
|
249
253
|
const localeData = report.locales.get(locale);
|
|
250
254
|
if (!localeData) {
|
|
251
|
-
console.error(
|
|
255
|
+
console.error(node_util.styleText('red', `Error: Locale "${locale}" is not a valid secondary language.`));
|
|
252
256
|
return;
|
|
253
257
|
}
|
|
254
|
-
console.log(
|
|
258
|
+
console.log(node_util.styleText('bold', `\nKey Status for "${node_util.styleText('cyan', locale)}":`));
|
|
255
259
|
const totalKeysForLocale = localeData.totalKeys;
|
|
256
260
|
printProgressBar('Overall', localeData.totalTranslated, totalKeysForLocale);
|
|
257
261
|
const namespacesToDisplay = namespaceFilter ? [namespaceFilter] : Array.from(localeData.namespaces.keys()).sort();
|
|
@@ -259,19 +263,19 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
|
|
|
259
263
|
const nsData = localeData.namespaces.get(ns);
|
|
260
264
|
if (!nsData)
|
|
261
265
|
continue;
|
|
262
|
-
console.log(
|
|
266
|
+
console.log(node_util.styleText(['cyan', 'bold'], `\nNamespace: ${ns}`));
|
|
263
267
|
printProgressBar('Namespace Progress', nsData.translatedKeys, nsData.totalKeys);
|
|
264
268
|
nsData.keyDetails.forEach(({ key, isTranslated }) => {
|
|
265
|
-
const icon = isTranslated ?
|
|
269
|
+
const icon = isTranslated ? node_util.styleText('green', '✓') : node_util.styleText('red', '✗');
|
|
266
270
|
console.log(` ${icon} ${key}`);
|
|
267
271
|
});
|
|
268
272
|
}
|
|
269
273
|
const missingCount = totalKeysForLocale - localeData.totalTranslated;
|
|
270
274
|
if (missingCount > 0) {
|
|
271
|
-
console.log(
|
|
275
|
+
console.log(node_util.styleText(['yellow', 'bold'], `\nSummary: Found ${missingCount} missing translations for "${locale}".`));
|
|
272
276
|
}
|
|
273
277
|
else {
|
|
274
|
-
console.log(
|
|
278
|
+
console.log(node_util.styleText(['green', 'bold'], `\nSummary: 🎉 All keys are translated for "${locale}".`));
|
|
275
279
|
}
|
|
276
280
|
await printLocizeFunnel();
|
|
277
281
|
}
|
|
@@ -288,10 +292,10 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
|
|
|
288
292
|
async function displayNamespaceSummaryReport(report, config, namespace) {
|
|
289
293
|
const nsData = report.keysByNs.get(namespace);
|
|
290
294
|
if (!nsData) {
|
|
291
|
-
console.error(
|
|
295
|
+
console.error(node_util.styleText('red', `Error: Namespace "${namespace}" was not found in your source code.`));
|
|
292
296
|
return;
|
|
293
297
|
}
|
|
294
|
-
console.log(
|
|
298
|
+
console.log(node_util.styleText(['cyan', 'bold'], `\nStatus for Namespace: "${namespace}"`));
|
|
295
299
|
console.log('------------------------');
|
|
296
300
|
for (const [locale, localeData] of report.locales.entries()) {
|
|
297
301
|
const nsLocaleData = localeData.namespaces.get(namespace);
|
|
@@ -316,12 +320,13 @@ async function displayNamespaceSummaryReport(report, config, namespace) {
|
|
|
316
320
|
*/
|
|
317
321
|
async function displayOverallSummaryReport(report, config) {
|
|
318
322
|
const { primaryLanguage } = config.extract;
|
|
319
|
-
console.log(
|
|
323
|
+
console.log(node_util.styleText(['cyan', 'bold'], '\ni18next Project Status'));
|
|
320
324
|
console.log('------------------------');
|
|
321
|
-
console.log(`🔑 Keys Found: ${
|
|
322
|
-
console.log(`📚 Namespaces Found: ${
|
|
323
|
-
console.log(`🌍 Locales: ${
|
|
324
|
-
|
|
325
|
+
console.log(`🔑 Keys Found: ${node_util.styleText('bold', `${report.totalBaseKeys}`)}`);
|
|
326
|
+
console.log(`📚 Namespaces Found: ${node_util.styleText('bold', `${report.keysByNs.size}`)}`);
|
|
327
|
+
console.log(`🌍 Locales: ${node_util.styleText('bold', config.locales.join(', '))}`);
|
|
328
|
+
if (primaryLanguage)
|
|
329
|
+
console.log(`✅ Primary Language: ${node_util.styleText('bold', primaryLanguage)}`);
|
|
325
330
|
console.log('\nTranslation Progress:');
|
|
326
331
|
for (const [locale, localeData] of report.locales.entries()) {
|
|
327
332
|
const percentage = localeData.totalKeys > 0 ? Math.round((localeData.totalTranslated / localeData.totalKeys) * 100) : 100;
|
|
@@ -340,7 +345,7 @@ async function displayOverallSummaryReport(report, config) {
|
|
|
340
345
|
function printProgressBar(label, current, total) {
|
|
341
346
|
const percentage = total > 0 ? Math.round((current / total) * 100) : 100;
|
|
342
347
|
const bar = generateProgressBarText(percentage);
|
|
343
|
-
console.log(`${
|
|
348
|
+
console.log(`${node_util.styleText('bold', label)}: ${bar} ${percentage}% (${current}/${total})`);
|
|
344
349
|
}
|
|
345
350
|
/**
|
|
346
351
|
* Generates a visual progress bar string based on percentage completion.
|
|
@@ -355,14 +360,14 @@ function generateProgressBarText(percentage) {
|
|
|
355
360
|
const totalBars = 20;
|
|
356
361
|
const filledBars = Math.floor((percentage / 100) * totalBars);
|
|
357
362
|
const emptyBars = totalBars - filledBars;
|
|
358
|
-
return `[${
|
|
363
|
+
return `[${node_util.styleText('green', ''.padStart(filledBars, '■'))}${''.padStart(emptyBars, '□')}]`;
|
|
359
364
|
}
|
|
360
365
|
async function printLocizeFunnel() {
|
|
361
366
|
if (!(await funnelMsgTracker.shouldShowFunnel('status')))
|
|
362
367
|
return;
|
|
363
|
-
console.log(
|
|
368
|
+
console.log(node_util.styleText(['yellow', 'bold'], '\n✨ Take your localization to the next level!'));
|
|
364
369
|
console.log('Manage translations with your team in the cloud with Locize => https://www.locize.com/docs/getting-started');
|
|
365
|
-
console.log(`Run ${
|
|
370
|
+
console.log(`Run ${node_util.styleText('cyan', 'npx i18next-cli locize-migrate')} to get started.`);
|
|
366
371
|
return funnelMsgTracker.recordFunnelShown('status');
|
|
367
372
|
}
|
|
368
373
|
|
package/dist/cjs/syncer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var node_util = require('node:util');
|
|
4
4
|
var glob = require('glob');
|
|
5
5
|
var promises = require('node:fs/promises');
|
|
6
6
|
var node_path = require('node:path');
|
|
@@ -75,7 +75,7 @@ async function runSyncer(config, options = {}) {
|
|
|
75
75
|
continue;
|
|
76
76
|
const primaryTranslations = await fileUtils.loadTranslationFile(primaryPath);
|
|
77
77
|
if (!primaryTranslations) {
|
|
78
|
-
logMessages.push(` ${
|
|
78
|
+
logMessages.push(` ${node_util.styleText('yellow', '-')} Could not read primary file: ${primaryPath}`);
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
81
|
const primaryKeys = nestedObject.getNestedKeys(primaryTranslations, keySeparator ?? '.');
|
|
@@ -102,27 +102,27 @@ async function runSyncer(config, options = {}) {
|
|
|
102
102
|
const serializedContent = fileUtils.serializeTranslationFile(newSecondaryTranslations, perFileFormat, indentation, raw);
|
|
103
103
|
await promises.mkdir(node_path.dirname(fullSecondaryPath), { recursive: true });
|
|
104
104
|
await promises.writeFile(fullSecondaryPath, serializedContent);
|
|
105
|
-
logMessages.push(` ${
|
|
105
|
+
logMessages.push(` ${node_util.styleText('green', '✓')} Synchronized: ${secondaryPath}`);
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
logMessages.push(` ${
|
|
108
|
+
logMessages.push(` ${node_util.styleText('gray', '-')} Already in sync: ${secondaryPath}`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
spinner.succeed(
|
|
112
|
+
spinner.succeed(node_util.styleText('bold', 'Synchronization complete!'));
|
|
113
113
|
logMessages.forEach(msg => internalLogger.info ? internalLogger.info(msg) : console.log(msg));
|
|
114
114
|
if (wasAnythingSynced) {
|
|
115
115
|
await printLocizeFunnel();
|
|
116
116
|
}
|
|
117
117
|
else {
|
|
118
118
|
if (typeof internalLogger.info === 'function')
|
|
119
|
-
internalLogger.info(
|
|
119
|
+
internalLogger.info(node_util.styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
|
|
120
120
|
else
|
|
121
|
-
console.log(
|
|
121
|
+
console.log(node_util.styleText(['green', 'bold'], '\n✅ All locales are already in sync.'));
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
catch (error) {
|
|
125
|
-
spinner.fail(
|
|
125
|
+
spinner.fail(node_util.styleText('red', 'Synchronization failed.'));
|
|
126
126
|
if (typeof internalLogger.error === 'function')
|
|
127
127
|
internalLogger.error(error);
|
|
128
128
|
else
|
|
@@ -132,9 +132,9 @@ async function runSyncer(config, options = {}) {
|
|
|
132
132
|
async function printLocizeFunnel() {
|
|
133
133
|
if (!(await funnelMsgTracker.shouldShowFunnel('syncer')))
|
|
134
134
|
return;
|
|
135
|
-
console.log(
|
|
136
|
-
console.log(
|
|
137
|
-
console.log(` Get started with the official TMS for i18next: ${
|
|
135
|
+
console.log(node_util.styleText(['green', 'bold'], '\n✅ Sync complete.'));
|
|
136
|
+
console.log(node_util.styleText('yellow', '🚀 Ready to collaborate with translators? Move your files to the cloud.'));
|
|
137
|
+
console.log(` Get started with the official TMS for i18next: ${node_util.styleText('cyan', 'npx i18next-cli locize-migrate')}`);
|
|
138
138
|
return funnelMsgTracker.recordFunnelShown('syncer');
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -4,7 +4,7 @@ var logger = require('./utils/logger.js');
|
|
|
4
4
|
var i18nextResourcesForTs = require('i18next-resources-for-ts');
|
|
5
5
|
var glob = require('glob');
|
|
6
6
|
var wrapOra = require('./utils/wrap-ora.js');
|
|
7
|
-
var
|
|
7
|
+
var node_util = require('node:util');
|
|
8
8
|
var promises = require('node:fs/promises');
|
|
9
9
|
var node_path = require('node:path');
|
|
10
10
|
var core = require('@swc/core');
|
|
@@ -111,7 +111,7 @@ async function runTypesGenerator(config, options = {}) {
|
|
|
111
111
|
}
|
|
112
112
|
const nonObjectKeys = keys.filter(k => !parsedContent[k] || typeof parsedContent[k] !== 'object');
|
|
113
113
|
if (nonObjectKeys.length > 0) {
|
|
114
|
-
console.warn(
|
|
114
|
+
console.warn(node_util.styleText('yellow', `Warning: The file ${file} contains top-level keys that are not objects (${nonObjectKeys.join(', ')}). When 'mergeNamespaces' is enabled, top-level keys are treated as namespaces. These keys will be ignored.`));
|
|
115
115
|
}
|
|
116
116
|
continue;
|
|
117
117
|
}
|
|
@@ -127,7 +127,7 @@ ${i18nextResourcesForTs.mergeResourcesAsInterface(resources, { optimize: !!enabl
|
|
|
127
127
|
const resourcesOutputPath = node_path.resolve(process.cwd(), config.types.resourcesFile);
|
|
128
128
|
await promises.mkdir(node_path.dirname(resourcesOutputPath), { recursive: true });
|
|
129
129
|
await promises.writeFile(resourcesOutputPath, interfaceDefinition);
|
|
130
|
-
logMessages.push(` ${
|
|
130
|
+
logMessages.push(` ${node_util.styleText('green', '✓')} Resources interface written to ${config.types.resourcesFile}`);
|
|
131
131
|
let outputPathExists;
|
|
132
132
|
try {
|
|
133
133
|
await promises.access(outputPath);
|
|
@@ -154,13 +154,13 @@ declare module 'i18next' {
|
|
|
154
154
|
}`;
|
|
155
155
|
await promises.mkdir(node_path.dirname(outputPath), { recursive: true });
|
|
156
156
|
await promises.writeFile(outputPath, fileContent);
|
|
157
|
-
logMessages.push(` ${
|
|
157
|
+
logMessages.push(` ${node_util.styleText('green', '✓')} TypeScript definitions written to ${config.types.output || ''}`);
|
|
158
158
|
}
|
|
159
|
-
spinner.succeed(
|
|
159
|
+
spinner.succeed(node_util.styleText('bold', 'TypeScript definitions generated successfully.'));
|
|
160
160
|
logMessages.forEach(msg => typeof internalLogger.info === 'function' ? internalLogger.info(msg) : console.log(msg));
|
|
161
161
|
}
|
|
162
162
|
catch (error) {
|
|
163
|
-
spinner.fail(
|
|
163
|
+
spinner.fail(node_util.styleText('red', 'Failed to generate TypeScript definitions.'));
|
|
164
164
|
if (typeof internalLogger.error === 'function')
|
|
165
165
|
internalLogger.error(error);
|
|
166
166
|
else
|