i18ntk 1.10.2 → 2.0.2

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.
Files changed (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +141 -1191
  3. package/main/i18ntk-analyze.js +65 -84
  4. package/main/i18ntk-backup-class.js +420 -0
  5. package/main/i18ntk-backup.js +3 -3
  6. package/main/i18ntk-complete.js +90 -65
  7. package/main/i18ntk-doctor.js +123 -103
  8. package/main/i18ntk-fixer.js +61 -725
  9. package/main/i18ntk-go.js +14 -15
  10. package/main/i18ntk-init.js +77 -26
  11. package/main/i18ntk-java.js +27 -32
  12. package/main/i18ntk-js.js +70 -68
  13. package/main/i18ntk-manage.js +129 -30
  14. package/main/i18ntk-php.js +75 -75
  15. package/main/i18ntk-py.js +55 -56
  16. package/main/i18ntk-scanner.js +59 -57
  17. package/main/i18ntk-setup.js +9 -404
  18. package/main/i18ntk-sizing.js +6 -6
  19. package/main/i18ntk-summary.js +21 -18
  20. package/main/i18ntk-ui.js +11 -10
  21. package/main/i18ntk-usage.js +54 -18
  22. package/main/i18ntk-validate.js +13 -13
  23. package/main/manage/commands/AnalyzeCommand.js +1124 -0
  24. package/main/manage/commands/BackupCommand.js +62 -0
  25. package/main/manage/commands/CommandRouter.js +295 -0
  26. package/main/manage/commands/CompleteCommand.js +61 -0
  27. package/main/manage/commands/DoctorCommand.js +60 -0
  28. package/main/manage/commands/FixerCommand.js +624 -0
  29. package/main/manage/commands/InitCommand.js +62 -0
  30. package/main/manage/commands/ScannerCommand.js +654 -0
  31. package/main/manage/commands/SizingCommand.js +60 -0
  32. package/main/manage/commands/SummaryCommand.js +61 -0
  33. package/main/manage/commands/UsageCommand.js +60 -0
  34. package/main/manage/commands/ValidateCommand.js +978 -0
  35. package/main/manage/index-fixed.js +1447 -0
  36. package/main/manage/index.js +1462 -0
  37. package/main/manage/managers/DebugMenu.js +140 -0
  38. package/main/manage/managers/InteractiveMenu.js +177 -0
  39. package/main/manage/managers/LanguageMenu.js +62 -0
  40. package/main/manage/managers/SettingsMenu.js +53 -0
  41. package/main/manage/services/AuthenticationService.js +263 -0
  42. package/main/manage/services/ConfigurationService-fixed.js +449 -0
  43. package/main/manage/services/ConfigurationService.js +449 -0
  44. package/main/manage/services/FileManagementService.js +368 -0
  45. package/main/manage/services/FrameworkDetectionService.js +458 -0
  46. package/main/manage/services/InitService.js +1051 -0
  47. package/main/manage/services/SetupService.js +462 -0
  48. package/main/manage/services/SummaryService.js +450 -0
  49. package/main/manage/services/UsageService.js +1502 -0
  50. package/package.json +32 -29
  51. package/runtime/enhanced.d.ts +221 -221
  52. package/runtime/index.d.ts +29 -29
  53. package/runtime/index.full.d.ts +331 -331
  54. package/runtime/index.js +7 -6
  55. package/scripts/build-lite.js +17 -17
  56. package/scripts/deprecate-versions.js +23 -6
  57. package/scripts/export-translations.js +5 -5
  58. package/scripts/fix-all-i18n.js +3 -3
  59. package/scripts/fix-and-purify-i18n.js +3 -2
  60. package/scripts/fix-locale-control-chars.js +110 -0
  61. package/scripts/lint-locales.js +80 -0
  62. package/scripts/locale-optimizer.js +8 -8
  63. package/scripts/prepublish.js +21 -21
  64. package/scripts/security-check.js +117 -117
  65. package/scripts/sync-translations.js +4 -4
  66. package/scripts/sync-ui-locales.js +9 -8
  67. package/scripts/validate-all-translations.js +8 -7
  68. package/scripts/verify-deprecations.js +157 -161
  69. package/scripts/verify-translations.js +6 -5
  70. package/settings/i18ntk-config.json +282 -282
  71. package/settings/language-config.json +5 -5
  72. package/settings/settings-cli.js +9 -9
  73. package/settings/settings-manager.js +18 -18
  74. package/ui-locales/de.json +2417 -2348
  75. package/ui-locales/en.json +2415 -2352
  76. package/ui-locales/es.json +2425 -2353
  77. package/ui-locales/fr.json +2418 -2348
  78. package/ui-locales/ja.json +2463 -2361
  79. package/ui-locales/ru.json +2463 -2359
  80. package/ui-locales/zh.json +2418 -2351
  81. package/utils/admin-auth.js +2 -2
  82. package/utils/admin-cli.js +297 -297
  83. package/utils/admin-pin.js +9 -9
  84. package/utils/cli-helper.js +9 -9
  85. package/utils/config-helper.js +73 -104
  86. package/utils/config-manager.js +204 -171
  87. package/utils/config.js +5 -4
  88. package/utils/env-manager.js +249 -263
  89. package/utils/framework-detector.js +27 -24
  90. package/utils/i18n-helper.js +85 -41
  91. package/utils/init-helper.js +152 -94
  92. package/utils/json-output.js +98 -98
  93. package/utils/mini-commander.js +179 -0
  94. package/utils/missing-key-validator.js +5 -5
  95. package/utils/plugin-loader.js +40 -29
  96. package/utils/prompt.js +14 -44
  97. package/utils/safe-json.js +40 -0
  98. package/utils/secure-errors.js +3 -3
  99. package/utils/security-check-improved.js +390 -0
  100. package/utils/security-config.js +5 -5
  101. package/utils/security-fixed.js +607 -0
  102. package/utils/security.js +652 -602
  103. package/utils/setup-enforcer.js +136 -44
  104. package/utils/setup-validator.js +33 -32
  105. package/utils/ultra-performance-optimizer.js +11 -9
  106. package/utils/watch-locales.js +2 -1
  107. package/utils/prompt-fixed.js +0 -55
  108. package/utils/security-check.js +0 -454
@@ -0,0 +1,624 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * I18NTK FIXER COMMAND
5
+ *
6
+ * Handles translation fixing operations.
7
+ * Contains embedded business logic for fixing translation issues.
8
+ */
9
+
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+ const SecurityUtils = require('../../../utils/security');
13
+ const cliHelper = require('../../../utils/cli-helper');
14
+ const { loadTranslations, t } = require('../../../utils/i18n-helper');
15
+ const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../../../utils/config-helper');
16
+ const JsonOutput = require('../../../utils/json-output');
17
+ const SetupEnforcer = require('../../../utils/setup-enforcer');
18
+
19
+ class FixerCommand {
20
+ constructor(config = {}, ui = null) {
21
+ this.config = config;
22
+ this.ui = ui;
23
+ this.prompt = null;
24
+ this.isNonInteractiveMode = false;
25
+ this.safeClose = null;
26
+
27
+ // Initialize fixer properties
28
+ this.sourceDir = null;
29
+ this.outputDir = null;
30
+ this.backupDir = null;
31
+ this.dryRun = false;
32
+ this.force = false;
33
+ }
34
+
35
+ /**
36
+ * Set runtime dependencies for interactive operations
37
+ */
38
+ setRuntimeDependencies(prompt, isNonInteractiveMode, safeClose) {
39
+ this.prompt = prompt;
40
+ this.isNonInteractiveMode = isNonInteractiveMode;
41
+ this.safeClose = safeClose;
42
+ }
43
+
44
+ /**
45
+ * Initialize the fixer with configuration
46
+ */
47
+ async initialize() {
48
+ try {
49
+ const args = this.parseArgs();
50
+ if (args.help) {
51
+ displayHelp('i18ntk-fixer', {
52
+ 'source-dir': 'Source directory to scan (default: ./locales)',
53
+ 'languages': 'Comma separated list of languages to fix',
54
+ 'markers': 'Comma separated markers to treat as untranslated',
55
+ 'no-backup': 'Skip automatic backup creation',
56
+ 'dry-run': 'Show what would be fixed without making changes',
57
+ 'force': 'Force fixes without confirmation',
58
+ 'output-dir': 'Output directory for fixed files'
59
+ });
60
+ process.exit(0);
61
+ }
62
+
63
+ const baseConfig = await getUnifiedConfig('fixer', args);
64
+ this.config = { ...baseConfig, ...(this.config || {}) };
65
+
66
+ const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
67
+ loadTranslations(uiLanguage, path.resolve(__dirname, '../../../resources', 'i18n', 'ui-locales'));
68
+
69
+ this.sourceDir = this.config.sourceDir;
70
+ this.outputDir = this.config.outputDir;
71
+ this.backupDir = path.join(this.sourceDir, 'backup');
72
+
73
+ // Validate source directory exists
74
+ const { validateSourceDir } = require('../../../utils/config-helper');
75
+ validateSourceDir(this.sourceDir, 'i18ntk-fixer');
76
+
77
+ } catch (error) {
78
+ console.error(`Fatal fixer error: ${error.message}`);
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ parseArgs() {
84
+ try {
85
+ const args = process.argv.slice(2);
86
+ const parsed = parseCommonArgs(args);
87
+
88
+ args.forEach(arg => {
89
+ if (arg.startsWith('--')) {
90
+ const [key, value] = arg.substring(2).split('=');
91
+ const sanitizedKey = SecurityUtils.sanitizeInput(key);
92
+ const sanitizedValue = value ? SecurityUtils.sanitizeInput(value) : true;
93
+
94
+ if (sanitizedKey === 'source-dir') {
95
+ parsed.sourceDir = sanitizedValue;
96
+ } else if (sanitizedKey === 'languages') {
97
+ parsed.languages = sanitizedValue.split(',').map(l => l.trim());
98
+ } else if (sanitizedKey === 'markers') {
99
+ parsed.markers = sanitizedValue.split(',').map(m => m.trim());
100
+ } else if (sanitizedKey === 'no-backup') {
101
+ parsed.noBackup = true;
102
+ } else if (sanitizedKey === 'dry-run') {
103
+ parsed.dryRun = true;
104
+ } else if (sanitizedKey === 'force') {
105
+ parsed.force = true;
106
+ } else if (sanitizedKey === 'output-dir') {
107
+ parsed.outputDir = sanitizedValue;
108
+ }
109
+ }
110
+ });
111
+
112
+ return parsed;
113
+ } catch (error) {
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ // Get all available languages
119
+ getAvailableLanguages() {
120
+ try {
121
+ const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
122
+ if (!items) {
123
+ console.error('Error reading source directory: Unable to access directory');
124
+ return [];
125
+ }
126
+
127
+ const languages = [];
128
+
129
+ // Check for directory-based structure
130
+ const directories = items
131
+ .filter(item => item.isDirectory())
132
+ .map(item => item.name)
133
+ .filter(name => name !== 'node_modules' && !name.startsWith('.') && name !== this.config.sourceLanguage);
134
+
135
+ // Check for monolith files (language.json files)
136
+ const files = items
137
+ .filter(item => item.isFile() && item.name.endsWith('.json'))
138
+ .map(item => item.name);
139
+
140
+ // Add directories as languages
141
+ languages.push(...directories);
142
+
143
+ // Add monolith files as languages (without .json extension)
144
+ const monolithLanguages = files
145
+ .map(file => file.replace('.json', ''))
146
+ .filter(lang => !languages.includes(lang) && lang !== this.config.sourceLanguage);
147
+ languages.push(...monolithLanguages);
148
+
149
+ return [...new Set(languages)].sort();
150
+ } catch (error) {
151
+ console.error('Error reading source directory:', error.message);
152
+ return [];
153
+ }
154
+ }
155
+
156
+ // Get all JSON files from a language directory
157
+ getLanguageFiles(language) {
158
+ if (!this.sourceDir) {
159
+ console.warn('Source directory not set');
160
+ return [];
161
+ }
162
+
163
+ const languageDir = path.resolve(this.sourceDir, language);
164
+ const languageFile = path.resolve(this.sourceDir, `${language}.json`);
165
+ const files = [];
166
+
167
+ // Handle monolith file structure
168
+ const languageFileStat = SecurityUtils.safeStatSync(languageFile, this.sourceDir);
169
+ if (languageFileStat && languageFileStat.isFile()) {
170
+ return [path.basename(languageFile)];
171
+ }
172
+
173
+ // Handle directory-based structure
174
+ const languageDirStat = SecurityUtils.safeStatSync(languageDir, this.sourceDir);
175
+ if (languageDirStat && languageDirStat.isDirectory()) {
176
+ try {
177
+ // Ensure the path is within the source directory for security
178
+ const validatedPath = SecurityUtils.validatePath(languageDir, this.sourceDir);
179
+ if (!validatedPath) {
180
+ console.warn(`Language directory not found or invalid: ${languageDir}`);
181
+ return [];
182
+ }
183
+
184
+ const findJsonFiles = (dir) => {
185
+ const results = [];
186
+ const items = SecurityUtils.safeReaddirSync(dir, this.sourceDir, { withFileTypes: true });
187
+
188
+ if (!items) return results;
189
+
190
+ for (const item of items) {
191
+ const fullPath = path.join(dir, item.name);
192
+
193
+ if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
194
+ // Recursively search subdirectories
195
+ results.push(...findJsonFiles(fullPath));
196
+ } else if (item.isFile() && item.name.endsWith('.json')) {
197
+ // Check exclusion patterns
198
+ const relativePath = path.relative(this.sourceDir, fullPath);
199
+ const shouldExclude = (this.config.excludeFiles || []).some(pattern => {
200
+ if (typeof pattern === 'string') {
201
+ return relativePath === pattern || relativePath.endsWith(path.sep + pattern);
202
+ }
203
+ if (pattern instanceof RegExp) {
204
+ return pattern.test(relativePath);
205
+ }
206
+ return false;
207
+ });
208
+
209
+ if (!shouldExclude && !item.name.startsWith('.')) {
210
+ results.push(path.relative(languageDir, fullPath));
211
+ }
212
+ }
213
+ }
214
+
215
+ return results;
216
+ };
217
+
218
+ return findJsonFiles(validatedPath);
219
+ } catch (error) {
220
+ console.error(`Error reading language directory ${languageDir}:`, error.message);
221
+ return [];
222
+ }
223
+ }
224
+
225
+ return files;
226
+ }
227
+
228
+ // Get all keys recursively from an object
229
+ getAllKeys(obj, prefix = '') {
230
+ const keys = new Set();
231
+
232
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
233
+ return keys;
234
+ }
235
+
236
+ for (const [key, value] of Object.entries(obj)) {
237
+ const fullKey = prefix ? `${prefix}.${key}` : key;
238
+ keys.add(fullKey);
239
+
240
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
241
+ const nestedKeys = this.getAllKeys(value, fullKey);
242
+ nestedKeys.forEach(k => keys.add(k));
243
+ }
244
+ }
245
+
246
+ return keys;
247
+ }
248
+
249
+ // Get value by key path
250
+ getValueByPath(obj, keyPath) {
251
+ const keys = keyPath.split('.');
252
+ let current = obj;
253
+
254
+ for (const key of keys) {
255
+ if (current && typeof current === 'object' && key in current) {
256
+ current = current[key];
257
+ } else {
258
+ return undefined;
259
+ }
260
+ }
261
+
262
+ return current;
263
+ }
264
+
265
+ // Set value by key path
266
+ setValueByPath(obj, keyPath, value) {
267
+ const keys = keyPath.split('.');
268
+ let current = obj;
269
+
270
+ for (let i = 0; i < keys.length - 1; i++) {
271
+ const key = keys[i];
272
+ if (!current[key] || typeof current[key] !== 'object') {
273
+ current[key] = {};
274
+ }
275
+ current = current[key];
276
+ }
277
+
278
+ current[keys[keys.length - 1]] = value;
279
+ }
280
+
281
+ // Create backup of translation files
282
+ async createBackup() {
283
+ if (this.dryRun) return;
284
+
285
+ try {
286
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
287
+ this.backupDir = path.join(this.sourceDir, `backup-${timestamp}`);
288
+
289
+ console.log(t('fixer.creatingBackup', { dir: this.backupDir }));
290
+
291
+ // Ensure backup directory exists
292
+ const dirCreated = SecurityUtils.safeMkdirSync(this.backupDir, process.cwd(), { recursive: true });
293
+ if (!dirCreated) {
294
+ console.warn('Failed to create backup directory');
295
+ return;
296
+ }
297
+
298
+ // Copy all translation files to backup
299
+ const languages = this.getAvailableLanguages();
300
+ languages.push(this.config.sourceLanguage); // Include source language
301
+
302
+ for (const language of languages) {
303
+ const languageFiles = this.getLanguageFiles(language);
304
+
305
+ for (const fileName of languageFiles) {
306
+ const sourcePath = path.join(this.sourceDir, language, fileName);
307
+ const backupPath = path.join(this.backupDir, language, fileName);
308
+
309
+ // Ensure backup subdirectory exists
310
+ const backupSubDir = path.dirname(backupPath);
311
+ SecurityUtils.safeMkdirSync(backupSubDir, process.cwd(), { recursive: true });
312
+
313
+ // Copy file
314
+ if (SecurityUtils.safeExistsSync(sourcePath, this.sourceDir)) {
315
+ const content = SecurityUtils.safeReadFileSync(sourcePath, this.sourceDir, 'utf8');
316
+ SecurityUtils.safeWriteFileSync(backupPath, content, process.cwd(), 'utf8');
317
+ }
318
+ }
319
+ }
320
+
321
+ console.log(t('fixer.backupCreated'));
322
+ } catch (error) {
323
+ console.warn(`Failed to create backup: ${error.message}`);
324
+ }
325
+ }
326
+
327
+ // Analyze translation issues for fixing
328
+ analyzeIssues(language, fileName) {
329
+ const issues = [];
330
+ const sourceFiles = this.getLanguageFiles(this.config.sourceLanguage);
331
+ const targetFiles = this.getLanguageFiles(language);
332
+
333
+ if (!sourceFiles.includes(fileName) || !targetFiles.includes(fileName)) {
334
+ return issues;
335
+ }
336
+
337
+ const sourceFilePath = path.join(this.sourceDir, this.config.sourceLanguage, fileName);
338
+ const targetFilePath = path.join(this.sourceDir, language, fileName);
339
+
340
+ try {
341
+ const sourceContent = SecurityUtils.safeReadFileSync(sourceFilePath, this.sourceDir, 'utf8');
342
+ const targetContent = SecurityUtils.safeReadFileSync(targetFilePath, this.sourceDir, 'utf8');
343
+
344
+ if (!sourceContent || !targetContent) {
345
+ return issues;
346
+ }
347
+
348
+ const sourceObj = SecurityUtils.safeParseJSON(sourceContent);
349
+ const targetObj = SecurityUtils.safeParseJSON(targetContent);
350
+
351
+ if (!sourceObj || !targetObj) {
352
+ return issues;
353
+ }
354
+
355
+ const sourceKeys = this.getAllKeys(sourceObj);
356
+
357
+ for (const key of sourceKeys) {
358
+ const sourceValue = this.getValueByPath(sourceObj, key);
359
+ const targetValue = this.getValueByPath(targetObj, key);
360
+
361
+ if (targetValue === undefined) {
362
+ // Missing key
363
+ issues.push({
364
+ type: 'missing_key',
365
+ key,
366
+ sourceValue,
367
+ fix: () => this.setValueByPath(targetObj, key, sourceValue)
368
+ });
369
+ } else if (targetValue === '') {
370
+ // Empty value
371
+ issues.push({
372
+ type: 'empty_value',
373
+ key,
374
+ sourceValue,
375
+ fix: () => this.setValueByPath(targetObj, key, sourceValue)
376
+ });
377
+ } else {
378
+ const markers = this.config.notTranslatedMarkers || [this.config.notTranslatedMarker];
379
+ if (markers.some(m => targetValue === m)) {
380
+ // Untranslated marker
381
+ issues.push({
382
+ type: 'untranslated_marker',
383
+ key,
384
+ sourceValue,
385
+ fix: () => this.setValueByPath(targetObj, key, sourceValue)
386
+ });
387
+ }
388
+ }
389
+ }
390
+
391
+ return issues;
392
+ } catch (error) {
393
+ console.warn(`Error analyzing ${language}/${fileName}: ${error.message}`);
394
+ return issues;
395
+ }
396
+ }
397
+
398
+ // Fix translation issues for a language
399
+ async fixLanguage(language) {
400
+ const fixes = {
401
+ language,
402
+ files: {},
403
+ totalIssues: 0,
404
+ fixedIssues: 0
405
+ };
406
+
407
+ const sourceFiles = this.getLanguageFiles(this.config.sourceLanguage);
408
+
409
+ for (const fileName of sourceFiles) {
410
+ const issues = this.analyzeIssues(language, fileName);
411
+
412
+ if (issues.length > 0) {
413
+ fixes.files[fileName] = {
414
+ issues: issues.length,
415
+ fixed: 0
416
+ };
417
+
418
+ fixes.totalIssues += issues.length;
419
+
420
+ if (!this.dryRun) {
421
+ // Apply fixes
422
+ const targetFilePath = path.join(this.sourceDir, language, fileName);
423
+
424
+ try {
425
+ const targetContent = SecurityUtils.safeReadFileSync(targetFilePath, this.sourceDir, 'utf8');
426
+ if (!targetContent) continue;
427
+
428
+ const targetObj = SecurityUtils.safeParseJSON(targetContent);
429
+ if (!targetObj) continue;
430
+
431
+ for (const issue of issues) {
432
+ if (typeof issue.fix === 'function') {
433
+ issue.fix();
434
+ fixes.files[fileName].fixed++;
435
+ fixes.fixedIssues++;
436
+ }
437
+ }
438
+
439
+ // Write back the fixed content
440
+ const fixedContent = JSON.stringify(targetObj, null, 2);
441
+ SecurityUtils.safeWriteFileSync(targetFilePath, fixedContent, process.cwd(), 'utf8');
442
+
443
+ } catch (error) {
444
+ console.warn(`Error fixing ${language}/${fileName}: ${error.message}`);
445
+ }
446
+ } else {
447
+ // In dry run mode, just count potential fixes
448
+ fixes.files[fileName].fixed = issues.length;
449
+ fixes.fixedIssues += issues.length;
450
+ }
451
+ }
452
+ }
453
+
454
+ return fixes;
455
+ }
456
+
457
+ // Main fixing process
458
+ async fix() {
459
+ try {
460
+ const args = this.parseArgs();
461
+ const jsonOutput = new JsonOutput('fixer');
462
+
463
+ // Set options from args
464
+ this.dryRun = args.dryRun || false;
465
+ this.force = args.force || false;
466
+
467
+ if (!args.json) {
468
+ console.log(t('fixer.starting'));
469
+ console.log(t('fixer.sourceDirectory', { dir: path.resolve(this.sourceDir) }));
470
+ console.log(t('fixer.dryRunMode', { mode: this.dryRun ? 'ON' : 'OFF' }));
471
+ }
472
+
473
+ // Create backup unless disabled
474
+ if (!args.noBackup && !this.dryRun) {
475
+ await this.createBackup();
476
+ }
477
+
478
+ const languages = this.getAvailableLanguages();
479
+
480
+ if (languages.length === 0) {
481
+ const error = t('fixer.noLanguages') || 'No target languages found.';
482
+ if (args.json) {
483
+ jsonOutput.setStatus('error', error);
484
+ console.log(JSON.stringify(jsonOutput.data, null, args.indent || 2));
485
+ return;
486
+ }
487
+ console.log(error);
488
+ return;
489
+ }
490
+
491
+ if (!args.json) {
492
+ console.log(t('fixer.foundLanguages', { count: languages.length, languages: languages.join(', ') }));
493
+ }
494
+
495
+ const results = {};
496
+ let totalIssues = 0;
497
+ let totalFixed = 0;
498
+
499
+ for (const language of languages) {
500
+ if (!args.json) {
501
+ console.log(t('fixer.fixing', { language }));
502
+ }
503
+
504
+ const fixes = await this.fixLanguage(language);
505
+ results[language] = fixes;
506
+
507
+ totalIssues += fixes.totalIssues;
508
+ totalFixed += fixes.fixedIssues;
509
+
510
+ if (!args.json) {
511
+ console.log(t('fixer.languageFixed', {
512
+ language,
513
+ issues: fixes.totalIssues,
514
+ fixed: fixes.fixedIssues
515
+ }));
516
+ }
517
+ }
518
+
519
+ // Prepare JSON output
520
+ if (args.json) {
521
+ jsonOutput.setStatus(totalFixed > 0 ? 'ok' : 'info', 'Fixer completed');
522
+ jsonOutput.addStats({
523
+ issues: totalIssues,
524
+ fixed: totalFixed,
525
+ languages: languages.length
526
+ });
527
+
528
+ console.log(JSON.stringify(jsonOutput.getOutput(), null, args.indent || 2));
529
+ return { success: true, totalIssues, totalFixed, results };
530
+ }
531
+
532
+ // Summary
533
+ console.log(t('fixer.summary'));
534
+ console.log('='.repeat(50));
535
+ console.log(t('fixer.totalIssues', { count: totalIssues }));
536
+ console.log(t('fixer.totalFixed', { count: totalFixed }));
537
+
538
+ if (this.backupDir && !args.noBackup) {
539
+ console.log(t('fixer.backupLocation', { dir: this.backupDir }));
540
+ }
541
+
542
+ console.log(t('fixer.completed'));
543
+
544
+ return { success: true, totalIssues, totalFixed, results };
545
+
546
+ } catch (error) {
547
+ console.error(t('fixer.error', { error: error.message }));
548
+ throw error;
549
+ }
550
+ }
551
+
552
+ // Main run method for compatibility
553
+ async run(options = {}) {
554
+ const fromMenu = options.fromMenu || false;
555
+
556
+ try {
557
+ const args = this.parseArgs();
558
+
559
+ if (args.help) {
560
+ this.showHelp();
561
+ return;
562
+ }
563
+
564
+ // Initialize configuration properly when called from menu
565
+ if (fromMenu && !this.sourceDir) {
566
+ const baseConfig = await getUnifiedConfig('fixer', args);
567
+ this.config = { ...baseConfig, ...this.config };
568
+
569
+ const uiLanguage = this.config.uiLanguage || 'en';
570
+ loadTranslations(uiLanguage, path.resolve(__dirname, '../../../resources', 'i18n', 'ui-locales'));
571
+
572
+ this.sourceDir = this.config.sourceDir;
573
+ this.outputDir = this.config.outputDir;
574
+ }
575
+
576
+ return await this.fix();
577
+
578
+ } catch (error) {
579
+ console.error(t('fixer.error', { error: error.message }));
580
+ if (!fromMenu) {
581
+ process.exit(1);
582
+ }
583
+ }
584
+ }
585
+
586
+ // Show help message
587
+ showHelp() {
588
+ console.log(t('fixer.help_message'));
589
+ }
590
+
591
+ /**
592
+ * Execute the fixer command
593
+ */
594
+ async execute(options = {}) {
595
+ try {
596
+ await this.initialize();
597
+ await this.run(options);
598
+ return { success: true, command: 'fix' };
599
+ } catch (error) {
600
+ console.error(`Fixer command failed: ${error.message}`);
601
+ throw error;
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Get command metadata
607
+ */
608
+ getMetadata() {
609
+ return {
610
+ name: 'fix',
611
+ description: 'Automatically fix translation issues and inconsistencies',
612
+ category: 'maintenance',
613
+ aliases: ['fixer'],
614
+ usage: 'fix [options]',
615
+ examples: [
616
+ 'fix',
617
+ 'fix --dry-run',
618
+ 'fix --backup'
619
+ ]
620
+ };
621
+ }
622
+ }
623
+
624
+ module.exports = FixerCommand;
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * I18NTK INIT COMMAND
5
+ *
6
+ * Handles project initialization and setup functionality.
7
+ */
8
+
9
+ const I18nInitializer = require('../../i18ntk-init');
10
+
11
+ class InitCommand {
12
+ constructor(config = {}, ui = null) {
13
+ this.config = config;
14
+ this.ui = ui;
15
+ this.prompt = null;
16
+ this.isNonInteractiveMode = false;
17
+ this.safeClose = null;
18
+ }
19
+
20
+ /**
21
+ * Set runtime dependencies for interactive operations
22
+ */
23
+ setRuntimeDependencies(prompt, isNonInteractiveMode, safeClose) {
24
+ this.prompt = prompt;
25
+ this.isNonInteractiveMode = isNonInteractiveMode;
26
+ this.safeClose = safeClose;
27
+ }
28
+
29
+ /**
30
+ * Execute the init command
31
+ */
32
+ async execute(options = {}) {
33
+ try {
34
+ const initializer = new I18nInitializer(this.config);
35
+ await initializer.run(options);
36
+ return { success: true, command: 'init' };
37
+ } catch (error) {
38
+ console.error(`Init command failed: ${error.message}`);
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get command metadata
45
+ */
46
+ getMetadata() {
47
+ return {
48
+ name: 'init',
49
+ description: 'Initialize i18n project structure',
50
+ category: 'setup',
51
+ aliases: [],
52
+ usage: 'init [options]',
53
+ examples: [
54
+ 'init',
55
+ 'init --source-dir=./src/locales',
56
+ 'init --i18n-dir=./locales'
57
+ ]
58
+ };
59
+ }
60
+ }
61
+
62
+ module.exports = InitCommand;