locai 2.6.1 → 2.7.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 (131) hide show
  1. package/README.md +23 -25
  2. package/dist/cli/helpers.d.ts +34 -2
  3. package/dist/cli/helpers.js +96 -37
  4. package/dist/cli.js +40 -39
  5. package/dist/commands/review.d.ts +33 -8
  6. package/dist/commands/review.js +89 -22
  7. package/dist/commands/translator.d.ts +4 -2
  8. package/dist/commands/translator.js +1 -1
  9. package/dist/config/index.d.ts +53 -66
  10. package/dist/config/index.js +2 -1
  11. package/dist/config/setup.d.ts +3 -2
  12. package/dist/config/setup.js +5 -3
  13. package/dist/core/adapters/android-adapter.d.ts +3 -2
  14. package/dist/core/adapters/android-adapter.js +12 -13
  15. package/dist/core/adapters/base-adapter.d.ts +13 -2
  16. package/dist/core/adapters/ios-adapter.d.ts +3 -2
  17. package/dist/core/adapters/json-adapter.d.ts +4 -3
  18. package/dist/core/adapters/json-adapter.js +1 -3
  19. package/dist/core/adapters/po-adapter.d.ts +3 -2
  20. package/dist/core/adapters/po-adapter.js +4 -5
  21. package/dist/core/adapters/properties-adapter.d.ts +3 -2
  22. package/dist/core/adapters/properties-adapter.js +2 -4
  23. package/dist/core/adapters/yaml-adapter.d.ts +4 -3
  24. package/dist/core/adapters/yaml-adapter.js +1 -3
  25. package/dist/core/context-processor.d.ts +21 -6
  26. package/dist/core/context-processor.js +2 -7
  27. package/dist/core/detection/framework-detector.js +1 -1
  28. package/dist/core/detection/structure-detector.js +1 -1
  29. package/dist/core/fallback-provider.d.ts +3 -4
  30. package/dist/core/fallback-provider.js +15 -21
  31. package/dist/core/orchestrator.d.ts +98 -26
  32. package/dist/core/orchestrator.js +56 -116
  33. package/dist/core/pipeline/Pipeline.d.ts +2 -2
  34. package/dist/core/pipeline/Pipeline.js +1 -3
  35. package/dist/core/pipeline/context.d.ts +69 -13
  36. package/dist/core/pipeline/context.js +1 -7
  37. package/dist/core/pipeline/steps/CacheReadStep.d.ts +10 -7
  38. package/dist/core/pipeline/steps/CacheReadStep.js +1 -12
  39. package/dist/core/pipeline/steps/CacheWriteStep.d.ts +2 -2
  40. package/dist/core/pipeline/steps/CacheWriteStep.js +1 -8
  41. package/dist/core/pipeline/steps/ConfidenceCheckStep.d.ts +5 -0
  42. package/dist/core/pipeline/steps/ConfidenceCheckStep.js +24 -44
  43. package/dist/core/pipeline/steps/ContextEnrichmentStep.js +3 -14
  44. package/dist/core/pipeline/steps/QualityCheckStep.d.ts +1 -1
  45. package/dist/core/pipeline/steps/QualityCheckStep.js +0 -2
  46. package/dist/core/pipeline/steps/ToneCheckStep.js +1 -1
  47. package/dist/core/pipeline/steps/TranslationStep.d.ts +13 -3
  48. package/dist/core/pipeline/steps/TranslationStep.js +21 -23
  49. package/dist/core/pipeline/steps/VectorCacheReadStep.d.ts +9 -1
  50. package/dist/core/pipeline/steps/VectorCacheReadStep.js +3 -5
  51. package/dist/core/pipeline/steps/VectorCacheWriteStep.d.ts +2 -1
  52. package/dist/core/pipeline/steps/VectorCacheWriteStep.js +2 -1
  53. package/dist/core/provider-factory.d.ts +15 -9
  54. package/dist/core/provider-factory.js +42 -32
  55. package/dist/providers/anthropic.d.ts +5 -4
  56. package/dist/providers/anthropic.js +3 -2
  57. package/dist/providers/api-types.d.ts +74 -0
  58. package/dist/providers/api-types.js +5 -0
  59. package/dist/providers/base-provider.d.ts +27 -31
  60. package/dist/providers/base-provider.js +22 -46
  61. package/dist/providers/dashscope.js +0 -2
  62. package/dist/providers/deepseek.js +0 -1
  63. package/dist/providers/gemini.js +0 -1
  64. package/dist/providers/openai.d.ts +3 -3
  65. package/dist/providers/openai.js +0 -1
  66. package/dist/providers/xai.js +0 -1
  67. package/dist/services/embedding-provider.js +4 -2
  68. package/dist/services/language-processor.d.ts +5 -3
  69. package/dist/services/language-processor.js +38 -43
  70. package/dist/services/source-analyzer.d.ts +24 -0
  71. package/dist/services/source-analyzer.js +161 -68
  72. package/dist/services/translation-service.d.ts +31 -13
  73. package/dist/services/translation-service.js +116 -71
  74. package/dist/services/vector-store.d.ts +15 -2
  75. package/dist/services/vector-store.js +14 -10
  76. package/dist/types/index.d.ts +31 -33
  77. package/dist/utils/ai-context-analyzer.d.ts +12 -3
  78. package/dist/utils/ai-context-analyzer.js +18 -10
  79. package/dist/utils/confidence-scorer.d.ts +16 -19
  80. package/dist/utils/confidence-scorer.js +36 -125
  81. package/dist/utils/error-helper.d.ts +27 -6
  82. package/dist/utils/error-helper.js +10 -9
  83. package/dist/utils/file-manager.d.ts +8 -7
  84. package/dist/utils/file-manager.js +22 -23
  85. package/dist/utils/glossary-manager.d.ts +22 -3
  86. package/dist/utils/glossary-manager.js +47 -28
  87. package/dist/utils/graceful-shutdown.d.ts +7 -1
  88. package/dist/utils/graceful-shutdown.js +1 -2
  89. package/dist/utils/input-validator.d.ts +53 -66
  90. package/dist/utils/input-validator.js +100 -151
  91. package/dist/utils/locale-paths.d.ts +7 -0
  92. package/dist/utils/locale-paths.js +16 -0
  93. package/dist/utils/logger.d.ts +8 -8
  94. package/dist/utils/logger.js +7 -15
  95. package/dist/utils/object-transformer.d.ts +20 -5
  96. package/dist/utils/object-transformer.js +20 -28
  97. package/dist/utils/progress-tracker.js +2 -3
  98. package/dist/utils/prompt-templates.d.ts +45 -5
  99. package/dist/utils/prompt-templates.js +61 -5
  100. package/dist/utils/quality/base-checker.d.ts +40 -18
  101. package/dist/utils/quality/base-checker.js +1 -2
  102. package/dist/utils/quality/code-block-checker.d.ts +13 -5
  103. package/dist/utils/quality/index.d.ts +2 -1
  104. package/dist/utils/quality/index.js +8 -27
  105. package/dist/utils/quality/length-checker.d.ts +35 -5
  106. package/dist/utils/quality/length-checker.js +2 -3
  107. package/dist/utils/quality/markdown-checker.d.ts +14 -7
  108. package/dist/utils/quality/special-characters-checker.d.ts +18 -1
  109. package/dist/utils/quality/special-characters-checker.js +0 -1
  110. package/dist/utils/quality/style-guide-checker.d.ts +1 -1
  111. package/dist/utils/quality/style-guide-checker.js +0 -2
  112. package/dist/utils/quality/tone-verifier.js +1 -1
  113. package/dist/utils/rate-limiter.d.ts +17 -1
  114. package/dist/utils/rate-limiter.js +12 -11
  115. package/dist/utils/retry-helper.d.ts +21 -3
  116. package/dist/utils/retry-helper.js +11 -3
  117. package/dist/utils/review-queue.d.ts +45 -0
  118. package/dist/utils/review-queue.js +53 -0
  119. package/dist/utils/state-manager.d.ts +12 -3
  120. package/dist/utils/state-manager.js +32 -7
  121. package/dist/utils/statistics-manager.d.ts +11 -1
  122. package/dist/utils/statistics-manager.js +8 -0
  123. package/dist/utils/ui-manager.d.ts +2 -1
  124. package/dist/utils/ui-manager.js +15 -1
  125. package/package.json +22 -17
  126. package/dist/commands/interactive-review.d.ts +0 -9
  127. package/dist/commands/interactive-review.js +0 -109
  128. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.d.ts +0 -1
  129. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +0 -100
  130. package/dist/utils/quality/__tests__/tone-verifier.test.d.ts +0 -1
  131. package/dist/utils/quality/__tests__/tone-verifier.test.js +0 -91
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Quality Confidence Scoring System
3
- * Calculates confidence scores for translations based on AI response and quality checks
2
+ * Confidence scoring for translations.
3
+ *
4
+ * The score is computed from the translation itself: checks for problems a model commonly
5
+ * introduces (lost placeholders or HTML tags, untranslated text, leftover commentary, unusual
6
+ * length or ending punctuation), scaled by fixed factors for the content category, the language
7
+ * pair and the provider. The model's own confidence (e.g. logprobs) is not used.
4
8
  */
