i18ntk 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +87 -50
  2. package/main/i18ntk-analyze.js +63 -63
  3. package/main/i18ntk-backup-class.js +37 -41
  4. package/main/i18ntk-backup.js +28 -30
  5. package/main/i18ntk-complete.js +75 -74
  6. package/main/i18ntk-doctor.js +7 -6
  7. package/main/i18ntk-fixer.js +3 -3
  8. package/main/i18ntk-init.js +49 -13
  9. package/main/i18ntk-scanner.js +2 -2
  10. package/main/i18ntk-sizing.js +36 -37
  11. package/main/i18ntk-summary.js +4 -4
  12. package/main/i18ntk-ui.js +95 -96
  13. package/main/i18ntk-usage.js +31 -19
  14. package/main/i18ntk-validate.js +78 -27
  15. package/main/manage/commands/AnalyzeCommand.js +71 -73
  16. package/main/manage/commands/CommandRouter.js +15 -12
  17. package/main/manage/commands/FixerCommand.js +94 -38
  18. package/main/manage/commands/ScannerCommand.js +2 -2
  19. package/main/manage/commands/ValidateCommand.js +87 -36
  20. package/main/manage/index.js +165 -152
  21. package/main/manage/managers/DebugMenu.js +6 -6
  22. package/main/manage/managers/InteractiveMenu.js +6 -6
  23. package/main/manage/managers/LanguageMenu.js +12 -6
  24. package/main/manage/managers/SettingsMenu.js +6 -6
  25. package/main/manage/services/AuthenticationService.js +5 -6
  26. package/main/manage/services/ConfigurationService.js +22 -34
  27. package/main/manage/services/FileManagementService.js +6 -6
  28. package/main/manage/services/InitService.js +44 -8
  29. package/main/manage/services/UsageService.js +24 -12
  30. package/package.json +21 -42
  31. package/settings/settings-cli.js +5 -5
  32. package/settings/settings-manager.js +984 -968
  33. package/ui-locales/de.json +12 -11
  34. package/ui-locales/en.json +12 -11
  35. package/ui-locales/es.json +12 -11
  36. package/ui-locales/fr.json +12 -11
  37. package/ui-locales/ja.json +12 -11
  38. package/ui-locales/ru.json +12 -11
  39. package/ui-locales/zh.json +12 -11
  40. package/utils/config-helper.js +27 -16
  41. package/utils/config-manager.js +8 -7
  42. package/utils/i18n-helper.js +161 -166
  43. package/utils/init-helper.js +3 -2
  44. package/utils/json-output.js +11 -10
  45. package/{scripts → utils}/locale-optimizer.js +61 -60
  46. package/utils/logger.js +4 -4
  47. package/utils/safe-json.js +3 -3
  48. package/utils/secure-backup.js +8 -7
  49. package/utils/setup-enforcer.js +63 -98
  50. package/main/i18ntk-go.js +0 -283
  51. package/main/i18ntk-java.js +0 -380
  52. package/main/i18ntk-js.js +0 -512
  53. package/main/i18ntk-manage.js +0 -1694
  54. package/main/i18ntk-php.js +0 -462
  55. package/main/i18ntk-py.js +0 -379
  56. package/main/i18ntk-settings.js +0 -23
  57. package/main/manage/index-fixed.js +0 -1447
  58. package/scripts/build-lite.js +0 -279
  59. package/scripts/deprecate-versions.js +0 -317
  60. package/scripts/export-translations.js +0 -84
  61. package/scripts/fix-all-i18n.js +0 -236
  62. package/scripts/fix-and-purify-i18n.js +0 -233
  63. package/scripts/fix-locale-control-chars.js +0 -110
  64. package/scripts/lint-locales.js +0 -80
  65. package/scripts/prepublish-dev.js +0 -221
  66. package/scripts/prepublish.js +0 -362
  67. package/scripts/security-check.js +0 -117
  68. package/scripts/sync-translations.js +0 -151
  69. package/scripts/sync-ui-locales.js +0 -20
  70. package/scripts/validate-all-translations.js +0 -195
  71. package/scripts/verify-deprecations.js +0 -157
  72. package/scripts/verify-translations.js +0 -63
  73. package/utils/security-fixed.js +0 -609
