i18ntk 1.0.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.
- package/CHANGELOG.md +401 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/dev/README.md +37 -0
- package/dev/debug/README.md +30 -0
- package/dev/debug/complete-console-translations.js +295 -0
- package/dev/debug/console-key-checker.js +408 -0
- package/dev/debug/console-translations.js +335 -0
- package/dev/debug/debugger.js +408 -0
- package/dev/debug/export-missing-keys.js +432 -0
- package/dev/debug/final-normalize.js +236 -0
- package/dev/debug/find-extra-keys.js +68 -0
- package/dev/debug/normalize-locales.js +153 -0
- package/dev/debug/refactor-locales.js +240 -0
- package/dev/debug/reorder-locales.js +85 -0
- package/dev/debug/replace-hardcoded-console.js +378 -0
- package/docs/INSTALLATION.md +449 -0
- package/docs/README.md +222 -0
- package/docs/TODO_ROADMAP.md +279 -0
- package/docs/api/API_REFERENCE.md +377 -0
- package/docs/api/COMPONENTS.md +492 -0
- package/docs/api/CONFIGURATION.md +651 -0
- package/docs/api/NPM_PUBLISHING_GUIDE.md +434 -0
- package/docs/debug/DEBUG_README.md +30 -0
- package/docs/debug/DEBUG_TOOLS.md +494 -0
- package/docs/development/AGENTS.md +351 -0
- package/docs/development/DEVELOPMENT_RULES.md +165 -0
- package/docs/development/DEV_README.md +37 -0
- package/docs/release-notes/RELEASE_NOTES_v1.0.0.md +173 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.0.md +141 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.1.md +185 -0
- package/docs/release-notes/RELEASE_NOTES_v1.6.3.md +199 -0
- package/docs/reports/ANALYSIS_README.md +17 -0
- package/docs/reports/CONSOLE_MISMATCH_BUG_REPORT_v1.5.0.md +181 -0
- package/docs/reports/SIZING_README.md +18 -0
- package/docs/reports/SUMMARY_README.md +18 -0
- package/docs/reports/TRANSLATION_BUG_REPORT_v1.5.0.md +129 -0
- package/docs/reports/USAGE_README.md +18 -0
- package/docs/reports/VALIDATION_README.md +18 -0
- package/locales/de/auth.json +3 -0
- package/locales/de/common.json +16 -0
- package/locales/de/pagination.json +6 -0
- package/locales/en/auth.json +3 -0
- package/locales/en/common.json +16 -0
- package/locales/en/pagination.json +6 -0
- package/locales/es/auth.json +3 -0
- package/locales/es/common.json +16 -0
- package/locales/es/pagination.json +6 -0
- package/locales/fr/auth.json +3 -0
- package/locales/fr/common.json +16 -0
- package/locales/fr/pagination.json +6 -0
- package/locales/ru/auth.json +3 -0
- package/locales/ru/common.json +16 -0
- package/locales/ru/pagination.json +6 -0
- package/main/i18ntk-analyze.js +625 -0
- package/main/i18ntk-autorun.js +461 -0
- package/main/i18ntk-complete.js +494 -0
- package/main/i18ntk-init.js +686 -0
- package/main/i18ntk-manage.js +848 -0
- package/main/i18ntk-sizing.js +557 -0
- package/main/i18ntk-summary.js +671 -0
- package/main/i18ntk-usage.js +1282 -0
- package/main/i18ntk-validate.js +762 -0
- package/main/ui-i18n.js +332 -0
- package/package.json +152 -0
- package/scripts/fix-missing-translation-keys.js +214 -0
- package/scripts/verify-package.js +168 -0
- package/ui-locales/de.json +637 -0
- package/ui-locales/en.json +688 -0
- package/ui-locales/es.json +637 -0
- package/ui-locales/fr.json +637 -0
- package/ui-locales/ja.json +637 -0
- package/ui-locales/ru.json +637 -0
- package/ui-locales/zh.json +637 -0
- package/utils/admin-auth.js +317 -0
- package/utils/admin-cli.js +353 -0
- package/utils/admin-pin.js +409 -0
- package/utils/detect-language-mismatches.js +454 -0
- package/utils/i18n-helper.js +128 -0
- package/utils/maintain-language-purity.js +433 -0
- package/utils/native-translations.js +478 -0
- package/utils/security.js +384 -0
- package/utils/test-complete-system.js +356 -0
- package/utils/test-console-i18n.js +402 -0
- package/utils/translate-mismatches.js +571 -0
- package/utils/validate-language-purity.js +531 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* I18N TRANSLATION COMPLETION SCRIPT
|
|
4
|
+
*
|
|
5
|
+
* This script automatically adds missing translation keys to achieve 100% coverage.
|
|
6
|
+
* It reads the usage analysis and adds all missing keys with proper markers.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node i18ntk-complete.js
|
|
10
|
+
* node i18ntk-complete.js --auto-translate
|
|
11
|
+
* node i18ntk-complete.js --source-dir=./src/i18n/locales
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
const SecurityUtils = require('../utils/security');
|
|
18
|
+
const settingsManager = require('../settings/settings-manager');
|
|
19
|
+
|
|
20
|
+
// Get configuration from settings manager securely
|
|
21
|
+
async function getConfig() {
|
|
22
|
+
try {
|
|
23
|
+
const settings = await settingsManager.getSettings();
|
|
24
|
+
const config = {
|
|
25
|
+
sourceDir: settings.directories?.sourceDir || './locales',
|
|
26
|
+
sourceLanguage: settings.directories?.sourceLanguage || 'en',
|
|
27
|
+
notTranslatedMarker: settings.directories?.notTranslatedMarker || 'NOT_TRANSLATED',
|
|
28
|
+
excludeFiles: settings.processing?.excludeFiles || ['.DS_Store', 'Thumbs.db']
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Basic validation for required fields
|
|
32
|
+
if (!config.sourceDir || !config.sourceLanguage) {
|
|
33
|
+
throw new Error('Configuration validation failed: missing required fields');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
SecurityUtils.logSecurityEvent('Configuration loaded successfully', 'info');
|
|
37
|
+
return config;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
SecurityUtils.logSecurityEvent('Configuration loading failed', 'error', { error: error.message });
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Common missing keys that should be added to all projects
|
|
45
|
+
const COMMON_MISSING_KEYS = {
|
|
46
|
+
// Offline/Network
|
|
47
|
+
'offlineTitle': 'You are offline',
|
|
48
|
+
'offlineMessage': 'Please check your internet connection',
|
|
49
|
+
'tryReconnect': 'Try to reconnect',
|
|
50
|
+
|
|
51
|
+
// Common UI
|
|
52
|
+
'common': 'Common',
|
|
53
|
+
'logout': 'Logout',
|
|
54
|
+
'login': 'Login',
|
|
55
|
+
'amount': 'Amount',
|
|
56
|
+
|
|
57
|
+
// Pagination
|
|
58
|
+
'pagination.showing': 'Showing',
|
|
59
|
+
'pagination.of': 'of',
|
|
60
|
+
'pagination.items': 'items',
|
|
61
|
+
'pagination.rowsPerPage': 'Rows per page',
|
|
62
|
+
|
|
63
|
+
// Report Generator
|
|
64
|
+
'reportGenerator:reportTypes.prospects': 'Prospects Report',
|
|
65
|
+
'reportGenerator:reportTypes.activities': 'Activities Report',
|
|
66
|
+
'reportGenerator:reportTypes.goals': 'Goals Report',
|
|
67
|
+
'reportGenerator:reportTypes.team': 'Team Report',
|
|
68
|
+
'reportGenerator:reportTypes.hotLeads': 'Hot Leads Report',
|
|
69
|
+
|
|
70
|
+
'reportGenerator:timePeriods.allTime': 'All Time',
|
|
71
|
+
'reportGenerator:timePeriods.last7days': 'Last 7 Days',
|
|
72
|
+
'reportGenerator:timePeriods.last30days': 'Last 30 Days',
|
|
73
|
+
'reportGenerator:timePeriods.thisYear': 'This Year',
|
|
74
|
+
|
|
75
|
+
'reportGenerator:layouts.summary': 'Summary Layout',
|
|
76
|
+
'reportGenerator:layouts.detailed': 'Detailed Layout',
|
|
77
|
+
'reportGenerator:layouts.visual_charts': 'Visual Charts Layout',
|
|
78
|
+
|
|
79
|
+
// Common time periods
|
|
80
|
+
'common:timePeriods.customRange': 'Custom Range',
|
|
81
|
+
|
|
82
|
+
// Auth
|
|
83
|
+
'auth:supabaseNotConfigured': 'Supabase is not configured',
|
|
84
|
+
|
|
85
|
+
// Common values
|
|
86
|
+
'common:unknownUser': 'Unknown User',
|
|
87
|
+
'common:notSet': 'Not Set',
|
|
88
|
+
|
|
89
|
+
// Validation
|
|
90
|
+
'validationStep.invalidEmailFormat': 'Invalid email format',
|
|
91
|
+
|
|
92
|
+
// Admin
|
|
93
|
+
'announcement_id': 'Announcement ID',
|
|
94
|
+
'last_sign_in_at': 'Last Sign In'
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
class I18nCompletionTool {
|
|
98
|
+
constructor(config = {}) {
|
|
99
|
+
this.config = config;
|
|
100
|
+
this.sourceDir = null;
|
|
101
|
+
this.sourceLanguageDir = null;
|
|
102
|
+
|
|
103
|
+
// Initialize UI i18n for console messages
|
|
104
|
+
const UIi18n = require('./ui-i18n');
|
|
105
|
+
this.ui = new UIi18n();
|
|
106
|
+
this.t = this.ui.t.bind(this.ui);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async initialize() {
|
|
110
|
+
const baseConfig = await getConfig();
|
|
111
|
+
this.config = { ...baseConfig, ...this.config };
|
|
112
|
+
this.sourceDir = path.resolve(this.config.sourceDir);
|
|
113
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse command line arguments
|
|
117
|
+
parseArgs() {
|
|
118
|
+
const args = process.argv.slice(2);
|
|
119
|
+
const parsed = {};
|
|
120
|
+
|
|
121
|
+
args.forEach(arg => {
|
|
122
|
+
if (arg.startsWith('--')) {
|
|
123
|
+
const [key, value] = arg.substring(2).split('=');
|
|
124
|
+
if (key === 'source-dir') {
|
|
125
|
+
parsed.sourceDir = value;
|
|
126
|
+
} else if (key === 'auto-translate') {
|
|
127
|
+
parsed.autoTranslate = true;
|
|
128
|
+
} else if (key === 'dry-run') {
|
|
129
|
+
parsed.dryRun = true;
|
|
130
|
+
} else if (key === 'no-prompt') {
|
|
131
|
+
parsed.noPrompt = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Get all available languages
|
|
140
|
+
getAvailableLanguages() {
|
|
141
|
+
if (!fs.existsSync(this.sourceDir)) {
|
|
142
|
+
throw new Error(`Source directory not found: ${this.sourceDir}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return fs.readdirSync(this.sourceDir)
|
|
146
|
+
.filter(item => {
|
|
147
|
+
const itemPath = path.join(this.sourceDir, item);
|
|
148
|
+
return fs.statSync(itemPath).isDirectory();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Get all JSON files from a language directory
|
|
153
|
+
getLanguageFiles(language) {
|
|
154
|
+
const languageDir = path.join(this.sourceDir, language);
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync(languageDir)) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return fs.readdirSync(languageDir)
|
|
161
|
+
.filter(file => {
|
|
162
|
+
return file.endsWith('.json') &&
|
|
163
|
+
!this.config.excludeFiles.includes(file);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Parse key path and determine which file it belongs to
|
|
168
|
+
parseKeyPath(keyPath) {
|
|
169
|
+
// Handle namespace:key format (e.g., "reportGenerator:reportTypes.prospects")
|
|
170
|
+
if (keyPath.includes(':')) {
|
|
171
|
+
const [namespace, key] = keyPath.split(':', 2);
|
|
172
|
+
return {
|
|
173
|
+
file: `${namespace}.json`,
|
|
174
|
+
key: key
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Handle dot notation (e.g., "pagination.showing")
|
|
179
|
+
const parts = keyPath.split('.');
|
|
180
|
+
if (parts.length > 1) {
|
|
181
|
+
return {
|
|
182
|
+
file: `${parts[0]}.json`,
|
|
183
|
+
key: parts.slice(1).join('.')
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Default to common.json for simple keys
|
|
188
|
+
return {
|
|
189
|
+
file: 'common.json',
|
|
190
|
+
key: keyPath
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check if nested key exists in object
|
|
195
|
+
hasNestedKey(obj, keyPath) {
|
|
196
|
+
const keys = keyPath.split('.');
|
|
197
|
+
let current = obj;
|
|
198
|
+
|
|
199
|
+
for (const key of keys) {
|
|
200
|
+
if (!current || typeof current !== 'object' || !(key in current)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
current = current[key];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Set nested value in object
|
|
210
|
+
setNestedValue(obj, keyPath, value) {
|
|
211
|
+
const keys = keyPath.split('.');
|
|
212
|
+
let current = obj;
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
215
|
+
const key = keys[i];
|
|
216
|
+
if (!(key in current) || typeof current[key] !== 'object') {
|
|
217
|
+
current[key] = {};
|
|
218
|
+
}
|
|
219
|
+
current = current[key];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
current[keys[keys.length - 1]] = value;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Add missing keys to a language
|
|
226
|
+
addMissingKeysToLanguage(language, missingKeys, dryRun = false) {
|
|
227
|
+
const languageDir = path.join(this.sourceDir, language);
|
|
228
|
+
const changes = [];
|
|
229
|
+
|
|
230
|
+
// Group keys by file
|
|
231
|
+
const keysByFile = {};
|
|
232
|
+
|
|
233
|
+
missingKeys.forEach(keyPath => {
|
|
234
|
+
const { file, key } = this.parseKeyPath(keyPath);
|
|
235
|
+
if (!keysByFile[file]) {
|
|
236
|
+
keysByFile[file] = [];
|
|
237
|
+
}
|
|
238
|
+
keysByFile[file].push({ keyPath, key });
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Process each file
|
|
242
|
+
for (const [fileName, keys] of Object.entries(keysByFile)) {
|
|
243
|
+
const filePath = path.join(languageDir, fileName);
|
|
244
|
+
let fileContent = {};
|
|
245
|
+
|
|
246
|
+
// Load existing file or create new
|
|
247
|
+
if (fs.existsSync(filePath)) {
|
|
248
|
+
try {
|
|
249
|
+
fileContent = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.warn(this.t("completeTranslations.warning_could_not_parse_filepa", { filePath })); ;
|
|
252
|
+
fileContent = {};
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
// Create directory if it doesn't exist
|
|
256
|
+
if (!fs.existsSync(languageDir)) {
|
|
257
|
+
if (!dryRun) {
|
|
258
|
+
fs.mkdirSync(languageDir, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Add missing keys
|
|
264
|
+
let fileChanged = false;
|
|
265
|
+
keys.forEach(({ keyPath, key }) => {
|
|
266
|
+
// Check if key already exists
|
|
267
|
+
if (!this.hasNestedKey(fileContent, key)) {
|
|
268
|
+
const value = this.generateTranslationValue(keyPath, language);
|
|
269
|
+
|
|
270
|
+
this.setNestedValue(fileContent, key, value);
|
|
271
|
+
fileChanged = true;
|
|
272
|
+
|
|
273
|
+
changes.push({
|
|
274
|
+
file: fileName,
|
|
275
|
+
key: keyPath,
|
|
276
|
+
value,
|
|
277
|
+
action: 'added'
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Save file
|
|
283
|
+
if (fileChanged && !dryRun) {
|
|
284
|
+
fs.writeFileSync(filePath, JSON.stringify(fileContent, null, 2), 'utf8');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return changes;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Generate appropriate translation value based on key and language
|
|
292
|
+
generateTranslationValue(keyPath, language) {
|
|
293
|
+
// Use English as base value from COMMON_MISSING_KEYS or generate from key
|
|
294
|
+
const baseValue = COMMON_MISSING_KEYS[keyPath] || this.generateValueFromKey(keyPath);
|
|
295
|
+
|
|
296
|
+
// For source language, use the base value (never use namespace prefix)
|
|
297
|
+
if (language === this.config.sourceLanguage) {
|
|
298
|
+
return baseValue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// For other languages, use the not translated marker
|
|
302
|
+
return 'NOT_TRANSLATED';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Generate a readable value from a key path
|
|
306
|
+
generateValueFromKey(keyPath) {
|
|
307
|
+
// Extract the last part of the key (after dots and colons)
|
|
308
|
+
const keyName = keyPath.split('.').pop().split(':').pop();
|
|
309
|
+
|
|
310
|
+
// Convert camelCase to readable text
|
|
311
|
+
const readable = keyName
|
|
312
|
+
.replace(/([A-Z])/g, ' $1')
|
|
313
|
+
.replace(/^./, str => str.toUpperCase())
|
|
314
|
+
.trim();
|
|
315
|
+
|
|
316
|
+
// Never return the namespace prefix as the value
|
|
317
|
+
return readable || keyName;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Get missing keys from usage analysis
|
|
321
|
+
getMissingKeysFromUsage() {
|
|
322
|
+
const usageReportPath = path.join(process.cwd(), 'scripts', 'i18n', 'reports', 'usage-analysis.txt');
|
|
323
|
+
|
|
324
|
+
// Delete old report to ensure fresh data
|
|
325
|
+
if (fs.existsSync(usageReportPath)) {
|
|
326
|
+
console.log(this.t("operations.complete.deletingOldReport"));
|
|
327
|
+
fs.unlinkSync(usageReportPath);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Generate fresh usage analysis report
|
|
331
|
+
console.log(this.t("operations.complete.generatingFreshAnalysis"));
|
|
332
|
+
const { execSync } = require('child_process');
|
|
333
|
+
try {
|
|
334
|
+
execSync('node main/i18ntk-usage.js --output-report', {
|
|
335
|
+
stdio: 'inherit',
|
|
336
|
+
cwd: process.cwd()
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.log(this.t("operations.complete.couldNotGenerate"));
|
|
340
|
+
return Object.keys(COMMON_MISSING_KEYS);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!fs.existsSync(usageReportPath)) {
|
|
344
|
+
console.log(this.t("operations.complete.reportNotFound"));
|
|
345
|
+
return Object.keys(COMMON_MISSING_KEYS);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const reportContent = fs.readFileSync(usageReportPath, 'utf8');
|
|
350
|
+
const missingKeys = [];
|
|
351
|
+
|
|
352
|
+
// Extract missing keys from the report
|
|
353
|
+
const lines = reportContent.split('\n');
|
|
354
|
+
let inMissingSection = false;
|
|
355
|
+
|
|
356
|
+
for (const line of lines) {
|
|
357
|
+
if (line.includes('MISSING TRANSLATION KEYS')) {
|
|
358
|
+
inMissingSection = true;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (inMissingSection) {
|
|
363
|
+
// Stop when we reach another section
|
|
364
|
+
if (line.includes('DYNAMIC KEYS') || line.includes('USED KEYS') || line.includes('UNUSED KEYS')) {
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Extract key from lines like "⚠️ offlineTitle"
|
|
369
|
+
const match = line.match(/^⚠️\s+(.+)$/);
|
|
370
|
+
if (match) {
|
|
371
|
+
const key = match[1].trim();
|
|
372
|
+
// Skip lines that are file paths or other metadata
|
|
373
|
+
if (!key.includes('Used in:') && !key.includes('📄') && key.length > 0 && !missingKeys.includes(key)) {
|
|
374
|
+
missingKeys.push(key);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log(this.t("operations.complete.foundMissingKeys", { count: missingKeys.length }));
|
|
381
|
+
return missingKeys.length > 0 ? missingKeys : Object.keys(COMMON_MISSING_KEYS);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.log(this.t("operations.complete.couldNotParse"));
|
|
384
|
+
return Object.keys(COMMON_MISSING_KEYS);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Run the completion process
|
|
389
|
+
async run() {
|
|
390
|
+
SecurityUtils.logSecurityEvent('I18n completion tool started', 'info', {
|
|
391
|
+
version: '1.3.7',
|
|
392
|
+
nodeVersion: process.version,
|
|
393
|
+
platform: process.platform
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await this.initialize();
|
|
397
|
+
|
|
398
|
+
const args = this.parseArgs();
|
|
399
|
+
|
|
400
|
+
if (args.sourceDir) {
|
|
401
|
+
this.config.sourceDir = args.sourceDir;
|
|
402
|
+
this.sourceDir = path.resolve(this.config.sourceDir);
|
|
403
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
console.log(this.t("operations.complete.title"));
|
|
407
|
+
console.log(this.t("operations.complete.separator"));
|
|
408
|
+
console.log(this.t("operations.complete.sourceDir", { sourceDir: this.sourceDir }));
|
|
409
|
+
console.log(this.t("operations.complete.sourceLanguage", { sourceLanguage: this.config.sourceLanguage }));
|
|
410
|
+
|
|
411
|
+
if (args.dryRun) {
|
|
412
|
+
console.log(this.t("operations.complete.dryRunMode"));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
// Get available languages
|
|
417
|
+
const languages = this.getAvailableLanguages();
|
|
418
|
+
console.log(this.t("operations.complete.languages", { languages: languages.join(', ') }));
|
|
419
|
+
|
|
420
|
+
// Get missing keys from usage analysis or use common keys
|
|
421
|
+
const missingKeys = this.getMissingKeysFromUsage();
|
|
422
|
+
console.log(this.t("operations.complete.addingMissingKeys"));
|
|
423
|
+
|
|
424
|
+
let totalChanges = 0;
|
|
425
|
+
|
|
426
|
+
// Process each language
|
|
427
|
+
for (const language of languages) {
|
|
428
|
+
console.log(this.t("operations.complete.processing", { language }));
|
|
429
|
+
|
|
430
|
+
const changes = this.addMissingKeysToLanguage(language, missingKeys, args.dryRun);
|
|
431
|
+
|
|
432
|
+
if (changes.length > 0) {
|
|
433
|
+
console.log(this.t("operations.complete.addedKeys", { count: changes.length }));
|
|
434
|
+
totalChanges += changes.length;
|
|
435
|
+
|
|
436
|
+
// Show sample of changes
|
|
437
|
+
const sampleChanges = changes.slice(0, 3);
|
|
438
|
+
sampleChanges.forEach(change => {
|
|
439
|
+
console.log(this.t("operations.complete.changeDetails", { file: change.file, key: change.key }));
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
if (changes.length > 3) {
|
|
443
|
+
console.log(this.t("complete.andMore", { count: changes.length - 3 }));
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
console.log(this.t("operations.complete.noChangesNeeded", { language }));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
console.log('\n');
|
|
451
|
+
console.log(this.t("operations.complete.summaryTitle"));
|
|
452
|
+
console.log(this.t("operations.complete.separator"));
|
|
453
|
+
console.log(this.t("operations.complete.totalChanges", { totalChanges }));
|
|
454
|
+
console.log(this.t("operations.complete.languagesProcessed", { languagesProcessed: languages.length }));
|
|
455
|
+
console.log(this.t("operations.complete.missingKeysAdded", { missingKeysAdded: missingKeys.length }));
|
|
456
|
+
|
|
457
|
+
if (!args.dryRun) {
|
|
458
|
+
console.log('\n' + this.t("operations.complete.nextStepsTitle"));
|
|
459
|
+
console.log(this.t("operations.complete.separator"));
|
|
460
|
+
console.log(this.t("operations.complete.nextStep1"));
|
|
461
|
+
console.log(' node i18ntk-usage.js --output-report');
|
|
462
|
+
console.log(this.t("complete.nextStep2"));
|
|
463
|
+
console.log(' node i18ntk-validate.js');
|
|
464
|
+
console.log(this.t("complete.nextStep3"));
|
|
465
|
+
console.log(' node i18ntk-analyze.js');
|
|
466
|
+
console.log('\n' + this.t("operations.complete.allKeysAvailable"));
|
|
467
|
+
} else {
|
|
468
|
+
console.log('\n' + this.t("operations.complete.runWithoutDryRun"));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Only prompt when run from the menu (i.e., when a callback or menu context is present)
|
|
472
|
+
if (typeof this.prompt === "function" && args.fromMenu) {
|
|
473
|
+
console.log(this.ui.t('operations.completed'));
|
|
474
|
+
await this.prompt(this.ui.t('hardcodedTexts.pressEnterToContinue'));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error('Error during completion:', error.message);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Run if called directly
|
|
485
|
+
if (require.main === module) {
|
|
486
|
+
const tool = new I18nCompletionTool();
|
|
487
|
+
tool.run().catch(error => {
|
|
488
|
+
console.error('Fatal error:', error.message);
|
|
489
|
+
SecurityUtils.logSecurityEvent('I18n completion tool failed', 'error', { error: error.message });
|
|
490
|
+
process.exit(1);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = I18nCompletionTool;
|