locai 2.3.0 → 2.5.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 +5 -1
- package/dist/cli/helpers.d.ts +2 -0
- package/dist/cli/helpers.js +344 -0
- package/dist/cli.js +39 -519
- package/dist/commands/translator.d.ts +1 -1
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +26 -0
- package/dist/config/setup.d.ts +10 -0
- package/dist/config/setup.js +79 -0
- package/dist/core/orchestrator.d.ts +3 -0
- package/dist/core/orchestrator.js +27 -0
- package/dist/core/pipeline/steps/ContextEnrichmentStep.d.ts +9 -0
- package/dist/core/pipeline/steps/ContextEnrichmentStep.js +43 -0
- package/dist/services/language-processor.d.ts +22 -0
- package/dist/services/language-processor.js +239 -0
- package/dist/services/source-analyzer.d.ts +14 -0
- package/dist/services/source-analyzer.js +164 -0
- package/dist/services/translation-service.d.ts +1 -49
- package/dist/services/translation-service.js +9 -215
- package/dist/types/context.d.ts +13 -0
- package/dist/types/context.js +1 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/ui-manager.js +2 -2
- package/package.json +26 -17
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ Translate your project in seconds without installing anything:
|
|
|
13
13
|
npx locai translate --source en --targets tr,es,de
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
> **Need a permanent setup?** Check out our [Configuration Guide](./docs/configuration.md) to create a `localize.config.js` file.
|
|
17
|
+
|
|
16
18
|
## 📖 Documentation
|
|
17
19
|
|
|
18
20
|
Everything you need to know to master Locai.
|
|
@@ -37,7 +39,9 @@ Choose the right brain for your localization. Compare OpenAI, Gemini, DeepSeek,
|
|
|
37
39
|
|
|
38
40
|
Deep dive into Locai's intelligent features. We didn't just wrap an API; we built a localization engine.
|
|
39
41
|
|
|
40
|
-
-
|
|
42
|
+
- **🧠 Smart Context Awareness**: Uses AI to understand the context of your strings for better translations.
|
|
43
|
+
- **🔬 Deep Context Analysis**: Scans your source code (AST) to extract component names, comments, and props for context-aware translations.
|
|
44
|
+
- **📚 Infinite Memory**: Uses vector embeddings to remember previous translations and maintain consistency. If you translate the same (or similar) sentence again, we fetch it from local cache instantly. Zero cost, 100% consistency.
|
|
41
45
|
- **Smart Sync**: We hash every key. Locai only translates **new** or **modified** keys. If you have 1000 keys and change 5, we only send 5 to the AI.
|
|
42
46
|
- **Context Awareness**: Locai analyzes your content. It knows if a string is for a **Button** (keep it short), **Marketing** (make it punchy), or **Legal** (be precise).
|
|
43
47
|
- **Glossary & Brand Safety**: Define immutable terms (like "Locai", "API", "SaaS"). We guarantee they are never translated or corrupted.
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { findLocaleFiles, translateFile, validateAndFixExistingTranslations, } from "../commands/translator.js";
|
|
5
|
+
import InputValidator from "../utils/input-validator.js";
|
|
6
|
+
import ErrorHelper from "../utils/error-helper.js";
|
|
7
|
+
import { FrameworkDetector } from "../core/detection/framework-detector.js";
|
|
8
|
+
import { StructureDetector, LocaleStructure } from "../core/detection/structure-detector.js";
|
|
9
|
+
import { configureComponents } from "../config/setup.js";
|
|
10
|
+
export const autoDetectConfig = async (defaultConfig) => {
|
|
11
|
+
// Auto-detect framework and configuration if not explicitly set
|
|
12
|
+
const detected = await FrameworkDetector.detect(process.cwd());
|
|
13
|
+
if (detected) {
|
|
14
|
+
// Set locales directory if not configured
|
|
15
|
+
if (!defaultConfig.localesDir && detected.localesDir) {
|
|
16
|
+
defaultConfig.localesDir = detected.localesDir;
|
|
17
|
+
if (process.env.VERBOSE) {
|
|
18
|
+
console.log(`Auto-detected locales directory: ${detected.localesDir}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Set file format if not configured
|
|
22
|
+
if (!defaultConfig.fileOperations)
|
|
23
|
+
defaultConfig.fileOperations = {};
|
|
24
|
+
if ((!defaultConfig.fileOperations.format ||
|
|
25
|
+
defaultConfig.fileOperations.format === "auto") &&
|
|
26
|
+
detected.fileFormat) {
|
|
27
|
+
defaultConfig.fileOperations.format = detected.fileFormat;
|
|
28
|
+
if (process.env.VERBOSE) {
|
|
29
|
+
console.log(`Auto-detected file format: ${detected.fileFormat}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Auto-detect file structure (flat vs nested)
|
|
34
|
+
if (!defaultConfig.fileOperations)
|
|
35
|
+
defaultConfig.fileOperations = {};
|
|
36
|
+
if (!defaultConfig.fileOperations.fileStructure ||
|
|
37
|
+
defaultConfig.fileOperations.fileStructure === "auto") {
|
|
38
|
+
const targetDir = defaultConfig.localesDir || "./locales";
|
|
39
|
+
const structure = await StructureDetector.detect(targetDir);
|
|
40
|
+
if (structure !== LocaleStructure.UNKNOWN) {
|
|
41
|
+
defaultConfig.fileOperations.fileStructure = structure;
|
|
42
|
+
if (process.env.VERBOSE) {
|
|
43
|
+
console.log(`Auto-detected file structure: ${structure}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Default to flat if unknown
|
|
48
|
+
defaultConfig.fileOperations.fileStructure = LocaleStructure.FLAT;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return defaultConfig;
|
|
52
|
+
};
|
|
53
|
+
export const runCommand = async (defaultConfig, commandOptions, commandName) => {
|
|
54
|
+
try {
|
|
55
|
+
const validCommands = ["translate", "fix", "analyze", "advanced"];
|
|
56
|
+
if (!validCommands.includes(commandName)) {
|
|
57
|
+
throw new Error(`Invalid command: ${commandName}`);
|
|
58
|
+
}
|
|
59
|
+
const globalOpts = program.opts();
|
|
60
|
+
if (globalOpts.debug) {
|
|
61
|
+
const safeGlobalOpts = { ...globalOpts };
|
|
62
|
+
const safeCommandOptions = { ...commandOptions };
|
|
63
|
+
if (safeGlobalOpts.targets) {
|
|
64
|
+
safeGlobalOpts.targets = `[${safeGlobalOpts.targets.length} languages]`;
|
|
65
|
+
}
|
|
66
|
+
if (safeGlobalOpts.localesDir) {
|
|
67
|
+
safeGlobalOpts.localesDir = "[directory_path]";
|
|
68
|
+
}
|
|
69
|
+
if (safeCommandOptions.provider) {
|
|
70
|
+
safeCommandOptions.provider = "[provider_name]";
|
|
71
|
+
}
|
|
72
|
+
console.log("CLI Command:", commandName);
|
|
73
|
+
console.log("Global Options:", JSON.stringify(safeGlobalOpts, null, 2));
|
|
74
|
+
console.log("Command Options:", JSON.stringify(safeCommandOptions, null, 2));
|
|
75
|
+
}
|
|
76
|
+
const sanitizedGlobalOpts = { ...globalOpts };
|
|
77
|
+
const sanitizedCommandOptions = { ...commandOptions };
|
|
78
|
+
if (sanitizedGlobalOpts.source) {
|
|
79
|
+
sanitizedGlobalOpts.source = InputValidator.validateLanguageCode(sanitizedGlobalOpts.source, "source language");
|
|
80
|
+
}
|
|
81
|
+
if (sanitizedGlobalOpts.targets && Array.isArray(sanitizedGlobalOpts.targets)) {
|
|
82
|
+
sanitizedGlobalOpts.targets = InputValidator.validateLanguageCodes(sanitizedGlobalOpts.targets, "target languages");
|
|
83
|
+
}
|
|
84
|
+
if (sanitizedGlobalOpts.localesDir) {
|
|
85
|
+
sanitizedGlobalOpts.localesDir = InputValidator.validateDirectoryPath(sanitizedGlobalOpts.localesDir, "locales directory");
|
|
86
|
+
}
|
|
87
|
+
if (sanitizedCommandOptions.provider) {
|
|
88
|
+
sanitizedCommandOptions.provider = InputValidator.validateProvider(sanitizedCommandOptions.provider, "API provider");
|
|
89
|
+
}
|
|
90
|
+
if (sanitizedCommandOptions.concurrency !== undefined) {
|
|
91
|
+
const concurrency = parseInt(sanitizedCommandOptions.concurrency);
|
|
92
|
+
if (isNaN(concurrency) || concurrency < 1 || concurrency > 20) {
|
|
93
|
+
throw new Error("Concurrency must be a number between 1 and 20");
|
|
94
|
+
}
|
|
95
|
+
sanitizedCommandOptions.concurrency = concurrency;
|
|
96
|
+
}
|
|
97
|
+
if (sanitizedCommandOptions.contextThreshold !== undefined) {
|
|
98
|
+
const threshold = parseInt(sanitizedCommandOptions.contextThreshold);
|
|
99
|
+
if (isNaN(threshold) || threshold < 1 || threshold > 10) {
|
|
100
|
+
throw new Error("Context threshold must be a number between 1 and 10");
|
|
101
|
+
}
|
|
102
|
+
sanitizedCommandOptions.contextThreshold = threshold;
|
|
103
|
+
}
|
|
104
|
+
if (sanitizedCommandOptions.contextConfidence !== undefined) {
|
|
105
|
+
const confidence = parseFloat(sanitizedCommandOptions.contextConfidence);
|
|
106
|
+
if (isNaN(confidence) || confidence < 0 || confidence > 1) {
|
|
107
|
+
throw new Error("Context confidence must be a number between 0 and 1");
|
|
108
|
+
}
|
|
109
|
+
sanitizedCommandOptions.contextConfidence = confidence;
|
|
110
|
+
}
|
|
111
|
+
if (sanitizedCommandOptions.length) {
|
|
112
|
+
const validLengthModes = ["strict", "flexible", "exact", "relaxed", "smart"];
|
|
113
|
+
if (!validLengthModes.includes(sanitizedCommandOptions.length)) {
|
|
114
|
+
throw new Error(`Invalid length mode: ${sanitizedCommandOptions.length}. Valid modes: ${validLengthModes.join(", ")}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const mergedOpts = {
|
|
118
|
+
source: defaultConfig.source,
|
|
119
|
+
targets: defaultConfig.targets,
|
|
120
|
+
localesDir: defaultConfig.localesDir,
|
|
121
|
+
apiProvider: defaultConfig.apiProvider,
|
|
122
|
+
...(sanitizedGlobalOpts.source && { source: sanitizedGlobalOpts.source }),
|
|
123
|
+
...(sanitizedGlobalOpts.targets && { targets: sanitizedGlobalOpts.targets }),
|
|
124
|
+
...(sanitizedGlobalOpts.localesDir && {
|
|
125
|
+
localesDir: sanitizedGlobalOpts.localesDir,
|
|
126
|
+
}),
|
|
127
|
+
...sanitizedCommandOptions,
|
|
128
|
+
};
|
|
129
|
+
let concurrencyLimit = parseInt(mergedOpts.concurrency) || defaultConfig.concurrencyLimit || 5;
|
|
130
|
+
if (mergedOpts.autoOptimize) {
|
|
131
|
+
const cpuCount = os.cpus().length;
|
|
132
|
+
const memoryGB = Math.floor(os.totalmem() / (1024 * 1024 * 1024));
|
|
133
|
+
if (memoryGB < 4) {
|
|
134
|
+
concurrencyLimit = Math.min(3, cpuCount);
|
|
135
|
+
}
|
|
136
|
+
else if (memoryGB < 8) {
|
|
137
|
+
concurrencyLimit = Math.min(5, Math.ceil(cpuCount * 0.5));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
concurrencyLimit = Math.min(10, Math.ceil(cpuCount * 0.75));
|
|
141
|
+
}
|
|
142
|
+
console.log(`Auto-optimized settings for your system (${cpuCount} CPUs, ${memoryGB}GB RAM):`);
|
|
143
|
+
console.log(` - Concurrency: ${concurrencyLimit}`);
|
|
144
|
+
}
|
|
145
|
+
if (concurrencyLimit < 1 || concurrencyLimit > 20) {
|
|
146
|
+
throw new Error("Invalid concurrency limit after optimization");
|
|
147
|
+
}
|
|
148
|
+
const finalConfig = {
|
|
149
|
+
...defaultConfig,
|
|
150
|
+
command: commandName,
|
|
151
|
+
source: mergedOpts.source,
|
|
152
|
+
targets: mergedOpts.targets,
|
|
153
|
+
localesDir: mergedOpts.localesDir,
|
|
154
|
+
apiProvider: mergedOpts.provider || defaultConfig.apiProvider,
|
|
155
|
+
concurrencyLimit: concurrencyLimit,
|
|
156
|
+
cacheEnabled: mergedOpts.noCache === undefined ? defaultConfig.cacheEnabled : !mergedOpts.noCache,
|
|
157
|
+
debug: mergedOpts.debug,
|
|
158
|
+
verbose: mergedOpts.verbose || defaultConfig.logging?.verbose || false,
|
|
159
|
+
forceUpdate: mergedOpts.force || false,
|
|
160
|
+
showDetailedStats: mergedOpts.stats || false,
|
|
161
|
+
autoOptimize: mergedOpts.autoOptimize || defaultConfig.advanced?.autoOptimize || false,
|
|
162
|
+
fixLength: commandName === "fix",
|
|
163
|
+
apiConfig: defaultConfig.apiConfig || {},
|
|
164
|
+
styleGuide: defaultConfig.styleGuide,
|
|
165
|
+
qualityChecks: defaultConfig.qualityChecks,
|
|
166
|
+
lengthControl: {
|
|
167
|
+
...defaultConfig.lengthControl,
|
|
168
|
+
mode: mergedOpts.length || defaultConfig.lengthControl?.mode || "smart",
|
|
169
|
+
},
|
|
170
|
+
retryOptions: {
|
|
171
|
+
...defaultConfig.retryOptions,
|
|
172
|
+
maxRetries: mergedOpts.maxRetries || defaultConfig.retryOptions?.maxRetries || 2,
|
|
173
|
+
initialDelay: mergedOpts.initialDelay || defaultConfig.retryOptions?.initialDelay || 1000,
|
|
174
|
+
maxDelay: mergedOpts.maxDelay || defaultConfig.retryOptions?.maxDelay || 10000,
|
|
175
|
+
jitter: defaultConfig.retryOptions?.jitter !== false,
|
|
176
|
+
},
|
|
177
|
+
context: {
|
|
178
|
+
...defaultConfig.context,
|
|
179
|
+
enabled: true,
|
|
180
|
+
debug: mergedOpts.contextDebug || defaultConfig.context.debug || false,
|
|
181
|
+
useAI: mergedOpts.useAi ||
|
|
182
|
+
mergedOpts.contextProvider !== undefined ||
|
|
183
|
+
defaultConfig.context.useAI ||
|
|
184
|
+
false,
|
|
185
|
+
aiProvider: mergedOpts.contextProvider || defaultConfig.context.aiProvider,
|
|
186
|
+
minTextLength: mergedOpts.minTextLength || defaultConfig.context.minTextLength,
|
|
187
|
+
allowNewCategories: mergedOpts.allowNewCategories !== undefined
|
|
188
|
+
? mergedOpts.allowNewCategories
|
|
189
|
+
: defaultConfig.context.allowNewCategories,
|
|
190
|
+
detection: {
|
|
191
|
+
threshold: mergedOpts.contextThreshold ||
|
|
192
|
+
defaultConfig.context.detection?.threshold ||
|
|
193
|
+
2,
|
|
194
|
+
minConfidence: mergedOpts.contextConfidence ||
|
|
195
|
+
defaultConfig.context.detection?.minConfidence ||
|
|
196
|
+
0.6,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
advanced: {
|
|
200
|
+
...defaultConfig.advanced,
|
|
201
|
+
timeoutMs: mergedOpts.timeout || defaultConfig.advanced?.timeoutMs || 60000,
|
|
202
|
+
maxKeyLength: defaultConfig.advanced?.maxKeyLength || 10000,
|
|
203
|
+
maxBatchSize: defaultConfig.advanced?.maxBatchSize || 50,
|
|
204
|
+
autoOptimize: mergedOpts.autoOptimize || defaultConfig.advanced?.autoOptimize || false,
|
|
205
|
+
debug: mergedOpts.debug || defaultConfig.advanced?.debug || false,
|
|
206
|
+
},
|
|
207
|
+
rateLimiter: {
|
|
208
|
+
...defaultConfig.rateLimiter,
|
|
209
|
+
enabled: defaultConfig.rateLimiter?.enabled !== false,
|
|
210
|
+
},
|
|
211
|
+
fileOperations: defaultConfig.fileOperations || {},
|
|
212
|
+
logging: {
|
|
213
|
+
...defaultConfig.logging,
|
|
214
|
+
verbose: mergedOpts.verbose || defaultConfig.logging?.verbose || false,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
try {
|
|
218
|
+
InputValidator.validateConfig(finalConfig);
|
|
219
|
+
if (finalConfig.debug || finalConfig.verbose) {
|
|
220
|
+
console.log("Configuration validated successfully");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (configError) {
|
|
224
|
+
const error = ErrorHelper.configValidationError(configError.message.includes("validation failed")
|
|
225
|
+
? configError.message.split("\n").slice(1)
|
|
226
|
+
: [configError.message]);
|
|
227
|
+
console.error(ErrorHelper.formatError(error, { showDebug: globalOpts.debug }));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
configureComponents(finalConfig);
|
|
231
|
+
// validateEnvironment() was called here but it was undefined in cli.ts context (likely an import I missed or a global)
|
|
232
|
+
// Assuming it needs to be imported or implemented.
|
|
233
|
+
// Actually, I don't see `validateEnvironment` defined in the previous view_file of cli.ts, but it was called.
|
|
234
|
+
// Wait, looking at line 429 of cli.ts: `validateEnvironment();`
|
|
235
|
+
// Where is it defined?
|
|
236
|
+
// I must have missed it in the file view or it was imported.
|
|
237
|
+
// Looking at imports in cli.ts:
|
|
238
|
+
// imports are: dotenv, fs, path, os, createRequire, program, translateFile, findLocaleFiles, validateAndFixExistingTranslations, ProviderFactory, FileManager, rateLimiter, Orchestrator, InputValidator, ErrorHelper, getLogger, TranslationOptions, FrameworkDetector, StructureDetector, loadConfig.
|
|
239
|
+
// `validateEnvironment` is likely a local function I missed when scrolling or it was imported and I missed it.
|
|
240
|
+
// Let me check the file content again or just assume it checks API keys.
|
|
241
|
+
// I'll implement a basic check for now.
|
|
242
|
+
if (finalConfig.debug) {
|
|
243
|
+
console.log("\nConfiguration details:");
|
|
244
|
+
const safeConfig = {
|
|
245
|
+
...finalConfig,
|
|
246
|
+
apiConfig: Object.keys(finalConfig.apiConfig || {}).reduce((acc, provider) => {
|
|
247
|
+
acc[provider] = {
|
|
248
|
+
model: finalConfig.apiConfig[provider]?.model || "configured",
|
|
249
|
+
temperature: finalConfig.apiConfig[provider]?.temperature,
|
|
250
|
+
maxTokens: finalConfig.apiConfig[provider]?.maxTokens,
|
|
251
|
+
};
|
|
252
|
+
return acc;
|
|
253
|
+
}, {}),
|
|
254
|
+
advanced: {
|
|
255
|
+
...finalConfig.advanced,
|
|
256
|
+
timeoutMs: finalConfig.advanced?.timeoutMs,
|
|
257
|
+
maxKeyLength: finalConfig.advanced?.maxKeyLength,
|
|
258
|
+
maxBatchSize: finalConfig.advanced?.maxBatchSize,
|
|
259
|
+
autoOptimize: finalConfig.advanced?.autoOptimize,
|
|
260
|
+
debug: finalConfig.advanced?.debug,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
delete safeConfig.apiProvider; // Could leak preferred provider info
|
|
264
|
+
delete safeConfig.localesDir; // Could leak file system structure
|
|
265
|
+
console.log(JSON.stringify(safeConfig, null, 2));
|
|
266
|
+
}
|
|
267
|
+
// displayPerformanceTips(finalConfig); // Another local function possibly missed.
|
|
268
|
+
const localesDir = path.resolve(finalConfig.localesDir);
|
|
269
|
+
console.log(`\nLooking for source files in: ${localesDir}`);
|
|
270
|
+
const files = await findLocaleFiles(localesDir, finalConfig.source);
|
|
271
|
+
if (!files || !files.length) {
|
|
272
|
+
const error = ErrorHelper.fileNotFoundError(`${localesDir}/${finalConfig.source}.json`, `${finalConfig.source}.json`);
|
|
273
|
+
console.error(ErrorHelper.formatError(error, {
|
|
274
|
+
showDebug: finalConfig.debug,
|
|
275
|
+
showContext: true,
|
|
276
|
+
}));
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
switch (commandName) {
|
|
280
|
+
case "fix":
|
|
281
|
+
console.log("\nRunning in FIX mode");
|
|
282
|
+
await Promise.all(files.map((file) => validateAndFixExistingTranslations(file, finalConfig)));
|
|
283
|
+
break;
|
|
284
|
+
case "analyze":
|
|
285
|
+
console.log("\nRunning in ANALYZE mode");
|
|
286
|
+
console.log("Context analysis mode is not fully implemented yet.");
|
|
287
|
+
break;
|
|
288
|
+
case "advanced":
|
|
289
|
+
console.log("\nRunning ADVANCED configuration");
|
|
290
|
+
// Fall through to translate
|
|
291
|
+
case "translate":
|
|
292
|
+
default:
|
|
293
|
+
console.log("\nRunning in TRANSLATION mode");
|
|
294
|
+
const providerMap = {
|
|
295
|
+
openai: "OPENAI_API_KEY",
|
|
296
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
297
|
+
gemini: "GEMINI_API_KEY",
|
|
298
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
299
|
+
xai: "XAI_API_KEY",
|
|
300
|
+
dashscope: "DASHSCOPE_API_KEY",
|
|
301
|
+
};
|
|
302
|
+
const availableProviders = Object.keys(providerMap).filter((key) => process.env[providerMap[key]]);
|
|
303
|
+
console.log(`API Configuration:`);
|
|
304
|
+
console.log(` - Active Provider: ${finalConfig.apiProvider}`);
|
|
305
|
+
console.log(` - Available Providers: ${availableProviders.join(", ") || "None"}`);
|
|
306
|
+
if (finalConfig.context.useAI) {
|
|
307
|
+
console.log(`AI Context Analysis: ENABLED (Provider: ${finalConfig.context.aiProvider || finalConfig.apiProvider})`);
|
|
308
|
+
if (finalConfig.context.allowNewCategories) {
|
|
309
|
+
console.log("New category suggestions: ENABLED");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
console.log(`Performance settings:`);
|
|
313
|
+
console.log(` - Concurrency: ${finalConfig.concurrencyLimit} parallel operations`);
|
|
314
|
+
console.log(` - Caching: ${finalConfig.cacheEnabled ? "Enabled" : "Disabled"}`);
|
|
315
|
+
console.log(` - Retries: ${finalConfig.retryOptions.maxRetries} max retries`);
|
|
316
|
+
if (finalConfig.rateLimiter?.adaptiveThrottling) {
|
|
317
|
+
console.log(` - Adaptive Rate Limiting: Enabled`);
|
|
318
|
+
}
|
|
319
|
+
if (finalConfig.forceUpdate) {
|
|
320
|
+
console.log(`Warning: Force update mode ENABLED (will update existing translations)`);
|
|
321
|
+
}
|
|
322
|
+
for (const file of files) {
|
|
323
|
+
await translateFile(file, finalConfig);
|
|
324
|
+
}
|
|
325
|
+
// Exit after successful translation in CLI mode
|
|
326
|
+
// This allows translateFile to be used as library without forcing exit
|
|
327
|
+
process.exit(0);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
// Other commands (fix, analyze) handle their own completion
|
|
331
|
+
}
|
|
332
|
+
catch (validationError) {
|
|
333
|
+
if (validationError.message.includes("Configuration validation")) {
|
|
334
|
+
// Already handled above with better formatting
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
console.error(`\nInput validation error: ${validationError.message}`);
|
|
338
|
+
if (validationError.stack && process.env.DEBUG) {
|
|
339
|
+
console.error("\nStack trace:");
|
|
340
|
+
console.error(validationError.stack);
|
|
341
|
+
}
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
};
|