locai 2.2.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/interactive-review.d.ts +9 -0
- package/dist/commands/interactive-review.js +109 -0
- package/dist/commands/review.d.ts +1 -37
- package/dist/commands/review.js +40 -160
- 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/core/adapters/android-adapter.d.ts +12 -0
- package/dist/core/adapters/android-adapter.js +107 -0
- package/dist/core/adapters/factory.js +4 -0
- package/dist/core/adapters/ios-adapter.d.ts +10 -0
- package/dist/core/adapters/ios-adapter.js +45 -0
- package/dist/providers/openai.js +3 -3
- 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 +18 -7
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import ConfidenceScorer from "../utils/confidence-scorer.js";
|
|
5
|
+
export class InteractiveReviewer {
|
|
6
|
+
queue;
|
|
7
|
+
decisions;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.queue = [];
|
|
10
|
+
this.decisions = {
|
|
11
|
+
accepted: [],
|
|
12
|
+
edited: [],
|
|
13
|
+
rejected: [],
|
|
14
|
+
skipped: [],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
loadQueue() {
|
|
18
|
+
const reviewFile = path.join(process.cwd(), ".localize-cache", "review-queue.json");
|
|
19
|
+
if (!fs.existsSync(reviewFile))
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const data = JSON.parse(fs.readFileSync(reviewFile, "utf8"));
|
|
23
|
+
this.queue = data.items || [];
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async start() {
|
|
31
|
+
if (!this.loadQueue() || this.queue.length === 0) {
|
|
32
|
+
console.log("No items to review.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.clear();
|
|
36
|
+
console.log(`\nš Interactive Review Mode (${this.queue.length} items)\n`);
|
|
37
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
38
|
+
const item = this.queue[i];
|
|
39
|
+
const progress = `[${i + 1}/${this.queue.length}]`;
|
|
40
|
+
console.log(`\n${progress} ${item.language.toUpperCase()} | Key: ${item.key}`);
|
|
41
|
+
console.log(`Confidence: ${ConfidenceScorer.formatConfidence(item.confidence.score)} (${item.confidence.level})`);
|
|
42
|
+
const response = await prompts({
|
|
43
|
+
type: "select",
|
|
44
|
+
name: "action",
|
|
45
|
+
message: `Translation: "${item.translation}"\nSource: "${item.source}"\nAction:`,
|
|
46
|
+
choices: [
|
|
47
|
+
{ title: "ā
Approve", value: "approve" },
|
|
48
|
+
{ title: "āļø Edit", value: "edit" },
|
|
49
|
+
{ title: "āļø Skip", value: "skip" },
|
|
50
|
+
{ title: "šŖ Exit", value: "exit" },
|
|
51
|
+
],
|
|
52
|
+
initial: 0,
|
|
53
|
+
});
|
|
54
|
+
if (response.action === "exit")
|
|
55
|
+
break;
|
|
56
|
+
await this.handleAction(response.action, item);
|
|
57
|
+
}
|
|
58
|
+
this.saveDecisions();
|
|
59
|
+
}
|
|
60
|
+
async handleAction(action, item) {
|
|
61
|
+
switch (action) {
|
|
62
|
+
case "approve":
|
|
63
|
+
this.decisions.accepted.push(item);
|
|
64
|
+
break;
|
|
65
|
+
case "edit":
|
|
66
|
+
const editResponse = await prompts({
|
|
67
|
+
type: "text",
|
|
68
|
+
name: "newTranslation",
|
|
69
|
+
message: "Enter new translation:",
|
|
70
|
+
initial: item.translation,
|
|
71
|
+
});
|
|
72
|
+
if (editResponse.newTranslation) {
|
|
73
|
+
item.translation = editResponse.newTranslation;
|
|
74
|
+
item.edited = true;
|
|
75
|
+
this.decisions.edited.push(item);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.decisions.skipped.push(item);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case "skip":
|
|
82
|
+
this.decisions.skipped.push(item);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
saveDecisions() {
|
|
87
|
+
// Reuse existing ReviewCommand logic or implement save/apply logic here
|
|
88
|
+
// For now, let's save to a decision file that ReviewCommand can pick up or apply directly
|
|
89
|
+
const cacheDir = path.join(process.cwd(), ".localize-cache");
|
|
90
|
+
if (!fs.existsSync(cacheDir))
|
|
91
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
92
|
+
const decisionsFile = path.join(cacheDir, "review-decisions.json");
|
|
93
|
+
const data = {
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
decisions: this.decisions,
|
|
96
|
+
stats: {
|
|
97
|
+
accepted: this.decisions.accepted.length,
|
|
98
|
+
edited: this.decisions.edited.length,
|
|
99
|
+
rejected: this.decisions.rejected.length,
|
|
100
|
+
skipped: this.decisions.skipped.length,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
fs.writeFileSync(decisionsFile, JSON.stringify(data, null, 2));
|
|
104
|
+
console.log(`\nā
Review complete! Decisions saved to ${decisionsFile}`);
|
|
105
|
+
console.log(`Run 'locai review --apply' to apply changes to files.`);
|
|
106
|
+
// Note: The existing CLI 'review' command applies immediately in its class.
|
|
107
|
+
// We might want to trigger apply here or ensuring ReviewCommand is used.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive Review Command.
|
|
3
|
-
* Allows manual review of low-confidence translations.
|
|
4
|
-
*/
|
|
5
1
|
interface ReviewItem {
|
|
6
2
|
key: string;
|
|
7
3
|
language: string;
|
|
@@ -28,7 +24,6 @@ interface ReviewCommandConfig {
|
|
|
28
24
|
declare class ReviewCommand {
|
|
29
25
|
private config;
|
|
30
26
|
private reviewQueue;
|
|
31
|
-
private currentIndex;
|
|
32
27
|
private decisions;
|
|
33
28
|
/**
|
|
34
29
|
* Initialize ReviewCommand.
|
|
@@ -44,35 +39,10 @@ declare class ReviewCommand {
|
|
|
44
39
|
* Start interactive review session.
|
|
45
40
|
*/
|
|
46
41
|
startReview(): Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Review a single translation item.
|
|
49
|
-
* @param {ReviewItem} item - Translation item to review.
|
|
50
|
-
* @param {number} current - Current item index (1-based).
|
|
51
|
-
* @param {number} total - Total items.
|
|
52
|
-
* @returns {Promise<string>} - Decision result.
|
|
53
|
-
*/
|
|
54
|
-
reviewItem(item: ReviewItem, current: number, total: number): Promise<string>;
|
|
55
42
|
/**
|
|
56
43
|
* Handle user action input.
|
|
57
|
-
* @param {string} action - User action code.
|
|
58
|
-
* @param {ReviewItem} item - Current item.
|
|
59
|
-
* @returns {Promise<string>} - Result status.
|
|
60
|
-
*/
|
|
61
|
-
handleAction(action: string, item: ReviewItem): Promise<string>;
|
|
62
|
-
/**
|
|
63
|
-
* Edit translation interactively.
|
|
64
|
-
* @param {ReviewItem} item - Item to edit.
|
|
65
|
-
* @returns {Promise<ReviewItem>} - Edited item.
|
|
66
|
-
*/
|
|
67
|
-
editTranslation(item: ReviewItem): Promise<ReviewItem>;
|
|
68
|
-
/**
|
|
69
|
-
* Show help information to the user.
|
|
70
|
-
*/
|
|
71
|
-
showHelp(): void;
|
|
72
|
-
/**
|
|
73
|
-
* Show summary of review session.
|
|
74
44
|
*/
|
|
75
|
-
|
|
45
|
+
private handleAction;
|
|
76
46
|
/**
|
|
77
47
|
* Save review decisions to disk and apply them.
|
|
78
48
|
*/
|
|
@@ -88,12 +58,6 @@ declare class ReviewCommand {
|
|
|
88
58
|
* @param {any} value - Value to set.
|
|
89
59
|
*/
|
|
90
60
|
setNestedValue(obj: any, path: string, value: any): void;
|
|
91
|
-
/**
|
|
92
|
-
* Get user input from terminal.
|
|
93
|
-
* @param {string} prompt - Prompt to display.
|
|
94
|
-
* @returns {Promise<string>} - User input.
|
|
95
|
-
*/
|
|
96
|
-
getUserInput(prompt: string): Promise<string>;
|
|
97
61
|
/**
|
|
98
62
|
* Export review queue to JSON.
|
|
99
63
|
* @param {string} format - Export format (json or csv).
|
package/dist/commands/review.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive Review Command.
|
|
3
|
-
* Allows manual review of low-confidence translations.
|
|
4
|
-
*/
|
|
5
1
|
import fs from "fs";
|
|
6
2
|
import path from "path";
|
|
7
|
-
import
|
|
3
|
+
import prompts from "prompts";
|
|
8
4
|
import ConfidenceScorer from "../utils/confidence-scorer.js";
|
|
9
5
|
class ReviewCommand {
|
|
10
6
|
config;
|
|
11
7
|
reviewQueue;
|
|
12
|
-
currentIndex;
|
|
13
8
|
decisions;
|
|
14
9
|
/**
|
|
15
10
|
* Initialize ReviewCommand.
|
|
@@ -18,7 +13,6 @@ class ReviewCommand {
|
|
|
18
13
|
constructor(config) {
|
|
19
14
|
this.config = config;
|
|
20
15
|
this.reviewQueue = [];
|
|
21
|
-
this.currentIndex = 0;
|
|
22
16
|
this.decisions = {
|
|
23
17
|
accepted: [],
|
|
24
18
|
edited: [],
|
|
@@ -40,7 +34,6 @@ class ReviewCommand {
|
|
|
40
34
|
try {
|
|
41
35
|
const data = JSON.parse(fs.readFileSync(reviewFile, "utf8"));
|
|
42
36
|
this.reviewQueue = data.items || [];
|
|
43
|
-
console.log(`\nLoaded ${this.reviewQueue.length} items for review\n`);
|
|
44
37
|
return true;
|
|
45
38
|
}
|
|
46
39
|
catch (error) {
|
|
@@ -56,159 +49,63 @@ class ReviewCommand {
|
|
|
56
49
|
return;
|
|
57
50
|
}
|
|
58
51
|
if (this.reviewQueue.length === 0) {
|
|
59
|
-
console.log("
|
|
52
|
+
console.log("No items to review.");
|
|
60
53
|
return;
|
|
61
54
|
}
|
|
62
|
-
console.
|
|
63
|
-
console.log(
|
|
64
|
-
console.log("ā".repeat(70));
|
|
55
|
+
console.clear();
|
|
56
|
+
console.log(`\nInteractive Review Mode (${this.reviewQueue.length} items)\n`);
|
|
65
57
|
for (let i = 0; i < this.reviewQueue.length; i++) {
|
|
66
|
-
this.currentIndex = i;
|
|
67
58
|
const item = this.reviewQueue[i];
|
|
68
|
-
const
|
|
69
|
-
|
|
59
|
+
const progress = `[${i + 1}/${this.reviewQueue.length}]`;
|
|
60
|
+
console.log(`\n${progress} ${item.language.toUpperCase()} | Key: ${item.key}`);
|
|
61
|
+
console.log(`Confidence: ${ConfidenceScorer.formatConfidence(item.confidence.score)} (${item.confidence.level})`);
|
|
62
|
+
const response = await prompts({
|
|
63
|
+
type: "select",
|
|
64
|
+
name: "action",
|
|
65
|
+
message: `Translation: "${item.translation}"\nSource: "${item.source}"\nAction:`,
|
|
66
|
+
choices: [
|
|
67
|
+
{ title: "[Approve]", value: "approve" },
|
|
68
|
+
{ title: "[Edit]", value: "edit" },
|
|
69
|
+
{ title: "[Skip]", value: "skip" },
|
|
70
|
+
{ title: "[Exit]", value: "exit" },
|
|
71
|
+
],
|
|
72
|
+
initial: 0,
|
|
73
|
+
});
|
|
74
|
+
if (response.action === "exit")
|
|
70
75
|
break;
|
|
71
|
-
|
|
76
|
+
await this.handleAction(response.action, item);
|
|
72
77
|
}
|
|
73
|
-
this.showSummary();
|
|
74
78
|
this.saveDecisions();
|
|
75
79
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Review a single translation item.
|
|
78
|
-
* @param {ReviewItem} item - Translation item to review.
|
|
79
|
-
* @param {number} current - Current item index (1-based).
|
|
80
|
-
* @param {number} total - Total items.
|
|
81
|
-
* @returns {Promise<string>} - Decision result.
|
|
82
|
-
*/
|
|
83
|
-
async reviewItem(item, current, total) {
|
|
84
|
-
console.log(`\n[${current}/${total}] Translation Review`);
|
|
85
|
-
console.log("ā".repeat(70));
|
|
86
|
-
console.log(`Key: ${item.key}`);
|
|
87
|
-
console.log(`Language: ${item.language}`);
|
|
88
|
-
const score = item.confidence?.score || 0;
|
|
89
|
-
const level = item.confidence?.level || "unknown";
|
|
90
|
-
console.log(`Confidence: ${ConfidenceScorer.formatConfidence(score)} (${level})`);
|
|
91
|
-
console.log(`Category: ${item.category || "general"}`);
|
|
92
|
-
console.log(`\nSource:`);
|
|
93
|
-
console.log(` "${item.source}"`);
|
|
94
|
-
console.log(`\nTranslation:`);
|
|
95
|
-
console.log(` "${item.translation}"`);
|
|
96
|
-
// Show issues if any
|
|
97
|
-
if (item.confidence?.issues && item.confidence.issues.length > 0) {
|
|
98
|
-
console.log(`\nIssues Detected:`);
|
|
99
|
-
item.confidence.issues.forEach((issue) => {
|
|
100
|
-
const icon = issue.severity === "critical" ? "[!]" : "[*]";
|
|
101
|
-
console.log(` ${icon} ${issue.message}`);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
console.log(`\nActions:`);
|
|
105
|
-
console.log(` [A] Accept [E] Edit [R] Reject [S] Skip`);
|
|
106
|
-
console.log(` [N] Next [Q] Quit [?] Help`);
|
|
107
|
-
const action = await this.getUserInput("\nYour choice: ");
|
|
108
|
-
return this.handleAction(action.toLowerCase(), item);
|
|
109
|
-
}
|
|
110
80
|
/**
|
|
111
81
|
* Handle user action input.
|
|
112
|
-
* @param {string} action - User action code.
|
|
113
|
-
* @param {ReviewItem} item - Current item.
|
|
114
|
-
* @returns {Promise<string>} - Result status.
|
|
115
82
|
*/
|
|
116
83
|
async handleAction(action, item) {
|
|
117
84
|
switch (action) {
|
|
118
|
-
case "
|
|
119
|
-
case "accept":
|
|
85
|
+
case "approve":
|
|
120
86
|
this.decisions.accepted.push(item);
|
|
121
|
-
|
|
122
|
-
return "continue";
|
|
87
|
+
break;
|
|
123
88
|
case "edit": {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
89
|
+
const editResponse = await prompts({
|
|
90
|
+
type: "text",
|
|
91
|
+
name: "newTranslation",
|
|
92
|
+
message: "Enter new translation:",
|
|
93
|
+
initial: item.translation,
|
|
94
|
+
});
|
|
95
|
+
if (editResponse.newTranslation) {
|
|
96
|
+
item.translation = editResponse.newTranslation;
|
|
97
|
+
item.edited = true;
|
|
98
|
+
this.decisions.edited.push(item);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.decisions.skipped.push(item);
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
128
104
|
}
|
|
129
|
-
case "r":
|
|
130
|
-
case "reject":
|
|
131
|
-
this.decisions.rejected.push(item);
|
|
132
|
-
console.log("Rejected - will be retranslated");
|
|
133
|
-
return "continue";
|
|
134
|
-
case "s":
|
|
135
105
|
case "skip":
|
|
136
106
|
this.decisions.skipped.push(item);
|
|
137
|
-
|
|
138
|
-
return "continue";
|
|
139
|
-
case "n":
|
|
140
|
-
case "next":
|
|
141
|
-
return "continue";
|
|
142
|
-
case "q":
|
|
143
|
-
case "quit":
|
|
144
|
-
console.log("\nš Exiting review session...");
|
|
145
|
-
return "quit";
|
|
146
|
-
case "?":
|
|
147
|
-
case "help":
|
|
148
|
-
this.showHelp();
|
|
149
|
-
return this.handleAction(await this.getUserInput("\nYour choice: "), item);
|
|
150
|
-
default:
|
|
151
|
-
console.log("Invalid action. Try again.");
|
|
152
|
-
return this.handleAction(await this.getUserInput("\nYour choice: "), item);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Edit translation interactively.
|
|
157
|
-
* @param {ReviewItem} item - Item to edit.
|
|
158
|
-
* @returns {Promise<ReviewItem>} - Edited item.
|
|
159
|
-
*/
|
|
160
|
-
async editTranslation(item) {
|
|
161
|
-
console.log(`\nCurrent: "${item.translation}"`);
|
|
162
|
-
const newTranslation = await this.getUserInput("New translation: ");
|
|
163
|
-
if (newTranslation.trim() === "") {
|
|
164
|
-
console.log("Warning: Empty translation, keeping original");
|
|
165
|
-
return item;
|
|
107
|
+
break;
|
|
166
108
|
}
|
|
167
|
-
// Recalculate confidence for edited translation
|
|
168
|
-
const newConfidence = ConfidenceScorer.calculateConfidence({
|
|
169
|
-
aiConfidence: 1.0, // Manual edit gets high confidence
|
|
170
|
-
sourceText: item.source,
|
|
171
|
-
translation: newTranslation,
|
|
172
|
-
sourceLang: item.sourceLang || this.config.source,
|
|
173
|
-
targetLang: item.language,
|
|
174
|
-
provider: "manual",
|
|
175
|
-
category: item.category,
|
|
176
|
-
});
|
|
177
|
-
return {
|
|
178
|
-
...item,
|
|
179
|
-
translation: newTranslation,
|
|
180
|
-
confidence: newConfidence,
|
|
181
|
-
edited: true,
|
|
182
|
-
editedAt: new Date().toISOString(),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Show help information to the user.
|
|
187
|
-
*/
|
|
188
|
-
showHelp() {
|
|
189
|
-
console.log("\nāāā Help āāā");
|
|
190
|
-
console.log("A (Accept): Approve translation as-is");
|
|
191
|
-
console.log("E (Edit): Modify the translation manually");
|
|
192
|
-
console.log("R (Reject): Mark for retranslation");
|
|
193
|
-
console.log("S (Skip): Skip for now, review later");
|
|
194
|
-
console.log("N (Next): Skip to next item");
|
|
195
|
-
console.log("Q (Quit): Exit review session");
|
|
196
|
-
console.log("? (Help): Show this help message");
|
|
197
|
-
console.log("ā".repeat(70));
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Show summary of review session.
|
|
201
|
-
*/
|
|
202
|
-
showSummary() {
|
|
203
|
-
console.log("\nā".repeat(70));
|
|
204
|
-
console.log("Review Summary");
|
|
205
|
-
console.log("ā".repeat(70));
|
|
206
|
-
console.log(`Accepted: ${this.decisions.accepted.length}`);
|
|
207
|
-
console.log(`Edited: ${this.decisions.edited.length}`);
|
|
208
|
-
console.log(`Rejected: ${this.decisions.rejected.length}`);
|
|
209
|
-
console.log(`Skipped: ${this.decisions.skipped.length}`);
|
|
210
|
-
console.log(`Total: ${this.decisions.accepted.length + this.decisions.edited.length + this.decisions.rejected.length + this.decisions.skipped.length}/${this.reviewQueue.length}`);
|
|
211
|
-
console.log("ā".repeat(70));
|
|
212
109
|
}
|
|
213
110
|
/**
|
|
214
111
|
* Save review decisions to disk and apply them.
|
|
@@ -230,7 +127,7 @@ class ReviewCommand {
|
|
|
230
127
|
},
|
|
231
128
|
};
|
|
232
129
|
fs.writeFileSync(decisionsFile, JSON.stringify(data, null, 2));
|
|
233
|
-
console.log(`\
|
|
130
|
+
console.log(`\nReview complete! Decisions saved to ${decisionsFile}`);
|
|
234
131
|
// Apply accepted and edited translations
|
|
235
132
|
this.applyDecisions();
|
|
236
133
|
}
|
|
@@ -284,23 +181,6 @@ class ReviewCommand {
|
|
|
284
181
|
}
|
|
285
182
|
current[keys[keys.length - 1]] = value;
|
|
286
183
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Get user input from terminal.
|
|
289
|
-
* @param {string} prompt - Prompt to display.
|
|
290
|
-
* @returns {Promise<string>} - User input.
|
|
291
|
-
*/
|
|
292
|
-
getUserInput(prompt) {
|
|
293
|
-
return new Promise((resolve) => {
|
|
294
|
-
const rl = readline.createInterface({
|
|
295
|
-
input: process.stdin,
|
|
296
|
-
output: process.stdout,
|
|
297
|
-
});
|
|
298
|
-
rl.question(prompt, (answer) => {
|
|
299
|
-
rl.close();
|
|
300
|
-
resolve(answer.trim());
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
184
|
/**
|
|
305
185
|
* Export review queue to JSON.
|
|
306
186
|
* @param {string} format - Export format (json or csv).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GlobalStats, TranslationOptions } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Main translator function to process source file and create translations.
|
|
4
4
|
* @param {string} file - Source file path.
|
package/dist/config/index.d.ts
CHANGED
package/dist/config/index.js
CHANGED
|
@@ -50,3 +50,29 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
50
50
|
layers,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
+
export const validateEnvironment = (config) => {
|
|
54
|
+
const providerMap = {
|
|
55
|
+
openai: "OPENAI_API_KEY",
|
|
56
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
57
|
+
gemini: "GEMINI_API_KEY",
|
|
58
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
59
|
+
xai: "XAI_API_KEY",
|
|
60
|
+
dashscope: "DASHSCOPE_API_KEY",
|
|
61
|
+
};
|
|
62
|
+
// If a specific provider is configured, check its key
|
|
63
|
+
if (config?.apiProvider && config.apiProvider !== "auto") {
|
|
64
|
+
const requiredKey = providerMap[config.apiProvider];
|
|
65
|
+
if (requiredKey && !process.env[requiredKey]) {
|
|
66
|
+
console.warn(`\n\u26a0\ufe0f Warning: ${requiredKey} is missing for provider '${config.apiProvider}'`);
|
|
67
|
+
console.warn(` Please add it to your .env file or environment variables.\n`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// If auto or no provider, check if ANY valid key exists
|
|
72
|
+
const hasAnyKey = Object.values(providerMap).some((key) => process.env[key]);
|
|
73
|
+
if (!hasAnyKey) {
|
|
74
|
+
console.warn("\n\u26a0\ufe0f Warning: No supported API keys found in environment.");
|
|
75
|
+
console.warn(" Supported keys: " + Object.values(providerMap).join(", "));
|
|
76
|
+
console.warn(" Please add at least one to your .env file.\n");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load environment variables from .env files.
|
|
3
|
+
* Prioritizes .env.local over .env.
|
|
4
|
+
*/
|
|
5
|
+
export declare const loadEnvironmentVariables: () => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Configure global components (logger, rate limiter, etc.).
|
|
8
|
+
* @param {any} config - Configuration object.
|
|
9
|
+
*/
|
|
10
|
+
export declare const configureComponents: (config: any) => void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { FileManager } from "../utils/file-manager.js";
|
|
5
|
+
import { getLogger } from "../utils/logger.js";
|
|
6
|
+
import rateLimiter from "../utils/rate-limiter.js";
|
|
7
|
+
/**
|
|
8
|
+
* Load environment variables from .env files.
|
|
9
|
+
* Prioritizes .env.local over .env.
|
|
10
|
+
*/
|
|
11
|
+
export const loadEnvironmentVariables = async () => {
|
|
12
|
+
// Load environment files in priority order
|
|
13
|
+
// First load .env (base defaults), then .env.local (local overrides)
|
|
14
|
+
// This ensures .env.local values take precedence over .env values
|
|
15
|
+
const envFiles = [
|
|
16
|
+
{ file: ".env", override: false },
|
|
17
|
+
{ file: ".env.local", override: true },
|
|
18
|
+
];
|
|
19
|
+
let loadedCount = 0;
|
|
20
|
+
for (const { file: envFile, override } of envFiles) {
|
|
21
|
+
const envPath = path.resolve(process.cwd(), envFile);
|
|
22
|
+
try {
|
|
23
|
+
await fs.access(envPath);
|
|
24
|
+
const result = dotenv.config({ path: envPath, override });
|
|
25
|
+
if (!result.error) {
|
|
26
|
+
loadedCount++;
|
|
27
|
+
// Only log in verbose/debug mode to reduce noise
|
|
28
|
+
if (process.env.VERBOSE || process.env.DEBUG) {
|
|
29
|
+
console.log(`Loaded environment variables from ${envFile}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// ENOENT is expected when files don't exist - silently skip
|
|
35
|
+
if (error.code !== "ENOENT") {
|
|
36
|
+
console.warn(`Warning: Could not load ${envFile}: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Informative message only if verbose/debug and at least one file was loaded
|
|
41
|
+
if ((process.env.VERBOSE || process.env.DEBUG) && loadedCount > 0) {
|
|
42
|
+
console.log(`Environment: ${loadedCount} file(s) loaded`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Configure global components (logger, rate limiter, etc.).
|
|
47
|
+
* @param {any} config - Configuration object.
|
|
48
|
+
*/
|
|
49
|
+
export const configureComponents = (config) => {
|
|
50
|
+
// Configure FileManager with config settings
|
|
51
|
+
if (config.fileOperations) {
|
|
52
|
+
FileManager.configure(config.fileOperations);
|
|
53
|
+
}
|
|
54
|
+
// Initialize logger with config
|
|
55
|
+
if (config.logging) {
|
|
56
|
+
const logger = getLogger(config.logging);
|
|
57
|
+
if (config.logging.verbose || config.debug) {
|
|
58
|
+
logger.info("Logger initialized", {
|
|
59
|
+
saveErrorLogs: config.logging.saveErrorLogs,
|
|
60
|
+
logDirectory: config.logging.logDirectory,
|
|
61
|
+
rotationEnabled: config.logging.logRotation?.enabled,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (config.rateLimiter) {
|
|
66
|
+
rateLimiter.updateConfig({
|
|
67
|
+
queueStrategy: config.rateLimiter.queueStrategy || "priority",
|
|
68
|
+
queueTimeout: config.rateLimiter.queueTimeout || 30000,
|
|
69
|
+
adaptiveThrottling: config.rateLimiter.adaptiveThrottling !== false,
|
|
70
|
+
providerLimits: config.rateLimiter.providerLimits,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (config.advanced?.debug) {
|
|
74
|
+
process.env.DEBUG = "true";
|
|
75
|
+
}
|
|
76
|
+
if (config.logging?.verbose) {
|
|
77
|
+
process.env.VERBOSE = "true";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FileFormatAdapter } from "./base-adapter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for Android XML resources files (strings.xml)
|
|
4
|
+
*/
|
|
5
|
+
export declare class AndroidAdapter implements FileFormatAdapter {
|
|
6
|
+
extensions: string[];
|
|
7
|
+
private parser;
|
|
8
|
+
private builder;
|
|
9
|
+
constructor();
|
|
10
|
+
parse(content: string): Promise<Record<string, any>>;
|
|
11
|
+
serialize(data: Record<string, any>): Promise<string>;
|
|
12
|
+
}
|