i18ntk 2.0.4 → 2.2.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 (57) hide show
  1. package/README.md +38 -60
  2. package/main/i18ntk-analyze.js +49 -44
  3. package/main/i18ntk-complete.js +75 -74
  4. package/main/i18ntk-fixer.js +3 -3
  5. package/main/i18ntk-init.js +5 -5
  6. package/main/i18ntk-scanner.js +2 -2
  7. package/main/i18ntk-sizing.js +35 -35
  8. package/main/i18ntk-summary.js +4 -4
  9. package/main/i18ntk-ui.js +54 -8
  10. package/main/i18ntk-usage.js +14 -14
  11. package/main/i18ntk-validate.js +6 -5
  12. package/main/manage/commands/AnalyzeCommand.js +40 -35
  13. package/main/manage/commands/FixerCommand.js +2 -2
  14. package/main/manage/commands/ScannerCommand.js +2 -2
  15. package/main/manage/commands/ValidateCommand.js +9 -9
  16. package/main/manage/index.js +147 -75
  17. package/main/manage/managers/LanguageMenu.js +7 -2
  18. package/main/manage/services/UsageService.js +7 -7
  19. package/package.json +269 -290
  20. package/settings/settings-cli.js +3 -3
  21. package/ui-locales/de.json +161 -166
  22. package/ui-locales/en.json +13 -18
  23. package/ui-locales/es.json +171 -184
  24. package/ui-locales/fr.json +155 -161
  25. package/ui-locales/ja.json +192 -243
  26. package/ui-locales/ru.json +145 -196
  27. package/ui-locales/zh.json +179 -185
  28. package/utils/cli-helper.js +26 -98
  29. package/utils/extractors/regex.js +39 -12
  30. package/utils/i18n-helper.js +88 -40
  31. package/{scripts → utils}/locale-optimizer.js +61 -60
  32. package/utils/security-check-improved.js +16 -13
  33. package/utils/security.js +6 -4
  34. package/main/i18ntk-go.js +0 -283
  35. package/main/i18ntk-java.js +0 -380
  36. package/main/i18ntk-js.js +0 -512
  37. package/main/i18ntk-manage.js +0 -1694
  38. package/main/i18ntk-php.js +0 -462
  39. package/main/i18ntk-py.js +0 -379
  40. package/main/i18ntk-settings.js +0 -23
  41. package/main/manage/index-fixed.js +0 -1447
  42. package/main/manage/services/ConfigurationService-fixed.js +0 -449
  43. package/scripts/build-lite.js +0 -279
  44. package/scripts/deprecate-versions.js +0 -317
  45. package/scripts/export-translations.js +0 -84
  46. package/scripts/fix-all-i18n.js +0 -215
  47. package/scripts/fix-and-purify-i18n.js +0 -213
  48. package/scripts/fix-locale-control-chars.js +0 -110
  49. package/scripts/lint-locales.js +0 -80
  50. package/scripts/prepublish.js +0 -348
  51. package/scripts/security-check.js +0 -117
  52. package/scripts/sync-translations.js +0 -151
  53. package/scripts/sync-ui-locales.js +0 -20
  54. package/scripts/validate-all-translations.js +0 -139
  55. package/scripts/verify-deprecations.js +0 -157
  56. package/scripts/verify-translations.js +0 -63
  57. package/utils/security-fixed.js +0 -607
