i18ntk 2.1.0 → 2.3.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 +87 -50
- package/main/i18ntk-analyze.js +63 -63
- package/main/i18ntk-backup-class.js +37 -41
- package/main/i18ntk-backup.js +28 -30
- package/main/i18ntk-complete.js +75 -74
- package/main/i18ntk-doctor.js +7 -6
- package/main/i18ntk-fixer.js +3 -3
- package/main/i18ntk-init.js +49 -13
- package/main/i18ntk-scanner.js +2 -2
- package/main/i18ntk-sizing.js +36 -37
- package/main/i18ntk-summary.js +4 -4
- package/main/i18ntk-ui.js +95 -96
- package/main/i18ntk-usage.js +31 -19
- package/main/i18ntk-validate.js +78 -27
- package/main/manage/commands/AnalyzeCommand.js +71 -73
- package/main/manage/commands/CommandRouter.js +15 -12
- package/main/manage/commands/FixerCommand.js +94 -38
- package/main/manage/commands/ScannerCommand.js +2 -2
- package/main/manage/commands/ValidateCommand.js +87 -36
- package/main/manage/index.js +165 -152
- package/main/manage/managers/DebugMenu.js +6 -6
- package/main/manage/managers/InteractiveMenu.js +6 -6
- package/main/manage/managers/LanguageMenu.js +12 -6
- package/main/manage/managers/SettingsMenu.js +6 -6
- package/main/manage/services/AuthenticationService.js +5 -6
- package/main/manage/services/ConfigurationService.js +22 -34
- package/main/manage/services/FileManagementService.js +6 -6
- package/main/manage/services/InitService.js +44 -8
- package/main/manage/services/UsageService.js +24 -12
- package/package.json +21 -42
- package/settings/settings-cli.js +5 -5
- package/settings/settings-manager.js +984 -968
- package/ui-locales/de.json +12 -11
- package/ui-locales/en.json +12 -11
- package/ui-locales/es.json +12 -11
- package/ui-locales/fr.json +12 -11
- package/ui-locales/ja.json +12 -11
- package/ui-locales/ru.json +12 -11
- package/ui-locales/zh.json +12 -11
- package/utils/config-helper.js +27 -16
- package/utils/config-manager.js +8 -7
- package/utils/i18n-helper.js +161 -166
- package/utils/init-helper.js +3 -2
- package/utils/json-output.js +11 -10
- package/{scripts → utils}/locale-optimizer.js +61 -60
- package/utils/logger.js +4 -4
- package/utils/safe-json.js +3 -3
- package/utils/secure-backup.js +8 -7
- package/utils/setup-enforcer.js +63 -98
- package/main/i18ntk-go.js +0 -283
- package/main/i18ntk-java.js +0 -380
- package/main/i18ntk-js.js +0 -512
- package/main/i18ntk-manage.js +0 -1694
- package/main/i18ntk-php.js +0 -462
- package/main/i18ntk-py.js +0 -379
- package/main/i18ntk-settings.js +0 -23
- package/main/manage/index-fixed.js +0 -1447
- package/scripts/build-lite.js +0 -279
- package/scripts/deprecate-versions.js +0 -317
- package/scripts/export-translations.js +0 -84
- package/scripts/fix-all-i18n.js +0 -236
- package/scripts/fix-and-purify-i18n.js +0 -233
- package/scripts/fix-locale-control-chars.js +0 -110
- package/scripts/lint-locales.js +0 -80
- package/scripts/prepublish-dev.js +0 -221
- package/scripts/prepublish.js +0 -362
- package/scripts/security-check.js +0 -117
- package/scripts/sync-translations.js +0 -151
- package/scripts/sync-ui-locales.js +0 -20
- package/scripts/validate-all-translations.js +0 -195
- package/scripts/verify-deprecations.js +0 -157
- package/scripts/verify-translations.js +0 -63
- package/utils/security-fixed.js +0 -609
package/main/i18ntk-backup.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
const fs = require('fs
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const fsp = fs.promises;
|
|
6
7
|
const path = require('path');
|
|
7
8
|
|
|
8
9
|
// Simple CLI argument parser
|
|
@@ -36,16 +37,15 @@ function parseArgs(args) {
|
|
|
36
37
|
|
|
37
38
|
return result;
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const {
|
|
42
|
-
const { colors } = require('../utils/logger');
|
|
40
|
+
const configManager = require('../utils/config-manager');
|
|
41
|
+
const { logger } = require('../utils/logger');
|
|
42
|
+
const { colors } = require('../utils/logger');
|
|
43
43
|
const prompt = require('../utils/prompt');
|
|
44
44
|
|
|
45
45
|
// Backup configuration
|
|
46
46
|
const config = configManager.getConfig();
|
|
47
|
-
const backupDir = path.join(process.cwd(), '
|
|
48
|
-
const maxBackups = config.backup?.maxBackups ||
|
|
47
|
+
const backupDir = path.join(process.cwd(), 'i18ntk-backups');
|
|
48
|
+
const maxBackups = Math.min(Math.max(parseInt(config.backup?.maxBackups, 10) || 1, 1), 3);
|
|
49
49
|
|
|
50
50
|
// Main function to handle commands
|
|
51
51
|
async function main() {
|
|
@@ -121,7 +121,7 @@ async function handleCreate(args) {
|
|
|
121
121
|
|
|
122
122
|
// Create backup directory if it doesn't exist
|
|
123
123
|
try {
|
|
124
|
-
await
|
|
124
|
+
await fsp.mkdir(outputDir, { recursive: true });
|
|
125
125
|
logger.debug(`Created backup directory: ${outputDir}`);
|
|
126
126
|
} catch (err) {
|
|
127
127
|
if (err.code !== 'EEXIST') {
|
|
@@ -134,7 +134,7 @@ async function handleCreate(args) {
|
|
|
134
134
|
// Validate directory
|
|
135
135
|
const sourceDir = path.resolve(dir);
|
|
136
136
|
try {
|
|
137
|
-
const stats = await
|
|
137
|
+
const stats = await fsp.stat(sourceDir);
|
|
138
138
|
if (!stats.isDirectory()) {
|
|
139
139
|
throw new Error(`Path exists but is not a directory: ${sourceDir}`);
|
|
140
140
|
}
|
|
@@ -149,7 +149,7 @@ async function handleCreate(args) {
|
|
|
149
149
|
logger.info('\nCreating backup...');
|
|
150
150
|
|
|
151
151
|
// Read all files in the directory
|
|
152
|
-
const files = (await
|
|
152
|
+
const files = (await fsp.readdir(sourceDir, { withFileTypes: true }))
|
|
153
153
|
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
|
|
154
154
|
.map(dirent => dirent.name);
|
|
155
155
|
|
|
@@ -163,7 +163,7 @@ async function handleCreate(args) {
|
|
|
163
163
|
for (const file of files) {
|
|
164
164
|
const filePath = path.join(sourceDir, file);
|
|
165
165
|
try {
|
|
166
|
-
const content = JSON.parse(await
|
|
166
|
+
const content = JSON.parse(await fsp.readFile(filePath, 'utf8'));
|
|
167
167
|
translations[file] = content;
|
|
168
168
|
} catch (error) {
|
|
169
169
|
logger.error(`Could not read file ${file}: ${error.message}`);
|
|
@@ -171,8 +171,8 @@ async function handleCreate(args) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
// Create the backup
|
|
174
|
-
await
|
|
175
|
-
const stats = await
|
|
174
|
+
await fsp.writeFile(backupPath, JSON.stringify(translations, null, 2));
|
|
175
|
+
const stats = await fsp.stat(backupPath);
|
|
176
176
|
|
|
177
177
|
logger.success('Backup created successfully');
|
|
178
178
|
logger.info(` Location: ${backupPath}`);
|
|
@@ -203,12 +203,12 @@ async function handleRestore(args) {
|
|
|
203
203
|
|
|
204
204
|
try {
|
|
205
205
|
// Read the backup file
|
|
206
|
-
const backupData = await
|
|
206
|
+
const backupData = await fsp.readFile(backupPath, 'utf8');
|
|
207
207
|
const translations = JSON.parse(backupData);
|
|
208
208
|
|
|
209
209
|
// Create output directory if it doesn't exist
|
|
210
210
|
try {
|
|
211
|
-
await
|
|
211
|
+
await fsp.mkdir(outputDir, { recursive: true });
|
|
212
212
|
} catch (err) {
|
|
213
213
|
if (err.code !== 'EEXIST') throw err;
|
|
214
214
|
}
|
|
@@ -216,7 +216,7 @@ async function handleRestore(args) {
|
|
|
216
216
|
// Write the restored files
|
|
217
217
|
for (const [file, content] of Object.entries(translations)) {
|
|
218
218
|
const filePath = path.join(outputDir, file);
|
|
219
|
-
await
|
|
219
|
+
await fsp.writeFile(filePath, JSON.stringify(content, null, 2));
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
logger.success('Backup restored successfully');
|
|
@@ -230,7 +230,7 @@ async function handleList() {
|
|
|
230
230
|
try {
|
|
231
231
|
// Ensure backup directory exists
|
|
232
232
|
try {
|
|
233
|
-
await
|
|
233
|
+
await fsp.access(backupDir);
|
|
234
234
|
} catch (err) {
|
|
235
235
|
if (err.code === 'ENOENT') {
|
|
236
236
|
logger.warn('No backups found. The backup directory does not exist yet.');
|
|
@@ -240,14 +240,14 @@ async function handleList() {
|
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
const files = await
|
|
243
|
+
const files = await fsp.readdir(backupDir);
|
|
244
244
|
const backups = [];
|
|
245
245
|
|
|
246
246
|
for (const file of files) {
|
|
247
247
|
if (file.startsWith('backup-') && file.endsWith('.json')) {
|
|
248
248
|
try {
|
|
249
249
|
const filePath = path.join(backupDir, file);
|
|
250
|
-
|
|
250
|
+
const stats = await fsp.stat(filePath);
|
|
251
251
|
backups.push({
|
|
252
252
|
name: file,
|
|
253
253
|
path: filePath,
|
|
@@ -307,14 +307,14 @@ async function handleVerify(args) {
|
|
|
307
307
|
logger.info('\nVerifying backup...');
|
|
308
308
|
|
|
309
309
|
try {
|
|
310
|
-
const data = await
|
|
310
|
+
const data = await fsp.readFile(backupPath, 'utf8');
|
|
311
311
|
const content = JSON.parse(data);
|
|
312
312
|
|
|
313
313
|
if (typeof content === 'object' && content !== null) {
|
|
314
314
|
const fileCount = Object.keys(content).length;
|
|
315
315
|
logger.success('Backup is valid');
|
|
316
316
|
logger.info(` Contains ${fileCount} translation files`);
|
|
317
|
-
logger.info(` Last modified: ${(await
|
|
317
|
+
logger.info(` Last modified: ${(await fsp.stat(backupPath)).mtime.toLocaleString()}`);
|
|
318
318
|
} else {
|
|
319
319
|
throw new Error('Invalid backup format');
|
|
320
320
|
}
|
|
@@ -331,13 +331,13 @@ async function handleCleanup(args) {
|
|
|
331
331
|
logger.info('\nCleaning up old backups...');
|
|
332
332
|
|
|
333
333
|
try {
|
|
334
|
-
const files = await
|
|
334
|
+
const files = await fsp.readdir(backupDir);
|
|
335
335
|
const backupFiles = files
|
|
336
336
|
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
337
337
|
.map(file => ({
|
|
338
338
|
name: file,
|
|
339
339
|
path: path.join(backupDir, file),
|
|
340
|
-
time: fs.statSync(path.join(backupDir, file)).mtime.getTime()
|
|
340
|
+
time: fs.statSync(path.join(backupDir, file)).mtime.getTime()
|
|
341
341
|
}))
|
|
342
342
|
.sort((a, b) => b.time - a.time);
|
|
343
343
|
|
|
@@ -352,7 +352,7 @@ async function handleCleanup(args) {
|
|
|
352
352
|
// Delete old backups
|
|
353
353
|
for (const file of toDelete) {
|
|
354
354
|
try {
|
|
355
|
-
await
|
|
355
|
+
await fsp.unlink(file.path);
|
|
356
356
|
logger.info(` - Deleted: ${file.name}`);
|
|
357
357
|
} catch (err) {
|
|
358
358
|
logger.error(` - Failed to delete ${file.name}: ${err.message}`);
|
|
@@ -365,12 +365,10 @@ async function handleCleanup(args) {
|
|
|
365
365
|
} catch (error) {
|
|
366
366
|
logger.error('Error cleaning up backups:');
|
|
367
367
|
logger.error(` ${error.message}`);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
}
|
|
368
|
+
logger.debug(error.stack || error.message);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
374
372
|
|
|
375
373
|
// Start the application
|
|
376
374
|
// Handle unhandled promise rejections
|
package/main/i18ntk-complete.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* I18NTK TRANSLATION COMPLETION SCRIPT
|
|
4
4
|
*
|
|
@@ -19,15 +19,15 @@ const { loadTranslations, t } = require('../utils/i18n-helper');
|
|
|
19
19
|
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
20
20
|
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
21
21
|
|
|
22
|
-
// Ensure setup is complete before running, except for help output.
|
|
23
|
-
(async () => {
|
|
24
|
-
const isHelpRequest = process.argv.slice(2).some(arg => arg === '--help' || arg === '-h');
|
|
25
|
-
if (isHelpRequest) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
try {
|
|
29
|
-
await SetupEnforcer.checkSetupCompleteAsync();
|
|
30
|
-
} catch (error) {
|
|
22
|
+
// Ensure setup is complete before running, except for help output.
|
|
23
|
+
(async () => {
|
|
24
|
+
const isHelpRequest = process.argv.slice(2).some(arg => arg === '--help' || arg === '-h');
|
|
25
|
+
if (isHelpRequest) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await SetupEnforcer.checkSetupCompleteAsync();
|
|
30
|
+
} catch (error) {
|
|
31
31
|
console.error('Setup check failed:', error.message);
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
@@ -37,7 +37,7 @@ loadTranslations();
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class I18nCompletionTool {
|
|
40
|
+
class I18nCompletionTool {
|
|
41
41
|
constructor(config = {}) {
|
|
42
42
|
this.config = config;
|
|
43
43
|
this.sourceDir = null;
|
|
@@ -109,57 +109,57 @@ class I18nCompletionTool {
|
|
|
109
109
|
parsed.autoTranslate = true;
|
|
110
110
|
} else if (key === 'dry-run') {
|
|
111
111
|
parsed.dryRun = true;
|
|
112
|
-
} else if (key === 'no-prompt') {
|
|
113
|
-
parsed.noPrompt = true;
|
|
114
|
-
} else if (key === 'help' || key === 'h') {
|
|
115
|
-
parsed.help = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
});
|
|
112
|
+
} else if (key === 'no-prompt') {
|
|
113
|
+
parsed.noPrompt = true;
|
|
114
|
+
} else if (key === 'help' || key === 'h') {
|
|
115
|
+
parsed.help = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
119
|
|
|
120
120
|
return parsed;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
// Get all available languages
|
|
124
|
-
getAvailableLanguages() {
|
|
123
|
+
// Get all available languages
|
|
124
|
+
getAvailableLanguages() {
|
|
125
125
|
if (!SecurityUtils.safeExistsSync(this.sourceDir, this.config.projectRoot)) {
|
|
126
126
|
throw new Error(`Source directory not found: ${this.sourceDir}`);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Check for monolith JSON files (en.json, es.json, etc.)
|
|
130
|
-
const files = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot);
|
|
131
|
-
const languagesFromFiles = files
|
|
132
|
-
.filter(file => file.endsWith('.json'))
|
|
133
|
-
.map(file => path.basename(file, '.json'))
|
|
134
|
-
.filter(code => this.isValidLanguageCode(code));
|
|
135
|
-
|
|
136
|
-
// Also check for directory-based structure for backward compatibility
|
|
137
|
-
const directories = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot)
|
|
138
|
-
.filter(item => {
|
|
139
|
-
if (this.isExcludedLanguageDirectory(item)) return false;
|
|
140
|
-
if (!this.isValidLanguageCode(item)) return false;
|
|
141
|
-
const itemPath = path.join(this.sourceDir, item);
|
|
142
|
-
const stat = SecurityUtils.safeStatSync(itemPath, this.config.projectRoot);
|
|
143
|
-
if (!stat || !stat.isDirectory()) return false;
|
|
144
|
-
|
|
145
|
-
const localeFiles = SecurityUtils.safeReaddirSync(itemPath, this.config.projectRoot)
|
|
146
|
-
.filter(name => name.endsWith('.json'));
|
|
147
|
-
return localeFiles.length > 0;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return [...new Set([...languagesFromFiles, ...directories])];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
isValidLanguageCode(code) {
|
|
154
|
-
if (!code || typeof code !== 'string') return false;
|
|
155
|
-
return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
isExcludedLanguageDirectory(name) {
|
|
159
|
-
if (!name || typeof name !== 'string') return true;
|
|
160
|
-
const lowered = name.toLowerCase();
|
|
161
|
-
return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
|
|
162
|
-
}
|
|
130
|
+
const files = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot);
|
|
131
|
+
const languagesFromFiles = files
|
|
132
|
+
.filter(file => file.endsWith('.json'))
|
|
133
|
+
.map(file => path.basename(file, '.json'))
|
|
134
|
+
.filter(code => this.isValidLanguageCode(code));
|
|
135
|
+
|
|
136
|
+
// Also check for directory-based structure for backward compatibility
|
|
137
|
+
const directories = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot)
|
|
138
|
+
.filter(item => {
|
|
139
|
+
if (this.isExcludedLanguageDirectory(item)) return false;
|
|
140
|
+
if (!this.isValidLanguageCode(item)) return false;
|
|
141
|
+
const itemPath = path.join(this.sourceDir, item);
|
|
142
|
+
const stat = SecurityUtils.safeStatSync(itemPath, this.config.projectRoot);
|
|
143
|
+
if (!stat || !stat.isDirectory()) return false;
|
|
144
|
+
|
|
145
|
+
const localeFiles = SecurityUtils.safeReaddirSync(itemPath, this.config.projectRoot)
|
|
146
|
+
.filter(name => name.endsWith('.json'));
|
|
147
|
+
return localeFiles.length > 0;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return [...new Set([...languagesFromFiles, ...directories])];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
isValidLanguageCode(code) {
|
|
154
|
+
if (!code || typeof code !== 'string') return false;
|
|
155
|
+
return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
isExcludedLanguageDirectory(name) {
|
|
159
|
+
if (!name || typeof name !== 'string') return true;
|
|
160
|
+
const lowered = name.toLowerCase();
|
|
161
|
+
return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
|
|
162
|
+
}
|
|
163
163
|
|
|
164
164
|
// Get all JSON files from a language directory
|
|
165
165
|
getLanguageFiles(language) {
|
|
@@ -481,7 +481,8 @@ class I18nCompletionTool {
|
|
|
481
481
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
482
482
|
|
|
483
483
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
484
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
484
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
485
|
+
this.sourceDir = this.config.sourceDir;
|
|
485
486
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
486
487
|
} else {
|
|
487
488
|
await this.initialize();
|
|
@@ -552,28 +553,28 @@ class I18nCompletionTool {
|
|
|
552
553
|
console.log(t("complete.languagesProcessed", { languagesProcessed: languages.length }));
|
|
553
554
|
console.log(t("complete.missingKeysAdded", { missingKeysAdded: missingKeys.length }));
|
|
554
555
|
|
|
555
|
-
if (!args.dryRun && allChanges.length > 0 && !args.noPrompt) {
|
|
556
|
-
const rl = this.rl || this.initReadline();
|
|
557
|
-
const answer = await this.prompt('\n' + t('complete.generateReportPrompt') + ' (Y/N): ');
|
|
558
|
-
|
|
559
|
-
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
560
|
-
await this.generateReport(allChanges, languages);
|
|
556
|
+
if (!args.dryRun && allChanges.length > 0 && !args.noPrompt) {
|
|
557
|
+
const rl = this.rl || this.initReadline();
|
|
558
|
+
const answer = await this.prompt('\n' + t('complete.generateReportPrompt') + ' (Y/N): ');
|
|
559
|
+
|
|
560
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
561
|
+
await this.generateReport(allChanges, languages);
|
|
561
562
|
}
|
|
562
563
|
}
|
|
563
564
|
|
|
564
|
-
if (!args.dryRun) {
|
|
565
|
-
console.log('\n' + t("complete.nextStepsTitle"));
|
|
566
|
-
console.log(t("complete.separator"));
|
|
567
|
-
console.log('1. Run usage analysis:');
|
|
568
|
-
console.log(' i18ntk --command=usage');
|
|
569
|
-
console.log('2. Validate translations:');
|
|
570
|
-
console.log(' i18ntk --command=validate');
|
|
571
|
-
console.log('3. Analyze translation status:');
|
|
572
|
-
console.log(' i18ntk --command=analyze');
|
|
573
|
-
console.log('\n' + t("complete.allKeysAvailable"));
|
|
574
|
-
} else {
|
|
575
|
-
console.log('\n' + t("complete.runWithoutDryRun"));
|
|
576
|
-
}
|
|
565
|
+
if (!args.dryRun) {
|
|
566
|
+
console.log('\n' + t("complete.nextStepsTitle"));
|
|
567
|
+
console.log(t("complete.separator"));
|
|
568
|
+
console.log('1. Run usage analysis:');
|
|
569
|
+
console.log(' i18ntk --command=usage');
|
|
570
|
+
console.log('2. Validate translations:');
|
|
571
|
+
console.log(' i18ntk --command=validate');
|
|
572
|
+
console.log('3. Analyze translation status:');
|
|
573
|
+
console.log(' i18ntk --command=analyze');
|
|
574
|
+
console.log('\n' + t("complete.allKeysAvailable"));
|
|
575
|
+
} else {
|
|
576
|
+
console.log('\n' + t("complete.runWithoutDryRun"));
|
|
577
|
+
}
|
|
577
578
|
|
|
578
579
|
// Only prompt when run from the menu (i.e., when a callback or menu context is present)
|
|
579
580
|
if (typeof this.prompt === "function" && args.fromMenu) {
|
|
@@ -605,4 +606,4 @@ if (require.main === module) {
|
|
|
605
606
|
});
|
|
606
607
|
}
|
|
607
608
|
|
|
608
|
-
module.exports = I18nCompletionTool;
|
|
609
|
+
module.exports = I18nCompletionTool;
|
package/main/i18ntk-doctor.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const SecurityUtils = require('../utils/security');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const
|
|
2
|
+
const SecurityUtils = require('../utils/security');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const packageJson = require('../package.json');
|
|
6
|
+
const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../utils/config-helper');
|
|
6
7
|
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
7
8
|
|
|
8
9
|
// Ensure setup is complete before running (only for standalone execution)
|
|
@@ -97,7 +98,7 @@ class I18nDoctor {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
const pkgVersion =
|
|
101
|
+
const pkgVersion = packageJson.version;
|
|
101
102
|
if (config.version && config.version !== pkgVersion) {
|
|
102
103
|
issues.push(`Config version mismatch: ${config.version} != ${pkgVersion}`);
|
|
103
104
|
exitCode = Math.max(exitCode, ExitCodes.CONFIG_ERROR);
|
|
@@ -181,4 +182,4 @@ if (require.main === module) {
|
|
|
181
182
|
doctor.run();
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
module.exports = I18nDoctor;
|
|
185
|
+
module.exports = I18nDoctor;
|
package/main/i18ntk-fixer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* I18NTK TRANSLATION FIXER SCRIPT
|
|
@@ -27,7 +27,7 @@ const SetupEnforcer = require('../utils/setup-enforcer');
|
|
|
27
27
|
}
|
|
28
28
|
})();
|
|
29
29
|
|
|
30
|
-
loadTranslations('en', path.resolve(__dirname, '..', '
|
|
30
|
+
loadTranslations('en', path.resolve(__dirname, '..', 'ui-locales'));
|
|
31
31
|
|
|
32
32
|
class I18nFixer {
|
|
33
33
|
constructor(config = {}) {
|
|
@@ -51,7 +51,7 @@ class I18nFixer {
|
|
|
51
51
|
this.config = { ...baseConfig, ...(this.config || {}) };
|
|
52
52
|
|
|
53
53
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
54
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
54
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
55
55
|
|
|
56
56
|
this.sourceDir = this.config.sourceDir;
|
|
57
57
|
this.outputDir = this.config.outputDir;
|
package/main/i18ntk-init.js
CHANGED
|
@@ -754,7 +754,7 @@ class I18nInitializer {
|
|
|
754
754
|
}
|
|
755
755
|
|
|
756
756
|
// Interactive admin PIN setup
|
|
757
|
-
async promptAdminPinSetup() {
|
|
757
|
+
async promptAdminPinSetup() {
|
|
758
758
|
const { ask, askHidden, flushStdout } = require('../utils/cli');
|
|
759
759
|
|
|
760
760
|
console.log('\n' + t('init.adminPinSetupOptional'));
|
|
@@ -806,8 +806,31 @@ class I18nInitializer {
|
|
|
806
806
|
}
|
|
807
807
|
} else {
|
|
808
808
|
console.log(t('init.skippingAdminPinSetup'));
|
|
809
|
-
}
|
|
810
|
-
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async promptBackupConfiguration(skipPrompt = false) {
|
|
813
|
+
const defaultBackupConfig = { enabled: false, maxBackups: 1, location: './i18ntk-backups' };
|
|
814
|
+
if (skipPrompt || !isInteractive()) {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const { ask } = require('../utils/cli');
|
|
819
|
+
console.log('\nBackup Settings');
|
|
820
|
+
console.log('Backups are disabled by default to avoid backup recursion and repo pollution.');
|
|
821
|
+
const enableAnswer = await ask('Enable automatic backups? (y/N): ');
|
|
822
|
+
const enabled = ['y', 'yes'].includes(String(enableAnswer || '').trim().toLowerCase());
|
|
823
|
+
|
|
824
|
+
if (!enabled) {
|
|
825
|
+
return defaultBackupConfig;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const keepAnswer = await ask('How many backups should be kept automatically (1-3, default 1): ');
|
|
829
|
+
const parsedKeep = parseInt(String(keepAnswer || '').trim(), 10);
|
|
830
|
+
const maxBackups = Number.isInteger(parsedKeep) ? Math.min(Math.max(parsedKeep, 1), 3) : 1;
|
|
831
|
+
|
|
832
|
+
return { enabled: true, maxBackups, location: './i18ntk-backups' };
|
|
833
|
+
}
|
|
811
834
|
|
|
812
835
|
// Interactive language selection
|
|
813
836
|
async selectLanguages(skipPrompt = false) {
|
|
@@ -921,11 +944,24 @@ class I18nInitializer {
|
|
|
921
944
|
// Prompt for admin PIN setup if not already configured
|
|
922
945
|
const securitySettings = configManager.getConfig().security || {};
|
|
923
946
|
|
|
924
|
-
if (!securitySettings.adminPinEnabled && securitySettings.adminPinPromptOnInit !== false && !args.noPrompt) {
|
|
925
|
-
const { flushStdout } = require('../utils/cli');
|
|
926
|
-
await flushStdout();
|
|
927
|
-
await this.promptAdminPinSetup();
|
|
928
|
-
}
|
|
947
|
+
if (!securitySettings.adminPinEnabled && securitySettings.adminPinPromptOnInit !== false && !args.noPrompt) {
|
|
948
|
+
const { flushStdout } = require('../utils/cli');
|
|
949
|
+
await flushStdout();
|
|
950
|
+
await this.promptAdminPinSetup();
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const backupSettings = await this.promptBackupConfiguration(args.noPrompt);
|
|
954
|
+
if (backupSettings) {
|
|
955
|
+
await configManager.updateConfig({
|
|
956
|
+
backup: {
|
|
957
|
+
...(this.config.backup || {}),
|
|
958
|
+
...backupSettings
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
this.config.backup = { ...(this.config.backup || {}), ...backupSettings };
|
|
962
|
+
} else if (!this.config.backup) {
|
|
963
|
+
this.config.backup = { enabled: false, maxBackups: 1, location: './i18ntk-backups' };
|
|
964
|
+
}
|
|
929
965
|
|
|
930
966
|
// Get target languages - use args.languages if provided
|
|
931
967
|
let targetLanguages = args.languages || await this.selectLanguages(args.noPrompt);
|
|
@@ -1122,7 +1158,7 @@ class I18nInitializer {
|
|
|
1122
1158
|
|
|
1123
1159
|
try {
|
|
1124
1160
|
// Import locale optimizer directly
|
|
1125
|
-
const LocaleOptimizer = require('../
|
|
1161
|
+
const LocaleOptimizer = require('../utils/locale-optimizer');
|
|
1126
1162
|
|
|
1127
1163
|
// First run dry run to show current state
|
|
1128
1164
|
console.log('\n🔍 Running locale optimization preview...');
|
|
@@ -1139,12 +1175,12 @@ class I18nInitializer {
|
|
|
1139
1175
|
console.log('\n✅ Package optimization completed!');
|
|
1140
1176
|
} else {
|
|
1141
1177
|
console.log('\n💡 You can run locale optimization later with:');
|
|
1142
|
-
console.log(' node
|
|
1178
|
+
console.log(' node utils/locale-optimizer.js --interactive');
|
|
1143
1179
|
}
|
|
1144
1180
|
} catch (error) {
|
|
1145
1181
|
console.log('\n⚠️ Could not offer locale optimization:', error.message);
|
|
1146
1182
|
console.log('\n💡 You can run locale optimization later with:');
|
|
1147
|
-
console.log(' node
|
|
1183
|
+
console.log(' node utils/locale-optimizer.js --interactive');
|
|
1148
1184
|
}
|
|
1149
1185
|
} catch (error) {
|
|
1150
1186
|
console.log('\n⚠️ Could not offer locale optimization:', error.message);
|
|
@@ -1178,7 +1214,7 @@ class I18nInitializer {
|
|
|
1178
1214
|
// Load translations for UI messages
|
|
1179
1215
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
1180
1216
|
const { loadTranslations } = require('../utils/i18n-helper');
|
|
1181
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
1217
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
1182
1218
|
}
|
|
1183
1219
|
|
|
1184
1220
|
// Setup is now handled centrally by config manager
|
|
@@ -1261,7 +1297,7 @@ class I18nInitializer {
|
|
|
1261
1297
|
// Load translations for UI messages
|
|
1262
1298
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
1263
1299
|
const { loadTranslations } = require('../utils/i18n-helper');
|
|
1264
|
-
loadTranslations(uiLanguage, path.resolve(__dirname, '..', '
|
|
1300
|
+
loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
|
|
1265
1301
|
|
|
1266
1302
|
// Skip i18n framework check in non-interactive mode
|
|
1267
1303
|
console.log('Running initialization in non-interactive mode...');
|
package/main/i18ntk-scanner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* I18NTK TEXT SCANNER
|
|
@@ -47,7 +47,7 @@ class I18nTextScanner {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
loadLocale() {
|
|
50
|
-
const uiLocalesDir = path.join(__dirname, '..', '
|
|
50
|
+
const uiLocalesDir = path.join(__dirname, '..', 'ui-locales');
|
|
51
51
|
const localeFile = path.join(uiLocalesDir, 'en.json');
|
|
52
52
|
|
|
53
53
|
try {
|