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,8 +1,19 @@
|
|
|
1
|
+
/** Details for a code block count mismatch */
|
|
2
|
+
export interface CodeBlockCountDetails {
|
|
3
|
+
sourceCount: number;
|
|
4
|
+
translatedCount: number;
|
|
5
|
+
}
|
|
6
|
+
/** Details for a code block whose content changed */
|
|
7
|
+
export interface CodeBlockModifiedDetails {
|
|
8
|
+
index: number;
|
|
9
|
+
sourceCode: string;
|
|
10
|
+
translatedCode: string;
|
|
11
|
+
}
|
|
1
12
|
export interface CodeBlockIssue {
|
|
2
13
|
type: "code-block-count" | "code-block-modified" | "code-block-restore";
|
|
3
14
|
severity: "error" | "critical";
|
|
4
15
|
message: string;
|
|
5
|
-
details?:
|
|
16
|
+
details?: CodeBlockCountDetails | CodeBlockModifiedDetails;
|
|
6
17
|
}
|
|
7
18
|
export interface CodeBlockFixResult {
|
|
8
19
|
text: string;
|
|
@@ -40,10 +51,7 @@ declare class CodeBlockChecker {
|
|
|
40
51
|
/**
|
|
41
52
|
* Restore missing code blocks from source.
|
|
42
53
|
*/
|
|
43
|
-
restoreCodeBlocks(sourceText: string, translatedText: string, sourceCodeBlocks: CodeBlock[]):
|
|
44
|
-
text: string;
|
|
45
|
-
appliedFixes: any[];
|
|
46
|
-
};
|
|
54
|
+
restoreCodeBlocks(sourceText: string, translatedText: string, sourceCodeBlocks: CodeBlock[]): Pick<CodeBlockFixResult, "text" | "appliedFixes">;
|
|
47
55
|
/**
|
|
48
56
|
* Protect code blocks before translation.
|
|
49
57
|
* Replaces code blocks with tokens to prevent translation.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import BaseChecker, { CheckerOptions, FixResult, ValidationResult } from "./base-checker.js";
|
|
2
|
+
import { LengthCheckOptions } from "./length-checker.js";
|
|
2
3
|
/**
|
|
3
4
|
* Main Quality Checker Class.
|
|
4
5
|
* Orchestrates various quality checks and fixes for translated text.
|
|
@@ -26,7 +27,7 @@ declare class QualityChecker extends BaseChecker {
|
|
|
26
27
|
/**
|
|
27
28
|
* Validate translated text against source text.
|
|
28
29
|
*/
|
|
29
|
-
validate(sourceText: string, translatedText: string, options?:
|
|
30
|
+
validate(sourceText: string, translatedText: string, options?: LengthCheckOptions): ValidationResult;
|
|
30
31
|
/**
|
|
31
32
|
* Validate and attempt to fix translated text.
|
|
32
33
|
*/
|
|
@@ -43,7 +43,10 @@ class QualityChecker extends BaseChecker {
|
|
|
43
43
|
this.lengthChecker = new LengthChecker();
|
|
44
44
|
this.textSanitizer = new TextSanitizer();
|
|
45
45
|
this.quoteBalanceChecker = new QuoteBalanceChecker();
|
|
46
|
-
this.styleGuideChecker = new StyleGuideChecker(
|
|
46
|
+
this.styleGuideChecker = new StyleGuideChecker({
|
|
47
|
+
...this.styleGuide,
|
|
48
|
+
conventions: this.rules.styleGuide?.conventions,
|
|
49
|
+
});
|
|
47
50
|
this.markdownChecker = new MarkdownChecker();
|
|
48
51
|
this.codeBlockChecker = new CodeBlockChecker();
|
|
49
52
|
this.specialCharactersChecker = new SpecialCharactersChecker();
|
|
@@ -54,51 +57,37 @@ class QualityChecker extends BaseChecker {
|
|
|
54
57
|
validate(sourceText, translatedText, options = {}) {
|
|
55
58
|
const issues = [];
|
|
56
59
|
if (this.rules.placeholderConsistency) {
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
60
|
issues.push(...this.placeholderChecker.checkPlaceholders(sourceText, translatedText));
|
|
59
61
|
}
|
|
60
62
|
if (this.rules.htmlTagsConsistency) {
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
63
|
issues.push(...this.htmlTagChecker.checkHtmlTags(sourceText, translatedText));
|
|
63
64
|
}
|
|
64
65
|
if (this.rules.punctuationCheck) {
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
66
|
issues.push(...this.punctuationChecker.checkPunctuation(sourceText, translatedText));
|
|
67
67
|
}
|
|
68
68
|
// Quote balance validation
|
|
69
69
|
if (this.rules.quoteBalanceCheck !== false) {
|
|
70
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
70
|
issues.push(...this.quoteBalanceChecker.checkQuoteBalance(translatedText));
|
|
72
|
-
issues.push(
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
-
...this.quoteBalanceChecker.validateQuoteConsistency(sourceText, translatedText));
|
|
71
|
+
issues.push(...this.quoteBalanceChecker.validateQuoteConsistency(sourceText, translatedText));
|
|
75
72
|
}
|
|
76
73
|
if (this.rules.lengthValidation) {
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
74
|
issues.push(...this.lengthChecker.checkLength(sourceText, translatedText, options));
|
|
79
75
|
}
|
|
80
76
|
// Style guide validation
|
|
81
77
|
if (this.styleGuide && this.styleGuideChecker) {
|
|
82
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
78
|
issues.push(...this.styleGuideChecker.checkStyleGuide(sourceText, translatedText));
|
|
84
79
|
}
|
|
85
80
|
// Markdown preservation
|
|
86
81
|
if (this.rules.markdownPreservation) {
|
|
87
|
-
issues.push(
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
-
...this.markdownChecker.checkMarkdownPreservation(sourceText, translatedText));
|
|
82
|
+
issues.push(...this.markdownChecker.checkMarkdownPreservation(sourceText, translatedText));
|
|
90
83
|
}
|
|
91
84
|
// Code block preservation
|
|
92
85
|
if (this.rules.codeBlockPreservation) {
|
|
93
|
-
issues.push(
|
|
94
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
-
...this.codeBlockChecker.checkCodeBlockPreservation(sourceText, translatedText));
|
|
86
|
+
issues.push(...this.codeBlockChecker.checkCodeBlockPreservation(sourceText, translatedText));
|
|
96
87
|
}
|
|
97
88
|
// Special characters preservation
|
|
98
89
|
if (this.rules.specialCharacters) {
|
|
99
|
-
issues.push(
|
|
100
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
-
...this.specialCharactersChecker.checkSpecialCharacters(sourceText, translatedText));
|
|
90
|
+
issues.push(...this.specialCharactersChecker.checkSpecialCharacters(sourceText, translatedText));
|
|
102
91
|
}
|
|
103
92
|
if (this.rules.sanitizeOutput) {
|
|
104
93
|
translatedText = this.sanitizeTranslation(translatedText);
|
|
@@ -124,21 +113,18 @@ class QualityChecker extends BaseChecker {
|
|
|
124
113
|
if (this.rules.placeholderConsistency) {
|
|
125
114
|
const result = this.placeholderChecker.fixPlaceholders(sourceText, fixedText);
|
|
126
115
|
fixedText = result.text;
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
116
|
issues.push(...result.foundIssues);
|
|
129
117
|
fixes.push(...result.appliedFixes);
|
|
130
118
|
}
|
|
131
119
|
if (this.rules.htmlTagsConsistency) {
|
|
132
120
|
const result = this.htmlTagChecker.fixHtmlTags(sourceText, fixedText);
|
|
133
121
|
fixedText = result.text;
|
|
134
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
122
|
issues.push(...result.foundIssues);
|
|
136
123
|
fixes.push(...result.appliedFixes);
|
|
137
124
|
}
|
|
138
125
|
if (this.rules.punctuationCheck) {
|
|
139
126
|
const result = this.punctuationChecker.fixPunctuation(sourceText, fixedText);
|
|
140
127
|
fixedText = result.text;
|
|
141
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
142
128
|
issues.push(...result.foundIssues);
|
|
143
129
|
fixes.push(...result.appliedFixes);
|
|
144
130
|
}
|
|
@@ -146,7 +132,6 @@ class QualityChecker extends BaseChecker {
|
|
|
146
132
|
if (this.rules.quoteBalanceCheck !== false) {
|
|
147
133
|
const result = this.quoteBalanceChecker.fixQuoteBalance(fixedText);
|
|
148
134
|
fixedText = result.text;
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
150
135
|
issues.push(...result.foundIssues);
|
|
151
136
|
fixes.push(...result.appliedFixes);
|
|
152
137
|
}
|
|
@@ -154,7 +139,6 @@ class QualityChecker extends BaseChecker {
|
|
|
154
139
|
if (this.styleGuide && this.styleGuideChecker) {
|
|
155
140
|
const result = this.styleGuideChecker.fixStyleGuide(sourceText, fixedText);
|
|
156
141
|
fixedText = result.text;
|
|
157
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
142
|
issues.push(...result.foundIssues);
|
|
159
143
|
fixes.push(...result.appliedFixes);
|
|
160
144
|
}
|
|
@@ -162,7 +146,6 @@ class QualityChecker extends BaseChecker {
|
|
|
162
146
|
if (this.rules.markdownPreservation) {
|
|
163
147
|
const result = this.markdownChecker.fixMarkdownPreservation(sourceText, fixedText);
|
|
164
148
|
fixedText = result.text;
|
|
165
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
149
|
issues.push(...result.foundIssues);
|
|
167
150
|
fixes.push(...result.appliedFixes);
|
|
168
151
|
}
|
|
@@ -170,7 +153,6 @@ class QualityChecker extends BaseChecker {
|
|
|
170
153
|
if (this.rules.codeBlockPreservation) {
|
|
171
154
|
const result = this.codeBlockChecker.fixCodeBlockPreservation(sourceText, fixedText);
|
|
172
155
|
fixedText = result.text;
|
|
173
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
156
|
issues.push(...result.foundIssues);
|
|
175
157
|
fixes.push(...result.appliedFixes);
|
|
176
158
|
}
|
|
@@ -178,7 +160,6 @@ class QualityChecker extends BaseChecker {
|
|
|
178
160
|
if (this.rules.specialCharacters) {
|
|
179
161
|
const result = this.specialCharactersChecker.fixSpecialCharacters(sourceText, fixedText);
|
|
180
162
|
fixedText = result.text;
|
|
181
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
163
|
issues.push(...result.foundIssues);
|
|
183
164
|
fixes.push(...result.appliedFixes);
|
|
184
165
|
}
|
|
@@ -1,5 +1,35 @@
|
|
|
1
|
+
export type LengthMode = "strict" | "flexible" | "exact" | "relaxed" | "smart";
|
|
2
|
+
/** Modes whose tolerance is a single symmetric deviation */
|
|
3
|
+
export type StandardLengthMode = Exclude<LengthMode, "smart">;
|
|
4
|
+
/** Allowed deviation as a fraction of the source length (e.g. max 0.2 = up to 20% longer) */
|
|
5
|
+
export interface LengthDeviationBounds {
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SmartLengthRules {
|
|
10
|
+
/** Symmetric tolerance used when no language/context bound applies */
|
|
11
|
+
default?: number;
|
|
12
|
+
byLanguage?: Record<string, LengthDeviationBounds>;
|
|
13
|
+
byContext?: Record<string, LengthDeviationBounds>;
|
|
14
|
+
}
|
|
15
|
+
export interface LengthControlRules extends Partial<Record<StandardLengthMode, number>> {
|
|
16
|
+
smart?: SmartLengthRules;
|
|
17
|
+
}
|
|
18
|
+
/** The `lengthControl` section of the configuration */
|
|
19
|
+
export interface LengthControlOptions {
|
|
20
|
+
mode?: LengthMode;
|
|
21
|
+
rules?: LengthControlRules;
|
|
22
|
+
}
|
|
23
|
+
/** Options consulted by {@link LengthChecker.checkLength} */
|
|
24
|
+
export interface LengthCheckOptions {
|
|
25
|
+
lengthControl?: LengthControlOptions;
|
|
26
|
+
targetLang?: string;
|
|
27
|
+
detectedContext?: {
|
|
28
|
+
category?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
1
31
|
export interface LengthConfig {
|
|
2
|
-
mode:
|
|
32
|
+
mode: LengthMode;
|
|
3
33
|
maxDeviation: number;
|
|
4
34
|
minDeviation: number;
|
|
5
35
|
targetLang?: string;
|
|
@@ -34,7 +64,7 @@ declare class LengthChecker {
|
|
|
34
64
|
/**
|
|
35
65
|
* Check if translated text length is acceptable.
|
|
36
66
|
*/
|
|
37
|
-
checkLength(source: string, translated: string, options?:
|
|
67
|
+
checkLength(source: string, translated: string, options?: LengthCheckOptions): LengthIssue[];
|
|
38
68
|
/**
|
|
39
69
|
* Calculate length of text ignoring whitespace.
|
|
40
70
|
*/
|
|
@@ -42,15 +72,15 @@ declare class LengthChecker {
|
|
|
42
72
|
/**
|
|
43
73
|
* Get configuration for length check.
|
|
44
74
|
*/
|
|
45
|
-
getConfig(options:
|
|
75
|
+
getConfig(options: LengthCheckOptions): LengthConfig;
|
|
46
76
|
/**
|
|
47
77
|
* Get configuration for smart mode.
|
|
48
78
|
*/
|
|
49
|
-
getSmartModeConfig(lengthControl:
|
|
79
|
+
getSmartModeConfig(lengthControl: LengthControlOptions, targetLang: string, context: string): LengthConfig;
|
|
50
80
|
/**
|
|
51
81
|
* Get configuration for standard mode.
|
|
52
82
|
*/
|
|
53
|
-
getStandardModeConfig(lengthControl:
|
|
83
|
+
getStandardModeConfig(lengthControl: LengthControlOptions, mode: StandardLengthMode, targetLang: string, context: string): LengthConfig;
|
|
54
84
|
/**
|
|
55
85
|
* Calculate allowed length ratio range.
|
|
56
86
|
*/
|
|
@@ -52,7 +52,7 @@ class LengthChecker {
|
|
|
52
52
|
type: "length",
|
|
53
53
|
severity: "error",
|
|
54
54
|
message: "Length validation failed due to technical error",
|
|
55
|
-
details: { error: error.message },
|
|
55
|
+
details: { error: error instanceof Error ? error.message : String(error) },
|
|
56
56
|
},
|
|
57
57
|
];
|
|
58
58
|
}
|
|
@@ -100,8 +100,7 @@ class LengthChecker {
|
|
|
100
100
|
getStandardModeConfig(lengthControl, mode, targetLang, context) {
|
|
101
101
|
const deviation = lengthControl.rules?.[mode] || this.defaultConfig.maxDeviation;
|
|
102
102
|
return {
|
|
103
|
-
|
|
104
|
-
mode: mode,
|
|
103
|
+
mode,
|
|
105
104
|
targetLang,
|
|
106
105
|
context,
|
|
107
106
|
maxDeviation: deviation,
|
|
@@ -3,8 +3,19 @@ export interface MarkdownIssue {
|
|
|
3
3
|
severity: "error" | "warning" | "critical";
|
|
4
4
|
message: string;
|
|
5
5
|
pattern?: string;
|
|
6
|
-
details?:
|
|
6
|
+
details?: {
|
|
7
|
+
sourceCount: number;
|
|
8
|
+
translatedCount: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** A markdown link captured from source text */
|
|
12
|
+
export interface MarkdownLink {
|
|
13
|
+
full: string;
|
|
14
|
+
text: string;
|
|
15
|
+
url: string;
|
|
7
16
|
}
|
|
17
|
+
/** A captured markdown element: the matched text, or the parts of a link */
|
|
18
|
+
export type MarkdownElement = string | MarkdownLink;
|
|
8
19
|
export interface MarkdownFixResult {
|
|
9
20
|
text: string;
|
|
10
21
|
foundIssues: MarkdownIssue[];
|
|
@@ -35,15 +46,11 @@ declare class MarkdownChecker {
|
|
|
35
46
|
/**
|
|
36
47
|
* Extract markdown elements from text.
|
|
37
48
|
*/
|
|
38
|
-
extractMarkdownElements(text: string): Record<string,
|
|
49
|
+
extractMarkdownElements(text: string): Record<string, MarkdownElement[]>;
|
|
39
50
|
/**
|
|
40
51
|
* Restore markdown elements that were lost in translation.
|
|
41
52
|
*/
|
|
42
|
-
restoreMarkdownElements(text: string, elements:
|
|
43
|
-
text: string;
|
|
44
|
-
foundIssues: MarkdownIssue[];
|
|
45
|
-
appliedFixes: any[];
|
|
46
|
-
};
|
|
53
|
+
restoreMarkdownElements(text: string, elements: MarkdownElement[], type: string): MarkdownFixResult;
|
|
47
54
|
/**
|
|
48
55
|
* Fix broken markdown links.
|
|
49
56
|
*/
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
/** Details for a per-category character count mismatch */
|
|
2
|
+
export interface SpecialCharCountDetails {
|
|
3
|
+
sourceChars: string[];
|
|
4
|
+
translatedChars: string[];
|
|
5
|
+
missing: string[];
|
|
6
|
+
extra: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Details for a special character that disappeared from the translation */
|
|
9
|
+
export interface SpecialCharSubstitutionDetails {
|
|
10
|
+
originalChar: string;
|
|
11
|
+
charCode: number;
|
|
12
|
+
}
|
|
13
|
+
/** Details for an emoji count mismatch */
|
|
14
|
+
export interface EmojiDetails {
|
|
15
|
+
sourceEmojis: string[];
|
|
16
|
+
translatedEmojis: string[];
|
|
17
|
+
}
|
|
1
18
|
export interface SpecialCharIssue {
|
|
2
19
|
type: "special-characters" | "special-character-substitution" | "emoji-preservation" | "missing-special-chars" | "character-substitution";
|
|
3
20
|
category?: string;
|
|
4
21
|
severity: "error" | "warning";
|
|
5
22
|
message: string;
|
|
6
|
-
details?:
|
|
23
|
+
details?: SpecialCharCountDetails | SpecialCharSubstitutionDetails | EmojiDetails;
|
|
7
24
|
}
|
|
8
25
|
export interface SpecialCharFixResult {
|
|
9
26
|
text: string;
|
|
@@ -200,7 +200,6 @@ class SpecialCharactersChecker {
|
|
|
200
200
|
if (check && fixedText.includes(wrong)) {
|
|
201
201
|
const count = (fixedText.match(new RegExp(wrong, "g")) || []).length;
|
|
202
202
|
foundIssues.push({
|
|
203
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
203
|
type: "character-substitution",
|
|
205
204
|
severity: "warning", // Added severity
|
|
206
205
|
message: `Found ${count} instances of "${wrong}" that should be "${correct}"`,
|
|
@@ -21,7 +21,6 @@ class StyleGuideChecker {
|
|
|
21
21
|
/**
|
|
22
22
|
* Check if text follows style guide conventions.
|
|
23
23
|
*/
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
25
24
|
checkStyleGuide(_sourceText, translatedText) {
|
|
26
25
|
const issues = [];
|
|
27
26
|
if (this.styleGuide.conventions.useOxfordComma) {
|
|
@@ -85,7 +84,6 @@ class StyleGuideChecker {
|
|
|
85
84
|
/**
|
|
86
85
|
* Fix style guide issues.
|
|
87
86
|
*/
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
89
87
|
fixStyleGuide(sourceText, translatedText) {
|
|
90
88
|
let fixedText = translatedText;
|
|
91
89
|
const foundIssues = [];
|
|
@@ -14,7 +14,7 @@ export class ToneVerifier {
|
|
|
14
14
|
// Try to get a specific "judge" provider if configured, otherwise default
|
|
15
15
|
this.provider = ProviderFactory.getProvider(providerName, true);
|
|
16
16
|
}
|
|
17
|
-
catch
|
|
17
|
+
catch {
|
|
18
18
|
// If specific provider fails, might fall back to any available
|
|
19
19
|
const available = ProviderFactory.getAvailableProviders();
|
|
20
20
|
if (available.length > 0) {
|
|
@@ -7,6 +7,22 @@ export interface RateLimiterConfig {
|
|
|
7
7
|
concurrency?: number;
|
|
8
8
|
}>;
|
|
9
9
|
}
|
|
10
|
+
export interface ProviderStatus {
|
|
11
|
+
queueSize: number;
|
|
12
|
+
processing: number;
|
|
13
|
+
requestsUsed: number;
|
|
14
|
+
requestsLimit: number;
|
|
15
|
+
resetIn: number;
|
|
16
|
+
errorRate: number;
|
|
17
|
+
adjustments: {
|
|
18
|
+
rpm: number;
|
|
19
|
+
concurrency: number;
|
|
20
|
+
};
|
|
21
|
+
config: {
|
|
22
|
+
maxConcurrent: number;
|
|
23
|
+
rpm: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
10
26
|
/**
|
|
11
27
|
* Handles API rate limiting and request throttling.
|
|
12
28
|
* Supports provider-specific limits and adaptive throttling.
|
|
@@ -57,7 +73,7 @@ declare class RateLimiter {
|
|
|
57
73
|
* Get complete rate limiter status.
|
|
58
74
|
* @returns Status object including active metrics.
|
|
59
75
|
*/
|
|
60
|
-
getStatus(): Record<string,
|
|
76
|
+
getStatus(): Record<string, ProviderStatus>;
|
|
61
77
|
/**
|
|
62
78
|
* Get current configuration.
|
|
63
79
|
* @returns Configuration object.
|
|
@@ -122,7 +122,6 @@ class RateLimiter {
|
|
|
122
122
|
* @param priority - Task priority (higher is better).
|
|
123
123
|
* @returns Task result.
|
|
124
124
|
*/
|
|
125
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
125
|
async enqueue(provider, task, priority = 0) {
|
|
127
126
|
provider = provider.toLowerCase();
|
|
128
127
|
if (!this.providers[provider]) {
|
|
@@ -131,7 +130,8 @@ class RateLimiter {
|
|
|
131
130
|
return new Promise((resolve, reject) => {
|
|
132
131
|
const queueItem = {
|
|
133
132
|
task,
|
|
134
|
-
|
|
133
|
+
// Only ever called with this item's own task result, which is a T.
|
|
134
|
+
resolve: resolve,
|
|
135
135
|
reject,
|
|
136
136
|
timestamp: Date.now(),
|
|
137
137
|
priority,
|
|
@@ -194,7 +194,7 @@ class RateLimiter {
|
|
|
194
194
|
item.reject(new Error(`Request timed out in queue after ${this.config.queueTimeout}ms`));
|
|
195
195
|
}
|
|
196
196
|
catch (error) {
|
|
197
|
-
console.error(`Failed to reject timed out request: ${error.message}`);
|
|
197
|
+
console.error(`Failed to reject timed out request: ${error instanceof Error ? error.message : String(error)}`);
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
200
|
});
|
|
@@ -228,12 +228,15 @@ class RateLimiter {
|
|
|
228
228
|
}
|
|
229
229
|
catch (error) {
|
|
230
230
|
this._trackErrorRate(provider, true);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
231
|
+
// The caller reports the failure; the per-attempt details are for --debug
|
|
232
|
+
if (process.env.DEBUG === "true") {
|
|
233
|
+
console.error(`Provider ${provider} task failed:`, {
|
|
234
|
+
error: error instanceof Error ? error.message : String(error),
|
|
235
|
+
queueTime: queueTime,
|
|
236
|
+
processingTime: performance.now() - startTime,
|
|
237
|
+
queueSize: queue.length,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
237
240
|
reject(error);
|
|
238
241
|
}
|
|
239
242
|
finally {
|
|
@@ -337,9 +340,7 @@ class RateLimiter {
|
|
|
337
340
|
* Get complete rate limiter status.
|
|
338
341
|
* @returns Status object including active metrics.
|
|
339
342
|
*/
|
|
340
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
341
343
|
getStatus() {
|
|
342
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
343
344
|
const status = {};
|
|
344
345
|
Object.entries(this.providers).forEach(([name, config]) => {
|
|
345
346
|
const errorRates = this.metrics.errorRates[name];
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Retry helper for API operations with exponential backoff
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Error-like value thrown by a retried operation. The retry logic reads
|
|
6
|
+
* `status` / `code` / `message` and annotates the error with retry metadata.
|
|
7
|
+
*/
|
|
8
|
+
export interface RetryableError {
|
|
9
|
+
status?: number;
|
|
10
|
+
code?: string;
|
|
11
|
+
message?: string;
|
|
12
|
+
retryInfo?: RetryInfo;
|
|
13
|
+
retryStats?: RetryStats;
|
|
14
|
+
}
|
|
15
|
+
export interface RetryInfo {
|
|
16
|
+
attemptNumber: number;
|
|
17
|
+
maxRetries: number;
|
|
18
|
+
willRetry: boolean;
|
|
19
|
+
context: Record<string, unknown>;
|
|
20
|
+
provider?: string;
|
|
21
|
+
}
|
|
4
22
|
export interface RetryOptions {
|
|
5
23
|
provider?: string;
|
|
6
24
|
perProviderRetry?: Record<string, Partial<RetryConfig>>;
|
|
@@ -9,8 +27,8 @@ export interface RetryOptions {
|
|
|
9
27
|
initialDelay?: number;
|
|
10
28
|
maxDelay?: number;
|
|
11
29
|
context?: string;
|
|
12
|
-
logContext?: Record<string,
|
|
13
|
-
retryCondition?: (error:
|
|
30
|
+
logContext?: Record<string, unknown>;
|
|
31
|
+
retryCondition?: (error: RetryableError, attempts: number, maxRetries: number) => boolean | Promise<boolean>;
|
|
14
32
|
}
|
|
15
33
|
export interface RetryConfig {
|
|
16
34
|
maxRetries: number;
|
|
@@ -40,7 +58,7 @@ declare class RetryHelper {
|
|
|
40
58
|
* Default retry condition based on error type.
|
|
41
59
|
* Checks against retryableErrors list.
|
|
42
60
|
*/
|
|
43
|
-
static defaultRetryCondition(error:
|
|
61
|
+
static defaultRetryCondition(error: RetryableError, retryableErrors?: string[]): boolean;
|
|
44
62
|
/**
|
|
45
63
|
* Promise-based delay.
|
|
46
64
|
*/
|
|
@@ -60,7 +60,12 @@ class RetryHelper {
|
|
|
60
60
|
}
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
|
-
catch (
|
|
63
|
+
catch (caught) {
|
|
64
|
+
// Errors are annotated with retry metadata below, which a primitive rejection
|
|
65
|
+
// (a thrown string, undefined) cannot hold; wrap those so the message survives.
|
|
66
|
+
const error = typeof caught === "object" && caught !== null
|
|
67
|
+
? caught
|
|
68
|
+
: new Error(String(caught));
|
|
64
69
|
attempts++;
|
|
65
70
|
lastError = error;
|
|
66
71
|
if (!error.retryInfo) {
|
|
@@ -126,7 +131,10 @@ class RetryHelper {
|
|
|
126
131
|
return true;
|
|
127
132
|
}
|
|
128
133
|
// Check for server errors (5xx)
|
|
129
|
-
if (retryableErrors.includes("server") &&
|
|
134
|
+
if (retryableErrors.includes("server") &&
|
|
135
|
+
error.status !== undefined &&
|
|
136
|
+
error.status >= 500 &&
|
|
137
|
+
error.status < 600) {
|
|
130
138
|
return true;
|
|
131
139
|
}
|
|
132
140
|
// Check for unknown/general errors
|
|
@@ -152,7 +160,7 @@ class RetryHelper {
|
|
|
152
160
|
error.message?.includes("connection")) {
|
|
153
161
|
return true;
|
|
154
162
|
}
|
|
155
|
-
return error.status >= 500 || !error.status;
|
|
163
|
+
return (error.status !== undefined && error.status >= 500) || !error.status;
|
|
156
164
|
}
|
|
157
165
|
return false;
|
|
158
166
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ConfidenceResult } from "./confidence-scorer.js";
|
|
2
|
+
/** One key in one target language. */
|
|
3
|
+
export interface TranslationRef {
|
|
4
|
+
language: string;
|
|
5
|
+
key: string;
|
|
6
|
+
}
|
|
7
|
+
/** A translation flagged by confidence scoring, queued for `locai review`. */
|
|
8
|
+
export interface ReviewQueueItem extends TranslationRef {
|
|
9
|
+
source: string;
|
|
10
|
+
translation: string;
|
|
11
|
+
confidence: ConfidenceResult;
|
|
12
|
+
sourceLang: string;
|
|
13
|
+
category: string;
|
|
14
|
+
/** Scored below rejectThreshold, so the translation was not written to the locale file. */
|
|
15
|
+
rejected: boolean;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
}
|
|
18
|
+
/** Content of `.localize-cache/review-queue.json`. */
|
|
19
|
+
export interface ReviewQueueFile {
|
|
20
|
+
timestamp: string;
|
|
21
|
+
items: ReviewQueueItem[];
|
|
22
|
+
}
|
|
23
|
+
/** The review queue file of the project in the working directory. */
|
|
24
|
+
export declare const reviewQueuePath: () => string;
|
|
25
|
+
/**
|
|
26
|
+
* Read the review queue.
|
|
27
|
+
* @returns The queued items, or null when there is no queue file.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readReviewQueue(): ReviewQueueItem[] | null;
|
|
30
|
+
/**
|
|
31
|
+
* Add the flagged translations of a run to the review queue. Queued items for translations the
|
|
32
|
+
* run scored again are dropped first: the new score replaces them.
|
|
33
|
+
* @param scored - Every translation the run scored.
|
|
34
|
+
* @param flagged - The translations the run flagged for review or rejected.
|
|
35
|
+
* @returns The queue file and the number of items it now holds.
|
|
36
|
+
*/
|
|
37
|
+
export declare function updateReviewQueue(scored: TranslationRef[], flagged: ReviewQueueItem[]): {
|
|
38
|
+
file: string;
|
|
39
|
+
size: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Remove reviewed translations from the review queue.
|
|
43
|
+
* @param reviewed - The translations a review approved, edited or rejected.
|
|
44
|
+
*/
|
|
45
|
+
export declare function removeFromReviewQueue(reviewed: TranslationRef[]): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/** The review queue file of the project in the working directory. */
|
|
4
|
+
export const reviewQueuePath = () => path.join(process.cwd(), ".localize-cache", "review-queue.json");
|
|
5
|
+
/** Identity of a queued translation: its language and key. */
|
|
6
|
+
const refId = ({ language, key }) => JSON.stringify([language, key]);
|
|
7
|
+
/**
|
|
8
|
+
* Read the review queue.
|
|
9
|
+
* @returns The queued items, or null when there is no queue file.
|
|
10
|
+
*/
|
|
11
|
+
export function readReviewQueue() {
|
|
12
|
+
const file = reviewQueuePath();
|
|
13
|
+
if (!fs.existsSync(file)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
17
|
+
return data.items || [];
|
|
18
|
+
}
|
|
19
|
+
/** Write `items` as the review queue. */
|
|
20
|
+
function writeReviewQueue(items) {
|
|
21
|
+
const file = reviewQueuePath();
|
|
22
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
23
|
+
const data = { timestamp: new Date().toISOString(), items };
|
|
24
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2));
|
|
25
|
+
return file;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Add the flagged translations of a run to the review queue. Queued items for translations the
|
|
29
|
+
* run scored again are dropped first: the new score replaces them.
|
|
30
|
+
* @param scored - Every translation the run scored.
|
|
31
|
+
* @param flagged - The translations the run flagged for review or rejected.
|
|
32
|
+
* @returns The queue file and the number of items it now holds.
|
|
33
|
+
*/
|
|
34
|
+
export function updateReviewQueue(scored, flagged) {
|
|
35
|
+
const rescored = new Set(scored.map(refId));
|
|
36
|
+
const items = [
|
|
37
|
+
...(readReviewQueue() || []).filter((item) => !rescored.has(refId(item))),
|
|
38
|
+
...flagged,
|
|
39
|
+
];
|
|
40
|
+
return { file: writeReviewQueue(items), size: items.length };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove reviewed translations from the review queue.
|
|
44
|
+
* @param reviewed - The translations a review approved, edited or rejected.
|
|
45
|
+
*/
|
|
46
|
+
export function removeFromReviewQueue(reviewed) {
|
|
47
|
+
const items = readReviewQueue();
|
|
48
|
+
if (!items || reviewed.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const done = new Set(reviewed.map(refId));
|
|
52
|
+
writeReviewQueue(items.filter((item) => !done.has(refId(item))));
|
|
53
|
+
}
|