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.
- package/README.md +23 -25
- package/dist/cli/helpers.d.ts +34 -2
- package/dist/cli/helpers.js +96 -37
- package/dist/cli.js +40 -39
- package/dist/commands/review.d.ts +33 -8
- package/dist/commands/review.js +89 -22
- package/dist/commands/translator.d.ts +4 -2
- package/dist/commands/translator.js +1 -1
- package/dist/config/index.d.ts +53 -66
- package/dist/config/index.js +2 -1
- package/dist/config/setup.d.ts +3 -2
- package/dist/config/setup.js +5 -3
- package/dist/core/adapters/android-adapter.d.ts +3 -2
- package/dist/core/adapters/android-adapter.js +12 -13
- package/dist/core/adapters/base-adapter.d.ts +13 -2
- package/dist/core/adapters/ios-adapter.d.ts +3 -2
- package/dist/core/adapters/json-adapter.d.ts +4 -3
- package/dist/core/adapters/json-adapter.js +1 -3
- package/dist/core/adapters/po-adapter.d.ts +3 -2
- package/dist/core/adapters/po-adapter.js +4 -5
- package/dist/core/adapters/properties-adapter.d.ts +3 -2
- package/dist/core/adapters/properties-adapter.js +2 -4
- package/dist/core/adapters/yaml-adapter.d.ts +4 -3
- package/dist/core/adapters/yaml-adapter.js +1 -3
- package/dist/core/context-processor.d.ts +21 -6
- package/dist/core/context-processor.js +2 -7
- package/dist/core/detection/framework-detector.js +1 -1
- package/dist/core/detection/structure-detector.js +1 -1
- package/dist/core/fallback-provider.d.ts +3 -4
- package/dist/core/fallback-provider.js +15 -21
- package/dist/core/orchestrator.d.ts +98 -26
- package/dist/core/orchestrator.js +56 -116
- package/dist/core/pipeline/Pipeline.d.ts +2 -2
- package/dist/core/pipeline/Pipeline.js +1 -3
- package/dist/core/pipeline/context.d.ts +69 -13
- package/dist/core/pipeline/context.js +1 -7
- package/dist/core/pipeline/steps/CacheReadStep.d.ts +10 -7
- package/dist/core/pipeline/steps/CacheReadStep.js +1 -12
- package/dist/core/pipeline/steps/CacheWriteStep.d.ts +2 -2
- package/dist/core/pipeline/steps/CacheWriteStep.js +1 -8
- package/dist/core/pipeline/steps/ConfidenceCheckStep.d.ts +5 -0
- package/dist/core/pipeline/steps/ConfidenceCheckStep.js +24 -44
- package/dist/core/pipeline/steps/ContextEnrichmentStep.js +3 -14
- package/dist/core/pipeline/steps/QualityCheckStep.d.ts +1 -1
- package/dist/core/pipeline/steps/QualityCheckStep.js +0 -2
- package/dist/core/pipeline/steps/ToneCheckStep.js +1 -1
- package/dist/core/pipeline/steps/TranslationStep.d.ts +13 -3
- package/dist/core/pipeline/steps/TranslationStep.js +21 -23
- package/dist/core/pipeline/steps/VectorCacheReadStep.d.ts +9 -1
- package/dist/core/pipeline/steps/VectorCacheReadStep.js +3 -5
- package/dist/core/pipeline/steps/VectorCacheWriteStep.d.ts +2 -1
- package/dist/core/pipeline/steps/VectorCacheWriteStep.js +2 -1
- package/dist/core/provider-factory.d.ts +15 -9
- package/dist/core/provider-factory.js +42 -32
- package/dist/providers/anthropic.d.ts +5 -4
- package/dist/providers/anthropic.js +3 -2
- package/dist/providers/api-types.d.ts +74 -0
- package/dist/providers/api-types.js +5 -0
- package/dist/providers/base-provider.d.ts +27 -31
- package/dist/providers/base-provider.js +22 -46
- package/dist/providers/dashscope.js +0 -2
- package/dist/providers/deepseek.js +0 -1
- package/dist/providers/gemini.js +0 -1
- package/dist/providers/openai.d.ts +3 -3
- package/dist/providers/openai.js +0 -1
- package/dist/providers/xai.js +0 -1
- package/dist/services/embedding-provider.js +4 -2
- package/dist/services/language-processor.d.ts +5 -3
- package/dist/services/language-processor.js +38 -43
- package/dist/services/source-analyzer.d.ts +24 -0
- package/dist/services/source-analyzer.js +161 -68
- package/dist/services/translation-service.d.ts +31 -13
- package/dist/services/translation-service.js +116 -71
- package/dist/services/vector-store.d.ts +15 -2
- package/dist/services/vector-store.js +14 -10
- package/dist/types/index.d.ts +31 -33
- package/dist/utils/ai-context-analyzer.d.ts +12 -3
- package/dist/utils/ai-context-analyzer.js +18 -10
- package/dist/utils/confidence-scorer.d.ts +16 -19
- package/dist/utils/confidence-scorer.js +36 -125
- package/dist/utils/error-helper.d.ts +27 -6
- package/dist/utils/error-helper.js +10 -9
- package/dist/utils/file-manager.d.ts +8 -7
- package/dist/utils/file-manager.js +22 -23
- package/dist/utils/glossary-manager.d.ts +22 -3
- package/dist/utils/glossary-manager.js +47 -28
- package/dist/utils/graceful-shutdown.d.ts +7 -1
- package/dist/utils/graceful-shutdown.js +1 -2
- package/dist/utils/input-validator.d.ts +53 -66
- package/dist/utils/input-validator.js +100 -151
- package/dist/utils/locale-paths.d.ts +7 -0
- package/dist/utils/locale-paths.js +16 -0
- package/dist/utils/logger.d.ts +8 -8
- package/dist/utils/logger.js +7 -15
- package/dist/utils/object-transformer.d.ts +20 -5
- package/dist/utils/object-transformer.js +20 -28
- package/dist/utils/progress-tracker.js +2 -3
- package/dist/utils/prompt-templates.d.ts +45 -5
- package/dist/utils/prompt-templates.js +61 -5
- package/dist/utils/quality/base-checker.d.ts +40 -18
- package/dist/utils/quality/base-checker.js +1 -2
- package/dist/utils/quality/code-block-checker.d.ts +13 -5
- package/dist/utils/quality/index.d.ts +2 -1
- package/dist/utils/quality/index.js +8 -27
- package/dist/utils/quality/length-checker.d.ts +35 -5
- package/dist/utils/quality/length-checker.js +2 -3
- package/dist/utils/quality/markdown-checker.d.ts +14 -7
- package/dist/utils/quality/special-characters-checker.d.ts +18 -1
- package/dist/utils/quality/special-characters-checker.js +0 -1
- package/dist/utils/quality/style-guide-checker.d.ts +1 -1
- package/dist/utils/quality/style-guide-checker.js +0 -2
- package/dist/utils/quality/tone-verifier.js +1 -1
- package/dist/utils/rate-limiter.d.ts +17 -1
- package/dist/utils/rate-limiter.js +12 -11
- package/dist/utils/retry-helper.d.ts +21 -3
- package/dist/utils/retry-helper.js +11 -3
- package/dist/utils/review-queue.d.ts +45 -0
- package/dist/utils/review-queue.js +53 -0
- package/dist/utils/state-manager.d.ts +12 -3
- package/dist/utils/state-manager.js +32 -7
- package/dist/utils/statistics-manager.d.ts +11 -1
- package/dist/utils/statistics-manager.js +8 -0
- package/dist/utils/ui-manager.d.ts +2 -1
- package/dist/utils/ui-manager.js +15 -1
- package/package.json +22 -17
- package/dist/commands/interactive-review.d.ts +0 -9
- package/dist/commands/interactive-review.js +0 -109
- package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.d.ts +0 -1
- package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +0 -100
- package/dist/utils/quality/__tests__/tone-verifier.test.d.ts +0 -1
- package/dist/utils/quality/__tests__/tone-verifier.test.js +0 -91
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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: "
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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 {
|
|
13
|
-
|
|
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
|
-
//
|
|
22
|
-
const
|
|
23
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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?:
|
|
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
|
|
274
|
-
"
|
|
275
|
-
"
|
|
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
|
|
279
|
-
"
|
|
280
|
-
|
|
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?:
|
|
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<
|
|
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:
|
|
71
|
-
static readJSON(filePath: string, options?: FileOptions): Promise<
|
|
72
|
-
static writeJSON(filePath: string, data:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
151
|
-
console.error(`Backup failed (${filePath}): ${err
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
386
|
+
throw new Error(`Failed to create directory (${dirPath}): ${errorMessage(err)}`);
|
|
388
387
|
}
|
|
389
388
|
}
|
|
390
389
|
}
|