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.
- package/LICENSE +1 -1
- package/README.md +141 -1191
- package/main/i18ntk-analyze.js +65 -84
- package/main/i18ntk-backup-class.js +420 -0
- package/main/i18ntk-backup.js +3 -3
- package/main/i18ntk-complete.js +90 -65
- package/main/i18ntk-doctor.js +123 -103
- package/main/i18ntk-fixer.js +61 -725
- package/main/i18ntk-go.js +14 -15
- package/main/i18ntk-init.js +77 -26
- package/main/i18ntk-java.js +27 -32
- package/main/i18ntk-js.js +70 -68
- package/main/i18ntk-manage.js +129 -30
- package/main/i18ntk-php.js +75 -75
- package/main/i18ntk-py.js +55 -56
- package/main/i18ntk-scanner.js +59 -57
- package/main/i18ntk-setup.js +9 -404
- package/main/i18ntk-sizing.js +6 -6
- package/main/i18ntk-summary.js +21 -18
- package/main/i18ntk-ui.js +11 -10
- package/main/i18ntk-usage.js +54 -18
- package/main/i18ntk-validate.js +13 -13
- package/main/manage/commands/AnalyzeCommand.js +1124 -0
- package/main/manage/commands/BackupCommand.js +62 -0
- package/main/manage/commands/CommandRouter.js +295 -0
- package/main/manage/commands/CompleteCommand.js +61 -0
- package/main/manage/commands/DoctorCommand.js +60 -0
- package/main/manage/commands/FixerCommand.js +624 -0
- package/main/manage/commands/InitCommand.js +62 -0
- package/main/manage/commands/ScannerCommand.js +654 -0
- package/main/manage/commands/SizingCommand.js +60 -0
- package/main/manage/commands/SummaryCommand.js +61 -0
- package/main/manage/commands/UsageCommand.js +60 -0
- package/main/manage/commands/ValidateCommand.js +978 -0
- package/main/manage/index-fixed.js +1447 -0
- package/main/manage/index.js +1462 -0
- package/main/manage/managers/DebugMenu.js +140 -0
- package/main/manage/managers/InteractiveMenu.js +177 -0
- package/main/manage/managers/LanguageMenu.js +62 -0
- package/main/manage/managers/SettingsMenu.js +53 -0
- package/main/manage/services/AuthenticationService.js +263 -0
- package/main/manage/services/ConfigurationService-fixed.js +449 -0
- package/main/manage/services/ConfigurationService.js +449 -0
- package/main/manage/services/FileManagementService.js +368 -0
- package/main/manage/services/FrameworkDetectionService.js +458 -0
- package/main/manage/services/InitService.js +1051 -0
- package/main/manage/services/SetupService.js +462 -0
- package/main/manage/services/SummaryService.js +450 -0
- package/main/manage/services/UsageService.js +1502 -0
- package/package.json +32 -29
- package/runtime/enhanced.d.ts +221 -221
- package/runtime/index.d.ts +29 -29
- package/runtime/index.full.d.ts +331 -331
- package/runtime/index.js +7 -6
- package/scripts/build-lite.js +17 -17
- package/scripts/deprecate-versions.js +23 -6
- package/scripts/export-translations.js +5 -5
- package/scripts/fix-all-i18n.js +3 -3
- package/scripts/fix-and-purify-i18n.js +3 -2
- package/scripts/fix-locale-control-chars.js +110 -0
- package/scripts/lint-locales.js +80 -0
- package/scripts/locale-optimizer.js +8 -8
- package/scripts/prepublish.js +21 -21
- package/scripts/security-check.js +117 -117
- package/scripts/sync-translations.js +4 -4
- package/scripts/sync-ui-locales.js +9 -8
- package/scripts/validate-all-translations.js +8 -7
- package/scripts/verify-deprecations.js +157 -161
- package/scripts/verify-translations.js +6 -5
- package/settings/i18ntk-config.json +282 -282
- package/settings/language-config.json +5 -5
- package/settings/settings-cli.js +9 -9
- package/settings/settings-manager.js +18 -18
- package/ui-locales/de.json +2417 -2348
- package/ui-locales/en.json +2415 -2352
- package/ui-locales/es.json +2425 -2353
- package/ui-locales/fr.json +2418 -2348
- package/ui-locales/ja.json +2463 -2361
- package/ui-locales/ru.json +2463 -2359
- package/ui-locales/zh.json +2418 -2351
- package/utils/admin-auth.js +2 -2
- package/utils/admin-cli.js +297 -297
- package/utils/admin-pin.js +9 -9
- package/utils/cli-helper.js +9 -9
- package/utils/config-helper.js +73 -104
- package/utils/config-manager.js +204 -171
- package/utils/config.js +5 -4
- package/utils/env-manager.js +249 -263
- package/utils/framework-detector.js +27 -24
- package/utils/i18n-helper.js +85 -41
- package/utils/init-helper.js +152 -94
- package/utils/json-output.js +98 -98
- package/utils/mini-commander.js +179 -0
- package/utils/missing-key-validator.js +5 -5
- package/utils/plugin-loader.js +40 -29
- package/utils/prompt.js +14 -44
- package/utils/safe-json.js +40 -0
- package/utils/secure-errors.js +3 -3
- package/utils/security-check-improved.js +390 -0
- package/utils/security-config.js +5 -5
- package/utils/security-fixed.js +607 -0
- package/utils/security.js +652 -602
- package/utils/setup-enforcer.js +136 -44
- package/utils/setup-validator.js +33 -32
- package/utils/ultra-performance-optimizer.js +11 -9
- package/utils/watch-locales.js +2 -1
- package/utils/prompt-fixed.js +0 -55
- package/utils/security-check.js +0 -454
package/main/i18ntk-php.js
CHANGED
|
@@ -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 (
|
|
64
|
-
const content =
|
|
65
|
-
const composer =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
17
|
-
const
|
|
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 (!
|
|
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 (
|
|
120
|
-
const requirements =
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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":
|
|
236
|
-
"files_processed":
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
|
|
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 =
|
|
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
|
-
|
|
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('='
|
|
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;
|