i18ntk 1.10.2 → 2.0.3

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * i18ntk-php.js - PHP Language I18n Management Command
5
- *
5
+ *
6
6
  * Supports:
7
7
  * - Laravel i18n
8
8
  * - Symfony translations
@@ -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 PhpI18nManager {
52
52
  'ngettext(',
53
53
  'gettext('
54
54
  ];
55
-
55
+
56
56
  this.fileExtensions = ['.php', '.blade.php', '.twig', '.phtml', '.module', '.theme'];
57
57
  this.resourceFormats = ['.php', '.json', '.yml', '.yaml', '.po', '.mo'];
58
58
  }
@@ -60,46 +60,46 @@ class PhpI18nManager {
60
60
  async detectFramework(sourceDir) {
61
61
  // Check for Laravel
62
62
  const composerJson = path.join(sourceDir, 'composer.json');
63
- if (fs.existsSync(composerJson)) {
64
- const content = fs.readFileSync(composerJson, 'utf8');
65
- const composer = JSON.parse(content);
66
-
63
+ if (SecurityUtils.safeExistsSync(composerJson, sourceDir)) {
64
+ const content = SecurityUtils.safeReadFileSync(composerJson, sourceDir, 'utf8');
65
+ const composer = SecurityUtils.safeParseJSON(content);
66
+
67
67
  if (composer.require && composer.require['laravel/framework']) {
68
68
  return 'laravel';
69
69
  }
70
-
70
+
71
71
  if (composer.require && composer.require['symfony/framework-bundle']) {
72
72
  return 'symfony';
73
73
  }
74
-
74
+
75
75
  if (composer.require && composer.require['yiisoft/yii2']) {
76
76
  return 'yii2';
77
77
  }
78
-
78
+
79
79
  if (composer.require && composer.require['wordpress/wordpress']) {
80
80
  return 'wordpress';
81
81
  }
82
82
  }
83
-
83
+
84
84
  // Check for WordPress
85
85
  const wpConfig = path.join(sourceDir, 'wp-config.php');
86
- if (fs.existsSync(wpConfig)) {
86
+ if (SecurityUtils.safeExistsSync(wpConfig, sourceDir)) {
87
87
  return 'wordpress';
88
88
  }
89
-
89
+
90
90
  // Check for standard PHP
91
91
  const phpFiles = this.findFiles(sourceDir, '.php');
92
92
  if (phpFiles.length > 0) {
93
93
  return 'standard-php';
94
94
  }
95
-
95
+
96
96
  return 'generic';
97
97
  }
98
98
 
99
99
  async extractTranslations(sourceDir, options = {}) {
100
100
  const framework = await this.detectFramework(sourceDir);
101
101
  const translations = new Set();
102
-
102
+
103
103
  // Process PHP files
104
104
  const phpFiles = [
105
105
  ...this.findFiles(sourceDir, '.php'),
@@ -107,10 +107,10 @@ class PhpI18nManager {
107
107
  ...this.findFiles(sourceDir, '.twig'),
108
108
  ...this.findFiles(sourceDir, '.phtml')
109
109
  ];
110
-
110
+
111
111
  for (const file of phpFiles) {
112
- const content = fs.readFileSync(file, 'utf8');
113
-
112
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
113
+
114
114
  // Extract PHP i18n patterns
115
115
  const patterns = [
116
116
  /__\(\s*["']([^"']+)["']/g,
@@ -127,14 +127,14 @@ class PhpI18nManager {
127
127
  /ngettext\(\s*["']([^"']+)["']/g,
128
128
  /dngettext\(\s*["']([^"']+)["']/g
129
129
  ];
130
-
130
+
131
131
  for (const pattern of patterns) {
132
132
  let match;
133
133
  while ((match = pattern.exec(content)) !== null) {
134
134
  translations.add(match[1]);
135
135
  }
136
136
  }
137
-
137
+
138
138
  // Blade template patterns
139
139
  const bladePatterns = [
140
140
  /@lang\(['"]([^'"]+)['"]\)/g,
@@ -142,7 +142,7 @@ class PhpI18nManager {
142
142
  /{{\s*__\(['"]([^'"]+)['"]\)\s*}}/g,
143
143
  /{{\s*trans\(['"]([^'"]+)['"]\)\s*}}/g
144
144
  ];
145
-
145
+
146
146
  for (const pattern of bladePatterns) {
147
147
  let match;
148
148
  while ((match = pattern.exec(content)) !== null) {
@@ -150,7 +150,7 @@ class PhpI18nManager {
150
150
  }
151
151
  }
152
152
  }
153
-
153
+
154
154
  // Process translation files
155
155
  const translationFiles = [
156
156
  ...this.findFiles(sourceDir, '.php'),
@@ -158,12 +158,12 @@ class PhpI18nManager {
158
158
  ...this.findFiles(sourceDir, '.yml'),
159
159
  ...this.findFiles(sourceDir, '.yaml')
160
160
  ];
161
-
161
+
162
162
  for (const file of translationFiles) {
163
163
  if (file.includes('lang') || file.includes('translations') || file.includes('i18n')) {
164
164
  try {
165
- const content = fs.readFileSync(file, 'utf8');
166
-
165
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
166
+
167
167
  if (file.endsWith('.php')) {
168
168
  // PHP array translations
169
169
  const arrayPattern = /'([^']+)'\s*=>/g;
@@ -173,7 +173,7 @@ class PhpI18nManager {
173
173
  }
174
174
  } else if (file.endsWith('.json')) {
175
175
  // JSON translations
176
- const data = JSON.parse(content);
176
+ const data = SecurityUtils.safeParseJSON(content);
177
177
  const keys = this.extractKeysFromObject(data);
178
178
  keys.forEach(key => translations.add(key));
179
179
  } else if (file.endsWith('.yml') || file.endsWith('.yaml')) {
@@ -194,7 +194,7 @@ class PhpI18nManager {
194
194
  }
195
195
  }
196
196
  }
197
-
197
+
198
198
  return {
199
199
  framework,
200
200
  translations: Array.from(translations),
@@ -205,29 +205,29 @@ class PhpI18nManager {
205
205
 
206
206
  extractKeysFromObject(obj, prefix = '') {
207
207
  const keys = [];
208
-
208
+
209
209
  for (const [key, value] of Object.entries(obj)) {
210
210
  const fullKey = prefix ? `${prefix}.${key}` : key;
211
211
  keys.push(fullKey);
212
-
212
+
213
213
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
214
214
  keys.push(...this.extractKeysFromObject(value, fullKey));
215
215
  }
216
216
  }
217
-
217
+
218
218
  return keys;
219
219
  }
220
220
 
221
221
  async createLocaleStructure(outputDir, languages = ['en'], framework = 'standard-php') {
222
222
  const localesDir = path.join(outputDir, 'locales');
223
-
223
+
224
224
  for (const lang of languages) {
225
225
  const langDir = path.join(localesDir, lang);
226
226
  fs.mkdirSync(langDir, { recursive: true });
227
-
227
+
228
228
  if (framework === 'laravel') {
229
229
  // Laravel structure
230
- fs.writeFileSync(path.join(langDir, 'messages.php'), `<?php
230
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.php'), `<?php
231
231
  // Laravel i18n for ${lang}
232
232
  return [
233
233
  'hello' => 'Hello, World!',
@@ -237,34 +237,34 @@ return [
237
237
  ],
238
238
  'welcome' => 'Welcome to our application'
239
239
  ];
240
- `);
241
-
242
- fs.writeFileSync(path.join(langDir, 'validation.php'), `<?php
240
+ `, path.dirname(langDir));
241
+
242
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'validation.php'), `<?php
243
243
  // Laravel validation messages for ${lang}
244
244
  return [
245
245
  'required' => 'The :attribute field is required.',
246
246
  'email' => 'The :attribute must be a valid email address.',
247
247
  'unique' => 'The :attribute has already been taken.'
248
248
  ];
249
- `);
249
+ `, path.dirname(localesDir));
250
250
  } else if (framework === 'symfony') {
251
251
  // Symfony structure
252
- fs.writeFileSync(path.join(langDir, 'messages.yml'), `# Symfony i18n for ${lang}
252
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.yml'), `# Symfony i18n for ${lang}
253
253
  hello: "Hello, World!"
254
254
  items:
255
255
  one: "%count% item"
256
256
  other: "%count% items"
257
257
  welcome: "Welcome to our application"
258
- `);
259
-
260
- fs.writeFileSync(path.join(langDir, 'validators.yml'), `# Symfony validation for ${lang}
258
+ `, path.dirname(localesDir));
259
+
260
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'validators.yml'), `# Symfony validation for ${lang}
261
261
  required: "The {{ field }} field is required."
262
262
  email: "The {{ field }} must be a valid email address."
263
263
  unique: "The {{ field }} has already been taken."
264
- `);
264
+ `, path.dirname(localesDir));
265
265
  } else {
266
266
  // Standard PHP
267
- fs.writeFileSync(path.join(langDir, 'messages.php'), `<?php
267
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.php'), `<?php
268
268
  // PHP i18n for ${lang}
269
269
  return [
270
270
  'hello' => 'Hello, World!',
@@ -274,36 +274,36 @@ return [
274
274
  ],
275
275
  'welcome' => 'Welcome to our application'
276
276
  ];
277
- `);
278
-
279
- fs.writeFileSync(path.join(langDir, 'messages.json'), JSON.stringify({
277
+ `, path.dirname(langDir));
278
+
279
+ SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.json'), JSON.stringify({
280
280
  hello: "Hello, World!",
281
281
  items: {
282
282
  one: "{0} item",
283
283
  other: "{0} items"
284
284
  },
285
285
  welcome: "Welcome to our application"
286
- }, null, 2));
286
+ }, null, 2), path.dirname(langDir));
287
287
  }
288
288
  }
289
-
289
+
290
290
  return localesDir;
291
291
  }
292
292
 
293
293
  findFiles(dir, extension) {
294
294
  const files = [];
295
-
295
+
296
296
  function traverse(currentDir) {
297
- if (!fs.existsSync(currentDir)) return;
298
-
297
+ if (!SecurityUtils.safeExistsSync(currentDir, path.dirname(currentDir))) return;
298
+
299
299
  const items = fs.readdirSync(currentDir);
300
-
300
+
301
301
  for (const item of items) {
302
302
  const fullPath = path.join(currentDir, item);
303
303
  try {
304
304
  const stat = fs.statSync(fullPath);
305
-
306
- if (stat.isDirectory() && !item.startsWith('.') &&
305
+
306
+ if (stat.isDirectory() && !item.startsWith('.') &&
307
307
  !['node_modules', 'vendor', 'cache', 'storage'].includes(item)) {
308
308
  traverse(fullPath);
309
309
  } else if (stat.isFile() && path.extname(item) === extension) {
@@ -314,7 +314,7 @@ return [
314
314
  }
315
315
  }
316
316
  }
317
-
317
+
318
318
  traverse(dir);
319
319
  return files;
320
320
  }
@@ -329,32 +329,32 @@ return [
329
329
  patterns: results.patterns,
330
330
  recommendations: this.getRecommendations(results)
331
331
  };
332
-
332
+
333
333
  const reportPath = path.join(outputDir, 'i18ntk-php-report.json');
334
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
335
-
334
+ SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2), outputDir);
335
+
336
336
  return reportPath;
337
337
  }
338
338
 
339
339
  getRecommendations(results) {
340
340
  const recommendations = [];
341
-
341
+
342
342
  if (results.translations.length === 0) {
343
343
  recommendations.push('No translations found. Check your PHP i18n patterns');
344
344
  }
345
-
345
+
346
346
  if (results.files === 0) {
347
347
  recommendations.push('No PHP files found in source directory');
348
348
  }
349
-
349
+
350
350
  if (results.framework === 'generic') {
351
351
  recommendations.push('Consider using Laravel or Symfony for better i18n support');
352
352
  }
353
-
353
+
354
354
  if (results.framework === 'wordpress') {
355
355
  recommendations.push('Use WordPress i18n functions (__(), _e(), _n()) for consistency');
356
356
  }
357
-
357
+
358
358
  return recommendations;
359
359
  }
360
360
  }
@@ -376,13 +376,13 @@ program
376
376
  try {
377
377
  const manager = new PhpI18nManager();
378
378
  const languages = options.languages.split(',');
379
-
379
+
380
380
  const localesDir = await manager.createLocaleStructure(options.outputDir, languages, options.framework);
381
-
381
+
382
382
  console.log(`āœ… PHP i18n structure initialized in: ${localesDir}`);
383
383
  console.log(`šŸ“Š Languages: ${languages.join(', ')}`);
384
384
  console.log(`šŸŽÆ Framework: ${options.framework}`);
385
-
385
+
386
386
  } catch (error) {
387
387
  console.error('āŒ Error initializing PHP i18n:', error.message);
388
388
  process.exit(1);
@@ -398,19 +398,19 @@ program
398
398
  .action(async (options) => {
399
399
  try {
400
400
  SecurityUtils.validatePath(options.sourceDir);
401
-
401
+
402
402
  const manager = new PhpI18nManager();
403
403
  const results = await manager.extractTranslations(options.sourceDir);
404
-
404
+
405
405
  console.log(`šŸ” Framework detected: ${results.framework}`);
406
406
  console.log(`šŸ“Š Files processed: ${results.files}`);
407
407
  console.log(`šŸ“ Translations found: ${results.translations.length}`);
408
-
408
+
409
409
  if (!options.dryRun) {
410
410
  const reportPath = await manager.generateReport(results, options.outputDir);
411
411
  console.log(`šŸ“„ Report saved: ${reportPath}`);
412
412
  }
413
-
413
+
414
414
  } catch (error) {
415
415
  console.error('āŒ Error analyzing PHP i18n:', error.message);
416
416
  process.exit(1);
@@ -427,16 +427,16 @@ program
427
427
  .action(async (options) => {
428
428
  try {
429
429
  SecurityUtils.validatePath(options.sourceDir);
430
-
430
+
431
431
  const manager = new PhpI18nManager();
432
432
  const results = await manager.extractTranslations(options.sourceDir);
433
-
433
+
434
434
  await manager.createLocaleStructure(options.outputDir, options.languages.split(','), options.framework);
435
-
435
+
436
436
  console.log(`āœ… Extracted ${results.translations.length} translations`);
437
437
  console.log(`šŸ“ Locale structure created in: ${options.outputDir}`);
438
438
  console.log(`šŸŽÆ Framework: ${options.framework}`);
439
-
439
+
440
440
  } catch (error) {
441
441
  console.error('āŒ Error extracting PHP translations:', error.message);
442
442
  process.exit(1);
@@ -459,4 +459,4 @@ module.exports = { PhpI18nManager };
459
459
 
460
460
  if (require.main === module) {
461
461
  program.parse();
462
- }
462
+ }
package/main/i18ntk-py.js CHANGED
@@ -3,19 +3,18 @@
3
3
  /**
4
4
  * i18ntk Python Command
5
5
  * Specialized command for Python i18n management
6
- *
6
+ *
7
7
  * Usage: i18ntk-py [options]
8
8
  */
9
9
 
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
12
 
13
- const SecurityUtils = require(path.join(__dirname, '../utils/security'));
14
- const { getConfig, saveConfig } = require(path.join(__dirname, '../utils/config-helper'));
15
- const I18nHelper = require(path.join(__dirname, '../utils/i18n-helper'));
16
- const I18ntkCore = require(path.join(__dirname, '../framework/i18ntk-core.js'));
17
- const SetupEnforcer = require(path.join(__dirname, '../utils/setup-enforcer'));
18
- const { program } = require('commander');
13
+ const SecurityUtils = require(path.join(__dirname, '../utils/security'));
14
+ const { getConfig, saveConfig } = require(path.join(__dirname, '../utils/config-helper'));
15
+ const I18nHelper = require(path.join(__dirname, '../utils/i18n-helper'));
16
+ const SetupEnforcer = require(path.join(__dirname, '../utils/setup-enforcer'));
17
+ const { program } = require('../utils/mini-commander');
19
18
 
20
19
  (async () => {
21
20
  try {
@@ -42,7 +41,7 @@ class I18ntkPythonCommand {
42
41
 
43
42
  async init() {
44
43
  console.log('šŸ”§ Initializing i18ntk Python command...');
45
-
44
+
46
45
  program
47
46
  .name('i18ntk-py')
48
47
  .description('i18ntk specialized for Python applications')
@@ -61,13 +60,13 @@ class I18ntkPythonCommand {
61
60
  this.options = program.opts();
62
61
  this.sourceDir = path.resolve(this.options.sourceDir);
63
62
  this.localesDir = path.resolve(this.options.localesDir);
64
-
63
+
65
64
  await this.validateSourceDir();
66
65
  await this.loadConfig();
67
66
  }
68
67
 
69
68
  async validateSourceDir() {
70
- if (!fs.existsSync(this.sourceDir)) {
69
+ if (!SecurityUtils.safeExistsSync(this.sourceDir, path.dirname(this.sourceDir))) {
71
70
  console.error(`āŒ Source directory not found: ${this.sourceDir}`);
72
71
  process.exit(1);
73
72
  }
@@ -93,9 +92,9 @@ class I18ntkPythonCommand {
93
92
  if (this.options.django) return 'django';
94
93
  if (this.options.flask) return 'flask';
95
94
  if (this.options.generic) return 'generic';
96
-
95
+
97
96
  console.log('šŸ” Detecting Python framework...');
98
-
97
+
99
98
  // Check for Django
100
99
  const djangoIndicators = [
101
100
  'manage.py',
@@ -116,8 +115,8 @@ class I18ntkPythonCommand {
116
115
  try {
117
116
  // Check requirements.txt
118
117
  const requirementsPath = path.join(this.sourceDir, 'requirements.txt');
119
- if (fs.existsSync(requirementsPath)) {
120
- const requirements = fs.readFileSync(requirementsPath, 'utf8');
118
+ if (SecurityUtils.safeExistsSync(requirementsPath, this.sourceDir)) {
119
+ const requirements = SecurityUtils.safeReadFileSync(requirementsPath, this.sourceDir, 'utf8');
121
120
  if (requirements.includes('Django')) framework = 'django';
122
121
  else if (requirements.includes('Flask')) framework = 'flask';
123
122
  }
@@ -150,15 +149,15 @@ class I18ntkPythonCommand {
150
149
 
151
150
  findFiles(pattern) {
152
151
  const results = [];
153
-
152
+
154
153
  function scanDir(dir) {
155
- if (!fs.existsSync(dir)) return;
156
-
154
+ if (!SecurityUtils.safeExistsSync(dir, path.dirname(dir))) return;
155
+
157
156
  const items = fs.readdirSync(dir);
158
157
  for (const item of items) {
159
158
  const fullPath = path.join(dir, item);
160
159
  const stat = fs.statSync(fullPath);
161
-
160
+
162
161
  if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
163
162
  scanDir(fullPath);
164
163
  } else if (item.includes(pattern) || fullPath.includes(pattern)) {
@@ -166,33 +165,33 @@ class I18ntkPythonCommand {
166
165
  }
167
166
  }
168
167
  }
169
-
168
+
170
169
  scanDir(this.sourceDir);
171
170
  return results;
172
171
  }
173
172
 
174
173
  async extractTranslations() {
175
174
  console.log('šŸ“¦ Extracting Python translations...');
176
-
175
+
177
176
  const pythonFiles = this.findFiles('.py');
178
177
  const translations = new Set();
179
-
178
+
180
179
  for (const file of pythonFiles) {
181
180
  try {
182
- const content = fs.readFileSync(file, 'utf8');
183
-
181
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
182
+
184
183
  for (const pattern of this.pythonPatterns) {
185
184
  let match;
186
185
  while ((match = pattern.exec(content)) !== null) {
187
186
  translations.add(match[1]);
188
187
  }
189
188
  }
190
-
189
+
191
190
  // Reset regex state for next file
192
191
  for (const pattern of this.pythonPatterns) {
193
192
  pattern.lastIndex = 0;
194
193
  }
195
-
194
+
196
195
  } catch (error) {
197
196
  if (this.options.debug) {
198
197
  console.error(`Error reading ${file}:`, error.message);
@@ -205,46 +204,46 @@ class I18ntkPythonCommand {
205
204
  }
206
205
 
207
206
  async createLocaleStructure() {
208
- if (!fs.existsSync(this.localesDir)) {
207
+ if (!SecurityUtils.safeExistsSync(this.localesDir, path.dirname(this.localesDir))) {
209
208
  if (this.options.dryRun) {
210
209
  console.log(`šŸ“ Would create directory: ${this.localesDir}`);
211
210
  return;
212
211
  }
213
-
212
+
214
213
  fs.mkdirSync(this.localesDir, { recursive: true });
215
214
  console.log(`šŸ“ Created locales directory: ${this.localesDir}`);
216
215
  }
217
216
 
218
217
  const languages = ['en', 'es', 'fr', 'de', 'ja', 'ru', 'zh'];
219
-
218
+
220
219
  for (const lang of languages) {
221
220
  const langDir = path.join(this.localesDir, lang);
222
- if (!fs.existsSync(langDir)) {
221
+ if (!SecurityUtils.safeExistsSync(langDir, this.localesDir)) {
223
222
  if (this.options.dryRun) {
224
223
  console.log(`šŸ“ Would create directory: ${langDir}`);
225
224
  continue;
226
225
  }
227
-
226
+
228
227
  fs.mkdirSync(langDir, { recursive: true });
229
-
228
+
230
229
  // Create basic translation files
231
- const commonFile = path.join(langDir, 'common.json');
232
- const initialContent = {
233
- "python": {
234
- "welcome": `Welcome to Python (${lang})`,
235
- "framework_detected": `Framework detected: ${framework}`,
236
- "files_processed": `Processed ${count} files`
237
- }
238
- };
239
-
240
- fs.writeFileSync(commonFile, JSON.stringify(initialContent, null, 2));
230
+ const commonFile = path.join(langDir, 'common.json');
231
+ const initialContent = {
232
+ "python": {
233
+ "welcome": `Welcome to Python (${lang})`,
234
+ "framework_detected": "Framework detected: {framework}",
235
+ "files_processed": "Processed {count} files"
236
+ }
237
+ };
238
+
239
+ SecurityUtils.safeWriteFileSync(commonFile, JSON.stringify(initialContent, null, 2), this.localesDir);
241
240
  }
242
241
  }
243
242
  }
244
243
 
245
244
  async analyzeFramework(framework) {
246
245
  console.log(`šŸ” Analyzing ${framework} project...`);
247
-
246
+
248
247
  const analysis = {
249
248
  framework,
250
249
  files: {
@@ -280,14 +279,14 @@ class I18ntkPythonCommand {
280
279
  // Analyze patterns
281
280
  for (const file of pythonFiles) {
282
281
  try {
283
- const content = fs.readFileSync(file, 'utf8');
284
-
282
+ const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
283
+
285
284
  if (content.includes('gettext(')) analysis.patterns.gettext++;
286
285
  if (content.includes('gettext_lazy(')) analysis.patterns.gettext_lazy++;
287
286
  if (content.includes('ngettext(')) analysis.patterns.ngettext++;
288
287
  if (content.includes('django')) analysis.patterns.django++;
289
288
  if (content.includes('flask')) analysis.patterns.flask++;
290
-
289
+
291
290
  } catch (error) {
292
291
  // Skip unreadable files
293
292
  }
@@ -297,7 +296,7 @@ class I18ntkPythonCommand {
297
296
  if (framework === 'django' && analysis.patterns.gettext === 0) {
298
297
  analysis.recommendations.push('Consider adding Django gettext for i18n support');
299
298
  }
300
-
299
+
301
300
  if (framework === 'flask' && analysis.patterns.gettext === 0) {
302
301
  analysis.recommendations.push('Consider adding Flask-Babel for i18n support');
303
302
  }
@@ -325,12 +324,12 @@ class I18ntkPythonCommand {
325
324
  };
326
325
 
327
326
  const reportPath = path.join(this.sourceDir, 'i18ntk-py-report.json');
328
-
327
+
329
328
  if (this.options.dryRun) {
330
329
  console.log(`šŸ“Š Would create report: ${reportPath}`);
331
330
  console.log('šŸ“‹ Report contents:', JSON.stringify(report, null, 2));
332
331
  } else {
333
- fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
332
+ SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2), this.sourceDir);
334
333
  console.log(`šŸ“Š Report saved: ${reportPath}`);
335
334
  }
336
335
 
@@ -339,19 +338,19 @@ class I18ntkPythonCommand {
339
338
 
340
339
  async run() {
341
340
  try {
342
- console.log('šŸš€ i18ntk Python Command v1.10.1');
343
- console.log('=' * 50);
344
-
341
+ console.log('šŸš€ i18ntk Python Command v1.10.1');
342
+ console.log('='.repeat(50));
343
+
345
344
  await this.init();
346
-
345
+
347
346
  const framework = await this.detectFramework();
348
347
  const translations = await this.extractTranslations();
349
-
348
+
350
349
  if (!this.options.extractOnly) {
351
350
  await this.createLocaleStructure();
352
351
  const analysis = await this.analyzeFramework(framework);
353
352
  const report = await this.generateReport(analysis, translations);
354
-
353
+
355
354
  console.log('\nāœ… Analysis complete!');
356
355
  console.log(`šŸ“Š Framework: ${framework}`);
357
356
  console.log(`šŸ“„ Python files: ${analysis.files.python}`);
@@ -360,7 +359,7 @@ class I18ntkPythonCommand {
360
359
  } else {
361
360
  console.log(`šŸ“¦ Extracted ${translations.length} translation keys`);
362
361
  }
363
-
362
+
364
363
  } catch (error) {
365
364
  console.error('āŒ Error:', error.message);
366
365
  if (this.options.debug) {
@@ -377,4 +376,4 @@ if (require.main === module) {
377
376
  cmd.run();
378
377
  }
379
378
 
380
- module.exports = I18ntkPythonCommand;
379
+ module.exports = I18ntkPythonCommand;