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.
Files changed (72) hide show
  1. package/README.md +928 -0
  2. package/dist/cli.js +718 -0
  3. package/dist/commands/review.js +357 -0
  4. package/dist/commands/translator.js +29 -0
  5. package/dist/config/index.js +52 -0
  6. package/dist/core/adapters/base-adapter.js +1 -0
  7. package/dist/core/adapters/factory.js +44 -0
  8. package/dist/core/adapters/json-adapter.js +20 -0
  9. package/dist/core/adapters/po-adapter.js +50 -0
  10. package/dist/core/adapters/properties-adapter.js +31 -0
  11. package/dist/core/adapters/yaml-adapter.js +24 -0
  12. package/dist/core/context-processor.js +247 -0
  13. package/dist/core/detection/framework-detector.js +56 -0
  14. package/dist/core/detection/structure-detector.js +30 -0
  15. package/dist/core/fallback-provider.js +442 -0
  16. package/dist/core/orchestrator.js +438 -0
  17. package/dist/core/pipeline/Pipeline.js +48 -0
  18. package/dist/core/pipeline/PipelineStep.js +13 -0
  19. package/dist/core/pipeline/context.js +21 -0
  20. package/dist/core/pipeline/steps/CacheReadStep.js +80 -0
  21. package/dist/core/pipeline/steps/CacheWriteStep.js +32 -0
  22. package/dist/core/pipeline/steps/ConfidenceCheckStep.js +67 -0
  23. package/dist/core/pipeline/steps/GlossaryPostStep.js +17 -0
  24. package/dist/core/pipeline/steps/GlossaryPreStep.js +20 -0
  25. package/dist/core/pipeline/steps/InputValidationStep.js +26 -0
  26. package/dist/core/pipeline/steps/QualityCheckStep.js +48 -0
  27. package/dist/core/pipeline/steps/ToneCheckStep.js +58 -0
  28. package/dist/core/pipeline/steps/TranslationStep.js +65 -0
  29. package/dist/core/pipeline/steps/VectorCacheReadStep.js +84 -0
  30. package/dist/core/pipeline/steps/VectorCacheWriteStep.js +42 -0
  31. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +100 -0
  32. package/dist/core/provider-factory.js +139 -0
  33. package/dist/providers/base-provider.js +230 -0
  34. package/dist/providers/dashscope.js +128 -0
  35. package/dist/providers/deepseek.js +111 -0
  36. package/dist/providers/gemini.js +135 -0
  37. package/dist/providers/openai.js +203 -0
  38. package/dist/providers/xai.js +111 -0
  39. package/dist/services/embedding-provider.js +50 -0
  40. package/dist/services/translation-service.js +609 -0
  41. package/dist/services/vector-store.js +79 -0
  42. package/dist/utils/ai-context-analyzer.js +460 -0
  43. package/dist/utils/confidence-scorer.js +277 -0
  44. package/dist/utils/error-helper.js +386 -0
  45. package/dist/utils/file-manager.js +357 -0
  46. package/dist/utils/glossary-manager.js +194 -0
  47. package/dist/utils/graceful-shutdown.js +118 -0
  48. package/dist/utils/input-validator.js +663 -0
  49. package/dist/utils/logger.js +374 -0
  50. package/dist/utils/object-transformer.js +177 -0
  51. package/dist/utils/progress-tracker.js +277 -0
  52. package/dist/utils/prompt-templates.js +415 -0
  53. package/dist/utils/quality/__tests__/tone-verifier.test.js +91 -0
  54. package/dist/utils/quality/base-checker.js +73 -0
  55. package/dist/utils/quality/code-block-checker.js +214 -0
  56. package/dist/utils/quality/html-tag-checker.js +64 -0
  57. package/dist/utils/quality/index.js +220 -0
  58. package/dist/utils/quality/length-checker.js +140 -0
  59. package/dist/utils/quality/markdown-checker.js +220 -0
  60. package/dist/utils/quality/placeholder-checker.js +142 -0
  61. package/dist/utils/quality/punctuation-checker.js +50 -0
  62. package/dist/utils/quality/quote-balance-checker.js +143 -0
  63. package/dist/utils/quality/special-characters-checker.js +229 -0
  64. package/dist/utils/quality/style-guide-checker.js +192 -0
  65. package/dist/utils/quality/text-sanitizer.js +120 -0
  66. package/dist/utils/quality/tone-verifier.js +91 -0
  67. package/dist/utils/rate-limiter.js +398 -0
  68. package/dist/utils/retry-helper.js +166 -0
  69. package/dist/utils/state-manager.js +162 -0
  70. package/dist/utils/statistics-manager.js +51 -0
  71. package/dist/utils/ui-manager.js +98 -0
  72. 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;