locai 2.1.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/README.md +928 -0
- package/dist/cli.js +718 -0
- package/dist/commands/review.js +357 -0
- package/dist/commands/translator.js +29 -0
- package/dist/config/index.js +52 -0
- package/dist/core/adapters/base-adapter.js +1 -0
- package/dist/core/adapters/factory.js +44 -0
- package/dist/core/adapters/json-adapter.js +20 -0
- package/dist/core/adapters/po-adapter.js +50 -0
- package/dist/core/adapters/properties-adapter.js +31 -0
- package/dist/core/adapters/yaml-adapter.js +24 -0
- package/dist/core/context-processor.js +247 -0
- package/dist/core/detection/framework-detector.js +56 -0
- package/dist/core/detection/structure-detector.js +30 -0
- package/dist/core/fallback-provider.js +442 -0
- package/dist/core/orchestrator.js +438 -0
- package/dist/core/pipeline/Pipeline.js +48 -0
- package/dist/core/pipeline/PipelineStep.js +13 -0
- package/dist/core/pipeline/context.js +21 -0
- package/dist/core/pipeline/steps/CacheReadStep.js +80 -0
- package/dist/core/pipeline/steps/CacheWriteStep.js +32 -0
- package/dist/core/pipeline/steps/ConfidenceCheckStep.js +67 -0
- package/dist/core/pipeline/steps/GlossaryPostStep.js +17 -0
- package/dist/core/pipeline/steps/GlossaryPreStep.js +20 -0
- package/dist/core/pipeline/steps/InputValidationStep.js +26 -0
- package/dist/core/pipeline/steps/QualityCheckStep.js +48 -0
- package/dist/core/pipeline/steps/ToneCheckStep.js +58 -0
- package/dist/core/pipeline/steps/TranslationStep.js +65 -0
- package/dist/core/pipeline/steps/VectorCacheReadStep.js +84 -0
- package/dist/core/pipeline/steps/VectorCacheWriteStep.js +42 -0
- package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +100 -0
- package/dist/core/provider-factory.js +139 -0
- package/dist/providers/base-provider.js +230 -0
- package/dist/providers/dashscope.js +128 -0
- package/dist/providers/deepseek.js +111 -0
- package/dist/providers/gemini.js +135 -0
- package/dist/providers/openai.js +203 -0
- package/dist/providers/xai.js +111 -0
- package/dist/services/embedding-provider.js +50 -0
- package/dist/services/translation-service.js +609 -0
- package/dist/services/vector-store.js +79 -0
- package/dist/utils/ai-context-analyzer.js +460 -0
- package/dist/utils/confidence-scorer.js +277 -0
- package/dist/utils/error-helper.js +386 -0
- package/dist/utils/file-manager.js +357 -0
- package/dist/utils/glossary-manager.js +194 -0
- package/dist/utils/graceful-shutdown.js +118 -0
- package/dist/utils/input-validator.js +663 -0
- package/dist/utils/logger.js +374 -0
- package/dist/utils/object-transformer.js +177 -0
- package/dist/utils/progress-tracker.js +277 -0
- package/dist/utils/prompt-templates.js +415 -0
- package/dist/utils/quality/__tests__/tone-verifier.test.js +91 -0
- package/dist/utils/quality/base-checker.js +73 -0
- package/dist/utils/quality/code-block-checker.js +214 -0
- package/dist/utils/quality/html-tag-checker.js +64 -0
- package/dist/utils/quality/index.js +220 -0
- package/dist/utils/quality/length-checker.js +140 -0
- package/dist/utils/quality/markdown-checker.js +220 -0
- package/dist/utils/quality/placeholder-checker.js +142 -0
- package/dist/utils/quality/punctuation-checker.js +50 -0
- package/dist/utils/quality/quote-balance-checker.js +143 -0
- package/dist/utils/quality/special-characters-checker.js +229 -0
- package/dist/utils/quality/style-guide-checker.js +192 -0
- package/dist/utils/quality/text-sanitizer.js +120 -0
- package/dist/utils/quality/tone-verifier.js +91 -0
- package/dist/utils/rate-limiter.js +398 -0
- package/dist/utils/retry-helper.js +166 -0
- package/dist/utils/state-manager.js +162 -0
- package/dist/utils/statistics-manager.js +51 -0
- package/dist/utils/ui-manager.js +98 -0
- package/package.json +80 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Confidence Scoring System
|
|
3
|
+
* Calculates confidence scores for translations based on AI response and quality checks
|
|
4
|
+
*/
|
|
5
|
+
class ConfidenceScorer {
|
|
6
|
+
/**
|
|
7
|
+
* Calculate final confidence score
|
|
8
|
+
* @param {ConfidenceOptions} options
|
|
9
|
+
* @returns {ConfidenceResult} { score, details, issues }
|
|
10
|
+
*/
|
|
11
|
+
static calculateConfidence(options) {
|
|
12
|
+
const { aiConfidence = 0.8, sourceText, translation, sourceLang, targetLang, provider, category = "general", } = options;
|
|
13
|
+
// Quality checks
|
|
14
|
+
const qualityScore = this._calculateQualityScore(sourceText, translation);
|
|
15
|
+
// Category-specific adjustments
|
|
16
|
+
const categoryAdjustment = this._getCategoryAdjustment(category, provider);
|
|
17
|
+
// Language pair complexity
|
|
18
|
+
const languageFactor = this._getLanguagePairFactor(sourceLang, targetLang);
|
|
19
|
+
// Provider-specific reliability
|
|
20
|
+
const providerReliability = this._getProviderReliability(provider, category);
|
|
21
|
+
// Calculate weighted final score
|
|
22
|
+
const finalScore = aiConfidence * 0.4 + // AI's own confidence
|
|
23
|
+
qualityScore * 0.3 + // Our quality checks
|
|
24
|
+
categoryAdjustment * 0.15 + // Category-specific adjustment
|
|
25
|
+
languageFactor * 0.1 + // Language pair complexity
|
|
26
|
+
providerReliability * 0.05; // Provider reliability
|
|
27
|
+
// Clamp to 0-1 range
|
|
28
|
+
const score = Math.max(0, Math.min(1, finalScore));
|
|
29
|
+
// Determine confidence level
|
|
30
|
+
const level = this._getConfidenceLevel(score);
|
|
31
|
+
// Collect issues
|
|
32
|
+
const issues = this._detectIssues(sourceText, translation, score);
|
|
33
|
+
return {
|
|
34
|
+
score: parseFloat(score.toFixed(3)),
|
|
35
|
+
level,
|
|
36
|
+
aiConfidence,
|
|
37
|
+
qualityScore,
|
|
38
|
+
details: {
|
|
39
|
+
categoryAdjustment,
|
|
40
|
+
languageFactor,
|
|
41
|
+
providerReliability,
|
|
42
|
+
},
|
|
43
|
+
issues,
|
|
44
|
+
needsReview: score < 0.7,
|
|
45
|
+
autoApprove: score >= 0.9,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Calculate quality score based on internal checks
|
|
50
|
+
*/
|
|
51
|
+
static _calculateQualityScore(sourceText, translation) {
|
|
52
|
+
let score = 1.0;
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
54
|
+
const issues = [];
|
|
55
|
+
// Check 1: Placeholder preservation
|
|
56
|
+
const sourcePlaceholders = (sourceText.match(/\{[^}]+\}/g) || []).length;
|
|
57
|
+
const translationPlaceholders = (translation.match(/\{[^}]+\}/g) || []).length;
|
|
58
|
+
if (sourcePlaceholders !== translationPlaceholders) {
|
|
59
|
+
score -= 0.3;
|
|
60
|
+
issues.push("placeholder_mismatch");
|
|
61
|
+
}
|
|
62
|
+
// Check 2: HTML tag preservation
|
|
63
|
+
const sourceTags = (sourceText.match(/<[^>]+>/g) || []).length;
|
|
64
|
+
const translationTags = (translation.match(/<[^>]+>/g) || []).length;
|
|
65
|
+
if (sourceTags !== translationTags) {
|
|
66
|
+
score -= 0.2;
|
|
67
|
+
issues.push("html_tag_mismatch");
|
|
68
|
+
}
|
|
69
|
+
// Check 3: Length appropriateness
|
|
70
|
+
const lengthRatio = translation.length / sourceText.length;
|
|
71
|
+
if (lengthRatio < 0.3 || lengthRatio > 3.0) {
|
|
72
|
+
score -= 0.2;
|
|
73
|
+
issues.push("extreme_length_difference");
|
|
74
|
+
}
|
|
75
|
+
// Check 4: Punctuation consistency
|
|
76
|
+
const sourceEndsWithPunctuation = /[.!?;:]$/.test(sourceText.trim());
|
|
77
|
+
const translationEndsWithPunctuation = /[.!?;:]$/.test(translation.trim());
|
|
78
|
+
if (sourceEndsWithPunctuation !== translationEndsWithPunctuation) {
|
|
79
|
+
score -= 0.1;
|
|
80
|
+
issues.push("punctuation_inconsistency");
|
|
81
|
+
}
|
|
82
|
+
// Check 5: Empty or too short translation
|
|
83
|
+
if (translation.trim().length < 2) {
|
|
84
|
+
score -= 0.4;
|
|
85
|
+
issues.push("translation_too_short");
|
|
86
|
+
}
|
|
87
|
+
// Check 6: Same as source (no translation occurred)
|
|
88
|
+
if (sourceText.toLowerCase().trim() === translation.toLowerCase().trim()) {
|
|
89
|
+
score -= 0.3;
|
|
90
|
+
issues.push("identical_to_source");
|
|
91
|
+
}
|
|
92
|
+
// Check 7: Contains AI artifacts
|
|
93
|
+
const artifacts = [
|
|
94
|
+
/^translation:/i,
|
|
95
|
+
/^translated text:/i,
|
|
96
|
+
/^here is/i,
|
|
97
|
+
/^the translation is/i,
|
|
98
|
+
];
|
|
99
|
+
if (artifacts.some((pattern) => pattern.test(translation))) {
|
|
100
|
+
score -= 0.2;
|
|
101
|
+
issues.push("contains_ai_artifacts");
|
|
102
|
+
}
|
|
103
|
+
return Math.max(0, score);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get category-specific adjustment
|
|
107
|
+
*/
|
|
108
|
+
static _getCategoryAdjustment(category, provider) {
|
|
109
|
+
const categoryWeights = {
|
|
110
|
+
legal: 0.7, // Legal is complex, lower confidence
|
|
111
|
+
technical: 0.85, // Technical is consistent
|
|
112
|
+
defi: 0.75, // DeFi has specific terminology
|
|
113
|
+
marketing: 0.9, // Marketing is creative, higher tolerance
|
|
114
|
+
ui: 0.95, // UI is straightforward
|
|
115
|
+
general: 1.0,
|
|
116
|
+
};
|
|
117
|
+
// Provider-specific category expertise
|
|
118
|
+
const providerExpertise = {
|
|
119
|
+
openai: { legal: 1.0, technical: 1.0, general: 1.0 },
|
|
120
|
+
gemini: { technical: 0.95, marketing: 1.0, general: 0.95 },
|
|
121
|
+
deepseek: { technical: 1.0, general: 0.9 },
|
|
122
|
+
dashscope: { general: 0.9 },
|
|
123
|
+
xai: { technical: 0.95, general: 0.9 },
|
|
124
|
+
};
|
|
125
|
+
const baseWeight = categoryWeights[category] || 1.0;
|
|
126
|
+
const expertise = providerExpertise[provider]?.[category] || providerExpertise[provider]?.general || 0.9;
|
|
127
|
+
return baseWeight * expertise;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get language pair complexity factor
|
|
131
|
+
*/
|
|
132
|
+
static _getLanguagePairFactor(sourceLang, targetLang) {
|
|
133
|
+
// Similar language pairs get higher confidence
|
|
134
|
+
const similarPairs = {
|
|
135
|
+
en: ["es", "fr", "de", "pl"],
|
|
136
|
+
es: ["en", "fr", "pl"],
|
|
137
|
+
fr: ["en", "es", "de"],
|
|
138
|
+
de: ["en", "fr"],
|
|
139
|
+
ru: ["uk", "pl"],
|
|
140
|
+
zh: ["ja"],
|
|
141
|
+
ja: ["zh"],
|
|
142
|
+
};
|
|
143
|
+
// Complex languages (different scripts/grammar)
|
|
144
|
+
const complexLanguages = ["zh", "ja", "ar", "th", "hi", "yo"];
|
|
145
|
+
let factor = 1.0;
|
|
146
|
+
// Check if pair is similar
|
|
147
|
+
if (similarPairs[sourceLang]?.includes(targetLang)) {
|
|
148
|
+
factor += 0.1; // Boost for similar pairs
|
|
149
|
+
}
|
|
150
|
+
// Reduce confidence for complex target languages
|
|
151
|
+
if (complexLanguages.includes(targetLang)) {
|
|
152
|
+
factor -= 0.15;
|
|
153
|
+
}
|
|
154
|
+
// Reduce confidence for complex source languages
|
|
155
|
+
if (complexLanguages.includes(sourceLang)) {
|
|
156
|
+
factor -= 0.05;
|
|
157
|
+
}
|
|
158
|
+
return Math.max(0.5, Math.min(1.0, factor));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get provider reliability for specific tasks
|
|
162
|
+
*/
|
|
163
|
+
static _getProviderReliability(provider, category) {
|
|
164
|
+
const reliability = {
|
|
165
|
+
openai: { default: 0.95, legal: 0.95, technical: 0.95 },
|
|
166
|
+
gemini: { default: 0.9, marketing: 0.95 },
|
|
167
|
+
deepseek: { default: 0.85, technical: 0.9 },
|
|
168
|
+
dashscope: { default: 0.85 },
|
|
169
|
+
xai: { default: 0.85, technical: 0.9 },
|
|
170
|
+
};
|
|
171
|
+
return reliability[provider]?.[category] || reliability[provider]?.default || 0.8;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Determine confidence level from score
|
|
175
|
+
*/
|
|
176
|
+
static _getConfidenceLevel(score) {
|
|
177
|
+
if (score >= 0.9)
|
|
178
|
+
return "high";
|
|
179
|
+
if (score >= 0.7)
|
|
180
|
+
return "medium";
|
|
181
|
+
if (score >= 0.5)
|
|
182
|
+
return "low";
|
|
183
|
+
return "very_low";
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Detect specific issues with translation
|
|
187
|
+
*/
|
|
188
|
+
static _detectIssues(sourceText, translation, score) {
|
|
189
|
+
const issues = [];
|
|
190
|
+
if (score < 0.5) {
|
|
191
|
+
issues.push({
|
|
192
|
+
type: "quality",
|
|
193
|
+
severity: "critical",
|
|
194
|
+
message: "Translation quality is very low, manual review required",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else if (score < 0.7) {
|
|
198
|
+
issues.push({
|
|
199
|
+
type: "quality",
|
|
200
|
+
severity: "warning",
|
|
201
|
+
message: "Translation quality is below threshold, review recommended",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Check for specific patterns
|
|
205
|
+
const sourcePlaceholders = (sourceText.match(/\{[^}]+\}/g) || []).length;
|
|
206
|
+
const translationPlaceholders = (translation.match(/\{[^}]+\}/g) || []).length;
|
|
207
|
+
if (sourcePlaceholders !== translationPlaceholders) {
|
|
208
|
+
issues.push({
|
|
209
|
+
type: "placeholder",
|
|
210
|
+
severity: "error",
|
|
211
|
+
message: `Placeholder count mismatch: ${sourcePlaceholders} → ${translationPlaceholders}`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const lengthRatio = translation.length / sourceText.length;
|
|
215
|
+
if (lengthRatio > 2.5) {
|
|
216
|
+
issues.push({
|
|
217
|
+
type: "length",
|
|
218
|
+
severity: "warning",
|
|
219
|
+
message: `Translation is ${Math.round(lengthRatio * 100)}% of source length`,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return issues;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get confidence color for terminal display
|
|
226
|
+
*/
|
|
227
|
+
static getConfidenceColor(score) {
|
|
228
|
+
if (score >= 0.9)
|
|
229
|
+
return "[HIGH]"; // High
|
|
230
|
+
if (score >= 0.7)
|
|
231
|
+
return "[MED]"; // Medium
|
|
232
|
+
if (score >= 0.5)
|
|
233
|
+
return "[LOW]"; // Low
|
|
234
|
+
return "[VLOW]"; // Very Low
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Format confidence for display
|
|
238
|
+
*/
|
|
239
|
+
static formatConfidence(score) {
|
|
240
|
+
const color = this.getConfidenceColor(score);
|
|
241
|
+
const percentage = (score * 100).toFixed(1);
|
|
242
|
+
return `${color} ${percentage}%`;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Extract AI confidence from provider response
|
|
246
|
+
*/
|
|
247
|
+
static extractAIConfidence(response, provider) {
|
|
248
|
+
// OpenAI logprobs-based confidence
|
|
249
|
+
if (provider === "openai" && response.choices?.[0]?.logprobs) {
|
|
250
|
+
const logprobs = response.choices[0].logprobs.token_logprobs || [];
|
|
251
|
+
if (logprobs.length > 0) {
|
|
252
|
+
// Convert log probabilities to confidence
|
|
253
|
+
const avgLogProb = logprobs.reduce((sum, lp) => sum + (lp || -1), 0) /
|
|
254
|
+
logprobs.length;
|
|
255
|
+
return Math.exp(avgLogProb); // Convert back to probability
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Gemini confidence scores
|
|
259
|
+
if (provider === "gemini" && response.candidates?.[0]) {
|
|
260
|
+
const finishReason = response.candidates[0].finishReason;
|
|
261
|
+
if (finishReason === "STOP")
|
|
262
|
+
return 0.9; // Normal completion
|
|
263
|
+
if (finishReason === "MAX_TOKENS")
|
|
264
|
+
return 0.75; // Incomplete
|
|
265
|
+
return 0.8; // Default
|
|
266
|
+
}
|
|
267
|
+
// Default confidence based on finish reason
|
|
268
|
+
const finishReason = response.choices?.[0]?.finish_reason || response.finishReason;
|
|
269
|
+
if (finishReason === "stop" || finishReason === "STOP")
|
|
270
|
+
return 0.85;
|
|
271
|
+
if (finishReason === "length")
|
|
272
|
+
return 0.7;
|
|
273
|
+
// Fallback
|
|
274
|
+
return 0.8;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export default ConfidenceScorer;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Error Handling System
|
|
3
|
+
* Provides detailed, actionable error messages with context and solutions
|
|
4
|
+
*/
|
|
5
|
+
class ErrorHelper {
|
|
6
|
+
// Error categories with codes
|
|
7
|
+
static ErrorCodes = {
|
|
8
|
+
// API Errors (1xxx)
|
|
9
|
+
API_RATE_LIMIT: "ERR_API_1001",
|
|
10
|
+
API_AUTH_FAILED: "ERR_API_1002",
|
|
11
|
+
API_SERVER_ERROR: "ERR_API_1003",
|
|
12
|
+
API_NETWORK: "ERR_API_1004",
|
|
13
|
+
API_TIMEOUT: "ERR_API_1005",
|
|
14
|
+
API_INVALID_KEY: "ERR_API_1006",
|
|
15
|
+
API_QUOTA_EXCEEDED: "ERR_API_1007",
|
|
16
|
+
API_RESPONSE_ERROR: "ERR_API_1008",
|
|
17
|
+
// Configuration Errors (2xxx)
|
|
18
|
+
CONFIG_NOT_FOUND: "ERR_CFG_2001",
|
|
19
|
+
CONFIG_INVALID: "ERR_CFG_2002",
|
|
20
|
+
CONFIG_VALIDATION: "ERR_CFG_2003",
|
|
21
|
+
CONFIG_MISSING_FIELD: "ERR_CFG_2004",
|
|
22
|
+
// File Errors (3xxx)
|
|
23
|
+
FILE_NOT_FOUND: "ERR_FILE_3001",
|
|
24
|
+
FILE_READ_ERROR: "ERR_FILE_3002",
|
|
25
|
+
FILE_WRITE_ERROR: "ERR_FILE_3003",
|
|
26
|
+
FILE_PERMISSION: "ERR_FILE_3004",
|
|
27
|
+
// Validation Errors (4xxx)
|
|
28
|
+
VALIDATION_LANGUAGE: "ERR_VAL_4001",
|
|
29
|
+
VALIDATION_TEXT: "ERR_VAL_4002",
|
|
30
|
+
VALIDATION_PROVIDER: "ERR_VAL_4003",
|
|
31
|
+
VALIDATION_CONCURRENCY: "ERR_VAL_4004",
|
|
32
|
+
// Translation Errors (5xxx)
|
|
33
|
+
TRANSLATION_FAILED: "ERR_TRN_5001",
|
|
34
|
+
TRANSLATION_QUALITY: "ERR_TRN_5002",
|
|
35
|
+
TRANSLATION_TIMEOUT: "ERR_TRN_5003",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Create a detailed error with context and solutions
|
|
39
|
+
*/
|
|
40
|
+
static createError(type, details = {}) {
|
|
41
|
+
const errorInfo = this._getErrorInfo(type, details);
|
|
42
|
+
const error = new Error(errorInfo.message);
|
|
43
|
+
error.code = errorInfo.code;
|
|
44
|
+
error.type = type;
|
|
45
|
+
error.details = details;
|
|
46
|
+
error.solutions = errorInfo.solutions;
|
|
47
|
+
error.context = errorInfo.context;
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format error for display
|
|
52
|
+
*/
|
|
53
|
+
static formatError(error, options = {}) {
|
|
54
|
+
const { showDebug = false, showSolutions = true, showContext = true } = options;
|
|
55
|
+
const output = [];
|
|
56
|
+
// Error header
|
|
57
|
+
const code = error.code || "ERR_UNKNOWN";
|
|
58
|
+
const title = error.type || "Error";
|
|
59
|
+
output.push(`\n${title} [${code}]`);
|
|
60
|
+
output.push("");
|
|
61
|
+
// Problem description
|
|
62
|
+
if (error.message) {
|
|
63
|
+
output.push("Problem:");
|
|
64
|
+
const lines = error.message.split("\n");
|
|
65
|
+
lines.forEach((line) => output.push(` ${line}`));
|
|
66
|
+
output.push("");
|
|
67
|
+
}
|
|
68
|
+
// Context (why it happened)
|
|
69
|
+
if (showContext && error.context && error.context.length > 0) {
|
|
70
|
+
output.push("Why This Happened:");
|
|
71
|
+
error.context.forEach((ctx) => output.push(` • ${ctx}`));
|
|
72
|
+
output.push("");
|
|
73
|
+
}
|
|
74
|
+
// Solutions (how to fix)
|
|
75
|
+
if (showSolutions && error.solutions && error.solutions.length > 0) {
|
|
76
|
+
output.push("How to Fix:");
|
|
77
|
+
error.solutions.forEach((solution, i) => output.push(` ${i + 1}. ${solution}`));
|
|
78
|
+
output.push("");
|
|
79
|
+
}
|
|
80
|
+
// Debug information
|
|
81
|
+
if (showDebug && error.details) {
|
|
82
|
+
output.push("Debug Info:");
|
|
83
|
+
Object.entries(error.details).forEach(([key, value]) => {
|
|
84
|
+
const displayValue = typeof value === "object" ? JSON.stringify(value, null, 2) : value;
|
|
85
|
+
output.push(` ${key}: ${displayValue}`);
|
|
86
|
+
});
|
|
87
|
+
output.push("");
|
|
88
|
+
}
|
|
89
|
+
// Help section
|
|
90
|
+
output.push("Need Help?");
|
|
91
|
+
output.push(" • Documentation: https://github.com/ahmetenesdur/locai");
|
|
92
|
+
if (error.code) {
|
|
93
|
+
output.push(" • Report issue: https://github.com/ahmetenesdur/locai/issues");
|
|
94
|
+
}
|
|
95
|
+
output.push("");
|
|
96
|
+
return output.join("\n");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get error information based on type
|
|
100
|
+
*/
|
|
101
|
+
static _getErrorInfo(type, details) {
|
|
102
|
+
const errorMap = {
|
|
103
|
+
// API Rate Limit
|
|
104
|
+
API_RATE_LIMIT: {
|
|
105
|
+
code: this.ErrorCodes.API_RATE_LIMIT,
|
|
106
|
+
message: this._buildRateLimitMessage(details),
|
|
107
|
+
context: [
|
|
108
|
+
"You're sending requests too quickly for your API tier",
|
|
109
|
+
"Concurrent requests exceeded the provider's rate limit",
|
|
110
|
+
"Rate limiter might be disabled or misconfigured",
|
|
111
|
+
],
|
|
112
|
+
solutions: [
|
|
113
|
+
`Wait ${details.retryAfter || "60"} seconds for rate limit to reset`,
|
|
114
|
+
`Reduce concurrency: localize translate --concurrency ${Math.max(1, (details.currentConcurrency || 5) - 3)}`,
|
|
115
|
+
"Enable rate limiting in localize.config.js: rateLimiter.enabled = true",
|
|
116
|
+
`Check your ${details.provider || "API"} plan limits and consider upgrading`,
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
// API Authentication
|
|
120
|
+
API_AUTH_FAILED: {
|
|
121
|
+
code: this.ErrorCodes.API_AUTH_FAILED,
|
|
122
|
+
message: `Authentication failed for ${details.provider || "API provider"}${details.statusCode ? ` (${details.statusCode})` : ""}`,
|
|
123
|
+
context: [
|
|
124
|
+
"API key is missing, invalid, or expired",
|
|
125
|
+
"API key might not have the required permissions",
|
|
126
|
+
"Environment variable might not be set correctly",
|
|
127
|
+
],
|
|
128
|
+
solutions: [
|
|
129
|
+
`Set your API key in .env file: ${this._getEnvKeyName(details.provider)}=your-key-here`,
|
|
130
|
+
`Verify your API key at: ${this._getProviderConsoleUrl(details.provider)}`,
|
|
131
|
+
"Check that .env file is in your project root directory",
|
|
132
|
+
"Restart your terminal after setting environment variables",
|
|
133
|
+
"Ensure .env file is not in .gitignore (for local development)",
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
// API Server Error
|
|
137
|
+
API_SERVER_ERROR: {
|
|
138
|
+
code: this.ErrorCodes.API_SERVER_ERROR,
|
|
139
|
+
message: `${details.provider || "API"} server error${details.statusCode ? ` (${details.statusCode})` : ""}${details.apiMessage ? `: ${details.apiMessage}` : ""}`,
|
|
140
|
+
context: [
|
|
141
|
+
`${details.provider || "The API provider"} is experiencing technical difficulties`,
|
|
142
|
+
"This is a temporary issue on the provider's side",
|
|
143
|
+
"Your configuration and code are likely correct",
|
|
144
|
+
],
|
|
145
|
+
solutions: [
|
|
146
|
+
"Wait a few minutes and try again",
|
|
147
|
+
"Check provider status page for known issues",
|
|
148
|
+
`Use fallback provider: localize translate --provider ${this._suggestFallbackProvider(details.provider)}`,
|
|
149
|
+
"Enable automatic fallback: Set useFallback=true in config",
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
// Network Error
|
|
153
|
+
API_NETWORK: {
|
|
154
|
+
code: this.ErrorCodes.API_NETWORK,
|
|
155
|
+
message: `Cannot connect to ${details.provider || "API provider"}`,
|
|
156
|
+
context: [
|
|
157
|
+
"No internet connection or network is blocked",
|
|
158
|
+
"Firewall or proxy blocking API requests",
|
|
159
|
+
"DNS resolution failure",
|
|
160
|
+
"Provider's API endpoint might be down",
|
|
161
|
+
],
|
|
162
|
+
solutions: [
|
|
163
|
+
"Check your internet connection",
|
|
164
|
+
"Verify firewall/proxy settings allow HTTPS requests",
|
|
165
|
+
"Try using a VPN if the provider is blocked in your region",
|
|
166
|
+
"Check provider status page",
|
|
167
|
+
`Use alternative provider: localize translate --provider ${this._suggestFallbackProvider(details.provider)}`,
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
// API Timeout
|
|
171
|
+
API_TIMEOUT: {
|
|
172
|
+
code: this.ErrorCodes.API_TIMEOUT,
|
|
173
|
+
message: `Request to ${details.provider || "API"} timed out after ${details.timeout || "30000"}ms`,
|
|
174
|
+
context: [
|
|
175
|
+
"API response took too long",
|
|
176
|
+
"Network latency is high",
|
|
177
|
+
"Request might be too complex or large",
|
|
178
|
+
],
|
|
179
|
+
solutions: [
|
|
180
|
+
"Increase timeout in config: advanced.timeoutMs = 60000",
|
|
181
|
+
"Check your internet connection speed",
|
|
182
|
+
"Reduce batch size: advanced.maxBatchSize = 10",
|
|
183
|
+
"Split large translations into smaller chunks",
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
// Invalid API Key Format
|
|
187
|
+
API_INVALID_KEY: {
|
|
188
|
+
code: this.ErrorCodes.API_INVALID_KEY,
|
|
189
|
+
message: `Invalid API key format for ${details.provider || "provider"}`,
|
|
190
|
+
context: [
|
|
191
|
+
"API key doesn't match the expected format",
|
|
192
|
+
"Key might be corrupted or incomplete",
|
|
193
|
+
"Wrong provider key used",
|
|
194
|
+
],
|
|
195
|
+
solutions: [
|
|
196
|
+
`Get a valid API key from: ${this._getProviderConsoleUrl(details.provider)}`,
|
|
197
|
+
"Ensure no extra spaces or quotes in .env file",
|
|
198
|
+
"Check that you're using the correct provider's key",
|
|
199
|
+
"Regenerate your API key if needed",
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
// Configuration Not Found
|
|
203
|
+
CONFIG_NOT_FOUND: {
|
|
204
|
+
code: this.ErrorCodes.CONFIG_NOT_FOUND,
|
|
205
|
+
message: "Configuration file not found",
|
|
206
|
+
context: [
|
|
207
|
+
"localize.config.js or localize.config.cjs doesn't exist",
|
|
208
|
+
"You're running the command from wrong directory",
|
|
209
|
+
"Config file might have wrong extension",
|
|
210
|
+
],
|
|
211
|
+
solutions: [
|
|
212
|
+
"Create localize.config.js in your project root",
|
|
213
|
+
"Run the command from your project root directory",
|
|
214
|
+
"Copy example config: npx locai init (if available)",
|
|
215
|
+
"Check the README for configuration examples",
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
// Configuration Validation
|
|
219
|
+
CONFIG_VALIDATION: {
|
|
220
|
+
code: this.ErrorCodes.CONFIG_VALIDATION,
|
|
221
|
+
message: details.validationErrors
|
|
222
|
+
? `Configuration has ${details.validationErrors.length} error(s):\n${details.validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`
|
|
223
|
+
: "Configuration validation failed",
|
|
224
|
+
context: [
|
|
225
|
+
"One or more configuration values are invalid",
|
|
226
|
+
"Required fields might be missing",
|
|
227
|
+
"Values might be out of valid range",
|
|
228
|
+
],
|
|
229
|
+
solutions: [
|
|
230
|
+
"Run: localize validate-config --show-warnings",
|
|
231
|
+
"Check localize.config.js against the documentation",
|
|
232
|
+
"Fix the errors listed above",
|
|
233
|
+
"Use --debug flag to see full configuration details",
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
// File Not Found
|
|
237
|
+
FILE_NOT_FOUND: {
|
|
238
|
+
code: this.ErrorCodes.FILE_NOT_FOUND,
|
|
239
|
+
message: `File not found: ${details.filePath || "unknown"}`,
|
|
240
|
+
context: [
|
|
241
|
+
"The specified file doesn't exist",
|
|
242
|
+
"File path might be incorrect",
|
|
243
|
+
"File might have been moved or deleted",
|
|
244
|
+
],
|
|
245
|
+
solutions: [
|
|
246
|
+
`Create the file: ${details.filePath}`,
|
|
247
|
+
"Check the file path in your configuration",
|
|
248
|
+
"Verify localesDir points to the correct directory",
|
|
249
|
+
`Ensure source file exists: ${details.expectedSource || "locales/en.json"}`,
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
// Translation Failed
|
|
253
|
+
TRANSLATION_FAILED: {
|
|
254
|
+
code: this.ErrorCodes.TRANSLATION_FAILED,
|
|
255
|
+
message: `Translation failed for key "${details.key || "unknown"}"${details.language ? ` (${details.language})` : ""}`,
|
|
256
|
+
context: [
|
|
257
|
+
details.reason || "Unknown translation error",
|
|
258
|
+
"AI provider returned an error or invalid response",
|
|
259
|
+
"Text might contain unsupported characters or format",
|
|
260
|
+
],
|
|
261
|
+
solutions: [
|
|
262
|
+
"Check if the text contains special characters that need escaping",
|
|
263
|
+
"Try with a different provider using --provider flag",
|
|
264
|
+
"Enable debug mode to see full error details: --debug",
|
|
265
|
+
"Skip this key and continue: Use --force to retry",
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
// Validation Language
|
|
269
|
+
VALIDATION_LANGUAGE: {
|
|
270
|
+
code: this.ErrorCodes.VALIDATION_LANGUAGE,
|
|
271
|
+
message: `Invalid language code: ${details.langCode || "unknown"}`,
|
|
272
|
+
context: [
|
|
273
|
+
"Language code must be ISO 639-1 format (e.g., 'en', 'tr', 'de')",
|
|
274
|
+
"Only lowercase 2-letter codes are supported",
|
|
275
|
+
"Regional codes like 'en-US' are not yet supported",
|
|
276
|
+
],
|
|
277
|
+
solutions: [
|
|
278
|
+
"Use ISO 639-1 two-letter codes (e.g., 'en' instead of 'english')",
|
|
279
|
+
"Check supported languages: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes",
|
|
280
|
+
`Update your config: ${details.field || "targets"} = ["en", "tr", "de"]`,
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
return (errorMap[type] || {
|
|
285
|
+
code: "ERR_UNKNOWN",
|
|
286
|
+
message: details.message || "An unknown error occurred",
|
|
287
|
+
context: ["No additional context available"],
|
|
288
|
+
solutions: [
|
|
289
|
+
"Run with --debug flag for more information",
|
|
290
|
+
"Check the documentation",
|
|
291
|
+
"Report this issue if it persists",
|
|
292
|
+
],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Build rate limit message with details
|
|
297
|
+
*/
|
|
298
|
+
static _buildRateLimitMessage(details) {
|
|
299
|
+
const provider = details.provider || "API provider";
|
|
300
|
+
const limit = details.limit || "60";
|
|
301
|
+
const current = details.current || "unknown";
|
|
302
|
+
let msg = `${provider} rate limit exceeded`;
|
|
303
|
+
if (details.limit) {
|
|
304
|
+
msg += ` (${limit} requests/minute)`;
|
|
305
|
+
}
|
|
306
|
+
if (details.current) {
|
|
307
|
+
msg += `\n Current: ${current} requests in last minute`;
|
|
308
|
+
}
|
|
309
|
+
if (details.retryAfter) {
|
|
310
|
+
msg += `\n Retry after: ${details.retryAfter} seconds`;
|
|
311
|
+
}
|
|
312
|
+
return msg;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get environment variable name for provider
|
|
316
|
+
*/
|
|
317
|
+
static _getEnvKeyName(provider) {
|
|
318
|
+
const envMap = {
|
|
319
|
+
openai: "OPENAI_API_KEY",
|
|
320
|
+
gemini: "GEMINI_API_KEY",
|
|
321
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
322
|
+
dashscope: "DASHSCOPE_API_KEY",
|
|
323
|
+
xai: "XAI_API_KEY",
|
|
324
|
+
};
|
|
325
|
+
return envMap[provider?.toLowerCase()] || `${provider?.toUpperCase()}_API_KEY`;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get provider console URL
|
|
329
|
+
*/
|
|
330
|
+
static _getProviderConsoleUrl(provider) {
|
|
331
|
+
const urlMap = {
|
|
332
|
+
openai: "https://platform.openai.com/api-keys",
|
|
333
|
+
gemini: "https://makersuite.google.com/app/apikey",
|
|
334
|
+
deepseek: "https://platform.deepseek.com/api-keys",
|
|
335
|
+
dashscope: "https://dashscope.console.aliyun.com/",
|
|
336
|
+
xai: "https://console.x.ai/",
|
|
337
|
+
};
|
|
338
|
+
return urlMap[provider?.toLowerCase()] || "provider console";
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Suggest fallback provider
|
|
342
|
+
*/
|
|
343
|
+
static _suggestFallbackProvider(currentProvider) {
|
|
344
|
+
const fallbackOrder = ["openai", "gemini", "deepseek", "dashscope", "xai"];
|
|
345
|
+
const current = currentProvider?.toLowerCase();
|
|
346
|
+
const index = fallbackOrder.indexOf(current);
|
|
347
|
+
if (index === -1 || index === fallbackOrder.length - 1) {
|
|
348
|
+
return fallbackOrder[0];
|
|
349
|
+
}
|
|
350
|
+
return fallbackOrder[index + 1];
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Quick error creators for common cases
|
|
354
|
+
*/
|
|
355
|
+
static rateLimitError(provider, details = {}) {
|
|
356
|
+
return this.createError("API_RATE_LIMIT", { provider, ...details });
|
|
357
|
+
}
|
|
358
|
+
static authError(provider, statusCode, apiMessage) {
|
|
359
|
+
return this.createError("API_AUTH_FAILED", { provider, statusCode, apiMessage });
|
|
360
|
+
}
|
|
361
|
+
static serverError(provider, statusCode, apiMessage) {
|
|
362
|
+
return this.createError("API_SERVER_ERROR", { provider, statusCode, apiMessage });
|
|
363
|
+
}
|
|
364
|
+
static networkError(provider) {
|
|
365
|
+
return this.createError("API_NETWORK", { provider });
|
|
366
|
+
}
|
|
367
|
+
static timeoutError(provider, timeout) {
|
|
368
|
+
return this.createError("API_TIMEOUT", { provider, timeout });
|
|
369
|
+
}
|
|
370
|
+
static configNotFoundError() {
|
|
371
|
+
return this.createError("CONFIG_NOT_FOUND");
|
|
372
|
+
}
|
|
373
|
+
static configValidationError(validationErrors) {
|
|
374
|
+
return this.createError("CONFIG_VALIDATION", { validationErrors });
|
|
375
|
+
}
|
|
376
|
+
static fileNotFoundError(filePath, expectedSource) {
|
|
377
|
+
return this.createError("FILE_NOT_FOUND", { filePath, expectedSource });
|
|
378
|
+
}
|
|
379
|
+
static translationError(key, language, reason) {
|
|
380
|
+
return this.createError("TRANSLATION_FAILED", { key, language, reason });
|
|
381
|
+
}
|
|
382
|
+
static languageValidationError(langCode, field) {
|
|
383
|
+
return this.createError("VALIDATION_LANGUAGE", { langCode, field });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
export default ErrorHelper;
|