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
package/main/i18ntk-go.js CHANGED
@@ -16,7 +16,7 @@ const SecurityUtils = require(path.join(__dirname, '../utils/security'));
16
16
  const { getConfig, saveConfig } = require(path.join(__dirname, '../utils/config-helper'));
17
17
  const I18nHelper = require(path.join(__dirname, '../utils/i18n-helper'));
18
18
  const SetupEnforcer = require(path.join(__dirname, '../utils/setup-enforcer'));
19
- const { program } = require('commander');
19
+ const { program } = require('../utils/mini-commander');
20
20
 
21
21
  (async () => {
22
22
  try {
@@ -42,14 +42,13 @@ class GoI18nManager {
42
42
  this.resourceFormats = ['.json', '.toml', '.yaml', '.yml'];
43
43
  }
44
44
 
45
- async detectFramework(sourceDir) {
46
- const goModPath = path.join(sourceDir, 'go.mod');
47
- if (fs.existsSync(goModPath)) {
48
- const content = fs.readFileSync(goModPath, 'utf8');
49
-
50
- if (content.includes('go-i18n')) return 'go-i18n';
51
- if (content.includes('nicksnyder/go-i18n')) return 'go-i18n-v2';
52
- if (content.includes('golang.org/x/text')) return 'golang-text';
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';
53
52
 
54
53
  return 'standard-go';
55
54
  }
@@ -69,8 +68,8 @@ class GoI18nManager {
69
68
 
70
69
  const goFiles = this.findFiles(sourceDir, '.go');
71
70
 
72
- for (const file of goFiles) {
73
- const content = fs.readFileSync(file, 'utf8');
71
+ for (const file of goFiles) {
72
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8') || '';
74
73
 
75
74
  // Extract Go i18n patterns
76
75
  const patterns = [
@@ -106,7 +105,7 @@ class GoI18nManager {
106
105
  fs.mkdirSync(langDir, { recursive: true });
107
106
 
108
107
  // Create Go i18n format files
109
- fs.writeFileSync(path.join(langDir, 'active.en.toml'), `# Go i18n translations for ${lang}
108
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'active.en.toml'), `# Go i18n translations for ${lang}
110
109
  [hello]
111
110
  other = "Hello, World!"
112
111
 
@@ -115,7 +114,7 @@ one = "{{.Count}} item"
115
114
  other = "{{.Count}} items"
116
115
  `);
117
116
 
118
- fs.writeFileSync(path.join(langDir, 'active.en.json'), JSON.stringify({
117
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'active.en.json'), JSON.stringify({
119
118
  hello: "Hello, World!",
120
119
  items: {
121
120
  one: "{{.Count}} item",
@@ -161,7 +160,7 @@ other = "{{.Count}} items"
161
160
  };
162
161
 
163
162
  const reportPath = path.join(outputDir, 'i18ntk-go-report.json');
164
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
163
+ SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2));
165
164
 
166
165
  return reportPath;
167
166
  }
@@ -281,4 +280,4 @@ module.exports = { GoI18nManager };
281
280
 
282
281
  if (require.main === module) {
283
282
  program.parse();
284
- }
283
+ }
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * I18NTK INITIALIZATION SCRIPT
4
4
  *
@@ -22,7 +22,7 @@ const { detectFramework } = require('../utils/framework-detector');
22
22
  const { getFormatAdapter } = require('../utils/format-manager');
23
23
  // Ensure UIi18n is available for this initializer class
24
24
  const UIi18n = require('./i18ntk-ui');
25
- loadTranslations(process.env.I18NTK_LANG);
25
+ loadTranslations();
26
26
  const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../utils/config-helper');
27
27
  const { showFrameworkWarningOnce } = require('../utils/cli-helper');
28
28
  const { createPrompt, isInteractive } = require('../utils/prompt-helper');
@@ -89,13 +89,13 @@ class I18nInitializer {
89
89
  async checkI18nDependencies(noPrompt = false) {
90
90
  const packageJsonPath = path.resolve('./package.json');
91
91
 
92
- if (!fs.existsSync(packageJsonPath)) {
92
+ if (!SecurityUtils.safeExistsSync(packageJsonPath)) {
93
93
  console.log(t('errors.noPackageJson'));
94
94
  return true; // Allow to continue without framework
95
95
  }
96
96
 
97
97
  try {
98
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
98
+ const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
99
99
  // Include peerDependencies in the check
100
100
  const dependencies = {
101
101
  ...packageJson.dependencies,
@@ -221,7 +221,7 @@ class I18nInitializer {
221
221
 
222
222
  // Check for existing translation directories
223
223
  for (const location of possibleLocations) {
224
- if (fs.existsSync(location)) {
224
+ if (SecurityUtils.safeExistsSync(location)) {
225
225
  try {
226
226
  const items = fs.readdirSync(location);
227
227
  const englishFormats = ['en', 'en-US', 'en-GB', 'english'];
@@ -229,7 +229,7 @@ class I18nInitializer {
229
229
  // Check for English directories first
230
230
  for (const format of englishFormats) {
231
231
  const englishPath = path.join(location, format);
232
- if (fs.existsSync(englishPath) && fs.statSync(englishPath).isDirectory()) {
232
+ if (SecurityUtils.safeExistsSync(englishPath) && fs.statSync(englishPath).isDirectory()) {
233
233
  const englishFiles = fs.readdirSync(englishPath).filter(file => file.endsWith(this.format.extension));
234
234
  if (englishFiles.length > 0) {
235
235
  // Found English files, prioritize this
@@ -324,7 +324,7 @@ class I18nInitializer {
324
324
  if (newDirName && newDirName.trim()) {
325
325
  const newDirPath = path.resolve(newDirName.trim());
326
326
 
327
- if (!fs.existsSync(newDirPath)) {
327
+ if (!SecurityUtils.safeExistsSync(newDirPath)) {
328
328
  fs.mkdirSync(newDirPath, { recursive: true });
329
329
  console.log(t('init.createdNewDirectory', { dir: newDirPath }));
330
330
  } else {
@@ -332,7 +332,7 @@ class I18nInitializer {
332
332
  }
333
333
 
334
334
  const sourceLangDir = path.join(newDirPath, this.config.sourceLanguage);
335
- if (!fs.existsSync(sourceLangDir)) {
335
+ if (!SecurityUtils.safeExistsSync(sourceLangDir)) {
336
336
  fs.mkdirSync(sourceLangDir, { recursive: true });
337
337
  console.log(t('init.createdSourceLanguageDirectory', { dir: sourceLangDir }));
338
338
  await this.createSampleTranslationFile(sourceLangDir);
@@ -387,10 +387,10 @@ class I18nInitializer {
387
387
  }
388
388
 
389
389
  // Create directories if they do not exist
390
- if (!fs.existsSync(validatedSourceDir)) {
390
+ if (!SecurityUtils.safeExistsSync(validatedSourceDir)) {
391
391
  fs.mkdirSync(validatedSourceDir, { recursive: true });
392
392
  }
393
- if (this.config.structure !== 'single' && !fs.existsSync(validatedSourceLanguageDir)) {
393
+ if (this.config.structure !== 'single' && !SecurityUtils.safeExistsSync(validatedSourceLanguageDir)) {
394
394
  fs.mkdirSync(validatedSourceLanguageDir, { recursive: true });
395
395
  }
396
396
 
@@ -445,7 +445,7 @@ class I18nInitializer {
445
445
  const i18ntkCommonFilePath = path.join(validatedSourceLanguageDir, `i18ntk-common${this.format.extension}`);
446
446
 
447
447
  let sampleFilePath;
448
- if (!fs.existsSync(commonFilePath)) {
448
+ if (!SecurityUtils.safeExistsSync(commonFilePath)) {
449
449
  sampleFilePath = commonFilePath;
450
450
  } else {
451
451
  sampleFilePath = i18ntkCommonFilePath;
@@ -471,36 +471,80 @@ class I18nInitializer {
471
471
 
472
472
  // Check if source directory and language exist
473
473
  validateSource() {
474
- if (!fs.existsSync(this.sourceDir)) {
474
+ if (!SecurityUtils.safeExistsSync(this.sourceDir)) {
475
475
  throw new Error(t('validate.sourceLanguageDirectoryNotFound', { sourceDir: this.sourceDir }) || `Source directory not found: ${this.sourceDir}`);
476
476
  }
477
477
 
478
- if (!fs.existsSync(this.sourceLanguageDir)) {
478
+ if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir)) {
479
479
  throw new Error(t('validate.sourceLanguageDirectoryNotFound', { sourceDir: this.sourceLanguageDir }) || `Source language directory not found: ${this.sourceLanguageDir}`);
480
480
  }
481
481
 
482
482
  return true;
483
483
  }
484
484
 
485
+ createBootstrapSourceFile(targetDir) {
486
+ try {
487
+ if (!SecurityUtils.safeExistsSync(targetDir)) {
488
+ fs.mkdirSync(targetDir, { recursive: true });
489
+ }
490
+
491
+ const sampleName = `common${this.format.extension}`;
492
+ const samplePath = path.join(targetDir, sampleName);
493
+
494
+ if (SecurityUtils.safeExistsSync(samplePath)) {
495
+ return sampleName;
496
+ }
497
+
498
+ const sampleContent = {
499
+ app: {
500
+ title: 'Application',
501
+ description: 'Application description'
502
+ },
503
+ common: {
504
+ yes: 'Yes',
505
+ no: 'No',
506
+ save: 'Save',
507
+ cancel: 'Cancel'
508
+ }
509
+ };
510
+
511
+ const serializer = typeof this.format?.write === 'function'
512
+ ? this.format.write.bind(this.format)
513
+ : (typeof this.format?.serialize === 'function'
514
+ ? this.format.serialize.bind(this.format)
515
+ : (data) => JSON.stringify(data, null, 2));
516
+
517
+ const serialized = serializer(sampleContent);
518
+ SecurityUtils.safeWriteFileSync(samplePath, `${serialized}\n`, path.dirname(samplePath), 'utf8');
519
+ console.log(t('init.createdSampleTranslationFile', { file: samplePath }) || `Created sample translation file: ${samplePath}`);
520
+
521
+ return sampleName;
522
+ } catch {
523
+ return null;
524
+ }
525
+ }
526
+
485
527
  // Get all JSON files from source language directory (supports single/modular)
486
528
  getSourceFiles() {
487
529
  try {
488
530
  if (this.config.structure === 'single') {
489
- if (!fs.existsSync(this.sourceDir)) {
531
+ if (!SecurityUtils.safeExistsSync(this.sourceDir)) {
490
532
  throw new Error(t('validate.sourceLanguageDirectoryNotFound', { sourceDir: this.sourceDir }) || `Source directory not found: ${this.sourceDir}`);
491
533
  }
492
534
  const files = fs.readdirSync(this.sourceDir)
493
535
  .filter(file => file.endsWith(this.format.extension) && !this.config.excludeFiles.includes(file));
494
536
  if (files.length === 0) {
537
+ const sampleFile = this.createBootstrapSourceFile(this.sourceDir);
538
+ if (sampleFile) return [sampleFile];
495
539
  throw new Error(t('validate.noJsonFilesFound', { sourceDir: this.sourceDir }) || `No JSON files found in source directory: ${this.sourceDir}`);
496
540
  }
497
541
  return files;
498
542
  }
499
543
 
500
- if (!fs.existsSync(this.sourceLanguageDir)) {
544
+ if (!SecurityUtils.safeExistsSync(this.sourceLanguageDir)) {
501
545
  // Try to find English files in parent directory or subdirectories
502
546
  const parentDir = path.dirname(this.sourceLanguageDir);
503
- if (fs.existsSync(parentDir)) {
547
+ if (SecurityUtils.safeExistsSync(parentDir)) {
504
548
  const subdirs = fs.readdirSync(parentDir).filter(item => {
505
549
  const fullPath = path.join(parentDir, item);
506
550
  return fs.statSync(fullPath).isDirectory();
@@ -509,7 +553,7 @@ class I18nInitializer {
509
553
  // Look for English files in any subdirectory
510
554
  for (const subdir of subdirs) {
511
555
  const englishDir = path.join(parentDir, subdir);
512
- if (fs.existsSync(englishDir)) {
556
+ if (SecurityUtils.safeExistsSync(englishDir)) {
513
557
  const files = fs.readdirSync(englishDir);
514
558
  const formatFiles = files.filter(file =>
515
559
  file.endsWith(this.format.extension) &&
@@ -533,6 +577,8 @@ class I18nInitializer {
533
577
  });
534
578
 
535
579
  if (files.length === 0) {
580
+ const sampleFile = this.createBootstrapSourceFile(this.sourceLanguageDir);
581
+ if (sampleFile) return [sampleFile];
536
582
  throw new Error(t('validate.noJsonFilesFound', { sourceDir: this.sourceLanguageDir }) || `No JSON files found in source directory: ${this.sourceLanguageDir}`);
537
583
  }
538
584
 
@@ -586,7 +632,7 @@ class I18nInitializer {
586
632
  } else {
587
633
  // Modular: folder per language mirroring source file
588
634
  const targetDir = path.join(this.sourceDir, targetLanguage);
589
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
635
+ if (!SecurityUtils.safeExistsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
590
636
  targetFilePath = path.join(targetDir, sourceFile);
591
637
  }
592
638
 
@@ -604,14 +650,14 @@ class I18nInitializer {
604
650
  }
605
651
 
606
652
  // Create target directory if it doesn't exist
607
- if (!fs.existsSync(targetDir)) {
653
+ if (!SecurityUtils.safeExistsSync(targetDir)) {
608
654
  fs.mkdirSync(targetDir, { recursive: true });
609
655
  }
610
656
 
611
657
  let targetContent;
612
658
 
613
659
  // If target file exists, preserve existing translations
614
- if (fs.existsSync(validatedTargetPath)) {
660
+ if (SecurityUtils.safeExistsSync(validatedTargetPath)) {
615
661
  try {
616
662
  const existingContent = await SecurityUtils.safeReadFile(validatedTargetPath, process.cwd());
617
663
  if (existingContent) {
@@ -844,6 +890,9 @@ class I18nInitializer {
844
890
  async initialize(hasI18n = true, args = {}) {
845
891
  console.log(t('init.initializingProject'));
846
892
 
893
+ // Preserve noPrompt setting in config
894
+ this.config.noPrompt = args.noPrompt || this.config.noPrompt;
895
+
847
896
  if (!hasI18n) {
848
897
  console.log(t('init.warningProceedingWithoutFramework'));
849
898
  console.log(t('init.translationFilesCreatedWarning'));
@@ -1040,7 +1089,7 @@ class I18nInitializer {
1040
1089
  async generateDetailedReport(results, targetLanguages) {
1041
1090
  try {
1042
1091
  const outputDir = this.config.outputDir || path.join(process.cwd(), 'i18ntk-reports');
1043
- if (!fs.existsSync(outputDir)) {
1092
+ if (!SecurityUtils.safeExistsSync(outputDir)) {
1044
1093
  fs.mkdirSync(outputDir, { recursive: true });
1045
1094
  }
1046
1095
 
@@ -1107,11 +1156,12 @@ class I18nInitializer {
1107
1156
  const fromMenu = options.fromMenu || false;
1108
1157
 
1109
1158
  try {
1110
- // Parse command line arguments
1111
- const args = this.parseArgs();
1159
+ // Parse command line arguments and merge with options
1160
+ const cliArgs = this.parseArgs();
1161
+ const args = { ...cliArgs, ...options }; // options override cliArgs
1112
1162
 
1113
1163
  // On first run, prompt user for preferred UI language
1114
- if (!fs.existsSync(configManager.CONFIG_PATH)) {
1164
+ if (!SecurityUtils.safeExistsSync(configManager.CONFIG_PATH)) {
1115
1165
  const { getGlobalReadline } = require('../utils/cli');
1116
1166
  getGlobalReadline();
1117
1167
  const selectedLang = await this.ui.selectLanguage();
@@ -1128,7 +1178,7 @@ class I18nInitializer {
1128
1178
  // Load translations for UI messages
1129
1179
  const uiLanguage = this.config.uiLanguage || 'en';
1130
1180
  const { loadTranslations } = require('../utils/i18n-helper');
1131
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
1181
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
1132
1182
  }
1133
1183
 
1134
1184
  // Setup is now handled centrally by config manager
@@ -1211,7 +1261,7 @@ class I18nInitializer {
1211
1261
  // Load translations for UI messages
1212
1262
  const uiLanguage = this.config.uiLanguage || 'en';
1213
1263
  const { loadTranslations } = require('../utils/i18n-helper');
1214
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
1264
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
1215
1265
 
1216
1266
  // Skip i18n framework check in non-interactive mode
1217
1267
  console.log('Running initialization in non-interactive mode...');
@@ -1230,6 +1280,7 @@ class I18nInitializer {
1230
1280
 
1231
1281
  module.exports = I18nInitializer;
1232
1282
 
1283
+
1233
1284
  // Run if called directly
1234
1285
  if (require.main === module) {
1235
1286
  async function main() {
@@ -19,7 +19,7 @@ const SecurityUtils = require(path.join(__dirname, '../utils/security'));
19
19
  const { getConfig, saveConfig } = require(path.join(__dirname, '../utils/config-helper'));
20
20
  const I18nHelper = require(path.join(__dirname, '../utils/i18n-helper'));
21
21
  const SetupEnforcer = require(path.join(__dirname, '../utils/setup-enforcer'));
22
- const { program } = require('commander');
22
+ const { program } = require('../utils/mini-commander');
23
23
 
24
24
  (async () => {
25
25
  try {
@@ -52,7 +52,7 @@ class JavaI18nManager {
52
52
  async detectFramework(sourceDir) {
53
53
  // Check for Android
54
54
  const androidManifest = path.join(sourceDir, 'AndroidManifest.xml');
55
- if (fs.existsSync(androidManifest)) {
55
+ if (SecurityUtils.safeExistsSync(androidManifest)) {
56
56
  return 'android';
57
57
  }
58
58
 
@@ -60,15 +60,15 @@ class JavaI18nManager {
60
60
  const pomXml = path.join(sourceDir, 'pom.xml');
61
61
  const gradleFile = path.join(sourceDir, 'build.gradle');
62
62
 
63
- if (fs.existsSync(pomXml)) {
64
- const content = fs.readFileSync(pomXml, 'utf8');
63
+ if (SecurityUtils.safeExistsSync(pomXml)) {
64
+ const content = SecurityUtils.safeReadFileSync(pomXml, path.dirname(pomXml), 'utf8');
65
65
  if (content.includes('spring-boot')) {
66
66
  return 'spring-boot';
67
67
  }
68
68
  }
69
69
 
70
- if (fs.existsSync(gradleFile)) {
71
- const content = fs.readFileSync(gradleFile, 'utf8');
70
+ if (SecurityUtils.safeExistsSync(gradleFile)) {
71
+ const content = SecurityUtils.safeReadFileSync(gradleFile, path.dirname(gradleFile), 'utf8');
72
72
  if (content.includes('spring-boot')) {
73
73
  return 'spring-boot';
74
74
  }
@@ -97,7 +97,7 @@ class JavaI18nManager {
97
97
  const javaFiles = [...this.findFiles(sourceDir, '.java'), ...this.findFiles(sourceDir, '.kt')];
98
98
 
99
99
  for (const file of javaFiles) {
100
- const content = fs.readFileSync(file, 'utf8');
100
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
101
101
 
102
102
  // Extract Android string references
103
103
  const androidPatterns = [
@@ -131,7 +131,7 @@ class JavaI18nManager {
131
131
  const xmlFiles = this.findFiles(sourceDir, '.xml');
132
132
  for (const file of xmlFiles) {
133
133
  if (file.includes('strings.xml')) {
134
- const content = fs.readFileSync(file, 'utf8');
134
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8') || '';
135
135
  const stringPatterns = [
136
136
  /<string name="([^"]+)"/g,
137
137
  /<string-array name="([^"]+)"/g,
@@ -151,7 +151,7 @@ class JavaI18nManager {
151
151
  const propertiesFiles = this.findFiles(sourceDir, '.properties');
152
152
  for (const file of propertiesFiles) {
153
153
  if (file.includes('messages') || file.includes('i18n')) {
154
- const content = fs.readFileSync(file, 'utf8');
154
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8') || '';
155
155
  const lines = content.split('\n');
156
156
 
157
157
  for (const line of lines) {
@@ -181,28 +181,23 @@ class JavaI18nManager {
181
181
 
182
182
  if (framework === 'android') {
183
183
  // Android string resources
184
- fs.writeFileSync(path.join(langDir, 'strings.xml'), `<?xml version="1.0" encoding="utf-8"?>
185
- <resources>
186
- <string name="app_name">My App</string>
187
- <string name="hello">Hello, World!</string>
188
- <string name="items_count">%d items</string>
189
-
190
- <plurals name="items">
191
- <item quantity="one">%d item</item>
192
- <item quantity="other">%d items</item>
193
- </plurals>
194
- </resources>
195
- `);
196
- } else {
197
- // Java properties format
198
- fs.writeFileSync(path.join(langDir, 'messages.properties'), `# Java i18n properties for ${lang}
199
- app.name=My Application
200
- hello.message=Hello, World!
201
- items.count={0} items
202
- `);
184
+ SecurityUtils.safeWriteFileSync(
185
+ path.join(langDir, 'strings.xml'),
186
+ `<?xml version="1.0" encoding="utf-8"?>
187
+ <resources>
188
+ <string name="app_name">My App</string>
189
+ <string name="hello">Hello, World!</string>
190
+ <string name="items_count">%d items</string>
191
+ <plurals name="items">
192
+ <item quantity="one">%d item</item>
193
+ <item quantity="other">%d items</item>
194
+ </plurals>
195
+ </resources>`,
196
+ path.dirname(path.join(langDir, 'strings.xml'))
197
+ );
203
198
 
204
199
  // JSON format
205
- fs.writeFileSync(path.join(langDir, 'messages.json'), JSON.stringify({
200
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.json'), JSON.stringify({
206
201
  app: { name: "My Application" },
207
202
  hello: { message: "Hello, World!" },
208
203
  items: { count: "{0} items" }
@@ -217,7 +212,7 @@ items.count={0} items
217
212
  const files = [];
218
213
 
219
214
  function traverse(currentDir) {
220
- if (!fs.existsSync(currentDir)) return;
215
+ if (!SecurityUtils.safeExistsSync(currentDir)) return;
221
216
 
222
217
  const items = fs.readdirSync(currentDir);
223
218
 
@@ -254,7 +249,7 @@ items.count={0} items
254
249
  };
255
250
 
256
251
  const reportPath = path.join(outputDir, 'i18ntk-java-report.json');
257
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
252
+ SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2));
258
253
 
259
254
  return reportPath;
260
255
  }
@@ -382,4 +377,4 @@ module.exports = { JavaI18nManager };
382
377
 
383
378
  if (require.main === module) {
384
379
  program.parse();
385
- }
380
+ }