locai 2.6.2 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -25
- package/dist/cli/helpers.d.ts +34 -2
- package/dist/cli/helpers.js +96 -37
- package/dist/cli.js +40 -39
- package/dist/commands/review.d.ts +33 -8
- package/dist/commands/review.js +89 -22
- package/dist/commands/translator.d.ts +4 -2
- package/dist/commands/translator.js +1 -1
- package/dist/config/index.d.ts +53 -66
- package/dist/config/index.js +2 -1
- package/dist/config/setup.d.ts +3 -2
- package/dist/config/setup.js +5 -3
- package/dist/core/adapters/android-adapter.d.ts +3 -2
- package/dist/core/adapters/android-adapter.js +12 -13
- package/dist/core/adapters/base-adapter.d.ts +13 -2
- package/dist/core/adapters/ios-adapter.d.ts +3 -2
- package/dist/core/adapters/json-adapter.d.ts +4 -3
- package/dist/core/adapters/json-adapter.js +1 -3
- package/dist/core/adapters/po-adapter.d.ts +3 -2
- package/dist/core/adapters/po-adapter.js +4 -5
- package/dist/core/adapters/properties-adapter.d.ts +3 -2
- package/dist/core/adapters/properties-adapter.js +2 -4
- package/dist/core/adapters/yaml-adapter.d.ts +4 -3
- package/dist/core/adapters/yaml-adapter.js +1 -3
- package/dist/core/context-processor.d.ts +21 -6
- package/dist/core/context-processor.js +2 -7
- package/dist/core/detection/framework-detector.js +1 -1
- package/dist/core/detection/structure-detector.js +1 -1
- package/dist/core/fallback-provider.d.ts +3 -4
- package/dist/core/fallback-provider.js +15 -21
- package/dist/core/orchestrator.d.ts +98 -26
- package/dist/core/orchestrator.js +56 -116
- package/dist/core/pipeline/Pipeline.d.ts +2 -2
- package/dist/core/pipeline/Pipeline.js +1 -3
- package/dist/core/pipeline/context.d.ts +69 -13
- package/dist/core/pipeline/context.js +1 -7
- package/dist/core/pipeline/steps/CacheReadStep.d.ts +10 -7
- package/dist/core/pipeline/steps/CacheReadStep.js +1 -12
- package/dist/core/pipeline/steps/CacheWriteStep.d.ts +2 -2
- package/dist/core/pipeline/steps/CacheWriteStep.js +1 -8
- package/dist/core/pipeline/steps/ConfidenceCheckStep.d.ts +5 -0
- package/dist/core/pipeline/steps/ConfidenceCheckStep.js +24 -44
- package/dist/core/pipeline/steps/ContextEnrichmentStep.js +3 -14
- package/dist/core/pipeline/steps/QualityCheckStep.d.ts +1 -1
- package/dist/core/pipeline/steps/QualityCheckStep.js +0 -2
- package/dist/core/pipeline/steps/ToneCheckStep.js +1 -1
- package/dist/core/pipeline/steps/TranslationStep.d.ts +13 -3
- package/dist/core/pipeline/steps/TranslationStep.js +21 -23
- package/dist/core/pipeline/steps/VectorCacheReadStep.d.ts +9 -1
- package/dist/core/pipeline/steps/VectorCacheReadStep.js +3 -5
- package/dist/core/pipeline/steps/VectorCacheWriteStep.d.ts +2 -1
- package/dist/core/pipeline/steps/VectorCacheWriteStep.js +2 -1
- package/dist/core/provider-factory.d.ts +15 -9
- package/dist/core/provider-factory.js +42 -32
- package/dist/providers/anthropic.d.ts +5 -4
- package/dist/providers/anthropic.js +3 -2
- package/dist/providers/api-types.d.ts +74 -0
- package/dist/providers/api-types.js +5 -0
- package/dist/providers/base-provider.d.ts +27 -31
- package/dist/providers/base-provider.js +22 -46
- package/dist/providers/dashscope.js +0 -2
- package/dist/providers/deepseek.js +0 -1
- package/dist/providers/gemini.js +0 -1
- package/dist/providers/openai.d.ts +3 -3
- package/dist/providers/openai.js +0 -1
- package/dist/providers/xai.js +0 -1
- package/dist/services/embedding-provider.js +4 -2
- package/dist/services/language-processor.d.ts +5 -3
- package/dist/services/language-processor.js +38 -43
- package/dist/services/source-analyzer.d.ts +24 -0
- package/dist/services/source-analyzer.js +161 -68
- package/dist/services/translation-service.d.ts +31 -13
- package/dist/services/translation-service.js +116 -71
- package/dist/services/vector-store.d.ts +15 -2
- package/dist/services/vector-store.js +14 -10
- package/dist/types/index.d.ts +31 -33
- package/dist/utils/ai-context-analyzer.d.ts +12 -3
- package/dist/utils/ai-context-analyzer.js +18 -10
- package/dist/utils/confidence-scorer.d.ts +16 -19
- package/dist/utils/confidence-scorer.js +36 -125
- package/dist/utils/error-helper.d.ts +27 -6
- package/dist/utils/error-helper.js +10 -9
- package/dist/utils/file-manager.d.ts +8 -7
- package/dist/utils/file-manager.js +22 -23
- package/dist/utils/glossary-manager.d.ts +22 -3
- package/dist/utils/glossary-manager.js +47 -28
- package/dist/utils/graceful-shutdown.d.ts +7 -1
- package/dist/utils/graceful-shutdown.js +1 -2
- package/dist/utils/input-validator.d.ts +53 -66
- package/dist/utils/input-validator.js +100 -151
- package/dist/utils/locale-paths.d.ts +7 -0
- package/dist/utils/locale-paths.js +16 -0
- package/dist/utils/logger.d.ts +8 -8
- package/dist/utils/logger.js +7 -15
- package/dist/utils/object-transformer.d.ts +20 -5
- package/dist/utils/object-transformer.js +20 -28
- package/dist/utils/progress-tracker.js +2 -3
- package/dist/utils/prompt-templates.d.ts +45 -5
- package/dist/utils/prompt-templates.js +61 -5
- package/dist/utils/quality/base-checker.d.ts +40 -18
- package/dist/utils/quality/base-checker.js +1 -2
- package/dist/utils/quality/code-block-checker.d.ts +13 -5
- package/dist/utils/quality/index.d.ts +2 -1
- package/dist/utils/quality/index.js +8 -27
- package/dist/utils/quality/length-checker.d.ts +35 -5
- package/dist/utils/quality/length-checker.js +2 -3
- package/dist/utils/quality/markdown-checker.d.ts +14 -7
- package/dist/utils/quality/special-characters-checker.d.ts +18 -1
- package/dist/utils/quality/special-characters-checker.js +0 -1
- package/dist/utils/quality/style-guide-checker.d.ts +1 -1
- package/dist/utils/quality/style-guide-checker.js +0 -2
- package/dist/utils/quality/tone-verifier.js +1 -1
- package/dist/utils/rate-limiter.d.ts +17 -1
- package/dist/utils/rate-limiter.js +12 -11
- package/dist/utils/retry-helper.d.ts +21 -3
- package/dist/utils/retry-helper.js +11 -3
- package/dist/utils/review-queue.d.ts +45 -0
- package/dist/utils/review-queue.js +53 -0
- package/dist/utils/state-manager.d.ts +12 -3
- package/dist/utils/state-manager.js +32 -7
- package/dist/utils/statistics-manager.d.ts +11 -1
- package/dist/utils/statistics-manager.js +8 -0
- package/dist/utils/ui-manager.d.ts +2 -1
- package/dist/utils/ui-manager.js +15 -1
- package/package.json +14 -9
- package/dist/commands/interactive-review.d.ts +0 -9
- package/dist/commands/interactive-review.js +0 -109
- package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.d.ts +0 -1
- package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +0 -100
- package/dist/utils/quality/__tests__/tone-verifier.test.d.ts +0 -1
- package/dist/utils/quality/__tests__/tone-verifier.test.js +0 -91
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** A queued translation as the review reads it: the fields of a ReviewQueueItem it uses. */
|
|
2
|
+
export interface ReviewItem {
|
|
2
3
|
key: string;
|
|
3
4
|
language: string;
|
|
4
5
|
source: string;
|
|
@@ -16,10 +17,19 @@ interface ReviewItem {
|
|
|
16
17
|
edited?: boolean;
|
|
17
18
|
editedAt?: string;
|
|
18
19
|
}
|
|
19
|
-
interface
|
|
20
|
+
export interface ReviewDecisions {
|
|
21
|
+
accepted: ReviewItem[];
|
|
22
|
+
edited: ReviewItem[];
|
|
23
|
+
rejected: ReviewItem[];
|
|
24
|
+
skipped: ReviewItem[];
|
|
25
|
+
}
|
|
26
|
+
export interface ReviewCommandConfig {
|
|
20
27
|
localesDir: string;
|
|
21
28
|
source: string;
|
|
22
|
-
|
|
29
|
+
}
|
|
30
|
+
/** Nested locale file content: leaves are translations, branches are key segments. */
|
|
31
|
+
export interface LocaleTree {
|
|
32
|
+
[key: string]: string | LocaleTree;
|
|
23
33
|
}
|
|
24
34
|
declare class ReviewCommand {
|
|
25
35
|
private config;
|
|
@@ -35,6 +45,18 @@ declare class ReviewCommand {
|
|
|
35
45
|
* @returns {boolean} - True if loaded successfully, else false.
|
|
36
46
|
*/
|
|
37
47
|
loadReviewQueue(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The locale file `locai translate` writes `language` to. As in
|
|
50
|
+
* FileManager.findLocaleFiles, a `<localesDir>/<source>/translation.json` source means the
|
|
51
|
+
* nested layout, anything else the flat `<localesDir>/<language>.json` one.
|
|
52
|
+
*/
|
|
53
|
+
private localeFile;
|
|
54
|
+
/**
|
|
55
|
+
* Remove the reviewed items from the review queue: the rejected ones, and the accepted or
|
|
56
|
+
* edited ones whose translation is now in the locale file. Skipped items, items the session
|
|
57
|
+
* did not reach and items applyDecisions did not write stay queued.
|
|
58
|
+
*/
|
|
59
|
+
private pruneReviewQueue;
|
|
38
60
|
/**
|
|
39
61
|
* Start interactive review session.
|
|
40
62
|
*/
|
|
@@ -44,7 +66,7 @@ declare class ReviewCommand {
|
|
|
44
66
|
*/
|
|
45
67
|
private handleAction;
|
|
46
68
|
/**
|
|
47
|
-
* Save review decisions to disk and
|
|
69
|
+
* Save review decisions to disk, apply them and remove the reviewed items from the queue.
|
|
48
70
|
*/
|
|
49
71
|
saveDecisions(): void;
|
|
50
72
|
/**
|
|
@@ -52,12 +74,15 @@ declare class ReviewCommand {
|
|
|
52
74
|
*/
|
|
53
75
|
applyDecisions(): void;
|
|
54
76
|
/**
|
|
55
|
-
* Set nested value in object using dot notation.
|
|
56
|
-
*
|
|
77
|
+
* Set nested value in object using dot notation. Missing parents are created; a parent
|
|
78
|
+
* that already holds a non-object value (e.g. a string translation) is never overwritten,
|
|
79
|
+
* and neither is a leaf that already holds nested keys.
|
|
80
|
+
* @param {LocaleTree} obj - Object to modify.
|
|
57
81
|
* @param {string} path - Dot notation path.
|
|
58
|
-
* @param {
|
|
82
|
+
* @param {string} value - Value to set.
|
|
83
|
+
* @returns {string | null} - Why the value could not be set, or null when it was set.
|
|
59
84
|
*/
|
|
60
|
-
setNestedValue(obj:
|
|
85
|
+
setNestedValue(obj: LocaleTree, path: string, value: string): string | null;
|
|
61
86
|
/**
|
|
62
87
|
* Export review queue to JSON.
|
|
63
88
|
* @param {string} format - Export format (json or csv).
|
package/dist/commands/review.js
CHANGED
|
@@ -2,6 +2,8 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import prompts from "prompts";
|
|
4
4
|
import ConfidenceScorer from "../utils/confidence-scorer.js";
|
|
5
|
+
import { targetLocaleFile } from "../utils/locale-paths.js";
|
|
6
|
+
import { readReviewQueue, removeFromReviewQueue } from "../utils/review-queue.js";
|
|
5
7
|
class ReviewCommand {
|
|
6
8
|
config;
|
|
7
9
|
reviewQueue;
|
|
@@ -25,22 +27,60 @@ class ReviewCommand {
|
|
|
25
27
|
* @returns {boolean} - True if loaded successfully, else false.
|
|
26
28
|
*/
|
|
27
29
|
loadReviewQueue() {
|
|
28
|
-
const reviewFile = path.join(process.cwd(), ".localize-cache", "review-queue.json");
|
|
29
|
-
if (!fs.existsSync(reviewFile)) {
|
|
30
|
-
console.log("\nNo review queue found.");
|
|
31
|
-
console.log(" Run translation with confidence scoring first: localize translate --min-confidence 0.8");
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
30
|
try {
|
|
35
|
-
const
|
|
36
|
-
|
|
31
|
+
const items = readReviewQueue();
|
|
32
|
+
if (!items) {
|
|
33
|
+
console.log("\nNo review queue found.");
|
|
34
|
+
console.log(" Run a translation that saves one first: locai translate --save-review-queue");
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
this.reviewQueue = items;
|
|
37
38
|
return true;
|
|
38
39
|
}
|
|
39
40
|
catch (error) {
|
|
40
|
-
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
console.error(`Error loading review queue: ${message}`);
|
|
41
43
|
return false;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The locale file `locai translate` writes `language` to. As in
|
|
48
|
+
* FileManager.findLocaleFiles, a `<localesDir>/<source>/translation.json` source means the
|
|
49
|
+
* nested layout, anything else the flat `<localesDir>/<language>.json` one.
|
|
50
|
+
*/
|
|
51
|
+
localeFile(language) {
|
|
52
|
+
const { localesDir, source } = this.config;
|
|
53
|
+
const nestedSource = path.join(localesDir, source, "translation.json");
|
|
54
|
+
const sourceFile = fs.existsSync(nestedSource)
|
|
55
|
+
? nestedSource
|
|
56
|
+
: path.join(localesDir, `${source}.json`);
|
|
57
|
+
return targetLocaleFile(sourceFile, language);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove the reviewed items from the review queue: the rejected ones, and the accepted or
|
|
61
|
+
* edited ones whose translation is now in the locale file. Skipped items, items the session
|
|
62
|
+
* did not reach and items applyDecisions did not write stay queued.
|
|
63
|
+
*/
|
|
64
|
+
pruneReviewQueue() {
|
|
65
|
+
const localeTrees = new Map();
|
|
66
|
+
const isWritten = (item) => {
|
|
67
|
+
let tree = localeTrees.get(item.language);
|
|
68
|
+
if (!tree) {
|
|
69
|
+
const localeFile = this.localeFile(item.language);
|
|
70
|
+
tree = fs.existsSync(localeFile)
|
|
71
|
+
? JSON.parse(fs.readFileSync(localeFile, "utf8"))
|
|
72
|
+
: {};
|
|
73
|
+
localeTrees.set(item.language, tree);
|
|
74
|
+
}
|
|
75
|
+
let value = tree;
|
|
76
|
+
for (const segment of item.key.split(".")) {
|
|
77
|
+
value = value !== null && typeof value === "object" ? value[segment] : undefined;
|
|
78
|
+
}
|
|
79
|
+
return value === item.translation;
|
|
80
|
+
};
|
|
81
|
+
const written = [...this.decisions.accepted, ...this.decisions.edited].filter(isWritten);
|
|
82
|
+
removeFromReviewQueue([...written, ...this.decisions.rejected]);
|
|
83
|
+
}
|
|
44
84
|
/**
|
|
45
85
|
* Start interactive review session.
|
|
46
86
|
*/
|
|
@@ -108,7 +148,7 @@ class ReviewCommand {
|
|
|
108
148
|
}
|
|
109
149
|
}
|
|
110
150
|
/**
|
|
111
|
-
* Save review decisions to disk and
|
|
151
|
+
* Save review decisions to disk, apply them and remove the reviewed items from the queue.
|
|
112
152
|
*/
|
|
113
153
|
saveDecisions() {
|
|
114
154
|
const cacheDir = path.join(process.cwd(), ".localize-cache");
|
|
@@ -128,8 +168,9 @@ class ReviewCommand {
|
|
|
128
168
|
};
|
|
129
169
|
fs.writeFileSync(decisionsFile, JSON.stringify(data, null, 2));
|
|
130
170
|
console.log(`\nReview complete! Decisions saved to ${decisionsFile}`);
|
|
131
|
-
// Apply accepted and edited translations
|
|
171
|
+
// Apply accepted and edited translations, then drop the reviewed items from the queue
|
|
132
172
|
this.applyDecisions();
|
|
173
|
+
this.pruneReviewQueue();
|
|
133
174
|
}
|
|
134
175
|
/**
|
|
135
176
|
* Apply accepted and edited translations to locale files.
|
|
@@ -148,38 +189,64 @@ class ReviewCommand {
|
|
|
148
189
|
}
|
|
149
190
|
byLanguage[item.language].push(item);
|
|
150
191
|
});
|
|
192
|
+
let skipped = 0;
|
|
151
193
|
Object.entries(byLanguage).forEach(([lang, items]) => {
|
|
152
|
-
const localeFile =
|
|
153
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
+
const localeFile = this.localeFile(lang);
|
|
154
195
|
let localeData = {};
|
|
155
196
|
if (fs.existsSync(localeFile)) {
|
|
156
197
|
localeData = JSON.parse(fs.readFileSync(localeFile, "utf8"));
|
|
157
198
|
}
|
|
199
|
+
let applied = 0;
|
|
158
200
|
items.forEach((item) => {
|
|
159
|
-
this.setNestedValue(localeData, item.key, item.translation);
|
|
201
|
+
const conflict = this.setNestedValue(localeData, item.key, item.translation);
|
|
202
|
+
if (conflict) {
|
|
203
|
+
skipped++;
|
|
204
|
+
console.warn(` Skipped "${item.key}" for ${lang}: ${conflict}; the existing value was kept`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
applied++;
|
|
208
|
+
}
|
|
160
209
|
});
|
|
210
|
+
if (applied === 0)
|
|
211
|
+
return;
|
|
212
|
+
fs.mkdirSync(path.dirname(localeFile), { recursive: true });
|
|
161
213
|
fs.writeFileSync(localeFile, JSON.stringify(localeData, null, 2) + "\n");
|
|
162
|
-
console.log(` Updated ${
|
|
214
|
+
console.log(` Updated ${path.relative(process.cwd(), localeFile)} (${applied} translations)`);
|
|
163
215
|
});
|
|
164
|
-
console.log(
|
|
216
|
+
console.log(skipped === 0
|
|
217
|
+
? "\nAll approved translations applied!"
|
|
218
|
+
: `\nApplied ${toApply.length - skipped} approved translations; skipped ${skipped} (see warnings above).`);
|
|
165
219
|
}
|
|
166
220
|
/**
|
|
167
|
-
* Set nested value in object using dot notation.
|
|
168
|
-
*
|
|
221
|
+
* Set nested value in object using dot notation. Missing parents are created; a parent
|
|
222
|
+
* that already holds a non-object value (e.g. a string translation) is never overwritten,
|
|
223
|
+
* and neither is a leaf that already holds nested keys.
|
|
224
|
+
* @param {LocaleTree} obj - Object to modify.
|
|
169
225
|
* @param {string} path - Dot notation path.
|
|
170
|
-
* @param {
|
|
226
|
+
* @param {string} value - Value to set.
|
|
227
|
+
* @returns {string | null} - Why the value could not be set, or null when it was set.
|
|
171
228
|
*/
|
|
172
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
173
229
|
setNestedValue(obj, path, value) {
|
|
174
230
|
const keys = path.split(".");
|
|
175
231
|
let current = obj;
|
|
176
232
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
177
|
-
|
|
233
|
+
const next = current[keys[i]];
|
|
234
|
+
if (next === undefined) {
|
|
178
235
|
current[keys[i]] = {};
|
|
179
236
|
}
|
|
237
|
+
else if (next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
238
|
+
const kind = next === null ? "null" : Array.isArray(next) ? "an array" : `a ${typeof next}`;
|
|
239
|
+
return `"${keys.slice(0, i + 1).join(".")}" already holds ${kind}, not nested keys`;
|
|
240
|
+
}
|
|
180
241
|
current = current[keys[i]];
|
|
181
242
|
}
|
|
182
|
-
|
|
243
|
+
const leaf = keys[keys.length - 1];
|
|
244
|
+
const existing = current[leaf];
|
|
245
|
+
if (existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
246
|
+
return `"${path}" already holds nested keys, not a translation`;
|
|
247
|
+
}
|
|
248
|
+
current[leaf] = value;
|
|
249
|
+
return null;
|
|
183
250
|
}
|
|
184
251
|
/**
|
|
185
252
|
* Export review queue to JSON.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { FixSummary } from "../services/translation-service.js";
|
|
1
2
|
import { GlobalStats, TranslationOptions } from "../types/index.js";
|
|
3
|
+
export type { FixSummary };
|
|
2
4
|
/**
|
|
3
5
|
* Main translator function to process source file and create translations.
|
|
4
6
|
* @param {string} file - Source file path.
|
|
@@ -17,7 +19,7 @@ declare function findLocaleFiles(localesDir: string, sourceLang: string): Promis
|
|
|
17
19
|
* Validate and fix existing translations that have length issues.
|
|
18
20
|
* @param {string} file - File path to validate.
|
|
19
21
|
* @param {TranslationOptions} options - Validation options.
|
|
20
|
-
* @returns {Promise<
|
|
22
|
+
* @returns {Promise<FixSummary>} - How many length issues were found and fixed.
|
|
21
23
|
*/
|
|
22
|
-
declare function validateAndFixExistingTranslations(file: string, options: TranslationOptions): Promise<
|
|
24
|
+
declare function validateAndFixExistingTranslations(file: string, options: TranslationOptions): Promise<FixSummary>;
|
|
23
25
|
export { translateFile, findLocaleFiles, validateAndFixExistingTranslations };
|
|
@@ -21,7 +21,7 @@ async function findLocaleFiles(localesDir, sourceLang) {
|
|
|
21
21
|
* Validate and fix existing translations that have length issues.
|
|
22
22
|
* @param {string} file - File path to validate.
|
|
23
23
|
* @param {TranslationOptions} options - Validation options.
|
|
24
|
-
* @returns {Promise<
|
|
24
|
+
* @returns {Promise<FixSummary>} - How many length issues were found and fixed.
|
|
25
25
|
*/
|
|
26
26
|
async function validateAndFixExistingTranslations(file, options) {
|
|
27
27
|
return await translationService.validateAndFixExistingTranslations(file, options);
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import type { OrchestratorOptions, ProviderRetryOptions } from "../core/orchestrator.js";
|
|
2
|
+
import type { ContextConfig } from "../core/context-processor.js";
|
|
3
|
+
import type { ProviderConfig } from "../providers/base-provider.js";
|
|
4
|
+
import type { SyncOptions } from "../types/index.js";
|
|
5
|
+
import type { LoggerConfig } from "../utils/logger.js";
|
|
6
|
+
/** The `context` section as written in a config file; {@link loadConfig} fills in the rest. */
|
|
7
|
+
export interface ContextConfigInput extends Partial<Omit<ContextConfig, "detection" | "fallback">> {
|
|
8
|
+
detection?: Partial<ContextConfig["detection"]>;
|
|
9
|
+
fallback?: Partial<ContextConfig["fallback"]>;
|
|
10
|
+
}
|
|
11
|
+
/** The `fileOperations` configuration section. */
|
|
12
|
+
export interface FileOperationsConfig {
|
|
13
|
+
/**
|
|
14
|
+
* File format to use (json, yaml, po, properties, etc.)
|
|
15
|
+
* @default "json"
|
|
16
|
+
*/
|
|
17
|
+
format?: "json" | "yaml" | "po" | "properties" | "arb" | "auto";
|
|
18
|
+
/**
|
|
19
|
+
* File structure (flat, nested, namespaced)
|
|
20
|
+
* @default "flat"
|
|
21
|
+
*/
|
|
22
|
+
fileStructure?: "flat" | "nested" | "namespaced" | "auto";
|
|
23
|
+
backupFiles?: boolean;
|
|
24
|
+
backupDir?: string;
|
|
25
|
+
atomic?: boolean;
|
|
26
|
+
createMissingDirs?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "context" | "sourceCodeAnalyzer"> {
|
|
4
29
|
/**
|
|
5
30
|
* Source language code (e.g. 'en')
|
|
6
31
|
*/
|
|
@@ -10,8 +35,9 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
|
|
|
10
35
|
*/
|
|
11
36
|
targets?: string[];
|
|
12
37
|
/**
|
|
13
|
-
* Deep Context (Source Code Analysis)
|
|
14
|
-
*
|
|
38
|
+
* Deep Context (Source Code Analysis): find where each key is used in the `.ts`/`.tsx` files
|
|
39
|
+
* under `src/`, `app/` and `components/`, and add the file, component, comments, JSX props and
|
|
40
|
+
* a code snippet of that usage to the translation prompt.
|
|
15
41
|
* @default true
|
|
16
42
|
*/
|
|
17
43
|
deepContext?: boolean;
|
|
@@ -42,16 +68,7 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
|
|
|
42
68
|
/**
|
|
43
69
|
* Synchronization options
|
|
44
70
|
*/
|
|
45
|
-
syncOptions?:
|
|
46
|
-
enabled?: boolean;
|
|
47
|
-
removeDeletedKeys?: boolean;
|
|
48
|
-
retranslateModified?: boolean;
|
|
49
|
-
stateTracking?: {
|
|
50
|
-
enabled?: boolean;
|
|
51
|
-
stateFileName?: string;
|
|
52
|
-
stateDir?: string;
|
|
53
|
-
};
|
|
54
|
-
};
|
|
71
|
+
syncOptions?: SyncOptions;
|
|
55
72
|
/**
|
|
56
73
|
* Concurrency limit for parallel translations
|
|
57
74
|
* @default 5
|
|
@@ -66,22 +83,7 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
|
|
|
66
83
|
* Minimum confidence threshold (0-1)
|
|
67
84
|
*/
|
|
68
85
|
minConfidence?: number;
|
|
69
|
-
fileOperations?:
|
|
70
|
-
/**
|
|
71
|
-
* File format to use (json, yaml, po, properties, etc.)
|
|
72
|
-
* @default "json"
|
|
73
|
-
*/
|
|
74
|
-
format?: "json" | "yaml" | "po" | "properties" | "arb" | "auto";
|
|
75
|
-
/**
|
|
76
|
-
* File structure (flat, nested, namespaced)
|
|
77
|
-
* @default "flat"
|
|
78
|
-
*/
|
|
79
|
-
fileStructure?: "flat" | "nested" | "namespaced" | "auto";
|
|
80
|
-
backupFiles?: boolean;
|
|
81
|
-
backupDir?: string;
|
|
82
|
-
atomic?: boolean;
|
|
83
|
-
createMissingDirs?: boolean;
|
|
84
|
-
};
|
|
86
|
+
fileOperations?: FileOperationsConfig;
|
|
85
87
|
useAi?: boolean;
|
|
86
88
|
contextProvider?: string;
|
|
87
89
|
contextThreshold?: number;
|
|
@@ -101,42 +103,27 @@ export interface LocalizeConfig extends Omit<Partial<OrchestratorOptions>, "cont
|
|
|
101
103
|
force?: boolean;
|
|
102
104
|
verbose?: boolean;
|
|
103
105
|
version?: string;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
queueStrategy?: string;
|
|
107
|
-
queueTimeout?: number;
|
|
108
|
-
adaptiveThrottling?: boolean;
|
|
109
|
-
providerLimits?: any;
|
|
110
|
-
};
|
|
111
|
-
context?: {
|
|
112
|
-
enabled?: boolean;
|
|
113
|
-
useAI?: boolean;
|
|
114
|
-
aiProvider?: string;
|
|
115
|
-
minTextLength?: number;
|
|
116
|
-
allowNewCategories?: boolean;
|
|
117
|
-
debug?: boolean;
|
|
118
|
-
analysisOptions?: {
|
|
119
|
-
model?: string;
|
|
120
|
-
temperature?: number;
|
|
121
|
-
maxTokens?: number;
|
|
122
|
-
cacheAnalysis?: boolean;
|
|
123
|
-
};
|
|
124
|
-
detection?: {
|
|
125
|
-
threshold?: number;
|
|
126
|
-
minConfidence?: number;
|
|
127
|
-
};
|
|
128
|
-
categories?: any;
|
|
129
|
-
fallback?: any;
|
|
130
|
-
};
|
|
131
|
-
qualityChecks?: {
|
|
132
|
-
enabled?: boolean;
|
|
133
|
-
rules?: any;
|
|
134
|
-
autoFix?: boolean;
|
|
135
|
-
};
|
|
106
|
+
logging?: LoggerConfig;
|
|
107
|
+
context?: ContextConfigInput;
|
|
136
108
|
command?: string;
|
|
137
109
|
fixLength?: boolean;
|
|
138
110
|
showDetailedStats?: boolean;
|
|
139
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* The configuration returned by {@link loadConfig}: the user's settings merged over the
|
|
114
|
+
* defaults, so the defaulted settings are always present.
|
|
115
|
+
*/
|
|
116
|
+
export interface ResolvedLocalizeConfig extends LocalizeConfig {
|
|
117
|
+
source: string;
|
|
118
|
+
targets: string[];
|
|
119
|
+
localesDir: string;
|
|
120
|
+
concurrencyLimit: number;
|
|
121
|
+
cacheEnabled: boolean;
|
|
122
|
+
retryOptions: ProviderRetryOptions;
|
|
123
|
+
context: ContextConfig;
|
|
124
|
+
apiConfig: Record<string, ProviderConfig>;
|
|
125
|
+
fileOperations: FileOperationsConfig;
|
|
126
|
+
}
|
|
140
127
|
/**
|
|
141
128
|
* Type-safe configuration helper
|
|
142
129
|
*/
|
|
@@ -145,8 +132,8 @@ export declare function defineConfig(config: LocalizeConfig): LocalizeConfig;
|
|
|
145
132
|
* Load configuration using c12
|
|
146
133
|
*/
|
|
147
134
|
export declare function loadConfig(cwd?: string): Promise<{
|
|
148
|
-
config:
|
|
135
|
+
config: ResolvedLocalizeConfig;
|
|
149
136
|
configFile: string | undefined;
|
|
150
|
-
layers: ConfigLayer<LocalizeConfig, import("c12").ConfigLayerMeta>[] | undefined;
|
|
137
|
+
layers: import("c12").ConfigLayer<LocalizeConfig, import("c12").ConfigLayerMeta>[] | undefined;
|
|
151
138
|
}>;
|
|
152
|
-
export declare const validateEnvironment: (config?:
|
|
139
|
+
export declare const validateEnvironment: (config?: LocalizeConfig) => void;
|
package/dist/config/index.js
CHANGED
|
@@ -45,7 +45,8 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
45
45
|
},
|
|
46
46
|
});
|
|
47
47
|
return {
|
|
48
|
-
|
|
48
|
+
// c12 merges the user's config over `defaults`, so the defaulted settings are always set.
|
|
49
|
+
config: (config || {}),
|
|
49
50
|
configFile,
|
|
50
51
|
layers,
|
|
51
52
|
};
|
package/dist/config/setup.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LocalizeConfig } from "./index.js";
|
|
1
2
|
/**
|
|
2
3
|
* Load environment variables from .env files.
|
|
3
4
|
* Prioritizes .env.local over .env.
|
|
@@ -5,6 +6,6 @@
|
|
|
5
6
|
export declare const loadEnvironmentVariables: () => Promise<void>;
|
|
6
7
|
/**
|
|
7
8
|
* Configure global components (logger, rate limiter, etc.).
|
|
8
|
-
* @param
|
|
9
|
+
* @param config - Configuration object.
|
|
9
10
|
*/
|
|
10
|
-
export declare const configureComponents: (config:
|
|
11
|
+
export declare const configureComponents: (config: LocalizeConfig) => void;
|
package/dist/config/setup.js
CHANGED
|
@@ -32,8 +32,10 @@ export const loadEnvironmentVariables = async () => {
|
|
|
32
32
|
}
|
|
33
33
|
catch (error) {
|
|
34
34
|
// ENOENT is expected when files don't exist - silently skip
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const isMissingFile = error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
36
|
+
if (!isMissingFile) {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
console.warn(`Warning: Could not load ${envFile}: ${message}`);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
}
|
|
@@ -44,7 +46,7 @@ export const loadEnvironmentVariables = async () => {
|
|
|
44
46
|
};
|
|
45
47
|
/**
|
|
46
48
|
* Configure global components (logger, rate limiter, etc.).
|
|
47
|
-
* @param
|
|
49
|
+
* @param config - Configuration object.
|
|
48
50
|
*/
|
|
49
51
|
export const configureComponents = (config) => {
|
|
50
52
|
// Configure FileManager with config settings
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
1
2
|
import { FileFormatAdapter } from "./base-adapter.js";
|
|
2
3
|
/**
|
|
3
4
|
* Adapter for Android XML resources files (strings.xml)
|
|
@@ -7,6 +8,6 @@ export declare class AndroidAdapter implements FileFormatAdapter {
|
|
|
7
8
|
private parser;
|
|
8
9
|
private builder;
|
|
9
10
|
constructor();
|
|
10
|
-
parse(content: string): Promise<
|
|
11
|
-
serialize(data:
|
|
11
|
+
parse(content: string): Promise<TranslationObject>;
|
|
12
|
+
serialize(data: TranslationObject): Promise<string>;
|
|
12
13
|
}
|
|
@@ -51,14 +51,12 @@ export class AndroidAdapter {
|
|
|
51
51
|
return result;
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
|
-
throw new Error(`Failed to parse Android XML: ${error.message}`);
|
|
54
|
+
throw new Error(`Failed to parse Android XML: ${error instanceof Error ? error.message : String(error)}`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
async serialize(data) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
"string-array": [],
|
|
61
|
-
};
|
|
58
|
+
const stringNodes = [];
|
|
59
|
+
const stringArrayNodes = [];
|
|
62
60
|
// Collect keys to handle arrays properly
|
|
63
61
|
const arrays = {};
|
|
64
62
|
const strings = {};
|
|
@@ -76,25 +74,26 @@ export class AndroidAdapter {
|
|
|
76
74
|
}
|
|
77
75
|
// Add strings
|
|
78
76
|
for (const [key, value] of Object.entries(strings)) {
|
|
79
|
-
|
|
77
|
+
stringNodes.push({
|
|
80
78
|
"@_name": key,
|
|
81
79
|
"#text": value,
|
|
82
80
|
});
|
|
83
81
|
}
|
|
84
82
|
// Add arrays
|
|
85
83
|
for (const [key, items] of Object.entries(arrays)) {
|
|
86
|
-
|
|
84
|
+
stringArrayNodes.push({
|
|
87
85
|
"@_name": key,
|
|
88
86
|
item: items.filter((i) => i !== undefined),
|
|
89
87
|
});
|
|
90
88
|
}
|
|
91
89
|
// Sort by name for consistency
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
stringNodes.sort((a, b) => a["@_name"].localeCompare(b["@_name"]));
|
|
91
|
+
stringArrayNodes.sort((a, b) => a["@_name"].localeCompare(b["@_name"]));
|
|
92
|
+
const resources = {};
|
|
93
|
+
if (stringNodes.length > 0)
|
|
94
|
+
resources.string = stringNodes;
|
|
95
|
+
if (stringArrayNodes.length > 0)
|
|
96
|
+
resources["string-array"] = stringArrayNodes;
|
|
98
97
|
const xmlObj = {
|
|
99
98
|
"?xml": {
|
|
100
99
|
"@_version": "1.0",
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options accepted by {@link FileFormatAdapter.serialize}.
|
|
4
|
+
* Adapters ignore options that do not apply to their format.
|
|
5
|
+
*/
|
|
6
|
+
export interface SerializeOptions {
|
|
7
|
+
/** Indentation width (JSON, YAML) */
|
|
8
|
+
indent?: number;
|
|
9
|
+
/** Request compact output where the format supports it */
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
}
|
|
1
12
|
/**
|
|
2
13
|
* Interface for file format adapters
|
|
3
14
|
*/
|
|
@@ -10,11 +21,11 @@ export interface FileFormatAdapter {
|
|
|
10
21
|
* Parse file content into a key-value object
|
|
11
22
|
* @param content - Raw string content
|
|
12
23
|
*/
|
|
13
|
-
parse(content: string): Promise<
|
|
24
|
+
parse(content: string): Promise<TranslationObject>;
|
|
14
25
|
/**
|
|
15
26
|
* Serialize data object into string format
|
|
16
27
|
* @param data - Key-value object
|
|
17
28
|
* @param options - Serialization options
|
|
18
29
|
*/
|
|
19
|
-
serialize(data:
|
|
30
|
+
serialize(data: TranslationObject, options?: SerializeOptions): Promise<string>;
|
|
20
31
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
1
2
|
import { FileFormatAdapter } from "./base-adapter.js";
|
|
2
3
|
/**
|
|
3
4
|
* Adapter for iOS .strings files
|
|
@@ -5,6 +6,6 @@ import { FileFormatAdapter } from "./base-adapter.js";
|
|
|
5
6
|
*/
|
|
6
7
|
export declare class IosAdapter implements FileFormatAdapter {
|
|
7
8
|
extensions: string[];
|
|
8
|
-
parse(content: string): Promise<
|
|
9
|
-
serialize(data:
|
|
9
|
+
parse(content: string): Promise<TranslationObject>;
|
|
10
|
+
serialize(data: TranslationObject): Promise<string>;
|
|
10
11
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
2
|
+
import { FileFormatAdapter, SerializeOptions } from "./base-adapter.js";
|
|
2
3
|
/**
|
|
3
4
|
* Adapter for JSON files
|
|
4
5
|
*/
|
|
5
6
|
export declare class JsonAdapter implements FileFormatAdapter {
|
|
6
7
|
extensions: string[];
|
|
7
|
-
parse(content: string): Promise<
|
|
8
|
-
serialize(data:
|
|
8
|
+
parse(content: string): Promise<TranslationObject>;
|
|
9
|
+
serialize(data: TranslationObject, options?: SerializeOptions): Promise<string>;
|
|
9
10
|
}
|
|
@@ -3,16 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class JsonAdapter {
|
|
5
5
|
extensions = [".json", ".arb"];
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
6
|
async parse(content) {
|
|
8
7
|
try {
|
|
9
8
|
return JSON.parse(content);
|
|
10
9
|
}
|
|
11
10
|
catch (error) {
|
|
12
|
-
throw new Error(`JSON parse error: ${error.message}`);
|
|
11
|
+
throw new Error(`JSON parse error: ${error instanceof Error ? error.message : String(error)}`);
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
14
|
async serialize(data, options = {}) {
|
|
17
15
|
const indent = options.indent || 4;
|
|
18
16
|
return JSON.stringify(data, null, indent);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { TranslationObject } from "../../utils/object-transformer.js";
|
|
1
2
|
import { FileFormatAdapter } from "./base-adapter.js";
|
|
2
3
|
/**
|
|
3
4
|
* Adapter for Gettext PO files
|
|
4
5
|
*/
|
|
5
6
|
export declare class PoAdapter implements FileFormatAdapter {
|
|
6
7
|
extensions: string[];
|
|
7
|
-
parse(content: string): Promise<
|
|
8
|
-
serialize(data:
|
|
8
|
+
parse(content: string): Promise<TranslationObject>;
|
|
9
|
+
serialize(data: TranslationObject): Promise<string>;
|
|
9
10
|
}
|