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/runtime/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const SecurityUtils = require('../utils/security');
|
|
8
9
|
|
|
9
10
|
let configManager = null;
|
|
10
11
|
try { configManager = require('../utils/config-manager'); } catch (_) { /* optional */ }
|
|
@@ -27,7 +28,7 @@ function stripBOMAndComments(s) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function readJsonSafe(file) {
|
|
30
|
-
const raw =
|
|
31
|
+
const raw = SecurityUtils.safeReadFileSync(file, path.dirname(file), 'utf8');
|
|
31
32
|
return JSON.parse(stripBOMAndComments(raw));
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -85,7 +86,7 @@ function listJsonFilesRecursively(dir) {
|
|
|
85
86
|
const stack = [dir];
|
|
86
87
|
while (stack.length) {
|
|
87
88
|
const d = stack.pop();
|
|
88
|
-
if (!
|
|
89
|
+
if (!SecurityUtils.safeExistsSync(d)) continue;
|
|
89
90
|
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
90
91
|
const full = path.join(d, entry.name);
|
|
91
92
|
if (entry.isDirectory()) {
|
|
@@ -104,7 +105,7 @@ function readLanguageFromBase(baseDir, lang) {
|
|
|
104
105
|
const langDir = path.join(baseDir, lang);
|
|
105
106
|
|
|
106
107
|
// Prefer folder if exists, otherwise single file
|
|
107
|
-
if (
|
|
108
|
+
if (SecurityUtils.safeExistsSync(langDir) && fs.statSync(langDir).isDirectory()) {
|
|
108
109
|
const files = listJsonFilesRecursively(langDir);
|
|
109
110
|
for (const file of files) {
|
|
110
111
|
try {
|
|
@@ -114,7 +115,7 @@ function readLanguageFromBase(baseDir, lang) {
|
|
|
114
115
|
// Skip unreadable/invalid files
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
} else if (
|
|
118
|
+
} else if (SecurityUtils.safeExistsSync(langFile) && fs.statSync(langFile).isFile()) {
|
|
118
119
|
try {
|
|
119
120
|
const data = readJsonSafe(langFile);
|
|
120
121
|
if (data && typeof data === 'object') deepMerge(merged, data);
|
|
@@ -203,7 +204,7 @@ function getLanguage() {
|
|
|
203
204
|
function getAvailableLanguages() {
|
|
204
205
|
const langs = new Set();
|
|
205
206
|
if (!state.baseDir) state.baseDir = resolveBaseDir();
|
|
206
|
-
if (!
|
|
207
|
+
if (!SecurityUtils.safeExistsSync(state.baseDir)) return ['en'];
|
|
207
208
|
for (const entry of fs.readdirSync(state.baseDir, { withFileTypes: true })) {
|
|
208
209
|
if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
|
|
209
210
|
langs.add(entry.name.replace(/\.json$/i, ''));
|
|
@@ -211,7 +212,7 @@ function getAvailableLanguages() {
|
|
|
211
212
|
// language folder convention
|
|
212
213
|
const lang = entry.name;
|
|
213
214
|
const idx = path.join(state.baseDir, lang, `${lang}.json`);
|
|
214
|
-
if (
|
|
215
|
+
if (SecurityUtils.safeExistsSync(idx)) langs.add(lang);
|
|
215
216
|
else langs.add(lang); // be permissive
|
|
216
217
|
}
|
|
217
218
|
}
|
package/scripts/build-lite.js
CHANGED
|
@@ -54,7 +54,7 @@ class LiteBuild {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async cleanBuildDir() {
|
|
57
|
-
if (
|
|
57
|
+
if (SecurityUtils.safeExistsSync(this.buildDir)) {
|
|
58
58
|
fs.rmSync(this.buildDir, { recursive: true, force: true });
|
|
59
59
|
}
|
|
60
60
|
fs.mkdirSync(this.buildDir, { recursive: true });
|
|
@@ -65,7 +65,7 @@ class LiteBuild {
|
|
|
65
65
|
'main',
|
|
66
66
|
'utils',
|
|
67
67
|
'settings',
|
|
68
|
-
'ui-locales/en',
|
|
68
|
+
'resources/i18n/ui-locales/en',
|
|
69
69
|
'scripts'
|
|
70
70
|
];
|
|
71
71
|
|
|
@@ -77,7 +77,7 @@ class LiteBuild {
|
|
|
77
77
|
async copyEssentialFiles() {
|
|
78
78
|
const essentialFiles = [
|
|
79
79
|
// Main CLI scripts
|
|
80
|
-
{ src: 'main/
|
|
80
|
+
{ src: 'main/manage/index.js', dest: 'main/manage/index.js' },
|
|
81
81
|
{ src: 'main/i18ntk-init.js', dest: 'main/i18ntk-init.js' },
|
|
82
82
|
{ src: 'main/i18ntk-analyze.js', dest: 'main/i18ntk-analyze.js' },
|
|
83
83
|
{ src: 'main/i18ntk-validate.js', dest: 'main/i18ntk-validate.js' },
|
|
@@ -106,17 +106,17 @@ class LiteBuild {
|
|
|
106
106
|
const srcPath = path.join(this.projectRoot, src);
|
|
107
107
|
const destPath = path.join(this.buildDir, dest);
|
|
108
108
|
|
|
109
|
-
if (
|
|
109
|
+
if (SecurityUtils.safeExistsSync(srcPath)) {
|
|
110
110
|
fs.copyFileSync(srcPath, destPath);
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
async copyEnglishLocale() {
|
|
116
|
-
const enLocaleDir = path.join(this.projectRoot, 'ui-locales', 'en');
|
|
117
|
-
const destDir = path.join(this.buildDir, 'ui-locales', 'en');
|
|
116
|
+
const enLocaleDir = path.join(this.projectRoot, 'resources', 'i18n', 'ui-locales', 'en');
|
|
117
|
+
const destDir = path.join(this.buildDir, 'resources', 'i18n', 'ui-locales', 'en');
|
|
118
118
|
|
|
119
|
-
if (
|
|
119
|
+
if (SecurityUtils.safeExistsSync(enLocaleDir)) {
|
|
120
120
|
const files = fs.readdirSync(enLocaleDir);
|
|
121
121
|
files.forEach(file => {
|
|
122
122
|
if (file.endsWith('.json')) {
|
|
@@ -137,7 +137,7 @@ class LiteBuild {
|
|
|
137
137
|
main: this.packageJson.main,
|
|
138
138
|
bin: this.packageJson.bin,
|
|
139
139
|
scripts: {
|
|
140
|
-
start: 'node main/
|
|
140
|
+
start: 'node main/manage/index.js',
|
|
141
141
|
analyze: 'node main/i18ntk-analyze.js',
|
|
142
142
|
validate: 'node main/i18ntk-validate.js'
|
|
143
143
|
},
|
|
@@ -150,7 +150,7 @@ class LiteBuild {
|
|
|
150
150
|
homepage: this.packageJson.homepage
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
SecurityUtils.safeWriteFileSync(
|
|
154
154
|
path.join(this.buildDir, 'package.json'),
|
|
155
155
|
JSON.stringify(litePackageJson, null, 2)
|
|
156
156
|
);
|
|
@@ -167,12 +167,12 @@ docs/
|
|
|
167
167
|
.i18ntk/
|
|
168
168
|
|
|
169
169
|
# Non-English locales
|
|
170
|
-
ui-locales/es/
|
|
171
|
-
ui-locales/fr/
|
|
172
|
-
ui-locales/de/
|
|
173
|
-
ui-locales/ja/
|
|
174
|
-
ui-locales/ru/
|
|
175
|
-
ui-locales/zh/
|
|
170
|
+
resources/i18n/ui-locales/es/
|
|
171
|
+
resources/i18n/ui-locales/fr/
|
|
172
|
+
resources/i18n/ui-locales/de/
|
|
173
|
+
resources/i18n/ui-locales/ja/
|
|
174
|
+
resources/i18n/ui-locales/ru/
|
|
175
|
+
resources/i18n/ui-locales/zh/
|
|
176
176
|
|
|
177
177
|
# Debug and test files
|
|
178
178
|
*.test.js
|
|
@@ -205,7 +205,7 @@ Thumbs.db
|
|
|
205
205
|
.DS_Store
|
|
206
206
|
`;
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
SecurityUtils.safeWriteFileSync(
|
|
209
209
|
path.join(this.buildDir, '.npmignore'),
|
|
210
210
|
npmIgnoreContent.trim()
|
|
211
211
|
);
|
|
@@ -225,7 +225,7 @@ Thumbs.db
|
|
|
225
225
|
}
|
|
226
226
|
};
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
SecurityUtils.safeWriteFileSync(
|
|
229
229
|
path.join(this.buildDir, 'build-report.json'),
|
|
230
230
|
JSON.stringify(report, null, 2)
|
|
231
231
|
);
|
|
@@ -12,13 +12,25 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const { execSync } = require('child_process');
|
|
15
|
-
const fs = require('fs');
|
|
16
15
|
const path = require('path');
|
|
16
|
+
const SecurityUtils = require('../utils/security');
|
|
17
17
|
|
|
18
18
|
// Configuration
|
|
19
19
|
const CONFIG_FILE = path.join(__dirname, '..', 'deprecation-config.json');
|
|
20
20
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
// Securely read package.json
|
|
23
|
+
const packageJsonContent = SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8');
|
|
24
|
+
if (!packageJsonContent) {
|
|
25
|
+
console.error('❌ Failed to read package.json');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
|
|
30
|
+
if (!packageJson) {
|
|
31
|
+
console.error('❌ Failed to parse package.json');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
22
34
|
|
|
23
35
|
const deprecationMessage = '⚠️ DEPRECATED: This version contains security vulnerabilities and missing features. Please upgrade to i18ntk@1.10.0 for: 🔒 Zero shell access security, 🚀 97% performance improvement, 🐍 Python framework support, and 🛡️ PIN protection. Run: npm install i18ntk@latest';
|
|
24
36
|
|
|
@@ -27,11 +39,16 @@ const defaultVersionsToDeprecate = packageJson.versionInfo.deprecations;
|
|
|
27
39
|
|
|
28
40
|
// Load admin configuration if available
|
|
29
41
|
function loadAdminConfig() {
|
|
30
|
-
if (
|
|
42
|
+
if (SecurityUtils.safeExistsSync(CONFIG_FILE, path.dirname(CONFIG_FILE))) {
|
|
31
43
|
try {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
const configContent = SecurityUtils.safeReadFileSync(CONFIG_FILE, path.dirname(CONFIG_FILE), 'utf8');
|
|
45
|
+
if (configContent) {
|
|
46
|
+
const config = SecurityUtils.safeParseJSON(configContent);
|
|
47
|
+
if (config) {
|
|
48
|
+
console.log('📋 Using admin configuration from deprecation-config.json');
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
35
52
|
} catch (error) {
|
|
36
53
|
console.warn('⚠️ Could not read admin config, using defaults:', error.message);
|
|
37
54
|
}
|
|
@@ -8,13 +8,13 @@ const { loadTranslations, t } = require('../utils/i18n-helper');
|
|
|
8
8
|
loadTranslations('en');
|
|
9
9
|
|
|
10
10
|
// Configuration
|
|
11
|
-
const SOURCE_DIR = path.join(__dirname, '..', 'ui-locales');
|
|
11
|
+
const SOURCE_DIR = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales');
|
|
12
12
|
const TARGET_LANGUAGES = ['de', 'fr', 'es', 'ru', 'ja', 'zh'];
|
|
13
13
|
|
|
14
14
|
function createLanguageTemplate(lang) {
|
|
15
15
|
const langDir = path.join(SOURCE_DIR, lang);
|
|
16
16
|
|
|
17
|
-
if (!
|
|
17
|
+
if (!SecurityUtils.safeExistsSync(langDir)) {
|
|
18
18
|
fs.mkdirSync(langDir, { recursive: true });
|
|
19
19
|
console.log(t('exportTranslations.createdDirectory', { dir: langDir }));
|
|
20
20
|
}
|
|
@@ -31,10 +31,10 @@ function createLanguageTemplate(lang) {
|
|
|
31
31
|
const sourceFile = path.join(enDir, file);
|
|
32
32
|
const targetFile = path.join(langDir, file);
|
|
33
33
|
|
|
34
|
-
if (!
|
|
35
|
-
const sourceContent = JSON.parse(
|
|
34
|
+
if (!SecurityUtils.safeExistsSync(targetFile)) {
|
|
35
|
+
const sourceContent = JSON.parse(SecurityUtils.safeReadFileSync(sourceFile, path.dirname(sourceFile), 'utf8'));
|
|
36
36
|
const templateContent = createTemplateFromEnglish(sourceContent);
|
|
37
|
-
|
|
37
|
+
SecurityUtils.safeWriteFileSync(targetFile, JSON.stringify(templateContent, null, 2));
|
|
38
38
|
console.log(t('exportTranslations.createdTemplate', { lang, file }));
|
|
39
39
|
} else {
|
|
40
40
|
console.log(t('exportTranslations.skippedExisting', { lang, file }));
|
package/scripts/fix-all-i18n.js
CHANGED
|
@@ -19,7 +19,7 @@ const argv = Object.fromEntries(
|
|
|
19
19
|
);
|
|
20
20
|
|
|
21
21
|
const SOURCE_DIR = path.resolve(argv['source-dir'] || './');
|
|
22
|
-
const I18N_DIR = path.resolve(argv['i18n-dir'] || './ui-locales');
|
|
22
|
+
const I18N_DIR = path.resolve(argv['i18n-dir'] || './resources/i18n/ui-locales');
|
|
23
23
|
const LANGS = (argv.languages || 'en,de,es,fr,ru,ja,zh').split(',').map(s => s.trim());
|
|
24
24
|
const WRITE = !!argv.write;
|
|
25
25
|
const PRUNE_EXTRAS = !!argv['prune-extras'];
|
|
@@ -36,8 +36,8 @@ const KEY_PATTERNS = [
|
|
|
36
36
|
|
|
37
37
|
const EXCLUDE_DIRS = new Set(['node_modules', '.git', path.basename(I18N_DIR)]);
|
|
38
38
|
|
|
39
|
-
function readUTF8(p) { try { return
|
|
40
|
-
function writeJSON(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
39
|
+
function readUTF8(p) { try { return SecurityUtils.safeReadFileSync(p, path.dirname(p), 'utf8'); } catch { return null; } }
|
|
40
|
+
function writeJSON(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true }); SecurityUtils.safeWriteFileSync(p, JSON.stringify(obj, null, 2) + '\n', 'utf8'); }
|
|
41
41
|
function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
|
|
42
42
|
function isFile(p) { try { return fs.statSync(p).isFile(); } catch { return false; } }
|
|
43
43
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
|
+
const SecurityUtils = require('../utils/security');
|
|
18
19
|
|
|
19
20
|
// ---------------- CLI ARGUMENTS ----------------
|
|
20
21
|
const argv = Object.fromEntries(
|
|
@@ -45,11 +46,11 @@ const COUNTRY_CODES = { de: 'DE', es: 'ES', fr: 'FR', ru: 'RU', ja: 'JA', zh: 'Z
|
|
|
45
46
|
|
|
46
47
|
// ---------------- HELPERS ----------------
|
|
47
48
|
function readUTF8(p) {
|
|
48
|
-
try { return
|
|
49
|
+
try { return SecurityUtils.safeReadFileSync(p, path.dirname(p), 'utf8'); } catch { return null; }
|
|
49
50
|
}
|
|
50
51
|
function writeJSON(p, obj) {
|
|
51
52
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
52
|
-
|
|
53
|
+
SecurityUtils.safeWriteFileSync(p, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
53
54
|
}
|
|
54
55
|
function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
|
|
55
56
|
function isFile(p) { try { return fs.statSync(p).isFile(); } catch { return false; } }
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fix control characters in locale files
|
|
4
|
+
* Removes problematic newlines and other control chars from JSON strings
|
|
5
|
+
*/
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const SecurityUtils = require('../utils/security');
|
|
9
|
+
|
|
10
|
+
const uiLocalesDirs = [
|
|
11
|
+
path.join(__dirname, '..', 'ui-locales'),
|
|
12
|
+
path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales')
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function sanitizeValue(value) {
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
// Remove/escape control characters but keep the structure
|
|
18
|
+
// Replace literal newlines/tabs with spaces, but preserve intended structure
|
|
19
|
+
return value
|
|
20
|
+
.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F]/g, '') // Remove other control chars
|
|
21
|
+
.replace(/\u000A/g, ' ') // Replace newlines with space
|
|
22
|
+
.replace(/\u000D/g, '') // Remove carriage returns
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
return value.map(sanitizeValue);
|
|
27
|
+
}
|
|
28
|
+
if (value && typeof value === 'object') {
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const [k, v] of Object.entries(value)) {
|
|
31
|
+
result[k] = sanitizeValue(v);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fixFile(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
const baseDir = path.dirname(filePath);
|
|
41
|
+
const raw = SecurityUtils.safeReadFileSync(filePath, baseDir, 'utf8');
|
|
42
|
+
if (!raw) {
|
|
43
|
+
console.error(`✗ Error reading ${path.basename(filePath)}`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const data = SecurityUtils.safeParseJSON(raw);
|
|
48
|
+
if (!data) {
|
|
49
|
+
console.error(`✗ Error parsing ${path.basename(filePath)}`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const locale = path.basename(filePath, '.json');
|
|
54
|
+
|
|
55
|
+
// Sanitize all values
|
|
56
|
+
const sanitized = sanitizeValue(data);
|
|
57
|
+
|
|
58
|
+
// Write back with proper formatting using SecurityUtils
|
|
59
|
+
const success = SecurityUtils.safeWriteFileSync(
|
|
60
|
+
filePath,
|
|
61
|
+
JSON.stringify(sanitized, null, 2) + '\n',
|
|
62
|
+
baseDir,
|
|
63
|
+
'utf8'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (success) {
|
|
67
|
+
console.log(` ✓ Fixed ${locale}.json`);
|
|
68
|
+
return true;
|
|
69
|
+
} else {
|
|
70
|
+
console.error(` ✗ Error writing ${path.basename(filePath)}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`✗ Error fixing ${path.basename(filePath)}: ${error.message}`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function main() {
|
|
80
|
+
console.log('Fixing control characters in locale files...\n');
|
|
81
|
+
|
|
82
|
+
let fixed = 0;
|
|
83
|
+
let failed = 0;
|
|
84
|
+
|
|
85
|
+
for (const uiLocalesDir of uiLocalesDirs) {
|
|
86
|
+
if (!SecurityUtils.safeExistsSync(uiLocalesDir)) {
|
|
87
|
+
console.log(`ℹ️ Skipping ${uiLocalesDir} (does not exist)\n`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`Processing: ${uiLocalesDir}`);
|
|
92
|
+
const files = SecurityUtils.safeReaddirSync(uiLocalesDir, path.dirname(uiLocalesDir))
|
|
93
|
+
.filter(f => f.endsWith('.json'))
|
|
94
|
+
.map(f => path.join(uiLocalesDir, f));
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
if (fixFile(file)) {
|
|
98
|
+
fixed++;
|
|
99
|
+
} else {
|
|
100
|
+
failed++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`\nFixed ${fixed} file(s)${failed ? `, ${failed} failed` : ''}`);
|
|
107
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main();
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Simple locale lint:
|
|
4
|
+
* - flags control characters in any locale string
|
|
5
|
+
* - en locale: ensures a small set of CLI strings stay plain ASCII (catches mojibake)
|
|
6
|
+
*/
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const SecurityUtils = require('../utils/security');
|
|
10
|
+
|
|
11
|
+
const uiLocalesDir = path.join(__dirname, '..', 'ui-locales');
|
|
12
|
+
const asciiOnlyKeys = new Set([
|
|
13
|
+
'ui.autoDetectedI18nDirectory',
|
|
14
|
+
'ui.executingCommand',
|
|
15
|
+
'ui.unknownCommand',
|
|
16
|
+
'ui.errorExecutingCommand',
|
|
17
|
+
'ui.errorLoadingTranslationFile',
|
|
18
|
+
'ui.errorSavingLanguagePreference',
|
|
19
|
+
'ui.noActiveReadlineInterface',
|
|
20
|
+
'ui.uiLanguageUpdated',
|
|
21
|
+
'menu.invalidChoice',
|
|
22
|
+
'menu.returning',
|
|
23
|
+
'menu.invalidOption',
|
|
24
|
+
'menu.nonInteractiveModeWarning',
|
|
25
|
+
'menu.useDirectExecution',
|
|
26
|
+
'menu.useHelpForCommands',
|
|
27
|
+
'menu.autoDetectedI18nDirectory'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function walk(value, pathParts, onString) {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
onString(value, pathParts.join('.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
value.forEach((item, idx) => walk(item, pathParts.concat(idx), onString));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (value && typeof value === 'object') {
|
|
40
|
+
for (const [k, v] of Object.entries(value)) {
|
|
41
|
+
walk(v, pathParts.concat(k), onString);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function lintFile(filePath) {
|
|
47
|
+
const raw = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
|
|
48
|
+
if (!raw) {
|
|
49
|
+
return [`${path.basename(filePath, '.json')}: unable to read locale file`];
|
|
50
|
+
}
|
|
51
|
+
const data = JSON.parse(raw);
|
|
52
|
+
const locale = path.basename(filePath, '.json');
|
|
53
|
+
const issues = [];
|
|
54
|
+
|
|
55
|
+
walk(data, [], (str, keyPath) => {
|
|
56
|
+
if (/[\u0000-\u001F]/.test(str)) {
|
|
57
|
+
issues.push(`${locale}: control char in ${keyPath}`);
|
|
58
|
+
}
|
|
59
|
+
if (locale === 'en' && asciiOnlyKeys.has(keyPath) && /[^\x20-\x7E]/.test(str)) {
|
|
60
|
+
issues.push(`${locale}: non-ASCII in ${keyPath} -> "${str}"`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return issues;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
const files = fs.readdirSync(uiLocalesDir).filter(f => f.endsWith('.json'));
|
|
69
|
+
const allIssues = files.flatMap(f => lintFile(path.join(uiLocalesDir, f)));
|
|
70
|
+
|
|
71
|
+
if (allIssues.length) {
|
|
72
|
+
console.error('Locale lint failed:');
|
|
73
|
+
allIssues.forEach(msg => console.error(` - ${msg}`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('Locale lint passed.');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main();
|
|
@@ -21,7 +21,7 @@ const { getGlobalReadline } = require('../utils/cli');
|
|
|
21
21
|
|
|
22
22
|
class LocaleOptimizer {
|
|
23
23
|
constructor() {
|
|
24
|
-
this.uiLocalesDir = path.join(__dirname, '..', 'ui-locales');
|
|
24
|
+
this.uiLocalesDir = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales');
|
|
25
25
|
this.allLocales = ['en', 'de', 'es', 'fr', 'ja', 'ru', 'zh'];
|
|
26
26
|
this.backupDir = path.join(__dirname, '..', 'backups', 'locales');
|
|
27
27
|
this.rl = getGlobalReadline();
|
|
@@ -139,7 +139,7 @@ class LocaleOptimizer {
|
|
|
139
139
|
console.log(` Keeping: ${keepList.join(', ').toUpperCase()}`);
|
|
140
140
|
|
|
141
141
|
// Create backup directory
|
|
142
|
-
if (!
|
|
142
|
+
if (!SecurityUtils.safeExistsSync(this.backupDir)) {
|
|
143
143
|
fs.mkdirSync(this.backupDir, { recursive: true });
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -151,7 +151,7 @@ class LocaleOptimizer {
|
|
|
151
151
|
const filePath = path.join(this.uiLocalesDir, `${locale}.json`);
|
|
152
152
|
const backupPath = path.join(this.backupDir, `${locale}.json`);
|
|
153
153
|
|
|
154
|
-
if (!keepList.includes(locale) &&
|
|
154
|
+
if (!keepList.includes(locale) && SecurityUtils.safeExistsSync(filePath)) {
|
|
155
155
|
// Backup the file
|
|
156
156
|
fs.copyFileSync(filePath, backupPath);
|
|
157
157
|
|
|
@@ -173,7 +173,7 @@ class LocaleOptimizer {
|
|
|
173
173
|
|
|
174
174
|
// Create warning file
|
|
175
175
|
const warningPath = path.join(this.backupDir, 'REMOVED_LOCALES.txt');
|
|
176
|
-
|
|
176
|
+
SecurityUtils.safeWriteFileSync(warningPath, `Removed locales: ${removedLocales.join(',')}\nRestore with: node scripts/locale-optimizer.js --restore`);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const savedKB = (savedSpace / 1024).toFixed(1);
|
|
@@ -189,7 +189,7 @@ class LocaleOptimizer {
|
|
|
189
189
|
restoreLocales() {
|
|
190
190
|
console.log('🔄 Restoring locales from backup...');
|
|
191
191
|
|
|
192
|
-
if (!
|
|
192
|
+
if (!SecurityUtils.safeExistsSync(this.backupDir)) {
|
|
193
193
|
console.log(' ❌ No backup directory found');
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
@@ -348,7 +348,7 @@ class LocaleOptimizer {
|
|
|
348
348
|
getAvailableLocales() {
|
|
349
349
|
return this.allLocales.filter(locale => {
|
|
350
350
|
const filePath = path.join(this.uiLocalesDir, `${locale}.json`);
|
|
351
|
-
return
|
|
351
|
+
return SecurityUtils.safeExistsSync(filePath);
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
354
|
|
|
@@ -357,7 +357,7 @@ class LocaleOptimizer {
|
|
|
357
357
|
*/
|
|
358
358
|
getLocaleSize(locale) {
|
|
359
359
|
const filePath = path.join(this.uiLocalesDir, `${locale}.json`);
|
|
360
|
-
if (
|
|
360
|
+
if (SecurityUtils.safeExistsSync(filePath)) {
|
|
361
361
|
return fs.statSync(filePath).size / 1024;
|
|
362
362
|
}
|
|
363
363
|
return 0;
|
|
@@ -380,7 +380,7 @@ class LocaleOptimizer {
|
|
|
380
380
|
|
|
381
381
|
this.allLocales.forEach(locale => {
|
|
382
382
|
const filePath = path.join(this.uiLocalesDir, `${locale}.json`);
|
|
383
|
-
if (
|
|
383
|
+
if (SecurityUtils.safeExistsSync(filePath)) {
|
|
384
384
|
activeLocales.push(locale);
|
|
385
385
|
}
|
|
386
386
|
});
|