@@ -1,348 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Prepublish Script
5
- * Cleans up development artifacts before npm publish
6
- * Ensures fresh config and settings for public package
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
-
12
- class PrepublishCleaner {
13
- constructor() {
14
- this.projectRoot = path.join(__dirname, '..');
15
- this.directories = [
16
- 'scripts/debug/logs',
17
- 'scripts/debug/reports',
18
- 'settings/backups',
19
- 'i18ntk-reports',
20
- 'reports'
21
- ];
22
- this.files = [
23
- 'settings/.i18n-admin-config.json',
24
- 'test-*.json',
25
- 'debug-*.log',
26
- 'npm-debug.log',
27
- 'yarn-error.log'
28
- ];
29
-
30
- // Essential files that must exist for release
31
- this.essentialFiles = [
32
- 'package.json',
33
- 'main/manage/index.js',
34
- 'main/i18ntk-init.js',
35
- 'main/i18ntk-analyze.js',
36
- 'main/i18ntk-validate.js',
37
- 'main/i18ntk-usage.js',
38
- 'main/i18ntk-summary.js',
39
- 'main/i18ntk-sizing.js',
40
- 'main/i18ntk-complete.js',
41
- 'main/i18ntk-ui.js',
42
- 'main/i18ntk-autorun.js',
43
- 'utils/i18n-helper.js',
44
- 'utils/security.js',
45
- 'settings/settings-manager.js',
46
- 'settings/settings-cli.js',
47
- 'settings/i18ntk-config.json'
48
- ];
49
-
50
- // Essential locale files
51
- this.essentialLocales = [
52
- 'resources/i18n/ui-locales/en.json',
53
- 'resources/i18n/ui-locales/es.json',
54
- 'resources/i18n/ui-locales/fr.json',
55
- 'resources/i18n/ui-locales/de.json',
56
- 'resources/i18n/ui-locales/ja.json',
57
- 'resources/i18n/ui-locales/ru.json',
58
- 'resources/i18n/ui-locales/zh.json'
59
- ];
60
- }
61
-
62
- log(message) {
63
- console.log(`[Prepublish] ${message}`);
64
- }
65
-
66
- async clean() {
67
- this.log('Starting comprehensive pre-publish validation...');
68
-
69
- // Validate essential files exist
70
- await this.validateEssentialFiles();
71
-
72
- // Validate locale files
73
- await this.validateLocaleFiles();
74
-
75
- // Validate package.json
76
- await this.validatePackageJson();
77
-
78
- // Clean directories
79
- for (const dir of this.directories) {
80
- await this.cleanDirectory(path.join(this.projectRoot, dir));
81
- }
82
-
83
- // Clean files
84
- for (const file of this.files) {
85
- await this.cleanFile(file);
86
- }
87
-
88
- // Reset security settings
89
- await this.resetSecuritySettings();
90
-
91
- // Final validation
92
- await this.finalValidation();
93
-
94
- this.log('Pre-publish validation completed successfully!');
95
- }
96
-
97
- async cleanDirectory(dirPath) {
98
- if (!SecurityUtils.safeExistsSync(dirPath)) {
99
- return;
100
- }
101
-
102
- try {
103
- const files = fs.readdirSync(dirPath);
104
- let deletedCount = 0;
105
-
106
- for (const file of files) {
107
- const filePath = path.join(dirPath, file);
108
- const stat = fs.statSync(filePath);
109
-
110
- if (stat.isFile()) {
111
- fs.unlinkSync(filePath);
112
- deletedCount++;
113
- } else if (stat.isDirectory()) {
114
- // Recursively clean subdirectories
115
- await this.cleanDirectory(filePath);
116
- // Remove empty directories
117
- try {
118
- fs.rmdirSync(filePath);
119
- } catch (e) {
120
- // Directory not empty, skip
121
- }
122
- }
123
- }
124
-
125
- if (deletedCount > 0) {
126
- this.log(`Cleaned ${deletedCount} files from ${path.relative(this.projectRoot, dirPath)}`);
127
- }
128
- } catch (error) {
129
- this.log(`Warning: Could not clean ${dirPath}: ${error.message}`);
130
- }
131
- }
132
-
133
- async cleanFile(pattern) {
134
- const searchPath = path.join(this.projectRoot, pattern);
135
-
136
- if (pattern.includes('*')) {
137
- // Handle glob patterns
138
- const dir = path.dirname(searchPath);
139
- const filenamePattern = path.basename(searchPath);
140
-
141
- if (SecurityUtils.safeExistsSync(dir)) {
142
- const files = fs.readdirSync(dir);
143
- const regex = new RegExp(filenamePattern.replace('*', '.*'));
144
-
145
- for (const file of files) {
146
- if (regex.test(file)) {
147
- const filePath = path.join(dir, file);
148
- fs.unlinkSync(filePath);
149
- this.log(`Deleted ${path.relative(this.projectRoot, filePath)}`);
150
- }
151
- }
152
- }
153
- } else {
154
- // Handle exact files
155
- if (SecurityUtils.safeExistsSync(searchPath)) {
156
- fs.unlinkSync(searchPath);
157
- this.log(`Deleted ${path.relative(this.projectRoot, searchPath)}`);
158
- }
159
- }
160
- }
161
-
162
- async validateEssentialFiles() {
163
- this.log('Validating essential files...');
164
-
165
- let missingFiles = [];
166
- for (const file of this.essentialFiles) {
167
- const filePath = path.join(this.projectRoot, file);
168
- if (!SecurityUtils.safeExistsSync(filePath)) {
169
- missingFiles.push(file);
170
- } else if (!fs.statSync(filePath).isFile()) {
171
- this.log(`❌ ${file} is not a file`);
172
- process.exit(1);
173
- }
174
- }
175
-
176
- if (missingFiles.length > 0) {
177
- this.log(`❌ Missing essential files: ${missingFiles.join(', ')}`);
178
- process.exit(1);
179
- }
180
-
181
- this.log('✅ All essential files present');
182
- }
183
-
184
- async validateLocaleFiles() {
185
- this.log('Validating locale files...');
186
-
187
- let invalidFiles = [];
188
- for (const localeFile of this.essentialLocales) {
189
- const filePath = path.join(this.projectRoot, localeFile);
190
- if (!SecurityUtils.safeExistsSync(filePath)) {
191
- invalidFiles.push(localeFile);
192
- continue;
193
- }
194
-
195
- try {
196
- const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
197
- const parsed = JSON.parse(content);
198
-
199
- // Validate structure
200
- if (typeof parsed !== 'object' || parsed === null) {
201
- invalidFiles.push(`${localeFile}: Invalid structure`);
202
- }
203
-
204
- // Check for required keys
205
- if (!parsed.settings || !parsed.settings.title) {
206
- invalidFiles.push(`${localeFile}: Missing required keys`);
207
- }
208
-
209
- } catch (e) {
210
- invalidFiles.push(`${localeFile}: ${e.message}`);
211
- }
212
- }
213
-
214
- if (invalidFiles.length > 0) {
215
- this.log(`❌ Invalid locale files: ${invalidFiles.join(', ')}`);
216
- process.exit(1);
217
- }
218
-
219
- this.log('✅ All locale files valid');
220
- }
221
-
222
- async validatePackageJson() {
223
- this.log('Validating package.json...');
224
-
225
- const packagePath = path.join(this.projectRoot, 'package.json');
226
- try {
227
- const pkg = JSON.parse(SecurityUtils.safeReadFileSync(packagePath, path.dirname(packagePath), 'utf8'));
228
-
229
- // Validate required fields
230
- const requiredFields = ['name', 'version', 'description', 'main', 'bin', 'files'];
231
- for (const field of requiredFields) {
232
- if (!pkg[field]) {
233
- this.log(`❌ package.json missing required field: ${field}`);
234
- process.exit(1);
235
- }
236
- }
237
-
238
- // Validate version format
239
- if (!/^\d+\.\d+\.\d+/.test(pkg.version)) {
240
- this.log('❌ Invalid version format');
241
- process.exit(1);
242
- }
243
-
244
- // Validate bin entries
245
- const requiredBinEntries = [
246
- 'i18ntk', 'i18ntk-init', 'i18ntk-analyze', 'i18ntk-validate',
247
- 'i18ntk-usage', 'i18ntk-summary', 'i18ntk-sizing', 'i18ntk-complete',
248
- 'i18ntk-ui', 'i18ntk-autorun'
249
- ];
250
-
251
- for (const bin of requiredBinEntries) {
252
- if (!pkg.bin || !pkg.bin[bin]) {
253
- this.log(`❌ Missing bin entry: ${bin}`);
254
- process.exit(1);
255
- }
256
-
257
- const binPath = path.join(this.projectRoot, pkg.bin[bin]);
258
- if (!SecurityUtils.safeExistsSync(binPath)) {
259
- this.log(`❌ Missing bin script: ${pkg.bin[bin]}`);
260
- process.exit(1);
261
- }
262
- }
263
-
264
- this.log('✅ package.json validated');
265
- } catch (e) {
266
- this.log(`❌ Invalid package.json: ${e.message}`);
267
- process.exit(1);
268
- }
269
- }
270
-
271
- async finalValidation() {
272
- this.log('Running final validation checks...');
273
-
274
- // Check for development artifacts
275
- const devArtifacts = [
276
- 'dev/debug',
277
- 'benchmarks',
278
- '.github',
279
- 'test-usage-fix.html',
280
- '.i18ntk'
281
- ];
282
-
283
- for (const artifact of devArtifacts) {
284
- const artifactPath = path.join(this.projectRoot, artifact);
285
- if (SecurityUtils.safeExistsSync(artifactPath)) {
286
- this.log(`âš ī¸ Development artifact found: ${artifact}`);
287
- }
288
- }
289
-
290
- // Validate file permissions for executable scripts
291
- const scripts = [
292
- 'main/manage/index.js',
293
- 'main/i18ntk-init.js',
294
- 'main/i18ntk-analyze.js',
295
- 'main/i18ntk-validate.js',
296
- 'main/i18ntk-usage.js',
297
- 'main/i18ntk-summary.js',
298
- 'main/i18ntk-sizing.js',
299
- 'main/i18ntk-complete.js',
300
- 'main/i18ntk-ui.js',
301
- 'main/i18ntk-autorun.js'
302
- ];
303
-
304
- for (const script of scripts) {
305
- const scriptPath = path.join(this.projectRoot, script);
306
- if (SecurityUtils.safeExistsSync(scriptPath)) {
307
- try {
308
- fs.accessSync(scriptPath, fs.constants.X_OK);
309
- } catch (e) {
310
- this.log(`âš ī¸ Script not executable: ${script}`);
311
- }
312
- }
313
- }
314
-
315
- this.log('✅ Final validation complete');
316
- }
317
-
318
- async resetSecuritySettings() {
319
- const configPath = path.join(require('../settings/settings-manager').configDir, '.i18n-admin-config.json');
320
-
321
- if (SecurityUtils.safeExistsSync(configPath)) {
322
- const defaultConfig = {
323
- enabled: false,
324
- pinHash: null,
325
- sessionTimeout: 30,
326
- maxFailedAttempts: 3,
327
- lockoutDuration: 15,
328
- lastActivity: null,
329
- failedAttempts: 0,
330
- lockedUntil: null
331
- };
332
-
333
- SecurityUtils.safeWriteFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
334
- this.log('Reset security settings to defaults');
335
- }
336
- }
337
- }
338
-
339
- // Run if called directly
340
- if (require.main === module) {
341
- const cleaner = new PrepublishCleaner();
342
- cleaner.clean().catch(error => {
343
- console.error('Error during cleanup:', error);
344
- process.exit(1);
345
- });
346
- }
347
-
348
- module.exports = PrepublishCleaner;
@@ -1,117 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Security Check Script
4
- * Validates that no child_process usage exists in production code
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- class SecurityCheck {
11
- constructor() {
12
- this.productionDirs = ['main', 'utils', 'settings', 'scripts'];
13
- this.forbiddenPatterns = [
14
- /require\(['"]child_process['"]\)/,
15
- /import.*child_process/,
16
- /execSync\(/,
17
- /spawnSync\(/,
18
- /execFileSync\(/,
19
- /spawn\(/,
20
- /exec\(/,
21
- /execFile\(/
22
- ];
23
- this.allowedFiles = [
24
- 'dev/', // Development files allowed to use child_process
25
- 'benchmarks/', // Benchmark scripts
26
- 'test/', // Test files
27
- 'scripts/deprecate-versions.js', // Allowed to use child_process for npm commands
28
- 'verify-package.js' // Package verification (development)
29
- ];
30
- this.violations = [];
31
- }
32
-
33
- async run() {
34
- console.log('🔒 i18ntk Security Check - Production Code Validation');
35
- console.log('═'.repeat(55));
36
-
37
- for (const dir of this.productionDirs) {
38
- await this.checkDirectory(dir);
39
- }
40
-
41
- if (this.violations.length > 0) {
42
- console.error('\n❌ SECURITY VIOLATIONS FOUND:');
43
- this.violations.forEach(violation => {
44
- console.error(` ${violation.file}:${violation.line} - ${violation.pattern}`);
45
- });
46
- console.error('\n💡 Production code must not use child_process');
47
- process.exit(1);
48
- } else {
49
- console.log('\n✅ All security checks passed - no child_process usage in production code');
50
- }
51
- }
52
-
53
- async checkDirectory(dir) {
54
- const dirPath = path.join(process.cwd(), dir);
55
-
56
- if (!SecurityUtils.safeExistsSync(dirPath)) {
57
- return;
58
- }
59
-
60
- const files = this.getAllFiles(dirPath);
61
-
62
- for (const file of files) {
63
- await this.checkFile(file);
64
- }
65
- }
66
-
67
- getAllFiles(dirPath) {
68
- const files = [];
69
- const entries = fs.readdirSync(dirPath, { withFileTypes: true });
70
-
71
- for (const entry of entries) {
72
- const fullPath = path.join(dirPath, entry.name);
73
-
74
- if (entry.isDirectory()) {
75
- files.push(...this.getAllFiles(fullPath));
76
- } else if (entry.isFile() && entry.name.endsWith('.js')) {
77
- files.push(fullPath);
78
- }
79
- }
80
-
81
- return files;
82
- }
83
-
84
- async checkFile(filePath) {
85
- const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
86
-
87
- for (const allowed of this.allowedFiles) {
88
- if (relativePath.startsWith(allowed)) {
89
- return;
90
- }
91
- }
92
-
93
- const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
94
- const lines = content.split('\n');
95
-
96
- for (let i = 0; i < lines.length; i++) {
97
- const line = lines[i];
98
-
99
- for (const pattern of this.forbiddenPatterns) {
100
- if (pattern.test(line)) {
101
- this.violations.push({
102
- file: relativePath,
103
- line: i + 1,
104
- pattern: pattern.toString()
105
- });
106
- }
107
- }
108
- }
109
- }
110
- }
111
-
112
- if (require.main === module) {
113
- const check = new SecurityCheck();
114
- check.run().catch(console.error);
115
- }
116
-
117
- module.exports = SecurityCheck;
@@ -1,151 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Translation Sync Script
5
- *
6
- * This script completely replaces all foreign language UI translation files with English content,
7
- * except for Chinese (zh) which is already fully translated.
8
- * This shows engineers exactly what needs to be translated without any prefixes.
9
- *
10
- * Usage: node scripts/sync-translations.js
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
-
16
- // Configuration
17
- const UI_LOCALES_DIR = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales');
18
- const ENGLISH_DIR = path.join(UI_LOCALES_DIR, 'en');
19
- const TARGET_LANGUAGES = ['de', 'es', 'fr', 'ru', 'ja']; // Exclude Chinese (zh) as it's fully translated
20
-
21
- /**
22
- * Get all JSON files in the English directory
23
- */
24
- function getEnglishFiles() {
25
- try {
26
- const files = fs.readdirSync(ENGLISH_DIR);
27
- return files.filter(file => file.endsWith('.json'));
28
- } catch (error) {
29
- console.error('Error reading English directory:', error.message);
30
- return [];
31
- }
32
- }
33
-
34
- /**
35
- * Read and parse JSON file
36
- */
37
- function readJsonFile(filePath) {
38
- try {
39
- const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
40
- return JSON.parse(content);
41
- } catch (error) {
42
- console.error(`Error reading JSON file ${filePath}:`, error.message);
43
- return {};
44
- }
45
- }
46
-
47
- /**
48
- * Write JSON file with proper formatting
49
- */
50
- function writeJsonFile(filePath, data) {
51
- try {
52
- const jsonString = JSON.stringify(data, null, 2);
53
- SecurityUtils.safeWriteFileSync(filePath, jsonString + '\n');
54
- return true;
55
- } catch (error) {
56
- console.error(`Error writing JSON file ${filePath}:`, error.message);
57
- return false;
58
- }
59
- }
60
-
61
- /**
62
- * Copy English content directly to target language
63
- */
64
- function copyEnglishContent(source) {
65
- return JSON.parse(JSON.stringify(source)); // Deep copy of English content
66
- }
67
-
68
- /**
69
- * Sync translations for a specific language - completely replace with English
70
- */
71
- function syncLanguage(language) {
72
- console.log(`🔄 Syncing translations for ${language}...`);
73
-
74
- const languageDir = path.join(UI_LOCALES_DIR, language);
75
-
76
- // Ensure language directory exists
77
- if (!SecurityUtils.safeExistsSync(languageDir)) {
78
- fs.mkdirSync(languageDir, { recursive: true });
79
- }
80
-
81
- const englishFiles = getEnglishFiles();
82
- let syncedFiles = 0;
83
-
84
- for (const file of englishFiles) {
85
- const englishFilePath = path.join(ENGLISH_DIR, file);
86
- const targetFilePath = path.join(languageDir, file);
87
-
88
- const englishData = readJsonFile(englishFilePath);
89
-
90
- // Directly copy English content (no merging, no prefixes)
91
- const newData = copyEnglishContent(englishData);
92
-
93
- // Write updated file (completely replaces existing content)
94
- if (writeJsonFile(targetFilePath, newData)) {
95
- syncedFiles++;
96
- console.log(` ✅ ${file}: Replaced with English content`);
97
- }
98
- }
99
-
100
- console.log(`✅ ${language}: Synced ${syncedFiles} files with English content`);
101
- return { language, syncedFiles };
102
- }
103
-
104
- /**
105
- * Main sync function
106
- */
107
- function syncAllTranslations() {
108
- console.log('🌍 Starting translation synchronization...');
109
- console.log(`📁 Source: ${ENGLISH_DIR}`);
110
- console.log(`đŸŽ¯ Target languages: ${TARGET_LANGUAGES.join(', ')}`);
111
- console.log(`â„šī¸ Chinese (zh) skipped - already fully translated`);
112
- console.log('');
113
-
114
- const englishFiles = getEnglishFiles();
115
- if (englishFiles.length === 0) {
116
- console.error('❌ No English translation files found!');
117
- process.exit(1);
118
- }
119
-
120
- console.log(`📋 Found ${englishFiles.length} English files to sync:`);
121
- englishFiles.forEach(file => console.log(` 📄 ${file}`));
122
- console.log('');
123
-
124
- const results = [];
125
- for (const language of TARGET_LANGUAGES) {
126
- const result = syncLanguage(language);
127
- results.push(result);
128
- }
129
-
130
- console.log('');
131
- console.log('🎉 Translation synchronization completed!');
132
- console.log('');
133
- console.log('📊 Summary:');
134
- results.forEach(result => {
135
- console.log(` ${result.language}: ${result.syncedFiles} files updated`);
136
- });
137
-
138
- console.log('');
139
- console.log('💡 Next steps:');
140
- console.log(' 1. All non-Chinese locales now contain English content');
141
- console.log(' 2. Engineers can see exactly what text needs translation');
142
- console.log(' 3. Translate each string to the target language');
143
- console.log(' 4. Follow the same practices as the Chinese locale');
144
- }
145
-
146
- // Run the sync
147
- if (require.main === module) {
148
- syncAllTranslations();
149
- }
150
-
151
- module.exports = { syncAllTranslations, syncLanguage };
@@ -1,20 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const SecurityUtils = require('../utils/security');
4
- const baseDir = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales');
5
- const en = JSON.parse(SecurityUtils.safeReadFileSync(path.join(baseDir,'en.json'), baseDir, 'utf8'));
6
- function flatten(obj,pfx='',out={}){ for(const [k,v] of Object.entries(obj)){ const nk=pfx?`${pfx}.${k}`:k; if(v && typeof v==='object' && !Array.isArray(v)) flatten(v,nk,out); else out[nk]=v; } return out; }
7
- function unflatten(map){ const root={}; for(const [k,v] of Object.entries(map)){ const parts=k.split('.'); let cur=root; while(parts.length>1){ const p=parts.shift(); cur[p]=cur[p]||{}; cur=cur[p]; } cur[parts[0]]=v; } return root; }
8
- const enFlat = flatten(en);
9
- for (const file of fs.readdirSync(baseDir)) {
10
- if (!file.endsWith('.json') || file==='en.json') continue;
11
- const p = path.join(baseDir,file);
12
- const data = JSON.parse(SecurityUtils.safeReadFileSync(p, baseDir, 'utf8'));
13
- const flat = flatten(data);
14
- let changed = false;
15
- for (const [k,v] of Object.entries(enFlat)) {
16
- if (!(k in flat)) { flat[k] = v || 'NOT_TRANSLATED'; changed = true; }
17
- }
18
- if (changed) { SecurityUtils.safeWriteFileSync(p, JSON.stringify(unflatten(flat), null, 2), path.dirname(p), 'utf8'); }
19
- }
20
- console.log('UI locales synced.');