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,531 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Language Purity Validator
|
|
5
|
+
*
|
|
6
|
+
* This script validates that each locale file contains only content in its native language.
|
|
7
|
+
* It can be integrated into CI/CD pipelines and development workflows.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
class LanguagePurityValidator {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.localesDir = path.join(__dirname, 'ui-locales');
|
|
16
|
+
this.sourceLanguage = 'en';
|
|
17
|
+
this.exitCode = 0;
|
|
18
|
+
|
|
19
|
+
// Language validation rules
|
|
20
|
+
this.validationRules = {
|
|
21
|
+
de: {
|
|
22
|
+
name: 'German',
|
|
23
|
+
allowedMarkers: [], // No markers should remain
|
|
24
|
+
forbiddenMarkers: ['[TRANSLATE]', '[DE]', '[NOT TRANSLATED]'],
|
|
25
|
+
forbiddenPhrases: [
|
|
26
|
+
'debug tools', 'settings', 'configuration', 'invalid choice',
|
|
27
|
+
'please select', 'back to main menu', 'error', 'warning',
|
|
28
|
+
'success', 'failed', 'loading', 'saving', 'full system debug',
|
|
29
|
+
'configuration debug', 'translation debug', 'performance debug',
|
|
30
|
+
'admin pin setup', 'enter admin pin', 'confirm admin pin',
|
|
31
|
+
'authentication failed', 'access denied', 'security log'
|
|
32
|
+
],
|
|
33
|
+
requiredCharacteristics: {
|
|
34
|
+
// German-specific characteristics
|
|
35
|
+
hasGermanArticles: ['der', 'die', 'das', 'ein', 'eine'],
|
|
36
|
+
hasGermanConjunctions: ['und', 'oder', 'aber'],
|
|
37
|
+
hasGermanPrepositions: ['mit', 'von', 'zu', 'für', 'auf', 'in', 'an']
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
fr: {
|
|
41
|
+
name: 'French',
|
|
42
|
+
allowedMarkers: [],
|
|
43
|
+
forbiddenMarkers: ['[TRANSLATE]', '[FR]', '[NOT TRANSLATED]'],
|
|
44
|
+
forbiddenPhrases: [
|
|
45
|
+
'debug tools', 'settings', 'configuration', 'invalid choice',
|
|
46
|
+
'please select', 'back to main menu', 'error', 'warning',
|
|
47
|
+
'success', 'failed', 'loading', 'saving', 'full system debug',
|
|
48
|
+
'configuration debug', 'translation debug', 'performance debug',
|
|
49
|
+
'admin pin setup', 'enter admin pin', 'confirm admin pin',
|
|
50
|
+
'authentication failed', 'access denied', 'security log'
|
|
51
|
+
],
|
|
52
|
+
requiredCharacteristics: {
|
|
53
|
+
// French-specific characteristics
|
|
54
|
+
hasFrenchArticles: ['le', 'la', 'les', 'un', 'une', 'des'],
|
|
55
|
+
hasFrenchConjunctions: ['et', 'ou', 'mais'],
|
|
56
|
+
hasFrenchPrepositions: ['de', 'du', 'pour', 'sur', 'dans', 'par', 'avec']
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
es: {
|
|
60
|
+
name: 'Spanish',
|
|
61
|
+
allowedMarkers: [],
|
|
62
|
+
forbiddenMarkers: ['[TRANSLATE]', '[ES]', '[NOT TRANSLATED]'],
|
|
63
|
+
forbiddenPhrases: [
|
|
64
|
+
'debug tools', 'settings', 'configuration', 'invalid choice',
|
|
65
|
+
'please select', 'back to main menu', 'error', 'warning',
|
|
66
|
+
'success', 'failed', 'loading', 'saving', 'full system debug',
|
|
67
|
+
'configuration debug', 'translation debug', 'performance debug',
|
|
68
|
+
'admin pin setup', 'enter admin pin', 'confirm admin pin',
|
|
69
|
+
'authentication failed', 'access denied', 'security log'
|
|
70
|
+
],
|
|
71
|
+
requiredCharacteristics: {
|
|
72
|
+
// Spanish-specific characteristics
|
|
73
|
+
hasSpanishArticles: ['el', 'la', 'los', 'las', 'un', 'una'],
|
|
74
|
+
hasSpanishConjunctions: ['y', 'o', 'pero'],
|
|
75
|
+
hasSpanishPrepositions: ['de', 'del', 'para', 'por', 'en', 'a', 'con']
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
ru: {
|
|
79
|
+
name: 'Russian',
|
|
80
|
+
allowedMarkers: [],
|
|
81
|
+
forbiddenMarkers: ['[TRANSLATE]', '[RU]', '[NOT TRANSLATED]'],
|
|
82
|
+
forbiddenPhrases: [
|
|
83
|
+
'debug tools', 'settings', 'configuration', 'invalid choice',
|
|
84
|
+
'please select', 'back to main menu', 'error', 'warning',
|
|
85
|
+
'success', 'failed', 'loading', 'saving', 'full system debug',
|
|
86
|
+
'configuration debug', 'translation debug', 'performance debug',
|
|
87
|
+
'admin pin setup', 'enter admin pin', 'confirm admin pin',
|
|
88
|
+
'authentication failed', 'access denied', 'security log'
|
|
89
|
+
],
|
|
90
|
+
requiredCharacteristics: {
|
|
91
|
+
// Russian-specific characteristics (Cyrillic)
|
|
92
|
+
hasCyrillic: true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
ja: {
|
|
96
|
+
name: 'Japanese',
|
|
97
|
+
allowedMarkers: [],
|
|
98
|
+
forbiddenMarkers: ['[TRANSLATE]', '[JA]', '[NOT TRANSLATED]'],
|
|
99
|
+
forbiddenPhrases: [
|
|
100
|
+
'debug tools', 'settings', 'configuration', 'invalid choice',
|
|
101
|
+
'please select', 'back to main menu', 'error', 'warning',
|
|
102
|
+
'success', 'failed', 'loading', 'saving', 'full system debug',
|
|
103
|
+
'configuration debug', 'translation debug', 'performance debug',
|
|
104
|
+
'admin pin setup', 'enter admin pin', 'confirm admin pin',
|
|
105
|
+
'authentication failed', 'access denied', 'security log'
|
|
106
|
+
],
|
|
107
|
+
requiredCharacteristics: {
|
|
108
|
+
// Japanese-specific characteristics
|
|
109
|
+
hasJapanese: true // Hiragana, Katakana, or Kanji
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
zh: {
|
|
113
|
+
name: 'Chinese',
|
|
114
|
+
allowedMarkers: [],
|
|
115
|
+
forbiddenMarkers: ['[TRANSLATE]', '[ZH]', '[NOT TRANSLATED]'],
|
|
116
|
+
forbiddenPhrases: [
|
|
117
|
+
'debug tools', 'settings', 'configuration', 'invalid choice',
|
|
118
|
+
'please select', 'back to main menu', 'error', 'warning',
|
|
119
|
+
'success', 'failed', 'loading', 'saving', 'full system debug',
|
|
120
|
+
'configuration debug', 'translation debug', 'performance debug',
|
|
121
|
+
'admin pin setup', 'enter admin pin', 'confirm admin pin',
|
|
122
|
+
'authentication failed', 'access denied', 'security log'
|
|
123
|
+
],
|
|
124
|
+
requiredCharacteristics: {
|
|
125
|
+
// Chinese-specific characteristics
|
|
126
|
+
hasChinese: true // Chinese characters
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Validate all locale files
|
|
134
|
+
*/
|
|
135
|
+
async validateAll() {
|
|
136
|
+
console.log('š Language Purity Validator');
|
|
137
|
+
console.log('=============================\n');
|
|
138
|
+
|
|
139
|
+
const localeFiles = this.getLocaleFiles();
|
|
140
|
+
const results = {
|
|
141
|
+
totalFiles: 0,
|
|
142
|
+
validFiles: 0,
|
|
143
|
+
invalidFiles: 0,
|
|
144
|
+
totalViolations: 0,
|
|
145
|
+
fileResults: {}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
for (const file of localeFiles) {
|
|
149
|
+
if (file.language === this.sourceLanguage) {
|
|
150
|
+
continue; // Skip source language
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(`š Validating ${file.language}.json (${this.validationRules[file.language]?.name || file.language})...`);
|
|
154
|
+
const fileResult = await this.validateFile(file);
|
|
155
|
+
|
|
156
|
+
results.totalFiles++;
|
|
157
|
+
results.fileResults[file.language] = fileResult;
|
|
158
|
+
results.totalViolations += fileResult.violations.length;
|
|
159
|
+
|
|
160
|
+
if (fileResult.isValid) {
|
|
161
|
+
results.validFiles++;
|
|
162
|
+
console.log(` ā
Valid - No language purity violations`);
|
|
163
|
+
} else {
|
|
164
|
+
results.invalidFiles++;
|
|
165
|
+
console.log(` ā Invalid - ${fileResult.violations.length} violation(s) found`);
|
|
166
|
+
this.exitCode = 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.generateSummary(results);
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get all locale files
|
|
176
|
+
*/
|
|
177
|
+
getLocaleFiles() {
|
|
178
|
+
return fs.readdirSync(this.localesDir)
|
|
179
|
+
.filter(file => file.endsWith('.json'))
|
|
180
|
+
.map(file => ({
|
|
181
|
+
filename: file,
|
|
182
|
+
language: path.basename(file, '.json'),
|
|
183
|
+
path: path.join(this.localesDir, file)
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate a single file
|
|
189
|
+
*/
|
|
190
|
+
async validateFile(file) {
|
|
191
|
+
const result = {
|
|
192
|
+
filename: file.filename,
|
|
193
|
+
language: file.language,
|
|
194
|
+
isValid: true,
|
|
195
|
+
violations: [],
|
|
196
|
+
stats: {
|
|
197
|
+
totalKeys: 0,
|
|
198
|
+
validKeys: 0,
|
|
199
|
+
invalidKeys: 0
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const content = fs.readFileSync(file.path, 'utf8');
|
|
205
|
+
const translations = JSON.parse(content);
|
|
206
|
+
const rules = this.validationRules[file.language];
|
|
207
|
+
|
|
208
|
+
if (!rules) {
|
|
209
|
+
result.violations.push({
|
|
210
|
+
type: 'no_validation_rules',
|
|
211
|
+
message: `No validation rules defined for language: ${file.language}`
|
|
212
|
+
});
|
|
213
|
+
result.isValid = false;
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.validateObject(translations, '', rules, result);
|
|
218
|
+
|
|
219
|
+
result.isValid = result.violations.length === 0;
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
result.violations.push({
|
|
223
|
+
type: 'file_error',
|
|
224
|
+
message: `Error reading file: ${error.message}`
|
|
225
|
+
});
|
|
226
|
+
result.isValid = false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Recursively validate object properties
|
|
234
|
+
*/
|
|
235
|
+
validateObject(obj, keyPath, rules, result) {
|
|
236
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
237
|
+
const currentPath = keyPath ? `${keyPath}.${key}` : key;
|
|
238
|
+
|
|
239
|
+
if (typeof value === 'string') {
|
|
240
|
+
result.stats.totalKeys++;
|
|
241
|
+
const violations = this.validateString(value, currentPath, rules);
|
|
242
|
+
|
|
243
|
+
if (violations.length > 0) {
|
|
244
|
+
result.violations.push(...violations);
|
|
245
|
+
result.stats.invalidKeys++;
|
|
246
|
+
} else {
|
|
247
|
+
result.stats.validKeys++;
|
|
248
|
+
}
|
|
249
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
250
|
+
this.validateObject(value, currentPath, rules, result);
|
|
251
|
+
} else if (Array.isArray(value)) {
|
|
252
|
+
value.forEach((item, index) => {
|
|
253
|
+
if (typeof item === 'string') {
|
|
254
|
+
result.stats.totalKeys++;
|
|
255
|
+
const violations = this.validateString(item, `${currentPath}[${index}]`, rules);
|
|
256
|
+
|
|
257
|
+
if (violations.length > 0) {
|
|
258
|
+
result.violations.push(...violations);
|
|
259
|
+
result.stats.invalidKeys++;
|
|
260
|
+
} else {
|
|
261
|
+
result.stats.validKeys++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validate a single string
|
|
271
|
+
*/
|
|
272
|
+
validateString(text, keyPath, rules) {
|
|
273
|
+
const violations = [];
|
|
274
|
+
const lowerText = text.toLowerCase();
|
|
275
|
+
|
|
276
|
+
// Check for forbidden markers
|
|
277
|
+
for (const marker of rules.forbiddenMarkers) {
|
|
278
|
+
if (text.includes(marker)) {
|
|
279
|
+
violations.push({
|
|
280
|
+
type: 'forbidden_marker',
|
|
281
|
+
key: keyPath,
|
|
282
|
+
value: text,
|
|
283
|
+
issue: `Contains forbidden marker: ${marker}`,
|
|
284
|
+
severity: 'error'
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check for forbidden phrases
|
|
290
|
+
for (const phrase of rules.forbiddenPhrases) {
|
|
291
|
+
if (lowerText.includes(phrase.toLowerCase())) {
|
|
292
|
+
violations.push({
|
|
293
|
+
type: 'forbidden_phrase',
|
|
294
|
+
key: keyPath,
|
|
295
|
+
value: text,
|
|
296
|
+
issue: `Contains English phrase: "${phrase}"`,
|
|
297
|
+
severity: 'error'
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check for common English words that shouldn't be in foreign languages
|
|
303
|
+
const englishWords = ['the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'];
|
|
304
|
+
const foundEnglishWords = [];
|
|
305
|
+
|
|
306
|
+
for (const word of englishWords) {
|
|
307
|
+
const regex = new RegExp(`\\b${word}\\b`, 'i');
|
|
308
|
+
if (regex.test(text)) {
|
|
309
|
+
foundEnglishWords.push(word);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (foundEnglishWords.length > 0) {
|
|
314
|
+
violations.push({
|
|
315
|
+
type: 'english_words',
|
|
316
|
+
key: keyPath,
|
|
317
|
+
value: text,
|
|
318
|
+
issue: `Contains English words: ${foundEnglishWords.join(', ')}`,
|
|
319
|
+
severity: 'warning'
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Language-specific validation
|
|
324
|
+
if (rules.requiredCharacteristics) {
|
|
325
|
+
const langViolations = this.validateLanguageCharacteristics(text, keyPath, rules.requiredCharacteristics);
|
|
326
|
+
violations.push(...langViolations);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return violations;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Validate language-specific characteristics
|
|
334
|
+
*/
|
|
335
|
+
validateLanguageCharacteristics(text, keyPath, characteristics) {
|
|
336
|
+
const violations = [];
|
|
337
|
+
|
|
338
|
+
// Check for Cyrillic (Russian)
|
|
339
|
+
if (characteristics.hasCyrillic) {
|
|
340
|
+
const hasCyrillicChars = /[а-ŃŃ]/i.test(text);
|
|
341
|
+
if (!hasCyrillicChars && text.length > 10) { // Only check longer strings
|
|
342
|
+
violations.push({
|
|
343
|
+
type: 'missing_cyrillic',
|
|
344
|
+
key: keyPath,
|
|
345
|
+
value: text,
|
|
346
|
+
issue: 'Long text should contain Cyrillic characters for Russian',
|
|
347
|
+
severity: 'warning'
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check for Japanese characters
|
|
353
|
+
if (characteristics.hasJapanese) {
|
|
354
|
+
const hasJapaneseChars = /[ć²ćććŖć«ćæć«ćäø-龯]/u.test(text);
|
|
355
|
+
if (!hasJapaneseChars && text.length > 10) {
|
|
356
|
+
violations.push({
|
|
357
|
+
type: 'missing_japanese',
|
|
358
|
+
key: keyPath,
|
|
359
|
+
value: text,
|
|
360
|
+
issue: 'Long text should contain Japanese characters',
|
|
361
|
+
severity: 'warning'
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check for Chinese characters
|
|
367
|
+
if (characteristics.hasChinese) {
|
|
368
|
+
const hasChineseChars = /[\u4e00-\u9fff]/u.test(text);
|
|
369
|
+
if (!hasChineseChars && text.length > 10) {
|
|
370
|
+
violations.push({
|
|
371
|
+
type: 'missing_chinese',
|
|
372
|
+
key: keyPath,
|
|
373
|
+
value: text,
|
|
374
|
+
issue: 'Long text should contain Chinese characters',
|
|
375
|
+
severity: 'warning'
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return violations;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Generate validation summary
|
|
385
|
+
*/
|
|
386
|
+
generateSummary(results) {
|
|
387
|
+
console.log('\nš VALIDATION SUMMARY');
|
|
388
|
+
console.log('======================\n');
|
|
389
|
+
|
|
390
|
+
console.log(`š Overall Results:`);
|
|
391
|
+
console.log(` Total files validated: ${results.totalFiles}`);
|
|
392
|
+
console.log(` Valid files: ${results.validFiles}`);
|
|
393
|
+
console.log(` Invalid files: ${results.invalidFiles}`);
|
|
394
|
+
console.log(` Total violations: ${results.totalViolations}\n`);
|
|
395
|
+
|
|
396
|
+
if (results.invalidFiles > 0) {
|
|
397
|
+
console.log('ā VIOLATIONS BY FILE:\n');
|
|
398
|
+
|
|
399
|
+
for (const [language, fileResult] of Object.entries(results.fileResults)) {
|
|
400
|
+
if (!fileResult.isValid) {
|
|
401
|
+
console.log(`š ${fileResult.filename}:`);
|
|
402
|
+
console.log(` Total keys: ${fileResult.stats.totalKeys}`);
|
|
403
|
+
console.log(` Valid keys: ${fileResult.stats.validKeys}`);
|
|
404
|
+
console.log(` Invalid keys: ${fileResult.stats.invalidKeys}`);
|
|
405
|
+
console.log(` Violations: ${fileResult.violations.length}\n`);
|
|
406
|
+
|
|
407
|
+
// Show first 10 violations
|
|
408
|
+
const violationsToShow = fileResult.violations.slice(0, 10);
|
|
409
|
+
violationsToShow.forEach((violation, index) => {
|
|
410
|
+
const severity = violation.severity === 'error' ? 'ā' : 'ā ļø';
|
|
411
|
+
console.log(` ${index + 1}. ${severity} ${violation.key}`);
|
|
412
|
+
console.log(` Issue: ${violation.issue}`);
|
|
413
|
+
if (violation.value.length > 100) {
|
|
414
|
+
console.log(` Value: "${violation.value.substring(0, 100)}..."`);
|
|
415
|
+
} else {
|
|
416
|
+
console.log(` Value: "${violation.value}"`);
|
|
417
|
+
}
|
|
418
|
+
console.log('');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (fileResult.violations.length > 10) {
|
|
422
|
+
console.log(` ... and ${fileResult.violations.length - 10} more violations\n`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
console.log('ā
All locale files passed language purity validation!\n');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Recommendations
|
|
431
|
+
if (results.totalViolations > 0) {
|
|
432
|
+
console.log('š” RECOMMENDATIONS:\n');
|
|
433
|
+
console.log('1. Translate all entries marked with [TRANSLATE] or [NOT TRANSLATED]');
|
|
434
|
+
console.log('2. Remove language prefixes like [DE], [FR], [ES], etc.');
|
|
435
|
+
console.log('3. Replace English text with proper native language translations');
|
|
436
|
+
console.log('4. Use language-appropriate characters and writing systems');
|
|
437
|
+
console.log('5. Run automated translation tools to fix common issues');
|
|
438
|
+
console.log('6. Integrate this validator into your CI/CD pipeline\n');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Save detailed report
|
|
442
|
+
this.saveValidationReport(results);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Save validation report
|
|
447
|
+
*/
|
|
448
|
+
saveValidationReport(results) {
|
|
449
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
450
|
+
const reportPath = path.join(__dirname, 'i18ntk-reports', 'language-purity', `language-purity-${timestamp}.json`);
|
|
451
|
+
|
|
452
|
+
// Ensure directory exists
|
|
453
|
+
const reportDir = path.dirname(reportPath);
|
|
454
|
+
if (!fs.existsSync(reportDir)) {
|
|
455
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const report = {
|
|
459
|
+
timestamp: new Date().toISOString(),
|
|
460
|
+
summary: {
|
|
461
|
+
totalFiles: results.totalFiles,
|
|
462
|
+
validFiles: results.validFiles,
|
|
463
|
+
invalidFiles: results.invalidFiles,
|
|
464
|
+
totalViolations: results.totalViolations,
|
|
465
|
+
overallValid: results.invalidFiles === 0
|
|
466
|
+
},
|
|
467
|
+
fileResults: results.fileResults,
|
|
468
|
+
recommendations: [
|
|
469
|
+
'Translate all entries marked with [TRANSLATE] or [NOT TRANSLATED]',
|
|
470
|
+
'Remove language prefixes like [DE], [FR], [ES], etc.',
|
|
471
|
+
'Replace English text with proper native language translations',
|
|
472
|
+
'Use language-appropriate characters and writing systems',
|
|
473
|
+
'Run automated translation tools to fix common issues',
|
|
474
|
+
'Integrate this validator into your CI/CD pipeline'
|
|
475
|
+
]
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
480
|
+
console.log(`š Detailed validation report saved to: ${reportPath}\n`);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error(`ā Error saving validation report: ${error.message}\n`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get exit code for CI/CD integration
|
|
488
|
+
*/
|
|
489
|
+
getExitCode() {
|
|
490
|
+
return this.exitCode;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// CLI Interface
|
|
495
|
+
if (require.main === module) {
|
|
496
|
+
const args = process.argv.slice(2);
|
|
497
|
+
const validator = new LanguagePurityValidator();
|
|
498
|
+
|
|
499
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
500
|
+
console.log(`
|
|
501
|
+
Language Purity Validator
|
|
502
|
+
|
|
503
|
+
Usage:
|
|
504
|
+
node validate-language-purity.js [options]
|
|
505
|
+
|
|
506
|
+
Options:
|
|
507
|
+
--ci Run in CI mode (exit with error code if validation fails)
|
|
508
|
+
--help, -h Show this help message
|
|
509
|
+
|
|
510
|
+
Examples:
|
|
511
|
+
node validate-language-purity.js # Validate all locale files
|
|
512
|
+
node validate-language-purity.js --ci # CI/CD integration mode
|
|
513
|
+
|
|
514
|
+
Exit Codes:
|
|
515
|
+
0 - All validations passed
|
|
516
|
+
1 - Validation failures found
|
|
517
|
+
`);
|
|
518
|
+
process.exit(0);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
validator.validateAll().then(() => {
|
|
522
|
+
if (args.includes('--ci')) {
|
|
523
|
+
process.exit(validator.getExitCode());
|
|
524
|
+
}
|
|
525
|
+
}).catch(error => {
|
|
526
|
+
console.error('ā Validation Error:', error.message);
|
|
527
|
+
process.exit(1);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
module.exports = LanguagePurityValidator;
|