locai 2.6.2 → 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 +14 -9
- 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
|
@@ -4,7 +4,6 @@ import gettextParser from "gettext-parser";
|
|
|
4
4
|
*/
|
|
5
5
|
export class PoAdapter {
|
|
6
6
|
extensions = [".po", ".pot"];
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
7
|
async parse(content) {
|
|
9
8
|
try {
|
|
10
9
|
const po = gettextParser.po.parse(content);
|
|
@@ -22,11 +21,10 @@ export class PoAdapter {
|
|
|
22
21
|
return result;
|
|
23
22
|
}
|
|
24
23
|
catch (error) {
|
|
25
|
-
throw new Error(`PO parse error: ${error.message}`);
|
|
24
|
+
throw new Error(`PO parse error: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
async serialize(data, options = {}) {
|
|
27
|
+
async serialize(data) {
|
|
30
28
|
const translations = {
|
|
31
29
|
"": {},
|
|
32
30
|
};
|
|
@@ -34,7 +32,8 @@ export class PoAdapter {
|
|
|
34
32
|
for (const [key, value] of Object.entries(data)) {
|
|
35
33
|
translations[""][key] = {
|
|
36
34
|
msgid: key,
|
|
37
|
-
msgstr
|
|
35
|
+
// gettext-parser writes falsy msgstr values as "" and stringifies the rest
|
|
36
|
+
msgstr: [value ? String(value) : ""],
|
|
38
37
|
};
|
|
39
38
|
}
|
|
40
39
|
return gettextParser.po
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
1
2
|
import { FileFormatAdapter } from "./base-adapter.js";
|
|
2
3
|
/**
|
|
3
4
|
* Adapter for Java .properties files
|
|
4
5
|
*/
|
|
5
6
|
export declare class PropertiesAdapter implements FileFormatAdapter {
|
|
6
7
|
extensions: string[];
|
|
7
|
-
parse(content: string): Promise<
|
|
8
|
-
serialize(data:
|
|
8
|
+
parse(content: string): Promise<TranslationObject>;
|
|
9
|
+
serialize(data: TranslationObject): Promise<string>;
|
|
9
10
|
}
|
|
@@ -4,17 +4,15 @@ import dotProperties from "dot-properties";
|
|
|
4
4
|
*/
|
|
5
5
|
export class PropertiesAdapter {
|
|
6
6
|
extensions = [".properties"];
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
7
|
async parse(content) {
|
|
9
8
|
try {
|
|
10
9
|
return dotProperties.parse(content);
|
|
11
10
|
}
|
|
12
11
|
catch (error) {
|
|
13
|
-
throw new Error(`Properties parse error: ${error.message}`);
|
|
12
|
+
throw new Error(`Properties parse error: ${error instanceof Error ? error.message : String(error)}`);
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
async serialize(data, options = {}) {
|
|
15
|
+
async serialize(data) {
|
|
18
16
|
// dot-properties doesn't have a standardized stringify, simple implementation
|
|
19
17
|
// or used a different library, but let's do a simple one for now or use the lib if it supports it.
|
|
20
18
|
// Checking docs: dot-properties is mostly a parser. 'properties' npm package is better for writing but we installed dot-properties.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
2
|
+
import { FileFormatAdapter, SerializeOptions } from "./base-adapter.js";
|
|
2
3
|
/**
|
|
3
4
|
* Adapter for YAML files
|
|
4
5
|
*/
|
|
5
6
|
export declare class YamlAdapter implements FileFormatAdapter {
|
|
6
7
|
extensions: string[];
|
|
7
|
-
parse(content: string): Promise<
|
|
8
|
-
serialize(data:
|
|
8
|
+
parse(content: string): Promise<TranslationObject>;
|
|
9
|
+
serialize(data: TranslationObject, options?: SerializeOptions): Promise<string>;
|
|
9
10
|
}
|
|
@@ -4,16 +4,14 @@ import yaml from "js-yaml";
|
|
|
4
4
|
*/
|
|
5
5
|
export class YamlAdapter {
|
|
6
6
|
extensions = [".yaml", ".yml"];
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
7
|
async parse(content) {
|
|
9
8
|
try {
|
|
10
9
|
return yaml.load(content) || {};
|
|
11
10
|
}
|
|
12
11
|
catch (error) {
|
|
13
|
-
throw new Error(`YAML parse error: ${error.message}`);
|
|
12
|
+
throw new Error(`YAML parse error: ${error instanceof Error ? error.message : String(error)}`);
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
15
|
async serialize(data, options = {}) {
|
|
18
16
|
return yaml.dump(data, {
|
|
19
17
|
indent: options.indent || 2,
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
/** A content category and the keywords that identify it. */
|
|
2
|
+
export interface CategoryConfig {
|
|
2
3
|
keywords: string[];
|
|
4
|
+
/** Multiplier applied to this category's keyword matches (default 1.0). */
|
|
3
5
|
weight?: number;
|
|
6
|
+
/** Instruction added to the translation prompt for texts in this category. */
|
|
4
7
|
prompt?: string;
|
|
5
8
|
}
|
|
9
|
+
/** Settings of the AI-based analysis; the whole object is also read by AIContextAnalyzer. */
|
|
10
|
+
export interface ContextAnalysisOptions {
|
|
11
|
+
model?: string;
|
|
12
|
+
temperature?: number;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
retries?: number;
|
|
16
|
+
/** Cache analysis results (default true). */
|
|
17
|
+
cacheAnalysis?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** The `context` configuration section: category detection for translation texts. */
|
|
6
20
|
export interface ContextConfig {
|
|
7
21
|
enabled?: boolean;
|
|
8
22
|
useAI?: boolean;
|
|
9
23
|
aiProvider?: string;
|
|
10
24
|
minTextLength?: number;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
allowNewCategories?: boolean;
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
analysisOptions?: ContextAnalysisOptions;
|
|
14
28
|
categories: Record<string, CategoryConfig>;
|
|
15
29
|
detection: {
|
|
16
30
|
threshold: number;
|
|
@@ -21,7 +35,8 @@ export interface ContextConfig {
|
|
|
21
35
|
prompt: string;
|
|
22
36
|
};
|
|
23
37
|
}
|
|
24
|
-
|
|
38
|
+
/** Detected category of a text. */
|
|
39
|
+
export interface AnalysisResult {
|
|
25
40
|
category: string;
|
|
26
41
|
confidence: number;
|
|
27
42
|
prompt?: string;
|
|
@@ -42,7 +57,7 @@ declare class ContextProcessor {
|
|
|
42
57
|
* Create a new ContextProcessor instance.
|
|
43
58
|
* @param config - Configuration object.
|
|
44
59
|
*/
|
|
45
|
-
constructor(config:
|
|
60
|
+
constructor(config: ContextConfig);
|
|
46
61
|
/**
|
|
47
62
|
* Initialize keyword matching patterns from configuration.
|
|
48
63
|
*/
|
|
@@ -10,13 +10,11 @@ class ContextProcessor {
|
|
|
10
10
|
config;
|
|
11
11
|
keywordCache;
|
|
12
12
|
aiAnalyzer;
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
13
|
resultCache;
|
|
15
14
|
/**
|
|
16
15
|
* Create a new ContextProcessor instance.
|
|
17
16
|
* @param config - Configuration object.
|
|
18
17
|
*/
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
18
|
constructor(config) {
|
|
21
19
|
this.config = config;
|
|
22
20
|
this.keywordCache = new Map();
|
|
@@ -56,7 +54,6 @@ class ContextProcessor {
|
|
|
56
54
|
if (!texts || !Array.isArray(texts) || !this.config.enabled) {
|
|
57
55
|
return texts.map(() => this.getFallback());
|
|
58
56
|
}
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
57
|
const results = [];
|
|
61
58
|
const aiTexts = [];
|
|
62
59
|
const aiIndices = [];
|
|
@@ -120,7 +117,8 @@ class ContextProcessor {
|
|
|
120
117
|
}
|
|
121
118
|
}
|
|
122
119
|
catch (error) {
|
|
123
|
-
|
|
120
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
121
|
+
console.error("Batch AI context analysis failed:", message);
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
124
|
return results;
|
|
@@ -141,7 +139,6 @@ class ContextProcessor {
|
|
|
141
139
|
*/
|
|
142
140
|
_performKeywordAnalysis(text) {
|
|
143
141
|
const lowerText = text.toLowerCase();
|
|
144
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
142
|
const results = new Map();
|
|
146
143
|
let totalScore = 0;
|
|
147
144
|
const isShortText = text.length < 500;
|
|
@@ -183,7 +180,6 @@ class ContextProcessor {
|
|
|
183
180
|
/**
|
|
184
181
|
* Determine the best category match from analysis results.
|
|
185
182
|
*/
|
|
186
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
183
|
getBestMatch(results, totalScore) {
|
|
188
184
|
if (totalScore === 0)
|
|
189
185
|
return this.getFallback();
|
|
@@ -212,7 +208,6 @@ class ContextProcessor {
|
|
|
212
208
|
/**
|
|
213
209
|
* Generate a collision-resistant cache key for text and context.
|
|
214
210
|
*/
|
|
215
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
216
211
|
getCacheKey(text, context) {
|
|
217
212
|
let keyContent;
|
|
218
213
|
if (text.length <= 200) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { TranslateOptions, ProviderConfig } from "../providers/base-provider.js";
|
|
1
|
+
import { TranslateOptions, ProviderConfig, ChatMessage } from "../providers/base-provider.js";
|
|
2
2
|
export interface ProviderModule {
|
|
3
3
|
translate(text: string, sourceLang: string, targetLang: string, options?: TranslateOptions): Promise<string>;
|
|
4
4
|
analyze?(prompt: string, options?: ProviderConfig): Promise<string>;
|
|
5
|
-
chat?(messages:
|
|
6
|
-
[key: string]: any;
|
|
5
|
+
chat?(messages: ChatMessage[], options?: ProviderConfig): Promise<string>;
|
|
7
6
|
}
|
|
8
7
|
export interface ProviderWrapper {
|
|
9
8
|
name: string;
|
|
@@ -41,7 +40,7 @@ declare class FallbackProvider {
|
|
|
41
40
|
_calculatePriority(text: string): number;
|
|
42
41
|
translate(text: string, sourceLang: string, targetLang: string, options?: TranslateOptions): Promise<string>;
|
|
43
42
|
analyze(prompt: string, options?: ProviderConfig): Promise<string>;
|
|
44
|
-
chat(messages:
|
|
43
|
+
chat(messages: ChatMessage[], options?: ProviderConfig): Promise<string>;
|
|
45
44
|
getStats(): Record<string, ProviderStats>;
|
|
46
45
|
reset(): void;
|
|
47
46
|
resetStats(): void;
|
|
@@ -43,7 +43,6 @@ class FallbackProvider {
|
|
|
43
43
|
return 1;
|
|
44
44
|
}
|
|
45
45
|
async translate(text, sourceLang, targetLang, options = {}) {
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
46
|
const errors = [];
|
|
48
47
|
const startTime = Date.now();
|
|
49
48
|
const startIndex = this.currentIndex;
|
|
@@ -89,7 +88,6 @@ class FallbackProvider {
|
|
|
89
88
|
},
|
|
90
89
|
}), this._calculatePriority(text));
|
|
91
90
|
const responseTime = Date.now() - providerStartTime;
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
93
91
|
const stats = this.providerStats.get(providerName);
|
|
94
92
|
stats.success++;
|
|
95
93
|
stats.consecutiveFailures = 0;
|
|
@@ -102,14 +100,14 @@ class FallbackProvider {
|
|
|
102
100
|
return result;
|
|
103
101
|
}
|
|
104
102
|
catch (error) {
|
|
103
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
104
|
if (this.providerStats.has(providerName)) {
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
107
105
|
const stats = this.providerStats.get(providerName);
|
|
108
106
|
stats.failure++;
|
|
109
107
|
stats.consecutiveFailures++;
|
|
110
108
|
stats.lastError = {
|
|
111
109
|
time: new Date(),
|
|
112
|
-
message
|
|
110
|
+
message,
|
|
113
111
|
};
|
|
114
112
|
if (stats.consecutiveFailures >= 5) {
|
|
115
113
|
this._disableProvider(providerData, 2 * 60 * 1000);
|
|
@@ -119,13 +117,13 @@ class FallbackProvider {
|
|
|
119
117
|
this.lastErrorTime = Date.now();
|
|
120
118
|
errors.push({
|
|
121
119
|
provider: providerName,
|
|
122
|
-
error:
|
|
120
|
+
error: message,
|
|
123
121
|
attempt: currentAttempt + 1,
|
|
124
122
|
});
|
|
125
123
|
const safeProviderName = `Provider_${(currentProviderIndex % totalProviders) + 1}`;
|
|
126
|
-
const safeErrorMessage =
|
|
124
|
+
const safeErrorMessage = message.includes("API") || message.includes("key")
|
|
127
125
|
? "Authentication or API error"
|
|
128
|
-
:
|
|
126
|
+
: message.substring(0, 100);
|
|
129
127
|
console.warn(`${safeProviderName} failed (attempt ${currentAttempt + 1}/${maxAttempts}): ${safeErrorMessage}`);
|
|
130
128
|
currentAttempt++;
|
|
131
129
|
this.currentIndex++;
|
|
@@ -135,7 +133,6 @@ class FallbackProvider {
|
|
|
135
133
|
throw new Error(`All providers failed after ${maxAttempts} attempts (${Date.now() - startTime}ms):\n${JSON.stringify(errors, null, 2)}`);
|
|
136
134
|
}
|
|
137
135
|
async analyze(prompt, options = {}) {
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
136
|
const errors = [];
|
|
140
137
|
const startTime = Date.now();
|
|
141
138
|
const savedIndex = this.currentIndex;
|
|
@@ -185,7 +182,6 @@ class FallbackProvider {
|
|
|
185
182
|
},
|
|
186
183
|
}), this._calculatePriority(prompt));
|
|
187
184
|
const responseTime = Date.now() - providerStartTime;
|
|
188
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
189
185
|
const stats = this.providerStats.get(providerName);
|
|
190
186
|
stats.success++;
|
|
191
187
|
stats.consecutiveFailures = 0;
|
|
@@ -199,14 +195,14 @@ class FallbackProvider {
|
|
|
199
195
|
return result;
|
|
200
196
|
}
|
|
201
197
|
catch (error) {
|
|
198
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
202
199
|
if (this.providerStats.has(providerName)) {
|
|
203
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
204
200
|
const stats = this.providerStats.get(providerName);
|
|
205
201
|
stats.failure++;
|
|
206
202
|
stats.consecutiveFailures++;
|
|
207
203
|
stats.lastError = {
|
|
208
204
|
time: new Date(),
|
|
209
|
-
message
|
|
205
|
+
message,
|
|
210
206
|
};
|
|
211
207
|
if (stats.consecutiveFailures >= 5) {
|
|
212
208
|
this._disableProvider(providerData, 2 * 60 * 1000);
|
|
@@ -216,13 +212,13 @@ class FallbackProvider {
|
|
|
216
212
|
this.lastErrorTime = Date.now();
|
|
217
213
|
errors.push({
|
|
218
214
|
provider: providerName,
|
|
219
|
-
error:
|
|
215
|
+
error: message,
|
|
220
216
|
attempt: currentAttempt + 1,
|
|
221
217
|
});
|
|
222
218
|
const safeProviderName = `Provider_${(currentProviderIndex % totalProviders) + 1}`;
|
|
223
|
-
const safeErrorMessage =
|
|
219
|
+
const safeErrorMessage = message.includes("API") || message.includes("key")
|
|
224
220
|
? "Authentication or API error"
|
|
225
|
-
:
|
|
221
|
+
: message.substring(0, 100);
|
|
226
222
|
console.warn(`${safeProviderName} failed (attempt ${currentAttempt + 1}/${maxAttempts}): ${safeErrorMessage}`);
|
|
227
223
|
currentAttempt++;
|
|
228
224
|
this.currentIndex++;
|
|
@@ -232,7 +228,6 @@ class FallbackProvider {
|
|
|
232
228
|
throw new Error(`All providers failed for analysis after ${maxAttempts} attempts (${Date.now() - startTime}ms):\n${JSON.stringify(errors, null, 2)}`);
|
|
233
229
|
}
|
|
234
230
|
async chat(messages, options = {}) {
|
|
235
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
236
231
|
const errors = [];
|
|
237
232
|
const startTime = Date.now();
|
|
238
233
|
const savedIndex = this.currentIndex;
|
|
@@ -283,7 +278,6 @@ class FallbackProvider {
|
|
|
283
278
|
}), 1 // Priority 1 (normal)
|
|
284
279
|
);
|
|
285
280
|
const responseTime = Date.now() - providerStartTime;
|
|
286
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
287
281
|
const stats = this.providerStats.get(providerName);
|
|
288
282
|
stats.success++;
|
|
289
283
|
stats.consecutiveFailures = 0;
|
|
@@ -297,14 +291,14 @@ class FallbackProvider {
|
|
|
297
291
|
return result;
|
|
298
292
|
}
|
|
299
293
|
catch (error) {
|
|
294
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
300
295
|
if (this.providerStats.has(providerName)) {
|
|
301
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
302
296
|
const stats = this.providerStats.get(providerName);
|
|
303
297
|
stats.failure++;
|
|
304
298
|
stats.consecutiveFailures++;
|
|
305
299
|
stats.lastError = {
|
|
306
300
|
time: new Date(),
|
|
307
|
-
message
|
|
301
|
+
message,
|
|
308
302
|
};
|
|
309
303
|
if (stats.consecutiveFailures >= 5) {
|
|
310
304
|
this._disableProvider(providerData, 2 * 60 * 1000);
|
|
@@ -314,13 +308,13 @@ class FallbackProvider {
|
|
|
314
308
|
this.lastErrorTime = Date.now();
|
|
315
309
|
errors.push({
|
|
316
310
|
provider: providerName,
|
|
317
|
-
error:
|
|
311
|
+
error: message,
|
|
318
312
|
attempt: currentAttempt + 1,
|
|
319
313
|
});
|
|
320
314
|
const safeProviderName = `Provider_${(currentProviderIndex % totalProviders) + 1}`;
|
|
321
|
-
const safeErrorMessage =
|
|
315
|
+
const safeErrorMessage = message.includes("API") || message.includes("key")
|
|
322
316
|
? "Authentication or API error"
|
|
323
|
-
:
|
|
317
|
+
: message.substring(0, 100);
|
|
324
318
|
console.warn(`${safeProviderName} failed (attempt ${currentAttempt + 1}/${maxAttempts}): ${safeErrorMessage}`);
|
|
325
319
|
currentAttempt++;
|
|
326
320
|
this.currentIndex++;
|
|
@@ -1,21 +1,52 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ProviderStatus, RateLimiterConfig } from "../utils/rate-limiter.js";
|
|
2
|
+
import ProgressTracker, { ProgressOptions } from "../utils/progress-tracker.js";
|
|
2
3
|
import QualityChecker from "../utils/quality/index.js";
|
|
4
|
+
import type { QualityRules } from "../utils/quality/base-checker.js";
|
|
5
|
+
import type { LengthControlOptions } from "../utils/quality/length-checker.js";
|
|
6
|
+
import type { StyleGuideConfig } from "../utils/quality/style-guide-checker.js";
|
|
3
7
|
import { ContextConfig } from "./context-processor.js";
|
|
4
8
|
import { LRUCache } from "lru-cache";
|
|
5
|
-
import GlossaryManager from "../utils/glossary-manager.js";
|
|
6
|
-
import {
|
|
9
|
+
import GlossaryManager, { GlossaryConfig } from "../utils/glossary-manager.js";
|
|
10
|
+
import type { FlatTranslationValue } from "../utils/object-transformer.js";
|
|
11
|
+
import type { RetryConfig } from "../utils/retry-helper.js";
|
|
12
|
+
import type { ProviderConfig } from "../providers/base-provider.js";
|
|
13
|
+
import { TranslationMeta, TranslationResult } from "./pipeline/context.js";
|
|
7
14
|
import { CacheStats } from "./pipeline/steps/CacheReadStep.js";
|
|
8
15
|
import { ConfidenceSettings } from "./pipeline/steps/TranslationStep.js";
|
|
9
16
|
import { SourceCodeAnalyzer } from "../services/source-analyzer.js";
|
|
17
|
+
/** The `rateLimiter` configuration section. */
|
|
18
|
+
export interface RateLimiterOptions extends RateLimiterConfig {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** The `retryOptions` configuration section; providers read `maxRetries` and `initialDelay`. */
|
|
22
|
+
export interface ProviderRetryOptions {
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
initialDelay?: number;
|
|
25
|
+
maxDelay?: number;
|
|
26
|
+
jitter?: boolean;
|
|
27
|
+
retryableErrors?: string[];
|
|
28
|
+
perProviderRetry?: Record<string, Partial<RetryConfig>>;
|
|
29
|
+
}
|
|
30
|
+
/** The `vectorMemory` configuration section. */
|
|
31
|
+
export interface VectorMemoryOptions extends VectorStoreConfig {
|
|
32
|
+
embeddingProvider?: string;
|
|
33
|
+
embeddingModel?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Settings read by the orchestrator and its pipeline steps. The whole object is also
|
|
37
|
+
* forwarded to the provider's `translate()` (see TranslationStep).
|
|
38
|
+
*/
|
|
10
39
|
export interface OrchestratorOptions {
|
|
11
40
|
context: ContextConfig;
|
|
41
|
+
/** Scan the project's source code for where each key is used (default true). */
|
|
12
42
|
deepContext?: boolean;
|
|
13
|
-
progressOptions?:
|
|
14
|
-
styleGuide?:
|
|
15
|
-
lengthControl?:
|
|
43
|
+
progressOptions?: ProgressOptions;
|
|
44
|
+
styleGuide?: StyleGuideConfig;
|
|
45
|
+
lengthControl?: LengthControlOptions;
|
|
16
46
|
qualityChecks?: {
|
|
17
47
|
enabled?: boolean;
|
|
18
|
-
rules?:
|
|
48
|
+
rules?: QualityRules;
|
|
49
|
+
autoFix?: boolean;
|
|
19
50
|
};
|
|
20
51
|
advanced?: {
|
|
21
52
|
timeoutMs?: number;
|
|
@@ -24,34 +55,49 @@ export interface OrchestratorOptions {
|
|
|
24
55
|
autoOptimize?: boolean;
|
|
25
56
|
debug?: boolean;
|
|
26
57
|
};
|
|
27
|
-
glossary?:
|
|
28
|
-
rateLimiter?:
|
|
29
|
-
queueStrategy?: string;
|
|
30
|
-
queueTimeout?: number;
|
|
31
|
-
adaptiveThrottling?: boolean;
|
|
32
|
-
providerLimits?: any;
|
|
33
|
-
};
|
|
58
|
+
glossary?: GlossaryConfig;
|
|
59
|
+
rateLimiter?: RateLimiterOptions;
|
|
34
60
|
cacheSize?: number;
|
|
35
61
|
cacheTTL?: number;
|
|
36
62
|
updateAgeOnGet?: boolean;
|
|
63
|
+
/** @deprecated Has no effect: expired cache entries are always translated again. */
|
|
37
64
|
allowStaleCache?: boolean;
|
|
65
|
+
/** @deprecated Has no effect: the cache never refreshes entries in the background. */
|
|
38
66
|
staleWhileRevalidate?: boolean;
|
|
67
|
+
/** Turns confidence scoring on; translations scoring below it need review (`--min-confidence`). */
|
|
39
68
|
minConfidence?: number;
|
|
69
|
+
/** Turns confidence scoring on and saves the flagged translations for `locai review`. */
|
|
40
70
|
saveReviewQueue?: boolean;
|
|
41
71
|
confidenceScoring?: {
|
|
72
|
+
/** Score every new translation (default false). */
|
|
42
73
|
enabled?: boolean;
|
|
74
|
+
/** Translations scoring below it need review (default 0.7); the top-level minConfidence wins. */
|
|
43
75
|
minConfidence?: number;
|
|
76
|
+
/** Save the flagged translations to .localize-cache/review-queue.json for `locai review`. */
|
|
44
77
|
saveReviewQueue?: boolean;
|
|
78
|
+
/** Translations scoring at or above it are marked auto-approved (default 0.9). */
|
|
45
79
|
autoApproveThreshold?: number;
|
|
80
|
+
/** Older name of minConfidence; when both are set, the higher one applies. */
|
|
46
81
|
reviewThreshold?: number;
|
|
82
|
+
/** Translations scoring below it are rejected and not written (default 0.5). */
|
|
47
83
|
rejectThreshold?: number;
|
|
48
84
|
};
|
|
49
85
|
concurrencyLimit?: number;
|
|
50
86
|
cacheEnabled?: boolean;
|
|
87
|
+
/** Language of the source texts. */
|
|
51
88
|
source: string;
|
|
52
|
-
|
|
89
|
+
vectorMemory?: VectorMemoryOptions;
|
|
90
|
+
/** Analyzer shared between orchestrators; a new one is created when omitted. */
|
|
91
|
+
sourceCodeAnalyzer?: SourceCodeAnalyzer;
|
|
92
|
+
/** Translation provider; the available providers are tried in order when unset. */
|
|
93
|
+
apiProvider?: string;
|
|
94
|
+
useFallback?: boolean;
|
|
95
|
+
fallbackOrder?: string[];
|
|
96
|
+
/** Model settings per provider, keyed by provider name. */
|
|
97
|
+
apiConfig?: Record<string, ProviderConfig>;
|
|
98
|
+
retryOptions?: ProviderRetryOptions;
|
|
53
99
|
}
|
|
54
|
-
import { VectorStore } from "../services/vector-store.js";
|
|
100
|
+
import { VectorStore, VectorStoreConfig } from "../services/vector-store.js";
|
|
55
101
|
import { EmbeddingProvider } from "../services/embedding-provider.js";
|
|
56
102
|
import { VectorMemoryStats } from "./pipeline/steps/VectorCacheReadStep.js";
|
|
57
103
|
interface AdvancedSettings {
|
|
@@ -61,21 +107,44 @@ interface AdvancedSettings {
|
|
|
61
107
|
autoOptimize: boolean;
|
|
62
108
|
debug: boolean;
|
|
63
109
|
}
|
|
110
|
+
/** One key to translate, as passed to {@link Orchestrator.processTranslations}. */
|
|
64
111
|
interface TranslationItem {
|
|
65
112
|
key: string;
|
|
66
113
|
text: string;
|
|
67
114
|
targetLang: string;
|
|
68
|
-
existingTranslation?:
|
|
69
|
-
|
|
115
|
+
existingTranslation?: FlatTranslationValue;
|
|
116
|
+
}
|
|
117
|
+
/** Cache counters plus the cache's current size, capacity and hit rate. */
|
|
118
|
+
export interface CacheStatus extends CacheStats {
|
|
119
|
+
size: number;
|
|
120
|
+
capacity: number;
|
|
121
|
+
hitRate: number;
|
|
122
|
+
}
|
|
123
|
+
/** Snapshot returned by {@link Orchestrator.getStatus}. */
|
|
124
|
+
export interface OrchestratorStatus {
|
|
125
|
+
/** Empty when the cache is disabled. */
|
|
126
|
+
cache: CacheStatus | Record<string, never>;
|
|
127
|
+
rateLimiter: Record<string, ProviderStatus>;
|
|
128
|
+
concurrency: number;
|
|
129
|
+
advanced: AdvancedSettings;
|
|
130
|
+
confidence: {
|
|
131
|
+
minThreshold?: number;
|
|
132
|
+
autoApproveThreshold: number;
|
|
133
|
+
reviewThreshold: number;
|
|
134
|
+
rejectThreshold: number;
|
|
135
|
+
reviewQueueSize: number;
|
|
136
|
+
autoApprovedCount: number;
|
|
137
|
+
rejectedCount: number;
|
|
138
|
+
} | null;
|
|
70
139
|
}
|
|
71
140
|
declare class Orchestrator {
|
|
72
141
|
private options;
|
|
73
142
|
private contextProcessor;
|
|
74
143
|
progress: ProgressTracker;
|
|
75
|
-
qualityChecker: QualityChecker |
|
|
144
|
+
qualityChecker: QualityChecker | null;
|
|
76
145
|
advanced: AdvancedSettings;
|
|
77
146
|
glossaryManager: GlossaryManager;
|
|
78
|
-
translationCache: LRUCache<string,
|
|
147
|
+
translationCache: LRUCache<string, TranslationResult> | null;
|
|
79
148
|
cacheStats: CacheStats;
|
|
80
149
|
vectorStore: VectorStore;
|
|
81
150
|
embeddingProvider: EmbeddingProvider;
|
|
@@ -87,18 +156,21 @@ declare class Orchestrator {
|
|
|
87
156
|
private _shutdownCallback;
|
|
88
157
|
constructor(options: OrchestratorOptions);
|
|
89
158
|
private _buildPipeline;
|
|
90
|
-
processTranslation(key: string, text: string, targetLang: string, contextData:
|
|
159
|
+
processTranslation(key: string, text: string, targetLang: string, contextData: TranslationMeta, existingTranslation?: FlatTranslationValue): Promise<TranslationResult>;
|
|
160
|
+
/** Count a scored translation and queue it when it needs review or was rejected. */
|
|
161
|
+
private _recordConfidence;
|
|
91
162
|
processTranslations(items: TranslationItem[]): Promise<TranslationResult[]>;
|
|
92
163
|
private _chunkArray;
|
|
93
164
|
private _applyAutoOptimizations;
|
|
94
165
|
clearCache(): void;
|
|
95
166
|
resetCacheStats(): void;
|
|
96
|
-
getCacheStats():
|
|
97
|
-
|
|
98
|
-
|
|
167
|
+
getCacheStats(): CacheStatus | Record<string, never>;
|
|
168
|
+
getStatus(): OrchestratorStatus;
|
|
169
|
+
/**
|
|
170
|
+
* Add the translations this orchestrator flagged to `.localize-cache/review-queue.json`,
|
|
171
|
+
* when saving the review queue is enabled.
|
|
172
|
+
*/
|
|
99
173
|
saveReviewQueue(): void;
|
|
100
|
-
private _groupByLanguage;
|
|
101
|
-
private _groupByConfidenceLevel;
|
|
102
174
|
destroy(): void;
|
|
103
175
|
}
|
|
104
176
|
export default Orchestrator;
|