locai 2.1.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.
Files changed (72) hide show
  1. package/README.md +928 -0
  2. package/dist/cli.js +718 -0
  3. package/dist/commands/review.js +357 -0
  4. package/dist/commands/translator.js +29 -0
  5. package/dist/config/index.js +52 -0
  6. package/dist/core/adapters/base-adapter.js +1 -0
  7. package/dist/core/adapters/factory.js +44 -0
  8. package/dist/core/adapters/json-adapter.js +20 -0
  9. package/dist/core/adapters/po-adapter.js +50 -0
  10. package/dist/core/adapters/properties-adapter.js +31 -0
  11. package/dist/core/adapters/yaml-adapter.js +24 -0
  12. package/dist/core/context-processor.js +247 -0
  13. package/dist/core/detection/framework-detector.js +56 -0
  14. package/dist/core/detection/structure-detector.js +30 -0
  15. package/dist/core/fallback-provider.js +442 -0
  16. package/dist/core/orchestrator.js +438 -0
  17. package/dist/core/pipeline/Pipeline.js +48 -0
  18. package/dist/core/pipeline/PipelineStep.js +13 -0
  19. package/dist/core/pipeline/context.js +21 -0
  20. package/dist/core/pipeline/steps/CacheReadStep.js +80 -0
  21. package/dist/core/pipeline/steps/CacheWriteStep.js +32 -0
  22. package/dist/core/pipeline/steps/ConfidenceCheckStep.js +67 -0
  23. package/dist/core/pipeline/steps/GlossaryPostStep.js +17 -0
  24. package/dist/core/pipeline/steps/GlossaryPreStep.js +20 -0
  25. package/dist/core/pipeline/steps/InputValidationStep.js +26 -0
  26. package/dist/core/pipeline/steps/QualityCheckStep.js +48 -0
  27. package/dist/core/pipeline/steps/ToneCheckStep.js +58 -0
  28. package/dist/core/pipeline/steps/TranslationStep.js +65 -0
  29. package/dist/core/pipeline/steps/VectorCacheReadStep.js +84 -0
  30. package/dist/core/pipeline/steps/VectorCacheWriteStep.js +42 -0
  31. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +100 -0
  32. package/dist/core/provider-factory.js +139 -0
  33. package/dist/providers/base-provider.js +230 -0
  34. package/dist/providers/dashscope.js +128 -0
  35. package/dist/providers/deepseek.js +111 -0
  36. package/dist/providers/gemini.js +135 -0
  37. package/dist/providers/openai.js +203 -0
  38. package/dist/providers/xai.js +111 -0
  39. package/dist/services/embedding-provider.js +50 -0
  40. package/dist/services/translation-service.js +609 -0
  41. package/dist/services/vector-store.js +79 -0
  42. package/dist/utils/ai-context-analyzer.js +460 -0
  43. package/dist/utils/confidence-scorer.js +277 -0
  44. package/dist/utils/error-helper.js +386 -0
  45. package/dist/utils/file-manager.js +357 -0
  46. package/dist/utils/glossary-manager.js +194 -0
  47. package/dist/utils/graceful-shutdown.js +118 -0
  48. package/dist/utils/input-validator.js +663 -0
  49. package/dist/utils/logger.js +374 -0
  50. package/dist/utils/object-transformer.js +177 -0
  51. package/dist/utils/progress-tracker.js +277 -0
  52. package/dist/utils/prompt-templates.js +415 -0
  53. package/dist/utils/quality/__tests__/tone-verifier.test.js +91 -0
  54. package/dist/utils/quality/base-checker.js +73 -0
  55. package/dist/utils/quality/code-block-checker.js +214 -0
  56. package/dist/utils/quality/html-tag-checker.js +64 -0
  57. package/dist/utils/quality/index.js +220 -0
  58. package/dist/utils/quality/length-checker.js +140 -0
  59. package/dist/utils/quality/markdown-checker.js +220 -0
  60. package/dist/utils/quality/placeholder-checker.js +142 -0
  61. package/dist/utils/quality/punctuation-checker.js +50 -0
  62. package/dist/utils/quality/quote-balance-checker.js +143 -0
  63. package/dist/utils/quality/special-characters-checker.js +229 -0
  64. package/dist/utils/quality/style-guide-checker.js +192 -0
  65. package/dist/utils/quality/text-sanitizer.js +120 -0
  66. package/dist/utils/quality/tone-verifier.js +91 -0
  67. package/dist/utils/rate-limiter.js +398 -0
  68. package/dist/utils/retry-helper.js +166 -0
  69. package/dist/utils/state-manager.js +162 -0
  70. package/dist/utils/statistics-manager.js +51 -0
  71. package/dist/utils/ui-manager.js +98 -0
  72. package/package.json +80 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Progress tracking for translation operations
