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
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Base class for translation providers
|
|
3
3
|
*/
|
|
4
4
|
import ErrorHelper from "../utils/error-helper.js";
|
|
5
|
-
|
|
5
|
+
function isApiErrorLike(error) {
|
|
6
|
+
return typeof error === "object" && error !== null;
|
|
7
|
+
}
|
|
6
8
|
class BaseProvider {
|
|
7
9
|
name;
|
|
8
10
|
config;
|
|
@@ -24,10 +26,10 @@ class BaseProvider {
|
|
|
24
26
|
async translate(text, sourceLang, targetLang, _options = {}) {
|
|
25
27
|
throw new Error(`translate method must be implemented by ${this.name} provider`);
|
|
26
28
|
}
|
|
27
|
-
async analyze(
|
|
29
|
+
async analyze(_prompt, _options = {}) {
|
|
28
30
|
throw new Error(`analyze method must be implemented by ${this.name} provider if supported`);
|
|
29
31
|
}
|
|
30
|
-
async chat(
|
|
32
|
+
async chat(_messages, _options = {}) {
|
|
31
33
|
throw new Error(`chat method must be implemented by ${this.name} provider if supported`);
|
|
32
34
|
}
|
|
33
35
|
getApiKey() {
|
|
@@ -56,19 +58,20 @@ class BaseProvider {
|
|
|
56
58
|
}
|
|
57
59
|
return true;
|
|
58
60
|
}
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
60
61
|
handleApiError(error, providerName) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
62
|
+
const apiErrorLike = isApiErrorLike(error) ? error : {};
|
|
63
|
+
if (apiErrorLike.response) {
|
|
64
|
+
const { response } = apiErrorLike;
|
|
65
|
+
const status = response.status;
|
|
66
|
+
const apiMessage = response.data?.error?.message || response.data?.message || null;
|
|
64
67
|
// Rate limit error
|
|
65
68
|
if (status === 429) {
|
|
66
|
-
const retryAfter =
|
|
67
|
-
const limit =
|
|
68
|
-
const remaining =
|
|
69
|
+
const retryAfter = response.headers?.["retry-after"] || 60;
|
|
70
|
+
const limit = response.headers?.["x-ratelimit-limit"];
|
|
71
|
+
const remaining = response.headers?.["x-ratelimit-remaining"];
|
|
69
72
|
throw ErrorHelper.rateLimitError(providerName, {
|
|
70
73
|
limit,
|
|
71
|
-
current: limit ? limit - remaining : null,
|
|
74
|
+
current: limit ? Number(limit) - Number(remaining) : null,
|
|
72
75
|
retryAfter,
|
|
73
76
|
statusCode: status,
|
|
74
77
|
});
|
|
@@ -91,19 +94,18 @@ class BaseProvider {
|
|
|
91
94
|
throw apiError;
|
|
92
95
|
}
|
|
93
96
|
// Network errors
|
|
94
|
-
if (
|
|
97
|
+
if (apiErrorLike.code === "ECONNREFUSED" || apiErrorLike.code === "ENOTFOUND") {
|
|
95
98
|
throw ErrorHelper.networkError(providerName);
|
|
96
99
|
}
|
|
97
100
|
// Timeout errors
|
|
98
|
-
if (
|
|
99
|
-
throw ErrorHelper.timeoutError(providerName,
|
|
101
|
+
if (apiErrorLike.code === "ECONNABORTED" || apiErrorLike.code === "ETIMEDOUT") {
|
|
102
|
+
throw ErrorHelper.timeoutError(providerName, apiErrorLike.config?.timeout);
|
|
100
103
|
}
|
|
101
104
|
// Unknown errors - preserve original for debugging
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
throw unknownError;
|
|
105
|
+
throw Object.assign(new Error(`${providerName}: ${apiErrorLike.message}`), {
|
|
106
|
+
code: "ERR_UNKNOWN",
|
|
107
|
+
originalError: error,
|
|
108
|
+
});
|
|
107
109
|
}
|
|
108
110
|
generatePrompt(text, sourceLang, targetLang, options = {}) {
|
|
109
111
|
const context = options.detectedContext;
|
|
@@ -145,7 +147,6 @@ class BaseProvider {
|
|
|
145
147
|
endpoint: this.getEndpoint(),
|
|
146
148
|
};
|
|
147
149
|
}
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
149
150
|
extractTranslation(response, providerName) {
|
|
150
151
|
// Check for error response first
|
|
151
152
|
if (response.error) {
|
|
@@ -171,7 +172,7 @@ class BaseProvider {
|
|
|
171
172
|
else if (response.text) {
|
|
172
173
|
translation = response.text.trim();
|
|
173
174
|
}
|
|
174
|
-
else if (response.content) {
|
|
175
|
+
else if (typeof response.content === "string" && response.content) {
|
|
175
176
|
translation = response.content.trim();
|
|
176
177
|
}
|
|
177
178
|
else if (response.data &&
|
|
@@ -191,31 +192,6 @@ class BaseProvider {
|
|
|
191
192
|
}
|
|
192
193
|
return translation;
|
|
193
194
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Extract translation with confidence score
|
|
196
|
-
*/
|
|
197
|
-
extractTranslationWithConfidence(
|
|
198
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
199
|
-
response, providerName, sourceText, sourceLang, targetLang, category = "general") {
|
|
200
|
-
const translation = this.extractTranslation(response, providerName);
|
|
201
|
-
// Extract AI confidence from response
|
|
202
|
-
const aiConfidence = ConfidenceScorer.extractAIConfidence(response, providerName);
|
|
203
|
-
// Calculate comprehensive confidence score
|
|
204
|
-
const confidenceResult = ConfidenceScorer.calculateConfidence({
|
|
205
|
-
aiConfidence,
|
|
206
|
-
sourceText,
|
|
207
|
-
translation,
|
|
208
|
-
sourceLang,
|
|
209
|
-
targetLang,
|
|
210
|
-
provider: providerName,
|
|
211
|
-
category,
|
|
212
|
-
});
|
|
213
|
-
return {
|
|
214
|
-
translation,
|
|
215
|
-
confidence: confidenceResult,
|
|
216
|
-
rawResponse: response,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
195
|
sanitizeTranslation(translation) {
|
|
220
196
|
if (!translation || typeof translation !== "string") {
|
|
221
197
|
throw new Error("Invalid translation format");
|
|
@@ -22,7 +22,6 @@ class DashScopeProvider extends BaseProvider {
|
|
|
22
22
|
},
|
|
23
23
|
timeout: config.timeoutMs || 60000,
|
|
24
24
|
maxRedirects: 0,
|
|
25
|
-
validateStatus: (status) => status < 500,
|
|
26
25
|
});
|
|
27
26
|
// Generation client for analysis
|
|
28
27
|
this.generationClient = axios.create({
|
|
@@ -33,7 +32,6 @@ class DashScopeProvider extends BaseProvider {
|
|
|
33
32
|
},
|
|
34
33
|
timeout: config.timeoutMs || 60000,
|
|
35
34
|
maxRedirects: 0,
|
|
36
|
-
validateStatus: (status) => status < 500,
|
|
37
35
|
});
|
|
38
36
|
}
|
|
39
37
|
getApiKey() {
|
package/dist/providers/gemini.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import BaseProvider, { ProviderConfig, TranslateOptions } from "./base-provider.js";
|
|
1
|
+
import BaseProvider, { ChatMessage, ProviderConfig, TranslateOptions } from "./base-provider.js";
|
|
2
2
|
/**
|
|
3
3
|
* Provider implementation for OpenAI (GPT) models.
|
|
4
4
|
*/
|
|
@@ -16,13 +16,13 @@ declare class OpenAIProvider extends BaseProvider {
|
|
|
16
16
|
*/
|
|
17
17
|
translate(text: string, sourceLang: string, targetLang: string, options?: TranslateOptions): Promise<string>;
|
|
18
18
|
analyze(prompt: string, options?: ProviderConfig): Promise<string>;
|
|
19
|
-
chat(messages:
|
|
19
|
+
chat(messages: ChatMessage[], options?: ProviderConfig): Promise<string>;
|
|
20
20
|
embed(text: string, options?: ProviderConfig): Promise<number[]>;
|
|
21
21
|
embedBatch(texts: string[], options?: ProviderConfig): Promise<number[][]>;
|
|
22
22
|
}
|
|
23
23
|
declare function translate(text: string, sourceLang: string, targetLang: string, options?: TranslateOptions): Promise<string>;
|
|
24
24
|
declare function analyze(prompt: string, options?: ProviderConfig): Promise<string>;
|
|
25
|
-
declare function chat(messages:
|
|
25
|
+
declare function chat(messages: ChatMessage[], options?: ProviderConfig): Promise<string>;
|
|
26
26
|
declare function embed(text: string, options?: ProviderConfig): Promise<number[]>;
|
|
27
27
|
declare function embedBatch(texts: string[], options?: ProviderConfig): Promise<number[][]>;
|
|
28
28
|
export { translate, analyze, chat, embed, embedBatch, OpenAIProvider };
|
package/dist/providers/openai.js
CHANGED
package/dist/providers/xai.js
CHANGED
|
@@ -26,7 +26,8 @@ export class EmbeddingProvider {
|
|
|
26
26
|
return [];
|
|
27
27
|
}
|
|
28
28
|
catch (error) {
|
|
29
|
-
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
console.error(`Failed to generate embedding: ${message}`);
|
|
30
31
|
return [];
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -43,7 +44,8 @@ export class EmbeddingProvider {
|
|
|
43
44
|
return [];
|
|
44
45
|
}
|
|
45
46
|
catch (error) {
|
|
46
|
-
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
console.error(`Failed to generate batch embeddings: ${message}`);
|
|
47
49
|
return [];
|
|
48
50
|
}
|
|
49
51
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import { FlatTranslations } from "../utils/object-transformer.js";
|
|
1
2
|
import Orchestrator from "../core/orchestrator.js";
|
|
3
|
+
import { StateComparison } from "../utils/state-manager.js";
|
|
2
4
|
import { TranslationOptions, GlobalStats, LanguageProcessResult } from "../types/index.js";
|
|
3
5
|
export declare class LanguageProcessor {
|
|
4
6
|
/**
|
|
5
7
|
* Process a single language translation.
|
|
6
8
|
* @param {string} targetLang - Target language code.
|
|
7
9
|
* @param {string} sourceFile - Path to source file.
|
|
8
|
-
* @param {
|
|
10
|
+
* @param {FlatTranslations} flattenedSource - Flattened source content.
|
|
9
11
|
* @param {Orchestrator} orchestrator - Orchestrator instance.
|
|
10
12
|
* @param {TranslationOptions} options - Translation options.
|
|
11
13
|
* @param {GlobalStats} globalStats - Global statistics object.
|
|
12
|
-
* @param {
|
|
14
|
+
* @param {Pick<StateComparison, "newKeys" | "modifiedKeys">} comparison - State comparison result.
|
|
13
15
|
* @returns {Promise<LanguageProcessResult>} - Status and messages for this language.
|
|
14
16
|
*/
|
|
15
|
-
static processLanguage(targetLang: string, sourceFile: string, flattenedSource:
|
|
17
|
+
static processLanguage(targetLang: string, sourceFile: string, flattenedSource: FlatTranslations, orchestrator: Orchestrator, options: TranslationOptions, globalStats: GlobalStats, comparison: Pick<StateComparison, "newKeys" | "modifiedKeys">): Promise<LanguageProcessResult>;
|
|
16
18
|
/**
|
|
17
19
|
* Check if text only contains placeholders and whitespace.
|
|
18
20
|
* @param {string} text - Text to check.
|
|
@@ -2,6 +2,7 @@ import path from "path";
|
|
|
2
2
|
import { FileManager } from "../utils/file-manager.js";
|
|
3
3
|
import ObjectTransformer from "../utils/object-transformer.js";
|
|
4
4
|
import InputValidator from "../utils/input-validator.js";
|
|
5
|
+
import { targetLocaleFile } from "../utils/locale-paths.js";
|
|
5
6
|
import uiManager from "../utils/ui-manager.js";
|
|
6
7
|
import statisticsManager from "../utils/statistics-manager.js";
|
|
7
8
|
export class LanguageProcessor {
|
|
@@ -9,11 +10,11 @@ export class LanguageProcessor {
|
|
|
9
10
|
* Process a single language translation.
|
|
10
11
|
* @param {string} targetLang - Target language code.
|
|
11
12
|
* @param {string} sourceFile - Path to source file.
|
|
12
|
-
* @param {
|
|
13
|
+
* @param {FlatTranslations} flattenedSource - Flattened source content.
|
|
13
14
|
* @param {Orchestrator} orchestrator - Orchestrator instance.
|
|
14
15
|
* @param {TranslationOptions} options - Translation options.
|
|
15
16
|
* @param {GlobalStats} globalStats - Global statistics object.
|
|
16
|
-
* @param {
|
|
17
|
+
* @param {Pick<StateComparison, "newKeys" | "modifiedKeys">} comparison - State comparison result.
|
|
17
18
|
* @returns {Promise<LanguageProcessResult>} - Status and messages for this language.
|
|
18
19
|
*/
|
|
19
20
|
static async processLanguage(targetLang, sourceFile, flattenedSource, orchestrator, options, globalStats, comparison) {
|
|
@@ -42,25 +43,9 @@ export class LanguageProcessor {
|
|
|
42
43
|
let finalStatus = null;
|
|
43
44
|
let savedMessage = undefined;
|
|
44
45
|
statisticsManager.initLanguageStats(safeTargetLang);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let targetPath;
|
|
49
|
-
if (isNestedStructure) {
|
|
50
|
-
// Nested: locales/tr/translation.json
|
|
51
|
-
// We assume sourceDir ends with the language code (e.g. .../en)
|
|
52
|
-
// So we need to go up one level and then into target lang
|
|
53
|
-
const localesRoot = path.dirname(sourceDir);
|
|
54
|
-
const targetDir = path.join(localesRoot, safeTargetLang);
|
|
55
|
-
// Ensure target directory exists for nested structure
|
|
56
|
-
await FileManager.ensureDir(targetDir);
|
|
57
|
-
targetPath = path.join(targetDir, "translation.json");
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
// Flat: locales/tr.json
|
|
61
|
-
const safeTargetFilename = `${safeTargetLang}.json`;
|
|
62
|
-
targetPath = InputValidator.createSafeFilePath(sourceDir, safeTargetFilename);
|
|
63
|
-
}
|
|
46
|
+
// Same layout as the source: locales/tr/translation.json or locales/tr.json
|
|
47
|
+
const targetPath = targetLocaleFile(sourceFile, safeTargetLang);
|
|
48
|
+
await FileManager.ensureDir(path.dirname(targetPath));
|
|
64
49
|
let targetContent = {};
|
|
65
50
|
try {
|
|
66
51
|
targetContent = await FileManager.readJSON(targetPath);
|
|
@@ -70,12 +55,13 @@ export class LanguageProcessor {
|
|
|
70
55
|
}
|
|
71
56
|
}
|
|
72
57
|
catch (err) {
|
|
73
|
-
if (err.code === "ENOENT") {
|
|
58
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
74
59
|
await uiManager.log(`\ud83c\udd95 Creating new translation file for ${safeTargetLang}`);
|
|
75
60
|
}
|
|
76
61
|
else {
|
|
77
62
|
if (options.debug) {
|
|
78
|
-
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
console.warn(`Error reading ${targetPath}: ${message}, using empty object`);
|
|
79
65
|
}
|
|
80
66
|
}
|
|
81
67
|
targetContent = {};
|
|
@@ -88,20 +74,24 @@ export class LanguageProcessor {
|
|
|
88
74
|
}
|
|
89
75
|
}
|
|
90
76
|
catch (err) {
|
|
91
|
-
|
|
77
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78
|
+
console.warn(`Error flattening target content: ${message}, using empty object`);
|
|
92
79
|
flattenedTarget = {};
|
|
93
80
|
}
|
|
94
81
|
const missingKeys = [];
|
|
95
82
|
let hasPlaceholderOnlyChanges = false;
|
|
96
|
-
for (const [key,
|
|
83
|
+
for (const [key, sourceValue] of Object.entries(flattenedSource)) {
|
|
84
|
+
let sourceText;
|
|
97
85
|
try {
|
|
98
86
|
InputValidator.validateKey(key, "translation key");
|
|
99
|
-
InputValidator.validateText(
|
|
87
|
+
sourceText = InputValidator.validateText(sourceValue, "source text");
|
|
100
88
|
}
|
|
101
89
|
catch (keyError) {
|
|
102
|
-
|
|
90
|
+
const message = keyError instanceof Error ? keyError.message : String(keyError);
|
|
91
|
+
await uiManager.log(`\u26a0\ufe0f Skipping invalid key/text: ${message}`);
|
|
103
92
|
globalStats.languages[safeTargetLang].failed++;
|
|
104
93
|
globalStats.failed++;
|
|
94
|
+
globalStats.failedKeys.push(key);
|
|
105
95
|
continue;
|
|
106
96
|
}
|
|
107
97
|
globalStats.languages[safeTargetLang].processed++;
|
|
@@ -153,12 +143,10 @@ export class LanguageProcessor {
|
|
|
153
143
|
}
|
|
154
144
|
}
|
|
155
145
|
catch (err) {
|
|
156
|
-
|
|
146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
+
console.error(`Error processing translations for ${safeTargetLang}: ${message}`);
|
|
148
|
+
// Counted below: every missing key without a valid result has failed.
|
|
157
149
|
results = [];
|
|
158
|
-
if (globalStats.languages[safeTargetLang]) {
|
|
159
|
-
globalStats.languages[safeTargetLang].failed += missingKeys.length;
|
|
160
|
-
globalStats.failed += missingKeys.length;
|
|
161
|
-
}
|
|
162
150
|
}
|
|
163
151
|
}
|
|
164
152
|
else {
|
|
@@ -173,19 +161,20 @@ export class LanguageProcessor {
|
|
|
173
161
|
}
|
|
174
162
|
}
|
|
175
163
|
catch (err) {
|
|
176
|
-
|
|
164
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
165
|
+
console.warn(`Error getting orchestrator status: ${message}`);
|
|
177
166
|
}
|
|
178
167
|
let validResults = [];
|
|
179
168
|
try {
|
|
180
169
|
validResults = results.filter((result) => result && result.success === true);
|
|
181
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
170
|
validResults = validResults.filter((result) => result.key &&
|
|
183
171
|
typeof result.key === "string" &&
|
|
184
172
|
result.translated &&
|
|
185
173
|
typeof result.translated === "string");
|
|
186
174
|
}
|
|
187
175
|
catch (err) {
|
|
188
|
-
|
|
176
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
177
|
+
console.warn(`Error filtering results: ${message}`);
|
|
189
178
|
validResults = [];
|
|
190
179
|
}
|
|
191
180
|
if (validResults.length > 0) {
|
|
@@ -195,16 +184,21 @@ export class LanguageProcessor {
|
|
|
195
184
|
}
|
|
196
185
|
});
|
|
197
186
|
}
|
|
187
|
+
// Every missing key without a valid result failed, whether or not anything is written.
|
|
188
|
+
const translatedKeys = new Set(validResults.map((result) => result.key));
|
|
189
|
+
const failedKeys = missingKeys
|
|
190
|
+
.map((item) => item.key)
|
|
191
|
+
.filter((key) => !translatedKeys.has(key));
|
|
192
|
+
globalStats.total += validResults.length + failedKeys.length;
|
|
193
|
+
globalStats.success += validResults.length;
|
|
194
|
+
globalStats.failed += failedKeys.length;
|
|
195
|
+
globalStats.failedKeys.push(...failedKeys);
|
|
196
|
+
globalStats.languages[safeTargetLang].added += validResults.length;
|
|
197
|
+
globalStats.languages[safeTargetLang].failed += failedKeys.length;
|
|
198
198
|
const hasChangesToSave = validResults.length > 0 || hasPlaceholderOnlyChanges;
|
|
199
199
|
if (hasChangesToSave) {
|
|
200
200
|
const unflattened = ObjectTransformer.unflatten(flattenedTarget);
|
|
201
201
|
await FileManager.writeJSON(targetPath, unflattened);
|
|
202
|
-
globalStats.total += validResults.length;
|
|
203
|
-
globalStats.success += validResults.length;
|
|
204
|
-
globalStats.failed += results.length - validResults.length;
|
|
205
|
-
globalStats.languages[safeTargetLang].added += validResults.length;
|
|
206
|
-
globalStats.languages[safeTargetLang].failed +=
|
|
207
|
-
results.length - validResults.length;
|
|
208
202
|
validResults.forEach((result) => {
|
|
209
203
|
if (result.context) {
|
|
210
204
|
const category = result.context.category || "general";
|
|
@@ -227,9 +221,10 @@ export class LanguageProcessor {
|
|
|
227
221
|
return { status: finalStatus, savedMessage };
|
|
228
222
|
}
|
|
229
223
|
catch (error) {
|
|
230
|
-
const
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
const safeError = message.includes("outside working directory")
|
|
231
226
|
? "Invalid file path detected"
|
|
232
|
-
:
|
|
227
|
+
: message;
|
|
233
228
|
await uiManager.log(`\n\u274c Error processing ${targetLang}: ${safeError}`);
|
|
234
229
|
if (globalStats.languages[targetLang]) {
|
|
235
230
|
globalStats.languages[targetLang].error = safeError;
|
|
@@ -10,5 +10,29 @@ export declare class SourceCodeAnalyzer {
|
|
|
10
10
|
private processFile;
|
|
11
11
|
private processCallExpression;
|
|
12
12
|
private extractContext;
|
|
13
|
+
/**
|
|
14
|
+
* The component the call is in: the nearest enclosing function, class or function-valued
|
|
15
|
+
* variable whose name starts with an uppercase letter, otherwise the nearest one of any name.
|
|
16
|
+
* A variable counts only when the call is inside a function in its initializer
|
|
17
|
+
* (`const Dialog = () => ...`), so `const label = t("key")` does not name a component.
|
|
18
|
+
*/
|
|
19
|
+
private findComponent;
|
|
20
|
+
/**
|
|
21
|
+
* The JSX element the text belongs to: the one whose attribute holds the call
|
|
22
|
+
* (`<Button title={t("key")} />`) or whose child it is (`<Button>{t("key")}</Button>`).
|
|
23
|
+
* A call inside a nested function (`onClick={() => alert(t("key"))}`) belongs to none.
|
|
24
|
+
*/
|
|
25
|
+
private findJsxElement;
|
|
26
|
+
/**
|
|
27
|
+
* Comments about the call, nearest first: comments right above the call's expression, JSX
|
|
28
|
+
* attribute, JSX element or statement, `{/* ... *\/}` comments right before its JSX element,
|
|
29
|
+
* and the comment above its component. Tooling directives are left out.
|
|
30
|
+
*/
|
|
31
|
+
private getComments;
|
|
32
|
+
/**
|
|
33
|
+
* The code around the call with comments removed: the largest of the call, its enclosing
|
|
34
|
+
* expressions and its statement that fits in MAX_SNIPPET_LENGTH characters.
|
|
35
|
+
*/
|
|
36
|
+
private getSnippet;
|
|
13
37
|
getContext(key: string): CodeContext | undefined;
|
|
14
38
|
}
|