i18ntk 2.0.4 → 2.2.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 +38 -60
- package/main/i18ntk-analyze.js +49 -44
- package/main/i18ntk-complete.js +75 -74
- package/main/i18ntk-fixer.js +3 -3
- package/main/i18ntk-init.js +5 -5
- package/main/i18ntk-scanner.js +2 -2
- package/main/i18ntk-sizing.js +35 -35
- package/main/i18ntk-summary.js +4 -4
- package/main/i18ntk-ui.js +54 -8
- package/main/i18ntk-usage.js +14 -14
- package/main/i18ntk-validate.js +6 -5
- package/main/manage/commands/AnalyzeCommand.js +40 -35
- package/main/manage/commands/FixerCommand.js +2 -2
- package/main/manage/commands/ScannerCommand.js +2 -2
- package/main/manage/commands/ValidateCommand.js +9 -9
- package/main/manage/index.js +147 -75
- package/main/manage/managers/LanguageMenu.js +7 -2
- package/main/manage/services/UsageService.js +7 -7
- package/package.json +269 -290
- package/settings/settings-cli.js +3 -3
- package/ui-locales/de.json +161 -166
- package/ui-locales/en.json +13 -18
- package/ui-locales/es.json +171 -184
- package/ui-locales/fr.json +155 -161
- package/ui-locales/ja.json +192 -243
- package/ui-locales/ru.json +145 -196
- package/ui-locales/zh.json +179 -185
- package/utils/cli-helper.js +26 -98
- package/utils/extractors/regex.js +39 -12
- package/utils/i18n-helper.js +88 -40
- package/{scripts → utils}/locale-optimizer.js +61 -60
- package/utils/security-check-improved.js +16 -13
- package/utils/security.js +6 -4
- 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/main/manage/services/ConfigurationService-fixed.js +0 -449
- 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 -215
- package/scripts/fix-and-purify-i18n.js +0 -213
- package/scripts/fix-locale-control-chars.js +0 -110
- package/scripts/lint-locales.js +0 -80
- package/scripts/prepublish.js +0 -348
- 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 -139
- package/scripts/verify-deprecations.js +0 -157
- package/scripts/verify-translations.js +0 -63
- package/utils/security-fixed.js +0 -607
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
* during package initialization. Integrates with i18ntk init process.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
|
-
* node
|
|
11
|
-
* node
|
|
12
|
-
* node
|
|
13
|
-
* node
|
|
10
|
+
* node utils/locale-optimizer.js --interactive
|
|
11
|
+
* node utils/locale-optimizer.js --list
|
|
12
|
+
* node utils/locale-optimizer.js --keep en,es,de
|
|
13
|
+
* node utils/locale-optimizer.js --restore
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const SecurityUtils = require('./security');
|
|
19
|
+
const cliHelper = require('./cli-helper');
|
|
20
|
+
const JsonOutput = require('./json-output');
|
|
21
|
+
const { getGlobalReadline } = require('./cli');
|
|
21
22
|
|
|
22
23
|
class LocaleOptimizer {
|
|
23
24
|
constructor() {
|
|
@@ -77,19 +78,19 @@ class LocaleOptimizer {
|
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
console.log('\n
|
|
81
|
-
console.log('
|
|
81
|
+
console.log('\n📊 UI Locale Package Analysis');
|
|
82
|
+
console.log('â•'.repeat(40));
|
|
82
83
|
|
|
83
84
|
const available = this.getAvailableLocales();
|
|
84
85
|
let totalSize = 0;
|
|
85
86
|
|
|
86
87
|
if (available.length === 0) {
|
|
87
|
-
console.log('
|
|
88
|
+
console.log(' ⌠No locale files found');
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
console.log(' Language Size Status');
|
|
92
|
-
console.log('
|
|
93
|
+
console.log(' ─────────────────────────');
|
|
93
94
|
|
|
94
95
|
available.forEach(locale => {
|
|
95
96
|
const filePath = path.join(this.uiLocalesDir, `${locale}.json`);
|
|
@@ -105,11 +106,11 @@ class LocaleOptimizer {
|
|
|
105
106
|
|
|
106
107
|
const missing = this.allLocales.filter(l => !available.includes(l));
|
|
107
108
|
if (missing.length > 0) {
|
|
108
|
-
console.log(`\n
|
|
109
|
+
console.log(`\n âš ï¸ Missing: ${missing.join(', ').toUpperCase()}`);
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
console.log(`\n
|
|
112
|
-
console.log(
|
|
112
|
+
console.log(`\n📦 Total package size: ${(totalSize / 1024).toFixed(1)}KB`);
|
|
113
|
+
console.log(`💡 Potential savings: ${(totalSize/1024 - this.getLocaleSize('en')).toFixed(1)}KB (English only)`);
|
|
113
114
|
console.log(` Run --interactive to optimize now`);
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -135,7 +136,7 @@ class LocaleOptimizer {
|
|
|
135
136
|
keepLocales(localesToKeep) {
|
|
136
137
|
const keepList = Array.isArray(localesToKeep) ? localesToKeep : localesToKeep.split(',').map(l => l.trim().toLowerCase());
|
|
137
138
|
|
|
138
|
-
console.log('
|
|
139
|
+
console.log('🎯 Optimizing package size...');
|
|
139
140
|
console.log(` Keeping: ${keepList.join(', ').toUpperCase()}`);
|
|
140
141
|
|
|
141
142
|
// Create backup directory
|
|
@@ -162,22 +163,22 @@ class LocaleOptimizer {
|
|
|
162
163
|
removedCount++;
|
|
163
164
|
removedLocales.push(locale);
|
|
164
165
|
|
|
165
|
-
console.log(`
|
|
166
|
+
console.log(` ✅ Removed ${locale.toUpperCase()} (backed up)`);
|
|
166
167
|
}
|
|
167
168
|
});
|
|
168
169
|
|
|
169
170
|
if (removedCount > 0) {
|
|
170
|
-
console.log(`\
|
|
171
|
-
console.log(` If issues occur, restore with: node
|
|
171
|
+
console.log(`\nâš ï¸ WARNING: Removing locales may break UI functionality`);
|
|
172
|
+
console.log(` If issues occur, restore with: node utils/locale-optimizer.js --restore`);
|
|
172
173
|
console.log(` Or reinstall the package: npm install -g i18ntk`);
|
|
173
174
|
|
|
174
175
|
// Create warning file
|
|
175
176
|
const warningPath = path.join(this.backupDir, 'REMOVED_LOCALES.txt');
|
|
176
|
-
SecurityUtils.safeWriteFileSync(warningPath, `Removed locales: ${removedLocales.join(',')}\nRestore with: node
|
|
177
|
+
SecurityUtils.safeWriteFileSync(warningPath, `Removed locales: ${removedLocales.join(',')}\nRestore with: node utils/locale-optimizer.js --restore`);
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
const savedKB = (savedSpace / 1024).toFixed(1);
|
|
180
|
-
console.log(`\n
|
|
181
|
+
console.log(`\n🎉 Optimization complete!`);
|
|
181
182
|
console.log(` Removed: ${removedCount} locales`);
|
|
182
183
|
console.log(` Saved: ${savedKB}KB`);
|
|
183
184
|
console.log(` Backup location: ${this.backupDir}`);
|
|
@@ -187,17 +188,17 @@ class LocaleOptimizer {
|
|
|
187
188
|
* Restore locales from backup
|
|
188
189
|
*/
|
|
189
190
|
restoreLocales() {
|
|
190
|
-
console.log('
|
|
191
|
+
console.log('🔄 Restoring locales from backup...');
|
|
191
192
|
|
|
192
193
|
if (!SecurityUtils.safeExistsSync(this.backupDir)) {
|
|
193
|
-
console.log('
|
|
194
|
+
console.log(' ⌠No backup directory found');
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
const backupFiles = fs.readdirSync(this.backupDir).filter(f => f.endsWith('.json'));
|
|
198
199
|
|
|
199
200
|
if (backupFiles.length === 0) {
|
|
200
|
-
console.log('
|
|
201
|
+
console.log(' ⌠No backup files found');
|
|
201
202
|
return;
|
|
202
203
|
}
|
|
203
204
|
|
|
@@ -209,10 +210,10 @@ class LocaleOptimizer {
|
|
|
209
210
|
|
|
210
211
|
fs.copyFileSync(backupPath, restorePath);
|
|
211
212
|
restoredCount++;
|
|
212
|
-
console.log(`
|
|
213
|
+
console.log(` ✅ Restored ${file.replace('.json', '').toUpperCase()}`);
|
|
213
214
|
});
|
|
214
215
|
|
|
215
|
-
console.log(`\n
|
|
216
|
+
console.log(`\n🎉 Restoration complete!`);
|
|
216
217
|
console.log(` Restored: ${restoredCount} locales`);
|
|
217
218
|
}
|
|
218
219
|
|
|
@@ -220,13 +221,13 @@ class LocaleOptimizer {
|
|
|
220
221
|
* Dry run mode - show optimization scenarios without making changes
|
|
221
222
|
*/
|
|
222
223
|
dryRun() {
|
|
223
|
-
console.log('
|
|
224
|
-
console.log('
|
|
224
|
+
console.log('🔠LOCALE OPTIMIZER - DRY RUN MODE');
|
|
225
|
+
console.log('â•'.repeat(50));
|
|
225
226
|
|
|
226
227
|
const available = this.getAvailableLocales();
|
|
227
228
|
const totalSize = this.getTotalSize();
|
|
228
229
|
|
|
229
|
-
console.log(
|
|
230
|
+
console.log(`📊 Current state:`);
|
|
230
231
|
console.log(` Total locales: ${available.length}`);
|
|
231
232
|
console.log(` Total package size: ${totalSize.toFixed(1)}KB`);
|
|
232
233
|
console.log(` Individual locale sizes:`);
|
|
@@ -237,7 +238,7 @@ class LocaleOptimizer {
|
|
|
237
238
|
console.log(` - ${locale.toUpperCase()}: ${size.toFixed(1)}KB (${status})`);
|
|
238
239
|
});
|
|
239
240
|
|
|
240
|
-
console.log('\n
|
|
241
|
+
console.log('\n🎯 Optimization scenarios:');
|
|
241
242
|
|
|
242
243
|
// Scenario 1: English only
|
|
243
244
|
const enSize = this.getLocaleSize('en');
|
|
@@ -259,42 +260,42 @@ class LocaleOptimizer {
|
|
|
259
260
|
console.log(` - Size: ${enEsFrSize.toFixed(1)}KB`);
|
|
260
261
|
console.log(` - Savings: ${(totalSize - enEsFrSize).toFixed(1)}KB (${(((totalSize - enEsFrSize) / totalSize) * 100).toFixed(1)}%)`);
|
|
261
262
|
|
|
262
|
-
console.log('\n
|
|
263
|
-
console.log(' node
|
|
264
|
-
console.log('\n
|
|
263
|
+
console.log('\n💡 To run actual optimization:');
|
|
264
|
+
console.log(' node utils/locale-optimizer.js --interactive');
|
|
265
|
+
console.log('\n✅ Dry run complete - no files were modified');
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
/**
|
|
268
269
|
* Interactive locale selection with enhanced UI
|
|
269
270
|
*/
|
|
270
271
|
async interactiveSelect() {
|
|
271
|
-
console.log('\n
|
|
272
|
-
console.log('
|
|
272
|
+
console.log('\n🌠i18ntk Locale Optimizer');
|
|
273
|
+
console.log('â•'.repeat(50));
|
|
273
274
|
console.log('Select which UI locales to keep (English is always kept)');
|
|
274
275
|
console.log('This will reduce package size by removing unused language files\n');
|
|
275
276
|
|
|
276
277
|
const available = this.getAvailableLocales();
|
|
277
278
|
const selections = new Set(['en']); // Always keep English
|
|
278
279
|
|
|
279
|
-
console.log('
|
|
280
|
+
console.log('📊 Available locales and sizes:');
|
|
280
281
|
available.forEach(locale => {
|
|
281
282
|
const size = this.getLocaleSize(locale);
|
|
282
|
-
const status = locale === 'en' ? '
|
|
283
|
+
const status = locale === 'en' ? '✅ (Required)' : '○';
|
|
283
284
|
console.log(` ${status} ${locale.toUpperCase()}: ${size}KB`);
|
|
284
285
|
});
|
|
285
286
|
|
|
286
|
-
console.log('\n
|
|
287
|
+
console.log('\n💡 Type locale codes separated by commas (e.g., en,es,de)');
|
|
287
288
|
console.log(' Press Enter to keep all, or type "cancel" to abort\n');
|
|
288
289
|
|
|
289
290
|
const answer = await cliHelper.prompt('Select locales to keep: ');
|
|
290
291
|
|
|
291
292
|
if (answer.toLowerCase() === 'cancel') {
|
|
292
|
-
console.log('
|
|
293
|
+
console.log('⌠Operation cancelled');
|
|
293
294
|
return false;
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
if (answer.trim() === '') {
|
|
297
|
-
console.log('
|
|
298
|
+
console.log('✅ Keeping all locales');
|
|
298
299
|
return true;
|
|
299
300
|
}
|
|
300
301
|
|
|
@@ -302,7 +303,7 @@ class LocaleOptimizer {
|
|
|
302
303
|
const valid = selected.filter(l => available.includes(l));
|
|
303
304
|
|
|
304
305
|
if (valid.length === 0) {
|
|
305
|
-
console.log('
|
|
306
|
+
console.log('âš ï¸ No valid locales selected, keeping all');
|
|
306
307
|
return true;
|
|
307
308
|
}
|
|
308
309
|
|
|
@@ -310,16 +311,16 @@ class LocaleOptimizer {
|
|
|
310
311
|
valid.push('en');
|
|
311
312
|
const unique = [...new Set(valid)];
|
|
312
313
|
|
|
313
|
-
console.log(`\n
|
|
314
|
+
console.log(`\n🎯 Selected: ${unique.join(', ').toUpperCase()}`);
|
|
314
315
|
this.showImpact(unique);
|
|
315
316
|
|
|
316
317
|
const confirm = await cliHelper.prompt('\nProceed? (y/N): ');
|
|
317
318
|
if (confirm.toLowerCase() === 'y' || confirm.toLowerCase() === 'yes') {
|
|
318
319
|
this.keepLocales(unique.join(','));
|
|
319
|
-
console.log('\n
|
|
320
|
-
console.log('
|
|
320
|
+
console.log('\n🎉 Package optimized successfully!');
|
|
321
|
+
console.log('💡 Use --restore to bring back removed locales');
|
|
321
322
|
} else {
|
|
322
|
-
console.log('
|
|
323
|
+
console.log('⌠Operation cancelled');
|
|
323
324
|
}
|
|
324
325
|
return true;
|
|
325
326
|
}
|
|
@@ -336,7 +337,7 @@ class LocaleOptimizer {
|
|
|
336
337
|
const saved = allSize - selectedSize;
|
|
337
338
|
const percentage = ((saved / allSize) * 100).toFixed(1);
|
|
338
339
|
|
|
339
|
-
console.log(`\n
|
|
340
|
+
console.log(`\n📈 Impact Analysis:`);
|
|
340
341
|
console.log(` Current: ${allSize.toFixed(1)}KB`);
|
|
341
342
|
console.log(` New: ${selectedSize.toFixed(1)}KB`);
|
|
342
343
|
console.log(` Saved: ${saved.toFixed(1)}KB (${percentage}%)`);
|
|
@@ -536,8 +537,8 @@ async function main() {
|
|
|
536
537
|
|
|
537
538
|
// Handle --init
|
|
538
539
|
if (args.includes('--init')) {
|
|
539
|
-
console.log('\n
|
|
540
|
-
console.log('
|
|
540
|
+
console.log('\n📦 Package Size Optimization');
|
|
541
|
+
console.log('â•'.repeat(30));
|
|
541
542
|
optimizer.listLocales();
|
|
542
543
|
|
|
543
544
|
const answer = await cliHelper.prompt('\nOptimize package size now? (Y/n): ');
|
|
@@ -552,20 +553,20 @@ async function main() {
|
|
|
552
553
|
}
|
|
553
554
|
|
|
554
555
|
if (!options.interactive && !options.list && !options.keep && !options.restore && !options.dryRun && !args.includes('--init')) {
|
|
555
|
-
console.log('
|
|
556
|
-
console.log('
|
|
556
|
+
console.log('🌠i18ntk Locale Optimizer v2.0');
|
|
557
|
+
console.log('â•'.repeat(35));
|
|
557
558
|
console.log('Interactive package size optimization for UI locales');
|
|
558
559
|
console.log('');
|
|
559
560
|
console.log('Usage:');
|
|
560
|
-
console.log(' node
|
|
561
|
-
console.log(' node
|
|
562
|
-
console.log(' node
|
|
563
|
-
console.log(' node
|
|
564
|
-
console.log(' node
|
|
565
|
-
console.log(' node
|
|
561
|
+
console.log(' node utils/locale-optimizer.js --interactive 🎯 Interactive selection');
|
|
562
|
+
console.log(' node utils/locale-optimizer.js --list 📊 List all locales');
|
|
563
|
+
console.log(' node utils/locale-optimizer.js --keep en,es,de âš¡ Quick keep');
|
|
564
|
+
console.log(' node utils/locale-optimizer.js --restore 🔄 Restore all locales');
|
|
565
|
+
console.log(' node utils/locale-optimizer.js --init 🚀 Called during init');
|
|
566
|
+
console.log(' node utils/locale-optimizer.js --dry-run 🔠Simulation mode');
|
|
566
567
|
console.log('');
|
|
567
|
-
console.log('
|
|
568
|
-
console.log(' node
|
|
568
|
+
console.log('💡 Example: Keep only English and Spanish');
|
|
569
|
+
console.log(' node utils/locale-optimizer.js --keep en,es');
|
|
569
570
|
return;
|
|
570
571
|
}
|
|
571
572
|
|
|
@@ -581,4 +582,4 @@ if (require.main === module) {
|
|
|
581
582
|
main();
|
|
582
583
|
}
|
|
583
584
|
|
|
584
|
-
module.exports = LocaleOptimizer;
|
|
585
|
+
module.exports = LocaleOptimizer;
|
|
@@ -125,14 +125,12 @@ class SecurityChecker {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
// Check for dangerous
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
/
|
|
132
|
-
/
|
|
133
|
-
|
|
134
|
-
/Function\s*\(/g
|
|
135
|
-
];
|
|
128
|
+
// Check for dangerous code execution patterns in SecurityUtils itself.
|
|
129
|
+
// Direct fs usage is expected here because this module provides vetted wrappers.
|
|
130
|
+
const dangerousPatterns = [
|
|
131
|
+
/eval\s*\(/g,
|
|
132
|
+
/Function\s*\(/g
|
|
133
|
+
];
|
|
136
134
|
|
|
137
135
|
for (const pattern of dangerousPatterns) {
|
|
138
136
|
const matches = content.match(pattern);
|
|
@@ -251,11 +249,16 @@ class SecurityChecker {
|
|
|
251
249
|
// No action needed for legitimate package requires
|
|
252
250
|
}
|
|
253
251
|
|
|
254
|
-
async checkFilePermissions() {
|
|
255
|
-
this.log('Checking file permissions...');
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
252
|
+
async checkFilePermissions() {
|
|
253
|
+
this.log('Checking file permissions...');
|
|
254
|
+
|
|
255
|
+
// POSIX permission checks are noisy/non-actionable on Windows.
|
|
256
|
+
if (process.platform === 'win32') {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const criticalFiles = [
|
|
261
|
+
'utils/security.js',
|
|
259
262
|
'tests/security.test.js',
|
|
260
263
|
'package.json'
|
|
261
264
|
];
|
package/utils/security.js
CHANGED
|
@@ -531,10 +531,12 @@ class SecurityUtils {
|
|
|
531
531
|
'theme', 'ui', 'setup', 'reports', 'display', 'interface',
|
|
532
532
|
// Security and settings
|
|
533
533
|
'security', 'settings', 'preferences', 'config', 'configuration',
|
|
534
|
-
// Additional common properties
|
|
535
|
-
'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
|
|
536
|
-
'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled'
|
|
537
|
-
|
|
534
|
+
// Additional common properties
|
|
535
|
+
'autoSave', 'autoBackup', 'validateOnSave', 'showWarnings', 'verbose',
|
|
536
|
+
'timeout', 'retries', 'batchSize', 'maxConcurrency', 'cacheEnabled',
|
|
537
|
+
// Date and reporting options used by existing settings
|
|
538
|
+
'dateFormat', 'timeFormat', 'timezone', 'reportLanguage', 'dateTime'
|
|
539
|
+
]);
|
|
538
540
|
|
|
539
541
|
// Remove unknown properties
|
|
540
542
|
Object.keys(sanitized).forEach(key => {
|
package/main/i18ntk-go.js
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* i18ntk-go.js - Go Language I18n Management Command
|
|
5
|
-
*
|
|
6
|
-
* Supports:
|
|
7
|
-
* - Standard Go i18n patterns
|
|
8
|
-
* - go-i18n library
|
|
9
|
-
* - Custom Go i18n implementations
|
|
10
|
-
* - Resource file (.json, .toml) management
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
const SecurityUtils = require(path.join(__dirname, '../utils/security'));
|
|
16
|
-
const { getConfig, saveConfig } = require(path.join(__dirname, '../utils/config-helper'));
|
|
17
|
-
const I18nHelper = require(path.join(__dirname, '../utils/i18n-helper'));
|
|
18
|
-
const SetupEnforcer = require(path.join(__dirname, '../utils/setup-enforcer'));
|
|
19
|
-
const { program } = require('../utils/mini-commander');
|
|
20
|
-
|
|
21
|
-
(async () => {
|
|
22
|
-
try {
|
|
23
|
-
await SetupEnforcer.checkSetupCompleteAsync();
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error('Setup check failed:', error.message);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
})();
|
|
29
|
-
|
|
30
|
-
class GoI18nManager {
|
|
31
|
-
constructor() {
|
|
32
|
-
this.supportedPatterns = [
|
|
33
|
-
'T("key")',
|
|
34
|
-
'Localize("key")',
|
|
35
|
-
'i18n.T("key")',
|
|
36
|
-
'tr("key")',
|
|
37
|
-
'message.Printer.Printf',
|
|
38
|
-
'i18n.MustLocalize'
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
this.fileExtensions = ['.go', '.mod', '.sum'];
|
|
42
|
-
this.resourceFormats = ['.json', '.toml', '.yaml', '.yml'];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async detectFramework(sourceDir) {
|
|
46
|
-
const goModPath = path.join(sourceDir, 'go.mod');
|
|
47
|
-
if (SecurityUtils.safeExistsSync(goModPath)) {
|
|
48
|
-
const content = SecurityUtils.safeReadFileSync(goModPath, sourceDir, 'utf8') || '';
|
|
49
|
-
|
|
50
|
-
if (content.includes('go-i18n')) return 'go-i18n-v2';
|
|
51
|
-
if (/x\/text\b/.test(content)) return 'golang-text';
|
|
52
|
-
|
|
53
|
-
return 'standard-go';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Check for Go files
|
|
57
|
-
const goFiles = this.findFiles(sourceDir, '.go');
|
|
58
|
-
if (goFiles.length > 0) {
|
|
59
|
-
return 'standard-go';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return 'generic';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async extractTranslations(sourceDir, options = {}) {
|
|
66
|
-
const framework = await this.detectFramework(sourceDir);
|
|
67
|
-
const translations = new Set();
|
|
68
|
-
|
|
69
|
-
const goFiles = this.findFiles(sourceDir, '.go');
|
|
70
|
-
|
|
71
|
-
for (const file of goFiles) {
|
|
72
|
-
const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8') || '';
|
|
73
|
-
|
|
74
|
-
// Extract Go i18n patterns
|
|
75
|
-
const patterns = [
|
|
76
|
-
/T\("([^"]+)"\)/g,
|
|
77
|
-
/Localize\("([^"]+)"\)/g,
|
|
78
|
-
/i18n\.T\("([^"]+)"\)/g,
|
|
79
|
-
/tr\("([^"]+)"\)/g,
|
|
80
|
-
/message\.Printer\.Printf\("([^"]+)"/g,
|
|
81
|
-
/i18n\.MustLocalize\(&i18n\.LocalizeConfig\{MessageID:\s*"([^"]+)"/g
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
for (const pattern of patterns) {
|
|
85
|
-
let match;
|
|
86
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
87
|
-
translations.add(match[1]);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
framework,
|
|
94
|
-
translations: Array.from(translations),
|
|
95
|
-
files: goFiles.length,
|
|
96
|
-
patterns: this.supportedPatterns
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async createLocaleStructure(outputDir, languages = ['en']) {
|
|
101
|
-
const localesDir = path.join(outputDir, 'locales');
|
|
102
|
-
|
|
103
|
-
for (const lang of languages) {
|
|
104
|
-
const langDir = path.join(localesDir, lang);
|
|
105
|
-
fs.mkdirSync(langDir, { recursive: true });
|
|
106
|
-
|
|
107
|
-
// Create Go i18n format files
|
|
108
|
-
SecurityUtils.safeWriteFileSync(path.join(langDir, 'active.en.toml'), `# Go i18n translations for ${lang}
|
|
109
|
-
[hello]
|
|
110
|
-
other = "Hello, World!"
|
|
111
|
-
|
|
112
|
-
[items]
|
|
113
|
-
one = "{{.Count}} item"
|
|
114
|
-
other = "{{.Count}} items"
|
|
115
|
-
`);
|
|
116
|
-
|
|
117
|
-
SecurityUtils.safeWriteFileSync(path.join(langDir, 'active.en.json'), JSON.stringify({
|
|
118
|
-
hello: "Hello, World!",
|
|
119
|
-
items: {
|
|
120
|
-
one: "{{.Count}} item",
|
|
121
|
-
other: "{{.Count}} items"
|
|
122
|
-
}
|
|
123
|
-
}, null, 2));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return localesDir;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
findFiles(dir, extension) {
|
|
130
|
-
const files = [];
|
|
131
|
-
|
|
132
|
-
function traverse(currentDir) {
|
|
133
|
-
const items = fs.readdirSync(currentDir);
|
|
134
|
-
|
|
135
|
-
for (const item of items) {
|
|
136
|
-
const fullPath = path.join(currentDir, item);
|
|
137
|
-
const stat = fs.statSync(fullPath);
|
|
138
|
-
|
|
139
|
-
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
|
|
140
|
-
traverse(fullPath);
|
|
141
|
-
} else if (stat.isFile() && path.extname(item) === extension) {
|
|
142
|
-
files.push(fullPath);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
traverse(dir);
|
|
148
|
-
return files;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async generateReport(results, outputDir) {
|
|
152
|
-
const report = {
|
|
153
|
-
timestamp: new Date().toISOString(),
|
|
154
|
-
framework: results.framework,
|
|
155
|
-
totalTranslations: results.translations.length,
|
|
156
|
-
translations: results.translations,
|
|
157
|
-
filesProcessed: results.files,
|
|
158
|
-
patterns: results.patterns,
|
|
159
|
-
recommendations: this.getRecommendations(results)
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const reportPath = path.join(outputDir, 'i18ntk-go-report.json');
|
|
163
|
-
SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
164
|
-
|
|
165
|
-
return reportPath;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
getRecommendations(results) {
|
|
169
|
-
const recommendations = [];
|
|
170
|
-
|
|
171
|
-
if (results.translations.length === 0) {
|
|
172
|
-
recommendations.push('No translations found. Check your Go i18n patterns');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (results.files === 0) {
|
|
176
|
-
recommendations.push('No Go files found in source directory');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (results.framework === 'generic') {
|
|
180
|
-
recommendations.push('Consider using go-i18n library for better i18n support');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return recommendations;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// CLI Implementation
|
|
188
|
-
program
|
|
189
|
-
.name('i18ntk-go')
|
|
190
|
-
.description('Go language i18n management tool')
|
|
191
|
-
.version('1.10.1');
|
|
192
|
-
|
|
193
|
-
program
|
|
194
|
-
.command('init')
|
|
195
|
-
.description('Initialize Go i18n structure')
|
|
196
|
-
.option('-s, --source-dir <dir>', 'Source directory', './')
|
|
197
|
-
.option('-o, --output-dir <dir>', 'Output directory', './i18ntk-reports')
|
|
198
|
-
.option('-l, --languages <langs>', 'Languages (comma-separated)', 'en')
|
|
199
|
-
.action(async (options) => {
|
|
200
|
-
try {
|
|
201
|
-
const manager = new GoI18nManager();
|
|
202
|
-
const languages = options.languages.split(',');
|
|
203
|
-
|
|
204
|
-
const localesDir = await manager.createLocaleStructure(options.outputDir, languages);
|
|
205
|
-
|
|
206
|
-
console.log(`✅ Go i18n structure initialized in: ${localesDir}`);
|
|
207
|
-
console.log(`📊 Languages: ${languages.join(', ')}`);
|
|
208
|
-
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error('❌ Error initializing Go i18n:', error.message);
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
program
|
|
216
|
-
.command('analyze')
|
|
217
|
-
.description('Analyze Go i18n usage')
|
|
218
|
-
.option('-s, --source-dir <dir>', 'Source directory', './')
|
|
219
|
-
.option('-o, --output-dir <dir>', 'Output directory', './i18ntk-reports')
|
|
220
|
-
.option('--dry-run', 'Preview without making changes')
|
|
221
|
-
.action(async (options) => {
|
|
222
|
-
try {
|
|
223
|
-
SecurityUtils.validatePath(options.sourceDir);
|
|
224
|
-
|
|
225
|
-
const manager = new GoI18nManager();
|
|
226
|
-
const results = await manager.extractTranslations(options.sourceDir);
|
|
227
|
-
|
|
228
|
-
console.log(`🔍 Framework detected: ${results.framework}`);
|
|
229
|
-
console.log(`📊 Files processed: ${results.files}`);
|
|
230
|
-
console.log(`📝 Translations found: ${results.translations.length}`);
|
|
231
|
-
|
|
232
|
-
if (!options.dryRun) {
|
|
233
|
-
const reportPath = await manager.generateReport(results, options.outputDir);
|
|
234
|
-
console.log(`📄 Report saved: ${reportPath}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error('❌ Error analyzing Go i18n:', error.message);
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
program
|
|
244
|
-
.command('extract')
|
|
245
|
-
.description('Extract Go translations')
|
|
246
|
-
.option('-s, --source-dir <dir>', 'Source directory', './')
|
|
247
|
-
.option('-o, --output-dir <dir>', 'Output directory', './locales')
|
|
248
|
-
.option('-l, --languages <langs>', 'Languages (comma-separated)', 'en')
|
|
249
|
-
.action(async (options) => {
|
|
250
|
-
try {
|
|
251
|
-
SecurityUtils.validatePath(options.sourceDir);
|
|
252
|
-
|
|
253
|
-
const manager = new GoI18nManager();
|
|
254
|
-
const results = await manager.extractTranslations(options.sourceDir);
|
|
255
|
-
|
|
256
|
-
await manager.createLocaleStructure(options.outputDir, options.languages.split(','));
|
|
257
|
-
|
|
258
|
-
console.log(`✅ Extracted ${results.translations.length} translations`);
|
|
259
|
-
console.log(`📁 Locale structure created in: ${options.outputDir}`);
|
|
260
|
-
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.error('❌ Error extracting Go translations:', error.message);
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Handle uncaught errors
|
|
268
|
-
process.on('uncaughtException', (error) => {
|
|
269
|
-
console.error('❌ Uncaught exception:', error.message);
|
|
270
|
-
process.exit(1);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
process.on('unhandledRejection', (reason) => {
|
|
274
|
-
console.error('❌ Unhandled rejection:', reason);
|
|
275
|
-
process.exit(1);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Export for programmatic use
|
|
279
|
-
module.exports = { GoI18nManager };
|
|
280
|
-
|
|
281
|
-
if (require.main === module) {
|
|
282
|
-
program.parse();
|
|
283
|
-
}
|