3
+ */
4
+ class ProgressTracker {
5
+ logToConsole;
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ logFrequency;
8
+ _isUpdating;
9
+ _pendingUpdates;
10
+ _spinnerInterval;
11
+ _spinnerIndex;
12
+ _spinnerFrames;
13
+ total;
14
+ completed;
15
+ success;
16
+ failed;
17
+ startTime;
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
+ endTime;
20
+ lastUpdateTime;
21
+ recentOperationTimes;
22
+ language;
23
+ isCompleted;
24
+ statistics;
25
+ constructor(options = {}) {
26
+ this.logToConsole = options.logToConsole !== false;
27
+ this.logFrequency = options.logFrequency || 1;
28
+ this._isUpdating = false;
29
+ this._pendingUpdates = [];
30
+ this._spinnerInterval = null;
31
+ this._spinnerIndex = 0;
32
+ this._spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
33
+ // Initialize fields to satisfy TypeScript before calling reset()
34
+ this.total = 0;
35
+ this.completed = 0;
36
+ this.success = 0;
37
+ this.failed = 0;
38
+ this.startTime = null;
39
+ this.endTime = null;
40
+ this.lastUpdateTime = null;
41
+ this.recentOperationTimes = [];
42
+ this.language = null;
43
+ this.isCompleted = false;
44
+ this.statistics = {
45
+ avgTimePerItem: 0,
46
+ totalTime: 0,
47
+ estimatedTimeRemaining: 0,
48
+ itemsPerSecond: 0,
49
+ percentComplete: 0,
50
+ successRate: 0,
51
+ };
52
+ this.reset();
53
+ }
54
+ reset() {
55
+ // Stop any existing spinner
56
+ this._stopSpinner();
57
+ this.total = 0;
58
+ this.completed = 0;
59
+ this.success = 0;
60
+ this.failed = 0;
61
+ this.startTime = null;
62
+ this.endTime = null;
63
+ this.lastUpdateTime = null;
64
+ this.recentOperationTimes = [];
65
+ this.language = null;
66
+ this.isCompleted = false;
67
+ this._isUpdating = false;
68
+ this._pendingUpdates = [];
69
+ this.statistics = {
70
+ avgTimePerItem: 0,
71
+ totalTime: 0,
72
+ estimatedTimeRemaining: 0,
73
+ itemsPerSecond: 0,
74
+ percentComplete: 0,
75
+ successRate: 0,
76
+ };
77
+ }
78
+ start(total, language = null) {
79
+ this.reset();
80
+ this.total = Math.max(0, total);
81
+ this.startTime = Date.now();
82
+ this.lastUpdateTime = Date.now();
83
+ this.language = language;
84
+ this.isCompleted = false;
85
+ if (this.logToConsole && this.total > 0) {
86
+ // Start spinner animation
87
+ this._startSpinner();
88
+ this._renderProgress();
89
+ }
90
+ }
91
+ increment(type = "success") {
92
+ if (!this.startTime) {
93
+ throw new Error("Progress tracker not started");
94
+ }
95
+ if (this.isCompleted) {
96
+ return;
97
+ }
98
+ if (this._isUpdating) {
99
+ this._pendingUpdates.push({ type, timestamp: Date.now() });
100
+ return;
101
+ }
102
+ this._processUpdate(type);
103
+ }
104
+ _processUpdate(type) {
105
+ this._isUpdating = true;
106
+ try {
107
+ if (this.isCompleted) {
108
+ return;
109
+ }
110
+ const previousCompleted = this.completed;
111
+ this.completed = Math.min(this.total, this.completed + 1);
112
+ if (this.completed > previousCompleted) {
113
+ if (type === "success") {
114
+ this.success = Math.min(this.completed, this.success + 1);
115
+ }
116
+ else if (type === "failed") {
117
+ this.failed = Math.min(this.completed, this.failed + 1);
118
+ }
119
+ }
120
+ this._ensureDataConsistency();
121
+ const now = Date.now();
122
+ const timeSinceLastUpdate = now - (this.lastUpdateTime || now);
123
+ this.lastUpdateTime = now;
124
+ const MAX_OPERATION_TIMES = 10;
125
+ this.recentOperationTimes.push(timeSinceLastUpdate);
126
+ if (this.recentOperationTimes.length > MAX_OPERATION_TIMES) {
127
+ this.recentOperationTimes.shift(); // Keep only last 10
128
+ }
129
+ this._updateStatistics();
130
+ if (this.logToConsole && this._shouldLog()) {
131
+ this._renderProgress();
132
+ }
133
+ if (this.completed >= this.total && !this.isCompleted) {
134
+ this.endTime = Date.now();
135
+ this.isCompleted = true;
136
+ this._stopSpinner();
137
+ this._renderProgress(true); // Final render
138
+ if (this.logToConsole) {
139
+ this._finalReport();
140
+ }
141
+ }
142
+ }
143
+ finally {
144
+ this._isUpdating = false;
145
+ this._processPendingUpdates();
146
+ }
147
+ }
148
+ _ensureDataConsistency() {
149
+ this.completed = Math.min(this.total, Math.max(0, this.completed));
150
+ this.success = Math.min(this.completed, Math.max(0, this.success));
151
+ this.failed = Math.min(this.completed, Math.max(0, this.failed));
152
+ const totalCounted = this.success + this.failed;
153
+ if (totalCounted > this.completed) {
154
+ if (this.success > this.completed) {
155
+ this.success = this.completed;
156
+ this.failed = 0;
157
+ }
158
+ else {
159
+ this.failed = this.completed - this.success;
160
+ }
161
+ }
162
+ }
163
+ _processPendingUpdates() {
164
+ if (!this._pendingUpdates ||
165
+ !Array.isArray(this._pendingUpdates) ||
166
+ this._pendingUpdates.length === 0) {
167
+ return;
168
+ }
169
+ setImmediate(() => {
170
+ while (this._pendingUpdates && this._pendingUpdates.length > 0 && !this._isUpdating) {
171
+ const updateItem = this._pendingUpdates.shift();
172
+ if (updateItem && typeof updateItem === "object" && updateItem.type) {
173
+ this._processUpdate(updateItem.type);
174
+ }
175
+ }
176
+ });
177
+ }
178
+ _shouldLog() {
179
+ if (!this.logToConsole || this.total === 0)
180
+ return false;
181
+ return true;
182
+ }
183
+ _startSpinner() {
184
+ if (this._spinnerInterval)
185
+ return;
186
+ this._spinnerInterval = setInterval(() => {
187
+ if (!this.isCompleted) {
188
+ this._spinnerIndex = (this._spinnerIndex + 1) % this._spinnerFrames.length;
189
+ this._renderProgress();
190
+ }
191
+ }, 80);
192
+ }
193
+ _stopSpinner() {
194
+ if (this._spinnerInterval) {
195
+ clearInterval(this._spinnerInterval);
196
+ this._spinnerInterval = null;
197
+ }
198
+ }
199
+ _renderProgress(final = false) {
200
+ const percent = this.total > 0 ? (this.completed / this.total) * 100 : 0;
201
+ const langInfo = this.language ? `[${this.language}] ` : "";
202
+ const barWidth = 20;
203
+ const filledWidth = Math.max(0, Math.min(barWidth, Math.round((this.completed / this.total) * barWidth)));
204
+ const emptyWidth = barWidth - filledWidth;
205
+ const bar = "[" + "█".repeat(filledWidth) + "═".repeat(emptyWidth) + "]";
206
+ let etaText = "";
207
+ if (this.completed > 0 && this.completed < this.total) {
208
+ const eta = this.statistics.estimatedTimeRemaining;
209
+ if (eta < 60) {
210
+ etaText = ` | ETA: ${Math.round(eta)}s`;
211
+ }
212
+ else if (eta < 3600) {
213
+ etaText = ` | ETA: ${Math.floor(eta / 60)}m ${Math.round(eta % 60)}s`;
214
+ }
215
+ else {
216
+ etaText = ` | ETA: ${Math.floor(eta / 3600)}h ${Math.floor((eta % 3600) / 60)}m`;
217
+ }
218
+ }
219
+ const spinner = final ? "[OK]" : this._spinnerFrames[this._spinnerIndex];
220
+ const text = `${spinner} ${langInfo}${bar} ${percent.toFixed(1)}% | ${this.completed}/${this.total} items | OK ${this.success} | ERR ${this.failed}${etaText}`;
221
+ process.stderr.write("\r\x1b[K" + text);
222
+ if (final) {
223
+ process.stderr.write("\n");
224
+ }
225
+ }
226
+ _updateStatistics() {
227
+ if (!this.startTime)
228
+ return;
229
+ const elapsed = Date.now() - this.startTime;
230
+ const averageTime = this.completed > 0 ? elapsed / this.completed : 0;
231
+ let recentAverage = 0;
232
+ if (this.recentOperationTimes.length >= 3) {
233
+ recentAverage =
234
+ this.recentOperationTimes.reduce((a, b) => a + b, 0) /
235
+ this.recentOperationTimes.length;
236
+ }
237
+ else {
238
+ recentAverage = averageTime;
239
+ }
240
+ const remaining = Math.max(0, this.total - this.completed);
241
+ const estimatedTimeRemaining = remaining * Math.min(averageTime, recentAverage);
242
+ const percentComplete = Math.min(100, Math.max(0, (this.completed / this.total) * 100 || 0));
243
+ const successRate = this.completed > 0
244
+ ? Math.min(100, Math.max(0, (this.success / this.completed) * 100))
245
+ : 0;
246
+ this.statistics = {
247
+ avgTimePerItem: averageTime,
248
+ recentAvgTimePerItem: recentAverage,
249
+ totalTime: elapsed / 1000, // in seconds
250
+ estimatedTimeRemaining: estimatedTimeRemaining / 1000, // in seconds
251
+ itemsPerSecond: this.completed > 0 ? this.completed / (elapsed / 1000) : 0,
252
+ percentComplete: percentComplete,
253
+ successRate: successRate,
254
+ startTime: new Date(this.startTime).toISOString(),
255
+ estimatedEndTime: new Date(Date.now() + estimatedTimeRemaining).toISOString(),
256
+ };
257
+ }
258
+ _finalReport() {
259
+ // Progress bar already displays the summary visually
260
+ // Remove duplicate console output to keep it clean
261
+ }
262
+ getStatus() {
263
+ return {
264
+ total: this.total,
265
+ completed: this.completed,
266
+ success: this.success,
267
+ failed: this.failed,
268
+ language: this.language,
269
+ inProgress: !!(this.startTime && !this.isCompleted),
270
+ statistics: { ...this.statistics },
271
+ };
272
+ }
273
+ setLogToConsole(value) {
274
+ this.logToConsole = value;
275
+ }
276
+ }
277
+ export default ProgressTracker;
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Prompt template utilities for translation and analysis
3
+ */
4
+ const getLengthInstructions = (options) => {
5
+ // ... existing getLengthInstructions ...
6
+ if (!options || typeof options !== "object") {
7
+ console.warn("Invalid options provided to getLengthInstructions, using defaults");
8
+ options = {};
9
+ }
10
+ const { mode = "smart", lengthControl, targetLang, detectedContext } = options;
11
+ if (!targetLang || typeof targetLang !== "string") {
12
+ // Just return basic length instructions without failing
13
+ return "TRANSLATION LENGTH: Keep translation concise and natural.";
14
+ }
15
+ const context = detectedContext?.category || "general";
16
+ if (mode === "smart" && lengthControl?.rules?.smart) {
17
+ const langRules = lengthControl.rules.smart.byLanguage?.[targetLang] || {};
18
+ const contextRules = lengthControl.rules.smart.byContext?.[context] || {};
19
+ const langMax = typeof langRules.max === "number" ? langRules.max : 0.15;
20
+ const contextMax = typeof contextRules.max === "number" ? contextRules.max : 0.15;
21
+ return `TRANSLATION LENGTH REQUIREMENTS [${targetLang}]:
22
+ 1. Maximum allowed length: ${Math.round(langMax * 100)}% longer than source
23
+ 2. Context-specific [${context}] limit: ${Math.round(contextMax * 100)}% longer than source
24
+ 3. Shorter translations are preferred when possible
25
+ 4. Maintain semantic completeness while being concise`;
26
+ }
27
+ const templates = {
28
+ strict: () => {
29
+ const strictLimit = lengthControl?.rules?.strict;
30
+ const limit = typeof strictLimit === "number" ? strictLimit : 1.0;
31
+ return `CRITICAL: Translation must not exceed ${Math.round(limit * 100)}% of source length. Prefer shorter translations.`;
32
+ },
33
+ flexible: () => {
34
+ const flexibleLimit = lengthControl?.rules?.flexible;
35
+ const limit = typeof flexibleLimit === "number" ? flexibleLimit : 1.2;
36
+ return `IMPORTANT: Keep translation concise. Target length should not exceed source length by more than ${Math.round(limit * 100)}%.`;
37
+ },
38
+ exact: () => {
39
+ const exactLimit = lengthControl?.rules?.exact;
40
+ const limit = typeof exactLimit === "number" ? exactLimit : 1.05;
41
+ return `STRICT: Translation must closely match source length (max ${Math.round(limit * 100)}% deviation).`;
42
+ },
43
+ relaxed: () => {
44
+ const relaxedLimit = lengthControl?.rules?.relaxed;
45
+ const limit = typeof relaxedLimit === "number" ? relaxedLimit : 1.5;
46
+ return `GUIDELINE: Translation should be concise but can be up to ${Math.round(limit * 100)}% longer if needed.`;
47
+ },
48
+ };
49
+ const templateFn = templates[mode];
50
+ if (typeof templateFn === "function") {
51
+ try {
52
+ return templateFn();
53
+ }
54
+ catch (error) {
55
+ console.warn(`Error generating length template for mode ${mode}:`, error.message);
56
+ }
57
+ }
58
+ return "TRANSLATION LENGTH: Keep translation concise and natural.";
59
+ };
60
+ /**
61
+ * Base translation prompt template
62
+ */
63
+ const baseTranslationPromptTemplate = (sourceLang, targetLang, text, options = {}) => {
64
+ if (!sourceLang || typeof sourceLang !== "string") {
65
+ console.warn("Invalid sourceLang provided to baseTranslationPromptTemplate");
66
+ sourceLang = "en";
67
+ }
68
+ if (!targetLang || typeof targetLang !== "string") {
69
+ console.warn("Invalid targetLang provided to baseTranslationPromptTemplate");
70
+ targetLang = "es";
71
+ }
72
+ if (typeof text !== "string") {
73
+ console.warn("Invalid text provided to baseTranslationPromptTemplate");
74
+ text = "";
75
+ }
76
+ if (!options || typeof options !== "object") {
77
+ console.warn("Invalid options provided to baseTranslationPromptTemplate, using defaults");
78
+ options = {};
79
+ }
80
+ const context = options.detectedContext && typeof options.detectedContext === "object"
81
+ ? options.detectedContext
82
+ : {
83
+ category: "general",
84
+ confidence: 1.0,
85
+ prompt: "Provide a natural translation",
86
+ };
87
+ const safeContext = {
88
+ category: context.category || "general",
89
+ confidence: typeof context.confidence === "number" ? context.confidence : 1.0,
90
+ prompt: context.prompt || "Provide a natural translation",
91
+ existingTranslation: context.existingTranslation,
92
+ similarTranslation: context.similarTranslation,
93
+ };
94
+ const lengthInstructions = getLengthInstructions({
95
+ ...options,
96
+ targetLang: targetLang, // Pass targetLang explicitly
97
+ detectedContext: safeContext, // Also pass context
98
+ });
99
+ let additionalInstructions = "";
100
+ if (safeContext.existingTranslation && typeof safeContext.existingTranslation === "string") {
101
+ const truncatedTranslation = safeContext.existingTranslation.length > 200
102
+ ? safeContext.existingTranslation.substring(0, 200) + "..."
103
+ : safeContext.existingTranslation;
104
+ additionalInstructions = `\nREVISION REQUEST: The existing translation "${truncatedTranslation}" has length issues. Please provide a corrected version that matches the source text length requirements.`;
105
+ }
106
+ else if (safeContext.similarTranslation) {
107
+ const { source, target, similarity } = safeContext.similarTranslation;
108
+ const simPercent = Math.round(similarity * 100);
109
+ additionalInstructions = `\nREFERENCE CONTEXT (${simPercent}% Match):
110
+ Previously, a similar phrase: "${source}"
111
+ Was translated as: "${target}"
112
+ Please maintain consistency with this style and vocabulary where appropriate.`;
113
+ }
114
+ const formality = options.styleGuide?.formality || "neutral";
115
+ const toneOfVoice = options.styleGuide?.toneOfVoice || "professional";
116
+ return `
117
+ Translation Task: ${sourceLang} → ${targetLang}
118
+ ${additionalInstructions}
119
+
120
+ Category: ${safeContext.category}
121
+ Context Instructions: ${safeContext.prompt}
122
+
123
+ LENGTH CONTROL:
124
+ ${lengthInstructions}
125
+
126
+ STRICT OUTPUT REQUIREMENTS:
127
+ 1. RETURN ONLY THE TRANSLATED TEXT
128
+ 2. NO EXPLANATIONS OR COMMENTARY
129
+ 3. NO <think> BLOCKS OR MARKDOWN
130
+ 4. NO QUOTES OR FORMATTING
131
+ 5. PRESERVE ALL PLACEHOLDERS EXACTLY AS THEY APPEAR
132
+
133
+ CRITICAL PLACEHOLDER RULES:
134
+ - Keep ALL placeholders like {message}, {name}, {value} EXACTLY as they appear in the source
135
+ - DO NOT translate placeholder names: {message} must stay {message}, NOT {mesaj} or {mensaje}
136
+ - DO NOT add content around placeholders: {message} must NOT become {mesaj}{message} or Bạn{message}
137
+ - DO NOT modify the curly braces: { and } must remain unchanged
138
+ - Placeholders are variables that will be replaced with dynamic content later
139
+ - Only translate the text AROUND the placeholders, never the placeholders themselves
140
+
141
+ Style: ${formality}, ${toneOfVoice}
142
+
143
+ Text to Translate:
144
+ ${text}`;
145
+ };
146
+ /**
147
+ * Base analysis prompt template
148
+ */
149
+ const baseAnalysisPromptTemplate = (text, options = {}) => {
150
+ if (typeof text !== "string") {
151
+ console.warn("Invalid text provided to baseAnalysisPromptTemplate");
152
+ text = "";
153
+ }
154
+ if (!options || typeof options !== "object") {
155
+ console.warn("Invalid options provided to baseAnalysisPromptTemplate, using defaults");
156
+ options = {};
157
+ }
158
+ let categories = "technical, marketing, legal, defi, ui, general";
159
+ if (options.categories && typeof options.categories === "object") {
160
+ try {
161
+ const categoryKeys = Object.keys(options.categories);
162
+ if (Array.isArray(categoryKeys) && categoryKeys.length > 0) {
163
+ categories = categoryKeys.join(", ");
164
+ }
165
+ }
166
+ catch (error) {
167
+ console.warn("Error processing categories:", error.message);
168
+ }
169
+ }
170
+ const maxTextLength = 1500;
171
+ const truncatedText = text.length > maxTextLength ? text.substring(0, maxTextLength) + "..." : text;
172
+ const allowNewCategories = options.allowNewCategories === true;
173
+ const categoryNote = allowNewCategories
174
+ ? ", or suggest a new category if none of these fit"
175
+ : "";
176
+ return `
177
+ TASK: Analyze the following text and determine its context category.
178
+
179
+ TEXT TO ANALYZE:
180
+ """
181
+ ${truncatedText}
182
+ """
183
+
184
+ AVAILABLE CATEGORIES: ${categories}${categoryNote}
185
+
186
+ INSTRUCTIONS:
187
+ 1. Identify the primary context category of the text
188
+ 2. Provide a confidence score (0.0-1.0)
189
+ 3. Suggest 3-5 keywords that are relevant to this text
190
+ 4. Provide a brief explanation of your categorization
191
+
192
+ FORMAT YOUR RESPONSE AS JSON:
193
+ {
194
+ "category": "category_name",
195
+ "confidence": 0.0-1.0,
196
+ "keywords": ["keyword1", "keyword2", "keyword3"],
197
+ "explanation": "Brief explanation of why this category was chosen"
198
+ }
199
+ `;
200
+ };
201
+ // Provider-specific templates for translation
202
+ const translationPrompts = {
203
+ gemini: (sourceLang, targetLang, text, options) => ({
204
+ contents: [
205
+ {
206
+ parts: [
207
+ {
208
+ text: `${baseTranslationPromptTemplate(sourceLang, targetLang, text, options)}
209
+ Original Text: "${text}"`,
210
+ },
211
+ ],
212
+ },
213
+ ],
214
+ }),
215
+ openai: (sourceLang, targetLang, text, options) => ({
216
+ messages: [
217
+ {
218
+ role: "system",
219
+ content: baseTranslationPromptTemplate(sourceLang, targetLang, text, options),
220
+ },
221
+ {
222
+ role: "user",
223
+ content: text,
224
+ },
225
+ ],
226
+ }),
227
+ dashscope: (sourceLang, targetLang, text, options) => ({
228
+ messages: [
229
+ {
230
+ role: "system",
231
+ content: baseTranslationPromptTemplate(sourceLang, targetLang, text, options),
232
+ },
233
+ {
234
+ role: "user",
235
+ content: text,
236
+ },
237
+ ],
238
+ }),
239
+ deepseek: (sourceLang, targetLang, text, options) => ({
240
+ messages: [
241
+ {
242
+ role: "system",
243
+ content: baseTranslationPromptTemplate(sourceLang, targetLang, text, options),
244
+ },
245
+ {
246
+ role: "user",
247
+ content: text,
248
+ },
249
+ ],
250
+ }),
251
+ xai: (sourceLang, targetLang, text, options) => ({
252
+ messages: [
253
+ {
254
+ role: "system",
255
+ content: baseTranslationPromptTemplate(sourceLang, targetLang, text, options),
256
+ },
257
+ {
258
+ role: "user",
259
+ content: text,
260
+ },
261
+ ],
262
+ }),
263
+ default: (sourceLang, targetLang, text, options) => ({
264
+ messages: [
265
+ {
266
+ role: "system",
267
+ content: baseTranslationPromptTemplate(sourceLang, targetLang, text, options),
268
+ },
269
+ {
270
+ role: "user",
271
+ content: text,
272
+ },
273
+ ],
274
+ }),
275
+ };
276
+ // Provider-specific templates for analysis
277
+ const analysisPrompts = {
278
+ gemini: (text, options) => ({
279
+ contents: [
280
+ {
281
+ parts: [
282
+ {
283
+ text: baseAnalysisPromptTemplate(text, options),
284
+ },
285
+ ],
286
+ },
287
+ ],
288
+ }),
289
+ openai: (text, options) => ({
290
+ messages: [
291
+ {
292
+ role: "system",
293
+ content: "You are a context analysis assistant that helps identify the category and context of text.",
294
+ },
295
+ {
296
+ role: "user",
297
+ content: baseAnalysisPromptTemplate(text, options),
298
+ },
299
+ ],
300
+ }),
301
+ dashscope: (text, options) => ({
302
+ model: options.model || "qwen-plus",
303
+ input: {
304
+ messages: [
305
+ {
306
+ role: "system",
307
+ content: "You are a context analysis assistant that helps identify the category and context of text.",
308
+ },
309
+ {
310
+ role: "user",
311
+ content: baseAnalysisPromptTemplate(text, options),
312
+ },
313
+ ],
314
+ },
315
+ parameters: {
316
+ temperature: options.temperature || 0.2,
317
+ max_tokens: options.maxTokens || 1000,
318
+ },
319
+ }),
320
+ deepseek: (text, options) => ({
321
+ messages: [
322
+ {
323
+ role: "system",
324
+ content: "You are a context analysis assistant that helps identify the category and context of text.",
325
+ },
326
+ {
327
+ role: "user",
328
+ content: baseAnalysisPromptTemplate(text, options),
329
+ },
330
+ ],
331
+ }),
332
+ xai: (text, options) => ({
333
+ messages: [
334
+ {
335
+ role: "system",
336
+ content: "You are a context analysis assistant that helps identify the category and context of text.",
337
+ },
338
+ {
339
+ role: "user",
340
+ content: baseAnalysisPromptTemplate(text, options),
341
+ },
342
+ ],
343
+ }),
344
+ default: (text, options) => ({
345
+ messages: [
346
+ {
347
+ role: "system",
348
+ content: "You are a context analysis assistant that helps identify the category and context of text.",
349
+ },
350
+ {
351
+ role: "user",
352
+ content: baseAnalysisPromptTemplate(text, options),
353
+ },
354
+ ],
355
+ }),
356
+ };
357
+ /**
358
+ * Get translation prompt
359
+ */
360
+ const getPrompt = (provider, sourceLang, targetLang, text, options = {}) => {
361
+ if (!provider || typeof provider !== "string") {
362
+ console.warn("Invalid provider provided to getPrompt, using default");
363
+ provider = "default";
364
+ }
365
+ if (!sourceLang || typeof sourceLang !== "string") {
366
+ console.warn("Invalid sourceLang provided to getPrompt, using en");
367
+ sourceLang = "en";
368
+ }
369
+ if (!targetLang || typeof targetLang !== "string") {
370
+ console.warn("Invalid targetLang provided to getPrompt, using es");
371
+ targetLang = "es";
372
+ }
373
+ if (typeof text !== "string") {
374
+ console.warn("Invalid text provided to getPrompt, using empty string");
375
+ text = "";
376
+ }
377
+ if (!options || typeof options !== "object") {
378
+ console.warn("Invalid options provided to getPrompt, using defaults");
379
+ options = {};
380
+ }
381
+ const promptGenerator = translationPrompts[provider] || translationPrompts.default;
382
+ try {
383
+ return promptGenerator(sourceLang, targetLang, text, options);
384
+ }
385
+ catch (error) {
386
+ console.error(`Error generating prompt for provider ${provider}:`, error.message);
387
+ return translationPrompts.default(sourceLang, targetLang, text, options);
388
+ }
389
+ };
390
+ /**
391
+ * Get analysis prompt
392
+ */
393
+ const getAnalysisPrompt = (provider, text, options = {}) => {
394
+ if (!provider || typeof provider !== "string") {
395
+ console.warn("Invalid provider provided to getAnalysisPrompt, using default");
396
+ provider = "default";
397
+ }
398
+ if (typeof text !== "string") {
399
+ console.warn("Invalid text provided to getAnalysisPrompt, using empty string");
400
+ text = "";
401
+ }
402
+ if (!options || typeof options !== "object") {
403
+ console.warn("Invalid options provided to getAnalysisPrompt, using defaults");
404
+ options = {};
405
+ }
406
+ const promptGenerator = analysisPrompts[provider] || analysisPrompts.default;
407
+ try {
408
+ return promptGenerator(text, options);
409
+ }
410
+ catch (error) {
411
+ console.error(`Error generating analysis prompt for provider ${provider}:`, error.message);
412
+ return analysisPrompts.default(text, options);
413
+ }
414
+ };
415
+ export { getPrompt, getAnalysisPrompt };