5
9
  export interface ConfidenceIssue {
6
10
  type: string;
7
- severity: "critical" | "warning" | "error";
11
+ severity: "warning" | "error";
8
12
  message: string;
9
13
  }
10
14
  export interface ConfidenceOptions {
11
- aiConfidence?: number;
12
15
  sourceText: string;
13
16
  translation: string;
14
17
  sourceLang: string;
@@ -17,30 +20,32 @@ export interface ConfidenceOptions {
17
20
  category?: string;
18
21
  }
19
22
  export interface ConfidenceResult {
23
+ /** 0-1: the quality score scaled by the category, language pair and provider factors. */
20
24
  score: number;
21
25
  level: "high" | "medium" | "low" | "very_low";
22
- aiConfidence: number;
26
+ /** 0-1: 1 minus the penalties of the failed checks. */
23
27
  qualityScore: number;
24
28
  details: {
25
29
  categoryAdjustment: number;
26
30
  languageFactor: number;
27
31
  providerReliability: number;
28
32
  };
33
+ /** One entry per failed check. */
29
34
  issues: ConfidenceIssue[];
30
- needsReview: boolean;
31
- autoApprove: boolean;
32
35
  }
33
36
  declare class ConfidenceScorer {
34
37
  /**
35
- * Calculate final confidence score
38
+ * Score a translation against its source text.
36
39
  * @param {ConfidenceOptions} options
37
- * @returns {ConfidenceResult} { score, details, issues }
40
+ * @returns {ConfidenceResult} { score, level, qualityScore, details, issues }
38
41
  */
39
42
  static calculateConfidence(options: ConfidenceOptions): ConfidenceResult;
40
43
  /**
41
- * Calculate quality score based on internal checks
44
+ * Check the translation against its source. Each failed check adds an issue and lowers the
45
+ * quality score from 1: by 0.4 for a problem that breaks or invalidates the translation, by
46
+ * 0.2 for an unusual length and by 0.1 for different ending punctuation.
42
47
  */
43
- private static _calculateQualityScore;
48
+ private static _checkTranslation;
44
49
  /**
45
50
  * Get category-specific adjustment
46
51
  */
@@ -57,10 +62,6 @@ declare class ConfidenceScorer {
57
62
  * Determine confidence level from score
58
63
  */
59
64
  private static _getConfidenceLevel;
60
- /**
61
- * Detect specific issues with translation
62
- */
63
- private static _detectIssues;
64
65
  /**
65
66
  * Get confidence color for terminal display
66
67
  */
@@ -69,9 +70,5 @@ declare class ConfidenceScorer {
69
70
  * Format confidence for display
70
71
  */
71
72
  static formatConfidence(score: number): string;
72
- /**
73
- * Extract AI confidence from provider response
74
- */
75
- static extractAIConfidence(response: any, provider: string): number;
76
73
  }
77
74
  export default ConfidenceScorer;
@@ -1,39 +1,29 @@
1
1
  /**
2
- * Quality Confidence Scoring System
3
- * Calculates confidence scores for translations based on AI response and quality checks
2
+ * Confidence scoring for translations.
3
+ *
4
+ * The score is computed from the translation itself: checks for problems a model commonly
5
+ * introduces (lost placeholders or HTML tags, untranslated text, leftover commentary, unusual
6
+ * length or ending punctuation), scaled by fixed factors for the content category, the language
7
+ * pair and the provider. The model's own confidence (e.g. logprobs) is not used.
4
8
  */
5
9
  class ConfidenceScorer {
6
10
  /**
7
- * Calculate final confidence score
11
+ * Score a translation against its source text.
8
12
  * @param {ConfidenceOptions} options
9
- * @returns {ConfidenceResult} { score, details, issues }
13
+ * @returns {ConfidenceResult} { score, level, qualityScore, details, issues }
10
14
  */
11
15
  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 { sourceText, translation, sourceLang, targetLang, provider, category = "general", } = options;
17
+ const { qualityScore, issues } = this._checkTranslation(sourceText, translation);
16
18
  const categoryAdjustment = this._getCategoryAdjustment(category, provider);
17
- // Language pair complexity
18
19
  const languageFactor = this._getLanguagePairFactor(sourceLang, targetLang);
19
- // Provider-specific reliability
20
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);
21
+ // Harder categories, language pairs and providers lower the score (weighted 3:2:1)
22
+ const contextFactor = (categoryAdjustment * 3 + languageFactor * 2 + providerReliability) / 6;
23
+ const score = parseFloat((qualityScore * contextFactor).toFixed(3));
33
24
  return {
34
- score: parseFloat(score.toFixed(3)),
35
- level,
36
- aiConfidence,
25
+ score,
26
+ level: this._getConfidenceLevel(score),
37
27
  qualityScore,
38
28
  details: {
39
29
  categoryAdjustment,
@@ -41,55 +31,45 @@ class ConfidenceScorer {
41
31
  providerReliability,
42
32
  },
43
33
  issues,
44
- needsReview: score < 0.7,
45
- autoApprove: score >= 0.9,
46
34
  };
47
35
  }
48
36
  /**
49
- * Calculate quality score based on internal checks
37
+ * Check the translation against its source. Each failed check adds an issue and lowers the
38
+ * quality score from 1: by 0.4 for a problem that breaks or invalidates the translation, by
39
+ * 0.2 for an unusual length and by 0.1 for different ending punctuation.
50
40
  */
51
- static _calculateQualityScore(sourceText, translation) {
52
- let score = 1.0;
53
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
41
+ static _checkTranslation(sourceText, translation) {
54
42
  const issues = [];
55
- // Check 1: Placeholder preservation
43
+ let penalty = 0;
44
+ const fail = (type, severity, cost, message) => {
45
+ issues.push({ type, severity, message });
46
+ penalty += cost;
47
+ };
56
48
  const sourcePlaceholders = (sourceText.match(/\{[^}]+\}/g) || []).length;
57
49
  const translationPlaceholders = (translation.match(/\{[^}]+\}/g) || []).length;
58
50
  if (sourcePlaceholders !== translationPlaceholders) {
59
- score -= 0.3;
60
- issues.push("placeholder_mismatch");
51
+ fail("placeholder", "error", 0.4, `Placeholder count mismatch: ${sourcePlaceholders} → ${translationPlaceholders}`);
61
52
  }
62
- // Check 2: HTML tag preservation
63
53
  const sourceTags = (sourceText.match(/<[^>]+>/g) || []).length;
64
54
  const translationTags = (translation.match(/<[^>]+>/g) || []).length;
65
55
  if (sourceTags !== translationTags) {
66
- score -= 0.2;
67
- issues.push("html_tag_mismatch");
56
+ fail("html_tag", "error", 0.4, `HTML tag count mismatch: ${sourceTags} → ${translationTags}`);
68
57
  }
69
- // Check 3: Length appropriateness
70
58
  const lengthRatio = translation.length / sourceText.length;
71
59
  if (lengthRatio < 0.3 || lengthRatio > 3.0) {
72
- score -= 0.2;
73
- issues.push("extreme_length_difference");
60
+ fail("length", "warning", 0.2, `Translation is ${Math.round(lengthRatio * 100)}% of source length`);
74
61
  }
75
- // Check 4: Punctuation consistency
76
62
  const sourceEndsWithPunctuation = /[.!?;:]$/.test(sourceText.trim());
77
63
  const translationEndsWithPunctuation = /[.!?;:]$/.test(translation.trim());
78
64
  if (sourceEndsWithPunctuation !== translationEndsWithPunctuation) {
79
- score -= 0.1;
80
- issues.push("punctuation_inconsistency");
65
+ fail("punctuation", "warning", 0.1, "Ending punctuation differs from the source");
81
66
  }
82
- // Check 5: Empty or too short translation
83
67
  if (translation.trim().length < 2) {
84
- score -= 0.4;
85
- issues.push("translation_too_short");
68
+ fail("too_short", "error", 0.4, "Translation is empty or a single character");
86
69
  }
87
- // Check 6: Same as source (no translation occurred)
88
70
  if (sourceText.toLowerCase().trim() === translation.toLowerCase().trim()) {
89
- score -= 0.3;
90
- issues.push("identical_to_source");
71
+ fail("untranslated", "error", 0.4, "Translation is identical to the source");
91
72
  }
92
- // Check 7: Contains AI artifacts
93
73
  const artifacts = [
94
74
  /^translation:/i,
95
75
  /^translated text:/i,
@@ -97,10 +77,9 @@ class ConfidenceScorer {
97
77
  /^the translation is/i,
98
78
  ];
99
79
  if (artifacts.some((pattern) => pattern.test(translation))) {
100
- score -= 0.2;
101
- issues.push("contains_ai_artifacts");
80
+ fail("ai_artifact", "error", 0.4, "Translation starts with model commentary");
102
81
  }
103
- return Math.max(0, score);
82
+ return { qualityScore: parseFloat(Math.max(0, 1 - penalty).toFixed(3)), issues };
104
83
  }
105
84
  /**
106
85
  * Get category-specific adjustment
@@ -129,7 +108,10 @@ class ConfidenceScorer {
129
108
  /**
130
109
  * Get language pair complexity factor
131
110
  */
132
- static _getLanguagePairFactor(sourceLang, targetLang) {
111
+ static _getLanguagePairFactor(sourceTag, targetTag) {
112
+ // Compare on the primary language subtag so "zh-Hant" or "pt-BR" behave like "zh" / "pt"
113
+ const sourceLang = sourceTag.split("-")[0].toLowerCase();
114
+ const targetLang = targetTag.split("-")[0].toLowerCase();
133
115
  // Similar language pairs get higher confidence
134
116
  const similarPairs = {
135
117
  en: ["es", "fr", "de", "pl"],
@@ -182,45 +164,6 @@ class ConfidenceScorer {
182
164
  return "low";
183
165
  return "very_low";
184
166
  }
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
167
  /**
225
168
  * Get confidence color for terminal display
226
169
  */
@@ -241,37 +184,5 @@ class ConfidenceScorer {
241
184
  const percentage = (score * 100).toFixed(1);
242
185
  return `${color} ${percentage}%`;
243
186
  }
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
187
  }
277
188
  export default ConfidenceScorer;
@@ -2,16 +2,37 @@
2
2
  * Centralized Error Handling System
3
3
  * Provides detailed, actionable error messages with context and solutions
4
4
  */
5
+ /**
6
+ * Context attached to a helper error. The named fields are the ones the
7
+ * message templates read; callers may attach any extra debug fields.
8
+ */
9
+ export interface ErrorDetails {
10
+ provider?: string;
11
+ statusCode?: number | string;
12
+ apiMessage?: string | null;
13
+ retryAfter?: number | string;
14
+ limit?: number | string;
15
+ current?: number | string | null;
16
+ currentConcurrency?: number;
17
+ timeout?: number;
18
+ validationErrors?: string[];
19
+ filePath?: string;
20
+ expectedSource?: string;
21
+ key?: string;
22
+ language?: string;
23
+ reason?: string;
24
+ langCode?: string;
25
+ field?: string;
26
+ message?: string;
27
+ [key: string]: unknown;
28
+ }
5
29
  export interface HelperError extends Error {
6
30
  code?: string;
7
31
  type?: string;
8
- details?: Record<string, any>;
32
+ details?: ErrorDetails;
9
33
  solutions?: string[];
10
34
  context?: string[];
11
35
  }
12
- export interface ErrorDetails {
13
- [key: string]: any;
14
- }
15
36
  declare class ErrorHelper {
16
37
  static ErrorCodes: {
17
38
  API_RATE_LIMIT: string;
@@ -74,8 +95,8 @@ declare class ErrorHelper {
74
95
  * Quick error creators for common cases
75
96
  */
76
97
  static rateLimitError(provider: string, details?: ErrorDetails): HelperError;
77
- static authError(provider: string, statusCode?: number | string, apiMessage?: string): HelperError;
78
- static serverError(provider: string, statusCode?: number | string, apiMessage?: string): HelperError;
98
+ static authError(provider: string, statusCode?: number | string, apiMessage?: string | null): HelperError;
99
+ static serverError(provider: string, statusCode?: number | string, apiMessage?: string | null): HelperError;
79
100
  static networkError(provider: string): HelperError;
80
101
  static timeoutError(provider: string, timeout?: number): HelperError;
81
102
  static configNotFoundError(): HelperError;
@@ -270,14 +270,15 @@ class ErrorHelper {
270
270
  code: this.ErrorCodes.VALIDATION_LANGUAGE,
271
271
  message: `Invalid language code: ${details.langCode || "unknown"}`,
272
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",
273
+ "Language codes must be BCP 47 language tags",
274
+ "Format: 2-3 letter language, optional 4-letter script, optional 2-letter or 3-digit region",
275
+ "Examples: 'en', 'fil', 'pt-BR', 'zh-Hant', 'zh-Hant-TW', 'es-419'",
276
276
  ],
277
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"]`,
278
+ "Use a language tag instead of a language name (e.g., 'en' instead of 'english')",
279
+ "Separate subtags with '-' (e.g., 'pt-BR', not 'pt_BR')",
280
+ "Look up tags in the IANA Language Subtag Registry: https://www.iana.org/assignments/language-subtag-registry",
281
+ `Update your config: ${details.field || "targets"} = ["en", "pt-BR", "zh-Hant"]`,
281
282
  ],
282
283
  },
283
284
  };
@@ -323,7 +324,7 @@ class ErrorHelper {
323
324
  xai: "XAI_API_KEY",
324
325
  anthropic: "ANTHROPIC_API_KEY",
325
326
  };
326
- return envMap[provider?.toLowerCase()] || `${provider?.toUpperCase()}_API_KEY`;
327
+ return envMap[provider?.toLowerCase() ?? ""] || `${provider?.toUpperCase()}_API_KEY`;
327
328
  }
328
329
  /**
329
330
  * Get provider console URL
@@ -337,7 +338,7 @@ class ErrorHelper {
337
338
  xai: "https://console.x.ai/",
338
339
  anthropic: "https://console.anthropic.com/settings/keys",
339
340
  };
340
- return urlMap[provider?.toLowerCase()] || "provider console";
341
+ return urlMap[provider?.toLowerCase() ?? ""] || "provider console";
341
342
  }
342
343
  /**
343
344
  * Suggest fallback provider
@@ -345,7 +346,7 @@ class ErrorHelper {
345
346
  static _suggestFallbackProvider(currentProvider) {
346
347
  const fallbackOrder = ["openai", "anthropic", "gemini", "deepseek", "dashscope", "xai"];
347
348
  const current = currentProvider?.toLowerCase();
348
- const index = fallbackOrder.indexOf(current);
349
+ const index = current ? fallbackOrder.indexOf(current) : -1;
349
350
  if (index === -1 || index === fallbackOrder.length - 1) {
350
351
  return fallbackOrder[0];
351
352
  }
@@ -1,3 +1,4 @@
1
+ import type { TranslationObject } from "./object-transformer.js";
1
2
  export interface FileOptions {
2
3
  atomic?: boolean;
3
4
  createMissingDirs?: boolean;
@@ -18,7 +19,7 @@ export interface ValidationResult {
18
19
  line: number | null;
19
20
  } | null;
20
21
  key?: string;
21
- value?: any;
22
+ value?: unknown;
22
23
  }
23
24
  /**
24
25
  * FileManager - Modern asynchronous file operations
@@ -59,7 +60,7 @@ declare class FileManager {
59
60
  * @param options - Options for reading
60
61
  * @returns Parsed data
61
62
  */
62
- static readFile(filePath: string, options?: FileOptions): Promise<any>;
63
+ static readFile(filePath: string, options?: FileOptions): Promise<TranslationObject>;
63
64
  /**
64
65
  * Write data to file asynchronously
65
66
  * @param filePath - Path to write the file
@@ -67,9 +68,9 @@ declare class FileManager {
67
68
  * @param options - Options for writing
68
69
  * @returns Success status
69
70
  */
70
- static writeFile(filePath: string, data: any, options?: FileOptions): Promise<boolean>;
71
- static readJSON(filePath: string, options?: FileOptions): Promise<any>;
72
- static writeJSON(filePath: string, data: any, options?: FileOptions): Promise<boolean>;
71
+ static writeFile(filePath: string, data: TranslationObject, options?: FileOptions): Promise<boolean>;
72
+ static readJSON(filePath: string, options?: FileOptions): Promise<TranslationObject>;
73
+ static writeJSON(filePath: string, data: TranslationObject, options?: FileOptions): Promise<boolean>;
73
74
  /**
74
75
  * Generate a unique temporary file path
75
76
  */
@@ -108,7 +109,7 @@ declare class FileManager {
108
109
  * @param error - JSON parse error
109
110
  * @returns Error position details
110
111
  */
111
- static _extractErrorPosition(error: any): {
112
+ static _extractErrorPosition(error: unknown): {
112
113
  position: number | null;
113
114
  line: number | null;
114
115
  } | null;
@@ -118,7 +119,7 @@ declare class FileManager {
118
119
  * @param value - Translation value
119
120
  * @returns Validation result
120
121
  */
121
- static validateTranslationValue(key: string, value: any): ValidationResult;
122
+ static validateTranslationValue(key: string, value: unknown): ValidationResult;
122
123
  /**
123
124
  * List files in a directory
124
125
  * @param dirPath - Directory path
@@ -1,6 +1,11 @@
1
1
  import { promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import { FormatFactory } from "../core/adapters/factory.js";
4
+ /**
5
+ * Read `code` (e.g. "ENOENT") from a caught Node.js system error.
6
+ */
7
+ const errorCode = (error) => error instanceof Error && "code" in error ? error.code : undefined;
8
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
4
9
  /**
5
10
  * FileManager - Modern asynchronous file operations
6
11
  * Handles multiple file formats via Adapter pattern.
@@ -92,10 +97,10 @@ class FileManager {
92
97
  throw new Error(`Source language file not found for '${sourceLang}' in ${localesDir}`);
93
98
  }
94
99
  catch (err) {
95
- if (err.code === "ENOENT") {
100
+ if (errorCode(err) === "ENOENT") {
96
101
  throw new Error(`Locales directory not found: ${localesDir}`);
97
102
  }
98
- throw new Error(`Source language file search error: ${err.message}`);
103
+ throw new Error(`Source language file search error: ${errorMessage(err)}`);
99
104
  }
100
105
  }
101
106
  /**
@@ -104,7 +109,6 @@ class FileManager {
104
109
  * @param options - Options for reading
105
110
  * @returns Parsed data
106
111
  */
107
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
112
  static async readFile(filePath, options = {}) {
109
113
  const config = { ...this.getConfig(), ...options };
110
114
  try {
@@ -115,7 +119,7 @@ class FileManager {
115
119
  return await adapter.parse(content);
116
120
  }
117
121
  catch (err) {
118
- throw new Error(`File read error (${filePath}): ${err.message}`);
122
+ throw new Error(`File read error (${filePath}): ${errorMessage(err)}`);
119
123
  }
120
124
  }
121
125
  /**
@@ -125,7 +129,6 @@ class FileManager {
125
129
  * @param options - Options for writing
126
130
  * @returns Success status
127
131
  */
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
132
  static async writeFile(filePath, data, options = {}) {
130
133
  const config = { ...this.getConfig(), ...options };
131
134
  try {
@@ -147,8 +150,8 @@ class FileManager {
147
150
  }
148
151
  catch (err) {
149
152
  // Ignore if file doesn't exist
150
- if (err.code !== "ENOENT") {
151
- console.error(`Backup failed (${filePath}): ${err.message}`);
153
+ if (errorCode(err) !== "ENOENT") {
154
+ console.error(`Backup failed (${filePath}): ${errorMessage(err)}`);
152
155
  }
153
156
  }
154
157
  }
@@ -179,7 +182,7 @@ class FileManager {
179
182
  /* ignore cleanup error */
180
183
  }
181
184
  }
182
- throw new Error(`Atomic write failed (${filePath}): ${renameError.message}`);
185
+ throw new Error(`Atomic write failed (${filePath}): ${errorMessage(renameError)}`);
183
186
  }
184
187
  }
185
188
  else {
@@ -188,15 +191,13 @@ class FileManager {
188
191
  return true;
189
192
  }
190
193
  catch (err) {
191
- throw new Error(`File write error (${filePath}): ${err.message}`);
194
+ throw new Error(`File write error (${filePath}): ${errorMessage(err)}`);
192
195
  }
193
196
  }
194
197
  // Legacy method aliases for backward compatibility
195
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
198
  static async readJSON(filePath, options = {}) {
197
199
  return this.readFile(filePath, { ...options, format: "json" });
198
200
  }
199
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
200
201
  static async writeJSON(filePath, data, options = {}) {
201
202
  return this.writeFile(filePath, data, { ...options, format: "json" });
202
203
  }
@@ -216,7 +217,7 @@ class FileManager {
216
217
  await fs.mkdir(dir, { recursive: true });
217
218
  }
218
219
  catch (err) {
219
- if (err.code !== "EEXIST") {
220
+ if (errorCode(err) !== "EEXIST") {
220
221
  throw err;
221
222
  }
222
223
  }
@@ -232,7 +233,7 @@ class FileManager {
232
233
  .map((file) => path.join(localesDir, file));
233
234
  }
234
235
  catch (err) {
235
- throw new Error(`Error scanning locale directory: ${err.message}`);
236
+ throw new Error(`Error scanning locale directory: ${errorMessage(err)}`);
236
237
  }
237
238
  }
238
239
  /**
@@ -244,7 +245,7 @@ class FileManager {
244
245
  return stats.mtime;
245
246
  }
246
247
  catch (err) {
247
- throw new Error(`Error getting file stats: ${err.message}`);
248
+ throw new Error(`Error getting file stats: ${errorMessage(err)}`);
248
249
  }
249
250
  }
250
251
  /**
@@ -277,14 +278,14 @@ class FileManager {
277
278
  await fs.copyFile(filePath, backupPath);
278
279
  }
279
280
  catch (err) {
280
- console.error(`Backup failed (${filePath}): ${err.message}`);
281
+ console.error(`Backup failed (${filePath}): ${errorMessage(err)}`);
281
282
  }
282
283
  }
283
284
  await fs.unlink(filePath);
284
285
  return true;
285
286
  }
286
287
  catch (err) {
287
- throw new Error(`File deletion error (${filePath}): ${err.message}`);
288
+ throw new Error(`File deletion error (${filePath}): ${errorMessage(err)}`);
288
289
  }
289
290
  }
290
291
  /**
@@ -305,7 +306,7 @@ class FileManager {
305
306
  return {
306
307
  valid: false,
307
308
  filePath,
308
- error: error.message,
309
+ error: errorMessage(error),
309
310
  position: this._extractErrorPosition(error),
310
311
  };
311
312
  }
@@ -315,9 +316,8 @@ class FileManager {
315
316
  * @param error - JSON parse error
316
317
  * @returns Error position details
317
318
  */
318
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
319
  static _extractErrorPosition(error) {
320
- const message = error.message;
320
+ const message = errorMessage(error);
321
321
  const positionMatch = message.match(/position (\d+)/);
322
322
  const lineMatch = message.match(/line (\d+)/);
323
323
  if (positionMatch || lineMatch) {
@@ -334,7 +334,6 @@ class FileManager {
334
334
  * @param value - Translation value
335
335
  * @returns Validation result
336
336
  */
337
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
337
  static validateTranslationValue(key, value) {
339
338
  try {
340
339
  // Test if value can be part of valid JSON
@@ -352,7 +351,7 @@ class FileManager {
352
351
  valid: false,
353
352
  key,
354
353
  value,
355
- error: `JSON validation failed: ${error.message}`,
354
+ error: `JSON validation failed: ${errorMessage(error)}`,
356
355
  };
357
356
  }
358
357
  }
@@ -370,7 +369,7 @@ class FileManager {
370
369
  return files;
371
370
  }
372
371
  catch (err) {
373
- throw new Error(`Directory read error (${dirPath}): ${err.message}`);
372
+ throw new Error(`Directory read error (${dirPath}): ${errorMessage(err)}`);
374
373
  }
375
374
  }
376
375
  /**
@@ -384,7 +383,7 @@ class FileManager {
384
383
  return true;
385
384
  }
386
385
  catch (err) {
387
- throw new Error(`Failed to create directory (${dirPath}): ${err.message}`);
386
+ throw new Error(`Failed to create directory (${dirPath}): ${errorMessage(err)}`);
388
387
  }
389
388
  }
390
389
  }