locai 2.3.0 → 2.4.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/dist/cli/helpers.d.ts +2 -0
- package/dist/cli/helpers.js +344 -0
- package/dist/cli.js +38 -519
- package/dist/commands/translator.d.ts +1 -1
- package/dist/config/index.d.ts +1 -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/services/language-processor.d.ts +22 -0
- package/dist/services/language-processor.js +239 -0
- package/dist/services/translation-service.d.ts +1 -49
- package/dist/services/translation-service.js +3 -215
- 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 +15 -7
|
@@ -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
|
+
};
|