@@ -1,462 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * i18ntk-php.js - PHP Language I18n Management Command
5
- *
6
- * Supports:
7
- * - Laravel i18n
8
- * - Symfony translations
9
- * - WordPress i18n
10
- * - Standard PHP gettext
11
- * - PHP array translations
12
- * - JSON translations
13
- */
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
-
18
- const SecurityUtils = require(path.join(__dirname, '../utils/security'));
19
- const { getConfig, saveConfig } = require(path.join(__dirname, '../utils/config-helper'));
20
- const I18nHelper = require(path.join(__dirname, '../utils/i18n-helper'));
21
- const SetupEnforcer = require(path.join(__dirname, '../utils/setup-enforcer'));
22
- const { program } = require('../utils/mini-commander');
23
-
24
- (async () => {
25
- try {
26
- await SetupEnforcer.checkSetupCompleteAsync();
27
- } catch (error) {
28
- console.error('Setup check failed:', error.message);
29
- process.exit(1);
30
- }
31
- })();
32
-
33
- class PhpI18nManager {
34
- constructor() {
35
- this.supportedPatterns = [
36
- '__(',
37
- '_e(',
38
- '_n(',
39
- '_x(',
40
- '_ex(',
41
- '_nx(',
42
- 'trans(',
43
- 'trans_choice(',
44
- 'Lang::get(',
45
- 'Lang::choice(',
46
- '$this->trans(',
47
- 'Yii::t(',
48
- '$this->t(',
49
- 'dgettext(',
50
- 'dngettext(',
51
- 'dcgettext(',
52
- 'ngettext(',
53
- 'gettext('
54
- ];
55
-
56
- this.fileExtensions = ['.php', '.blade.php', '.twig', '.phtml', '.module', '.theme'];
57
- this.resourceFormats = ['.php', '.json', '.yml', '.yaml', '.po', '.mo'];
58
- }
59
-
60
- async detectFramework(sourceDir) {
61
- // Check for Laravel
62
- const composerJson = path.join(sourceDir, 'composer.json');
63
- if (SecurityUtils.safeExistsSync(composerJson, sourceDir)) {
64
- const content = SecurityUtils.safeReadFileSync(composerJson, sourceDir, 'utf8');
65
- const composer = SecurityUtils.safeParseJSON(content);
66
-
67
- if (composer.require && composer.require['laravel/framework']) {
68
- return 'laravel';
69
- }
70
-
71
- if (composer.require && composer.require['symfony/framework-bundle']) {
72
- return 'symfony';
73
- }
74
-
75
- if (composer.require && composer.require['yiisoft/yii2']) {
76
- return 'yii2';
77
- }
78
-
79
- if (composer.require && composer.require['wordpress/wordpress']) {
80
- return 'wordpress';
81
- }
82
- }
83
-
84
- // Check for WordPress
85
- const wpConfig = path.join(sourceDir, 'wp-config.php');
86
- if (SecurityUtils.safeExistsSync(wpConfig, sourceDir)) {
87
- return 'wordpress';
88
- }
89
-
90
- // Check for standard PHP
91
- const phpFiles = this.findFiles(sourceDir, '.php');
92
- if (phpFiles.length > 0) {
93
- return 'standard-php';
94
- }
95
-
96
- return 'generic';
97
- }
98
-
99
- async extractTranslations(sourceDir, options = {}) {
100
- const framework = await this.detectFramework(sourceDir);
101
- const translations = new Set();
102
-
103
- // Process PHP files
104
- const phpFiles = [
105
- ...this.findFiles(sourceDir, '.php'),
106
- ...this.findFiles(sourceDir, '.blade.php'),
107
- ...this.findFiles(sourceDir, '.twig'),
108
- ...this.findFiles(sourceDir, '.phtml')
109
- ];
110
-
111
- for (const file of phpFiles) {
112
- const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
113
-
114
- // Extract PHP i18n patterns
115
- const patterns = [
116
- /__\(\s*["']([^"']+)["']/g,
117
- /_e\(\s*["']([^"']+)["']/g,
118
- /_n\(\s*["']([^"']+)["']/g,
119
- /_x\(\s*["']([^"']+)["']/g,
120
- /trans\(\s*["']([^"']+)["']/g,
121
- /trans_choice\(\s*["']([^"']+)["']/g,
122
- /Lang::get\(\s*["']([^"']+)["']/g,
123
- /Lang::choice\(\s*["']([^"']+)["']/g,
124
- /Yii::t\(\s*["']([^"']+)["']/g,
125
- /gettext\(\s*["']([^"']+)["']/g,
126
- /dgettext\(\s*["']([^"']+)["']/g,
127
- /ngettext\(\s*["']([^"']+)["']/g,
128
- /dngettext\(\s*["']([^"']+)["']/g
129
- ];
130
-
131
- for (const pattern of patterns) {
132
- let match;
133
- while ((match = pattern.exec(content)) !== null) {
134
- translations.add(match[1]);
135
- }
136
- }
137
-
138
- // Blade template patterns
139
- const bladePatterns = [
140
- /@lang\(['"]([^'"]+)['"]\)/g,
141
- /@choice\(['"]([^'"]+)['"]\)/g,
142
- /{{\s*__\(['"]([^'"]+)['"]\)\s*}}/g,
143
- /{{\s*trans\(['"]([^'"]+)['"]\)\s*}}/g
144
- ];
145
-
146
- for (const pattern of bladePatterns) {
147
- let match;
148
- while ((match = pattern.exec(content)) !== null) {
149
- translations.add(match[1]);
150
- }
151
- }
152
- }
153
-
154
- // Process translation files
155
- const translationFiles = [
156
- ...this.findFiles(sourceDir, '.php'),
157
- ...this.findFiles(sourceDir, '.json'),
158
- ...this.findFiles(sourceDir, '.yml'),
159
- ...this.findFiles(sourceDir, '.yaml')
160
- ];
161
-
162
- for (const file of translationFiles) {
163
- if (file.includes('lang') || file.includes('translations') || file.includes('i18n')) {
164
- try {
165
- const content = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
166
-
167
- if (file.endsWith('.php')) {
168
- // PHP array translations
169
- const arrayPattern = /'([^']+)'\s*=>/g;
170
- let match;
171
- while ((match = arrayPattern.exec(content)) !== null) {
172
- translations.add(match[1]);
173
- }
174
- } else if (file.endsWith('.json')) {
175
- // JSON translations
176
- const data = SecurityUtils.safeParseJSON(content);
177
- const keys = this.extractKeysFromObject(data);
178
- keys.forEach(key => translations.add(key));
179
- } else if (file.endsWith('.yml') || file.endsWith('.yaml')) {
180
- // YAML translations (basic parsing)
181
- const lines = content.split('\n');
182
- for (const line of lines) {
183
- const trimmed = line.trim();
184
- if (trimmed && !trimmed.startsWith('#') && trimmed.includes(':')) {
185
- const key = trimmed.split(':')[0].trim();
186
- if (!key.startsWith(' ')) {
187
- translations.add(key);
188
- }
189
- }
190
- }
191
- }
192
- } catch (error) {
193
- // Skip files that can't be parsed
194
- }
195
- }
196
- }
197
-
198
- return {
199
- framework,
200
- translations: Array.from(translations),
201
- files: phpFiles.length + translationFiles.length,
202
- patterns: this.supportedPatterns
203
- };
204
- }
205
-
206
- extractKeysFromObject(obj, prefix = '') {
207
- const keys = [];
208
-
209
- for (const [key, value] of Object.entries(obj)) {
210
- const fullKey = prefix ? `${prefix}.${key}` : key;
211
- keys.push(fullKey);
212
-
213
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
214
- keys.push(...this.extractKeysFromObject(value, fullKey));
215
- }
216
- }
217
-
218
- return keys;
219
- }
220
-
221
- async createLocaleStructure(outputDir, languages = ['en'], framework = 'standard-php') {
222
- const localesDir = path.join(outputDir, 'locales');
223
-
224
- for (const lang of languages) {
225
- const langDir = path.join(localesDir, lang);
226
- fs.mkdirSync(langDir, { recursive: true });
227
-
228
- if (framework === 'laravel') {
229
- // Laravel structure
230
- SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.php'), `<?php
231
- // Laravel i18n for ${lang}
232
- return [
233
- 'hello' => 'Hello, World!',
234
- 'items' => [
235
- 'one' => ':count item',
236
- 'other' => ':count items'
237
- ],
238
- 'welcome' => 'Welcome to our application'
239
- ];
240
- `, path.dirname(langDir));
241
-
242
- SecurityUtils.safeWriteFileSync(path.join(langDir, 'validation.php'), `<?php
243
- // Laravel validation messages for ${lang}
244
- return [
245
- 'required' => 'The :attribute field is required.',
246
- 'email' => 'The :attribute must be a valid email address.',
247
- 'unique' => 'The :attribute has already been taken.'
248
- ];
249
- `, path.dirname(localesDir));
250
- } else if (framework === 'symfony') {
251
- // Symfony structure
252
- SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.yml'), `# Symfony i18n for ${lang}
253
- hello: "Hello, World!"
254
- items:
255
- one: "%count% item"
256
- other: "%count% items"
257
- welcome: "Welcome to our application"
258
- `, path.dirname(localesDir));
259
-
260
- SecurityUtils.safeWriteFileSync(path.join(langDir, 'validators.yml'), `# Symfony validation for ${lang}
261
- required: "The {{ field }} field is required."
262
- email: "The {{ field }} must be a valid email address."
263
- unique: "The {{ field }} has already been taken."
264
- `, path.dirname(localesDir));
265
- } else {
266
- // Standard PHP
267
- SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.php'), `<?php
268
- // PHP i18n for ${lang}
269
- return [
270
- 'hello' => 'Hello, World!',
271
- 'items' => [
272
- 'one' => '{0} item',
273
- 'other' => '{0} items'
274
- ],
275
- 'welcome' => 'Welcome to our application'
276
- ];
277
- `, path.dirname(langDir));
278
-
279
- SecurityUtils.safeWriteFileSync(path.join(langDir, 'messages.json'), JSON.stringify({
280
- hello: "Hello, World!",
281
- items: {
282
- one: "{0} item",
283
- other: "{0} items"
284
- },
285
- welcome: "Welcome to our application"
286
- }, null, 2), path.dirname(langDir));
287
- }
288
- }
289
-
290
- return localesDir;
291
- }
292
-
293
- findFiles(dir, extension) {
294
- const files = [];
295
-
296
- function traverse(currentDir) {
297
- if (!SecurityUtils.safeExistsSync(currentDir, path.dirname(currentDir))) return;
298
-
299
- const items = fs.readdirSync(currentDir);
300
-
301
- for (const item of items) {
302
- const fullPath = path.join(currentDir, item);
303
- try {
304
- const stat = fs.statSync(fullPath);
305
-
306
- if (stat.isDirectory() && !item.startsWith('.') &&
307
- !['node_modules', 'vendor', 'cache', 'storage'].includes(item)) {
308
- traverse(fullPath);
309
- } else if (stat.isFile() && path.extname(item) === extension) {
310
- files.push(fullPath);
311
- }
312
- } catch (error) {
313
- // Skip files we can't access
314
- }
315
- }
316
- }
317
-
318
- traverse(dir);
319
- return files;
320
- }
321
-
322
- async generateReport(results, outputDir) {
323
- const report = {
324
- timestamp: new Date().toISOString(),
325
- framework: results.framework,
326
- totalTranslations: results.translations.length,
327
- translations: results.translations,
328
- filesProcessed: results.files,
329
- patterns: results.patterns,
330
- recommendations: this.getRecommendations(results)
331
- };
332
-
333
- const reportPath = path.join(outputDir, 'i18ntk-php-report.json');
334
- SecurityUtils.safeWriteFileSync(reportPath, JSON.stringify(report, null, 2), outputDir);
335
-
336
- return reportPath;
337
- }
338
-
339
- getRecommendations(results) {
340
- const recommendations = [];
341
-
342
- if (results.translations.length === 0) {
343
- recommendations.push('No translations found. Check your PHP i18n patterns');
344
- }
345
-
346
- if (results.files === 0) {
347
- recommendations.push('No PHP files found in source directory');
348
- }
349
-
350
- if (results.framework === 'generic') {
351
- recommendations.push('Consider using Laravel or Symfony for better i18n support');
352
- }
353
-
354
- if (results.framework === 'wordpress') {
355
- recommendations.push('Use WordPress i18n functions (__(), _e(), _n()) for consistency');
356
- }
357
-
358
- return recommendations;
359
- }
360
- }
361
-
362
- // CLI Implementation
363
- program
364
- .name('i18ntk-php')
365
- .description('PHP language i18n management tool')
366
- .version('1.10.1');
367
-
368
- program
369
- .command('init')
370
- .description('Initialize PHP i18n structure')
371
- .option('-s, --source-dir <dir>', 'Source directory', './')
372
- .option('-o, --output-dir <dir>', 'Output directory', './i18ntk-reports')
373
- .option('-l, --languages <langs>', 'Languages (comma-separated)', 'en')
374
- .option('-f, --framework <framework>', 'Framework (laravel|symfony|wordpress|standard)', 'standard-php')
375
- .action(async (options) => {
376
- try {
377
- const manager = new PhpI18nManager();
378
- const languages = options.languages.split(',');
379
-
380
- const localesDir = await manager.createLocaleStructure(options.outputDir, languages, options.framework);
381
-
382
- console.log(`✅ PHP i18n structure initialized in: ${localesDir}`);
383
- console.log(`📊 Languages: ${languages.join(', ')}`);
384
- console.log(`🎯 Framework: ${options.framework}`);
385
-
386
- } catch (error) {
387
- console.error('❌ Error initializing PHP i18n:', error.message);
388
- process.exit(1);
389
- }
390
- });
391
-
392
- program
393
- .command('analyze')
394
- .description('Analyze PHP i18n usage')
395
- .option('-s, --source-dir <dir>', 'Source directory', './')
396
- .option('-o, --output-dir <dir>', 'Output directory', './i18ntk-reports')
397
- .option('--dry-run', 'Preview without making changes')
398
- .action(async (options) => {
399
- try {
400
- SecurityUtils.validatePath(options.sourceDir);
401
-
402
- const manager = new PhpI18nManager();
403
- const results = await manager.extractTranslations(options.sourceDir);
404
-
405
- console.log(`🔍 Framework detected: ${results.framework}`);
406
- console.log(`📊 Files processed: ${results.files}`);
407
- console.log(`📝 Translations found: ${results.translations.length}`);
408
-
409
- if (!options.dryRun) {
410
- const reportPath = await manager.generateReport(results, options.outputDir);
411
- console.log(`📄 Report saved: ${reportPath}`);
412
- }
413
-
414
- } catch (error) {
415
- console.error('❌ Error analyzing PHP i18n:', error.message);
416
- process.exit(1);
417
- }
418
- });
419
-
420
- program
421
- .command('extract')
422
- .description('Extract PHP translations')
423
- .option('-s, --source-dir <dir>', 'Source directory', './')
424
- .option('-o, --output-dir <dir>', 'Output directory', './locales')
425
- .option('-l, --languages <langs>', 'Languages (comma-separated)', 'en')
426
- .option('-f, --framework <framework>', 'Framework (laravel|symfony|wordpress|standard)', 'standard-php')
427
- .action(async (options) => {
428
- try {
429
- SecurityUtils.validatePath(options.sourceDir);
430
-
431
- const manager = new PhpI18nManager();
432
- const results = await manager.extractTranslations(options.sourceDir);
433
-
434
- await manager.createLocaleStructure(options.outputDir, options.languages.split(','), options.framework);
435
-
436
- console.log(`✅ Extracted ${results.translations.length} translations`);
437
- console.log(`📁 Locale structure created in: ${options.outputDir}`);
438
- console.log(`🎯 Framework: ${options.framework}`);
439
-
440
- } catch (error) {
441
- console.error('❌ Error extracting PHP translations:', error.message);
442
- process.exit(1);
443
- }
444
- });
445
-
446
- // Handle uncaught errors
447
- process.on('uncaughtException', (error) => {
448
- console.error('❌ Uncaught exception:', error.message);
449
- process.exit(1);
450
- });
451
-
452
- process.on('unhandledRejection', (reason) => {
453
- console.error('❌ Unhandled rejection:', reason);
454
- process.exit(1);
455
- });
456
-
457
- // Export for programmatic use
458
- module.exports = { PhpI18nManager };
459
-
460
- if (require.main === module) {
461
- program.parse();
462
- }