dialekt 0.1.0 → 0.1.1
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 +8 -10
- package/TESTING.md +29 -29
- package/dist/cli/main.d.mts +1 -1
- package/dist/cli/main.mjs +549 -362
- package/dist/formatters-De4Q-X1d.mjs +516 -435
- package/dist/index.d.mts +162 -25
- package/dist/index.mjs +119 -34
- package/package.json +3 -3
- package/pnpm-workspace.yaml +3 -3
- package/src/adapter/types.test.ts +57 -57
- package/src/adapter/types.ts +7 -4
- package/src/benchmark/metrics.test.ts +141 -69
- package/src/benchmark/metrics.ts +6 -6
- package/src/benchmark/report.test.ts +38 -38
- package/src/benchmark/report.ts +6 -6
- package/src/benchmark/runner.test.ts +70 -72
- package/src/benchmark/runner.ts +4 -4
- package/src/cli/commands/add.test.ts +90 -109
- package/src/cli/commands/add.ts +40 -28
- package/src/cli/commands/benchmark.test.ts +77 -64
- package/src/cli/commands/benchmark.ts +64 -41
- package/src/cli/commands/languages.test.ts +45 -42
- package/src/cli/commands/languages.ts +16 -12
- package/src/cli/commands/missing.test.ts +143 -92
- package/src/cli/commands/missing.ts +24 -17
- package/src/cli/commands/translate.test.ts +79 -79
- package/src/cli/commands/translate.ts +41 -31
- package/src/cli/commands/unused.test.ts +62 -51
- package/src/cli/commands/unused.ts +18 -14
- package/src/cli/commands/validate.test.ts +130 -72
- package/src/cli/commands/validate.ts +25 -20
- package/src/cli/config-resolution.test.ts +169 -49
- package/src/cli/config-resolution.ts +5 -7
- package/src/cli/format.test.ts +50 -50
- package/src/cli/format.ts +57 -60
- package/src/cli/formatters.test.ts +128 -106
- package/src/cli/formatters.ts +72 -95
- package/src/cli/main.ts +13 -13
- package/src/config/define-config.test.ts +44 -29
- package/src/config/define-config.ts +1 -1
- package/src/config/load-config.test.ts +21 -18
- package/src/config/load-config.ts +5 -5
- package/src/config/types.test.ts +50 -44
- package/src/config/types.ts +2 -2
- package/src/index.ts +22 -26
- package/src/keys/flatten.test.ts +52 -52
- package/src/keys/flatten.ts +7 -9
- package/src/sdk/file-io.test.ts +47 -59
- package/src/sdk/file-io.ts +2 -2
- package/src/sdk/node-layer.test.ts +18 -18
- package/src/sdk/node-layer.ts +2 -2
- package/src/sdk/php-array-reader.test.ts +49 -40
- package/src/sdk/php-array-reader.ts +5 -5
- package/src/translation/chunking.test.ts +52 -44
- package/src/translation/chunking.ts +1 -1
- package/src/translation/missing-keys.test.ts +86 -93
- package/src/translation/missing-keys.ts +4 -6
- package/src/translation/model-registry.test.ts +41 -32
- package/src/translation/model-registry.ts +9 -9
- package/src/translation/one-shot-strategy.test.ts +105 -86
- package/src/translation/one-shot-strategy.ts +10 -12
- package/src/translation/orchestrator.test.ts +90 -101
- package/src/translation/orchestrator.ts +26 -26
- package/src/translation/prompt.test.ts +76 -76
- package/src/translation/prompt.ts +2 -2
- package/src/translation/tool-loop-strategy.test.ts +134 -107
- package/src/translation/tool-loop-strategy.ts +14 -18
- package/src/translation/types.test.ts +22 -22
- package/src/translation/types.ts +3 -3
- package/tsdown.config.ts +3 -3
- package/vitest.config.ts +3 -3
|
@@ -5,30 +5,31 @@ import { createJiti } from "jiti";
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
//#region src/keys/flatten.ts
|
|
7
7
|
function flattenObject(input, prefix = "") {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
const output = {};
|
|
9
|
+
for (const [key, value] of Object.entries(input)) {
|
|
10
|
+
const fullKey = prefix === "" ? key : `${prefix}.${key}`;
|
|
11
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value))
|
|
12
|
+
Object.assign(output, flattenObject(value, fullKey));
|
|
13
|
+
else if (typeof value === "string") output[fullKey] = value;
|
|
14
|
+
}
|
|
15
|
+
return output;
|
|
15
16
|
}
|
|
16
17
|
function unflattenObject(input) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
const output = {};
|
|
19
|
+
for (const [dottedKey, value] of Object.entries(input)) {
|
|
20
|
+
const segments = dottedKey.split(".");
|
|
21
|
+
let cursor = output;
|
|
22
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
23
|
+
const segment = segments[i];
|
|
24
|
+
if (typeof cursor[segment] !== "object" || cursor[segment] === null) cursor[segment] = {};
|
|
25
|
+
cursor = cursor[segment];
|
|
26
|
+
}
|
|
27
|
+
cursor[segments[segments.length - 1]] = value;
|
|
28
|
+
}
|
|
29
|
+
return output;
|
|
29
30
|
}
|
|
30
31
|
function diffKeys(source, target) {
|
|
31
|
-
|
|
32
|
+
return Object.keys(source).filter((key) => !(key in target));
|
|
32
33
|
}
|
|
33
34
|
//#endregion
|
|
34
35
|
//#region src/translation/chunking.ts
|
|
@@ -36,60 +37,64 @@ const PROMPT_OVERHEAD = 600;
|
|
|
36
37
|
const ITEM_JSON_OVERHEAD = 20;
|
|
37
38
|
const MIN_EFFECTIVE_MAX_CHARS = 200;
|
|
38
39
|
function chunkKeys(keys, sourceMap, targetMap, config) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
40
|
+
const maxChars = config.maxTokens * config.charsPerToken;
|
|
41
|
+
const sourceJson = JSON.stringify(sourceMap);
|
|
42
|
+
const targetJson = JSON.stringify(targetMap);
|
|
43
|
+
const contextOverhead = (sourceJson?.length ?? 0) + (targetJson?.length ?? 0) + PROMPT_OVERHEAD;
|
|
44
|
+
const effectiveMaxChars = Math.max(MIN_EFFECTIVE_MAX_CHARS, maxChars - contextOverhead);
|
|
45
|
+
const chunks = [];
|
|
46
|
+
let currentChunk = [];
|
|
47
|
+
let currentChars = 0;
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const value = sourceMap[key] ?? "";
|
|
50
|
+
const itemChars = key.length + value.length + ITEM_JSON_OVERHEAD;
|
|
51
|
+
if (itemChars > effectiveMaxChars && currentChunk.length === 0) {
|
|
52
|
+
chunks.push([key]);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (currentChunk.length > 0 && currentChars + itemChars > effectiveMaxChars) {
|
|
56
|
+
chunks.push(currentChunk);
|
|
57
|
+
currentChunk = [];
|
|
58
|
+
currentChars = 0;
|
|
59
|
+
}
|
|
60
|
+
currentChunk.push(key);
|
|
61
|
+
currentChars += itemChars;
|
|
62
|
+
}
|
|
63
|
+
if (currentChunk.length > 0) chunks.push(currentChunk);
|
|
64
|
+
if (chunks.length === 0 && keys.length > 0) return keys.map((key) => [key]);
|
|
65
|
+
return chunks;
|
|
65
66
|
}
|
|
66
67
|
//#endregion
|
|
67
68
|
//#region src/translation/model-registry.ts
|
|
68
69
|
var UnknownProviderError = class extends Data.TaggedError("UnknownProviderError") {};
|
|
69
70
|
/**
|
|
70
|
-
* The one file in the entire codebase allowed to import AI SDK provider packages.
|
|
71
|
-
*/
|
|
71
|
+
* The one file in the entire codebase allowed to import AI SDK provider packages.
|
|
72
|
+
*/
|
|
72
73
|
function resolveModel(config) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
return Effect.tryPromise({
|
|
75
|
+
try: async () => {
|
|
76
|
+
switch (config.provider) {
|
|
77
|
+
case "openai": {
|
|
78
|
+
const { openai } = await import("@ai-sdk/openai");
|
|
79
|
+
return openai(config.modelId);
|
|
80
|
+
}
|
|
81
|
+
case "anthropic": {
|
|
82
|
+
const { anthropic } = await import("@ai-sdk/anthropic");
|
|
83
|
+
return anthropic(config.modelId);
|
|
84
|
+
}
|
|
85
|
+
case "google": {
|
|
86
|
+
const { google } = await import("@ai-sdk/google");
|
|
87
|
+
return google(config.modelId);
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
throw new UnknownProviderError({ provider: config.provider });
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
catch: (cause) =>
|
|
94
|
+
cause instanceof UnknownProviderError
|
|
95
|
+
? cause
|
|
96
|
+
: new UnknownProviderError({ provider: config.provider }),
|
|
97
|
+
});
|
|
93
98
|
}
|
|
94
99
|
//#endregion
|
|
95
100
|
//#region src/translation/types.ts
|
|
@@ -97,7 +102,7 @@ var TranslationFailedError = class extends Data.TaggedError("TranslationFailedEr
|
|
|
97
102
|
//#endregion
|
|
98
103
|
//#region src/translation/prompt.ts
|
|
99
104
|
function buildSystemPrompt(from, to) {
|
|
100
|
-
|
|
105
|
+
return `You are a professional software translator specializing in application localization.
|
|
101
106
|
You translate language strings from ${from} to ${to}.
|
|
102
107
|
|
|
103
108
|
Rules:
|
|
@@ -110,12 +115,12 @@ Rules:
|
|
|
110
115
|
- Escape double quotes in translations with a backslash when needed.`;
|
|
111
116
|
}
|
|
112
117
|
function buildUserPrompt(ctx) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
const sourceJson = JSON.stringify(ctx.sourceMap, null, 2);
|
|
119
|
+
const targetJson = JSON.stringify(ctx.targetMap, null, 2);
|
|
120
|
+
const keysWithValues = {};
|
|
121
|
+
for (const key of ctx.keys) keysWithValues[key] = ctx.sourceMap[key] ?? "";
|
|
122
|
+
const keysJson = JSON.stringify(keysWithValues, null, 2);
|
|
123
|
+
return `Translate the following language strings from ${ctx.sourceLocale} to ${ctx.targetLocale}.
|
|
119
124
|
|
|
120
125
|
<source-file>
|
|
121
126
|
${sourceJson}
|
|
@@ -134,444 +139,520 @@ Translate ALL keys listed in <keys-to-translate>. Use the existing translations
|
|
|
134
139
|
//#endregion
|
|
135
140
|
//#region src/translation/one-shot-strategy.ts
|
|
136
141
|
async function tryTranslateChunk$1(model, ctx) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
const schema = z.object(Object.fromEntries(ctx.keys.map((key) => [key, z.string()])));
|
|
143
|
+
const { output } = await generateText({
|
|
144
|
+
model,
|
|
145
|
+
system: buildSystemPrompt(ctx.sourceLocale, ctx.targetLocale),
|
|
146
|
+
prompt: buildUserPrompt(ctx),
|
|
147
|
+
output: Output.object({ schema }),
|
|
148
|
+
});
|
|
149
|
+
const missing = ctx.keys.filter((key) => !(key in output));
|
|
150
|
+
if (missing.length > 0) throw new Error(`Model omitted keys: ${missing.join(", ")}`);
|
|
151
|
+
return output;
|
|
147
152
|
}
|
|
148
153
|
function createOneShotStrategy(deps) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
return {
|
|
155
|
+
name: "one-shot",
|
|
156
|
+
translateChunk: (ctx) =>
|
|
157
|
+
Effect.tryPromise({
|
|
158
|
+
try: () => tryTranslateChunk$1(deps.model, ctx),
|
|
159
|
+
catch: (cause) => cause,
|
|
160
|
+
}).pipe(
|
|
161
|
+
Effect.retry(
|
|
162
|
+
Schedule.exponential(`${deps.retry.baseDelayMs} millis`).pipe(
|
|
163
|
+
Schedule.compose(Schedule.recurs(deps.retry.maxAttempts - 1)),
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
Effect.mapError(
|
|
167
|
+
(cause) =>
|
|
168
|
+
new TranslationFailedError({
|
|
169
|
+
keys: ctx.keys,
|
|
170
|
+
cause,
|
|
171
|
+
}),
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
};
|
|
159
175
|
}
|
|
160
176
|
//#endregion
|
|
161
177
|
//#region src/translation/tool-loop-strategy.ts
|
|
162
178
|
async function tryTranslateChunk(model, ctx) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
const schema = z.object(Object.fromEntries(ctx.keys.map((key) => [key, z.string()])));
|
|
180
|
+
let captured = null;
|
|
181
|
+
const submitTranslations = tool({
|
|
182
|
+
description:
|
|
183
|
+
"Submit the final translations for every requested key. Call this exactly once, with every key filled in.",
|
|
184
|
+
inputSchema: schema,
|
|
185
|
+
execute: async (input) => {
|
|
186
|
+
captured = input;
|
|
187
|
+
return { ok: true };
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
await new ToolLoopAgent({
|
|
191
|
+
model,
|
|
192
|
+
instructions: buildSystemPrompt(ctx.sourceLocale, ctx.targetLocale),
|
|
193
|
+
tools: { submitTranslations },
|
|
194
|
+
stopWhen: hasToolCall("submitTranslations"),
|
|
195
|
+
}).generate({ prompt: buildUserPrompt(ctx) });
|
|
196
|
+
if (captured === null) throw new Error("Agent finished without calling submitTranslations");
|
|
197
|
+
const missing = ctx.keys.filter((key) => !(key in captured));
|
|
198
|
+
if (missing.length > 0) throw new Error(`Model omitted keys: ${missing.join(", ")}`);
|
|
199
|
+
return captured;
|
|
183
200
|
}
|
|
184
201
|
function createToolLoopStrategy(deps) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
return {
|
|
203
|
+
name: "tool-loop-agent",
|
|
204
|
+
translateChunk: (ctx) =>
|
|
205
|
+
Effect.tryPromise({
|
|
206
|
+
try: () => tryTranslateChunk(deps.model, ctx),
|
|
207
|
+
catch: (cause) => cause,
|
|
208
|
+
}).pipe(
|
|
209
|
+
Effect.retry(
|
|
210
|
+
Schedule.exponential(`${deps.retry.baseDelayMs} millis`).pipe(
|
|
211
|
+
Schedule.compose(Schedule.recurs(deps.retry.maxAttempts - 1)),
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
Effect.mapError(
|
|
215
|
+
(cause) =>
|
|
216
|
+
new TranslationFailedError({
|
|
217
|
+
keys: ctx.keys,
|
|
218
|
+
cause,
|
|
219
|
+
}),
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
};
|
|
195
223
|
}
|
|
196
224
|
//#endregion
|
|
197
225
|
//#region src/translation/orchestrator.ts
|
|
198
226
|
function runTranslation(config) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
227
|
+
return Effect.gen(function* () {
|
|
228
|
+
const failures = [];
|
|
229
|
+
for (const adapter of config.adapters) {
|
|
230
|
+
const locales =
|
|
231
|
+
config.targetLocales.length > 0 ? config.targetLocales : yield* adapter.listLocales();
|
|
232
|
+
const sourceLocale = config.sourceLocale;
|
|
233
|
+
const targetLocales = locales.filter((l) => l !== sourceLocale);
|
|
234
|
+
for (const locale of targetLocales) {
|
|
235
|
+
const resources = yield* adapter.listResources(sourceLocale);
|
|
236
|
+
for (const resource of resources) {
|
|
237
|
+
const sourceMap = yield* adapter.readResource(sourceLocale, resource);
|
|
238
|
+
const targetMap = yield* adapter.readResource(locale, resource);
|
|
239
|
+
const missing = diffKeys(sourceMap, targetMap);
|
|
240
|
+
if (missing.length === 0) continue;
|
|
241
|
+
const chunks = chunkKeys(missing, sourceMap, targetMap, {
|
|
242
|
+
maxTokens: config.chunking.maxTokens,
|
|
243
|
+
charsPerToken: config.chunking.charsPerToken,
|
|
244
|
+
});
|
|
245
|
+
const translatedChunks = [];
|
|
246
|
+
yield* Effect.forEach(
|
|
247
|
+
chunks,
|
|
248
|
+
(chunkKeysArr) =>
|
|
249
|
+
Effect.gen(function* () {
|
|
250
|
+
const result = yield* config.strategy.translateChunk({
|
|
251
|
+
sourceLocale,
|
|
252
|
+
targetLocale: locale,
|
|
253
|
+
sourceMap,
|
|
254
|
+
targetMap,
|
|
255
|
+
keys: chunkKeysArr,
|
|
256
|
+
});
|
|
257
|
+
translatedChunks.push(result);
|
|
258
|
+
}).pipe(
|
|
259
|
+
Effect.catchAll((err) => {
|
|
260
|
+
failures.push(err);
|
|
261
|
+
return Effect.void;
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
{
|
|
265
|
+
concurrency: config.chunking.concurrency,
|
|
266
|
+
discard: true,
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
const merged = { ...targetMap };
|
|
270
|
+
for (const chunk of translatedChunks) Object.assign(merged, chunk);
|
|
271
|
+
yield* adapter.writeResource(locale, resource, merged);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (failures.length > 0)
|
|
276
|
+
return yield* Effect.fail(
|
|
277
|
+
new TranslationFailedError({
|
|
278
|
+
keys: failures.flatMap((f) => [...f.keys]),
|
|
279
|
+
cause: failures.map((f) => f.cause),
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
});
|
|
244
283
|
}
|
|
245
284
|
//#endregion
|
|
246
285
|
//#region src/translation/missing-keys.ts
|
|
247
286
|
function computeMissingKeys(adapter, sourceLocale, targetLocales) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
287
|
+
return Effect.gen(function* () {
|
|
288
|
+
const resources = yield* adapter.listResources(sourceLocale);
|
|
289
|
+
return (yield* Effect.forEach(resources, (resource) =>
|
|
290
|
+
Effect.gen(function* () {
|
|
291
|
+
const sourceMap = yield* adapter.readResource(sourceLocale, resource);
|
|
292
|
+
return (yield* Effect.forEach(targetLocales, (locale) =>
|
|
293
|
+
Effect.gen(function* () {
|
|
294
|
+
const missing = diffKeys(sourceMap, yield* adapter.readResource(locale, resource));
|
|
295
|
+
return missing.length > 0
|
|
296
|
+
? [
|
|
297
|
+
{
|
|
298
|
+
adapter: adapter.name,
|
|
299
|
+
locale,
|
|
300
|
+
resource,
|
|
301
|
+
missing,
|
|
302
|
+
},
|
|
303
|
+
]
|
|
304
|
+
: [];
|
|
305
|
+
}),
|
|
306
|
+
)).flat();
|
|
307
|
+
}),
|
|
308
|
+
)).flat();
|
|
309
|
+
});
|
|
263
310
|
}
|
|
264
311
|
//#endregion
|
|
265
312
|
//#region src/config/load-config.ts
|
|
266
313
|
var ConfigLoadError = class extends Data.TaggedError("ConfigLoadError") {};
|
|
267
314
|
function loadConfig(configPath) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
315
|
+
return Effect.tryPromise({
|
|
316
|
+
try: async () => {
|
|
317
|
+
const jiti = createJiti(process.cwd());
|
|
318
|
+
const absolutePath = resolve(configPath);
|
|
319
|
+
return await jiti.import(absolutePath, { default: true });
|
|
320
|
+
},
|
|
321
|
+
catch: (cause) =>
|
|
322
|
+
new ConfigLoadError({
|
|
323
|
+
path: configPath,
|
|
324
|
+
cause,
|
|
325
|
+
}),
|
|
326
|
+
});
|
|
279
327
|
}
|
|
280
328
|
//#endregion
|
|
281
329
|
//#region src/cli/format.ts
|
|
282
330
|
/**
|
|
283
|
-
* Environment variables that signal dialekt is running inside an AI agent.
|
|
284
|
-
* When any is set (truthy), or stdout is not a TTY, JSON mode is the default.
|
|
285
|
-
*/
|
|
331
|
+
* Environment variables that signal dialekt is running inside an AI agent.
|
|
332
|
+
* When any is set (truthy), or stdout is not a TTY, JSON mode is the default.
|
|
333
|
+
*/
|
|
286
334
|
const AGENT_ENV_VARS = [
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
335
|
+
"CLAUDE_CODE",
|
|
336
|
+
"CLAUDECODE",
|
|
337
|
+
"CURSOR",
|
|
338
|
+
"CURSOR_TRACE_ID",
|
|
339
|
+
"DEVIN",
|
|
340
|
+
"GEMINI_CLI",
|
|
341
|
+
"AGENT_TASK_ID",
|
|
342
|
+
"AIDER_CHAT",
|
|
295
343
|
];
|
|
296
344
|
/**
|
|
297
|
-
* Resolves the output format from explicit flag and environment.
|
|
298
|
-
* Precedence: explicit `--format` > auto-detection.
|
|
299
|
-
*
|
|
300
|
-
* Auto-detection picks `json` when stdout is not a TTY or an agent env var
|
|
301
|
-
* is present; otherwise `pretty`.
|
|
302
|
-
*/
|
|
345
|
+
* Resolves the output format from explicit flag and environment.
|
|
346
|
+
* Precedence: explicit `--format` > auto-detection.
|
|
347
|
+
*
|
|
348
|
+
* Auto-detection picks `json` when stdout is not a TTY or an agent env var
|
|
349
|
+
* is present; otherwise `pretty`.
|
|
350
|
+
*/
|
|
303
351
|
function detectFormat(explicit) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
352
|
+
if (explicit !== void 0) return explicit;
|
|
353
|
+
if (!process.stdout.isTTY) return "json";
|
|
354
|
+
if (AGENT_ENV_VARS.some((k) => process.env[k])) return "json";
|
|
355
|
+
return "pretty";
|
|
308
356
|
}
|
|
309
357
|
const C$1 = {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
358
|
+
reset: "\x1B[0m",
|
|
359
|
+
bold: "\x1B[1m",
|
|
360
|
+
dim: "\x1B[2m",
|
|
361
|
+
red: "\x1B[31m",
|
|
362
|
+
green: "\x1B[32m",
|
|
363
|
+
yellow: "\x1B[33m",
|
|
364
|
+
blue: "\x1B[34m",
|
|
365
|
+
cyan: "\x1B[36m",
|
|
366
|
+
white: "\x1B[37m",
|
|
319
367
|
};
|
|
320
368
|
function isTty() {
|
|
321
|
-
|
|
369
|
+
return process.stdout.isTTY === true;
|
|
322
370
|
}
|
|
323
371
|
/** Wraps text in ANSI codes only when stdout is a TTY; otherwise returns it bare. */
|
|
324
372
|
function color(text, ...codes) {
|
|
325
|
-
|
|
326
|
-
|
|
373
|
+
if (!isTty()) return text;
|
|
374
|
+
return `${codes.join("")}${text}${C$1.reset}`;
|
|
327
375
|
}
|
|
328
376
|
const PRETTY_GLYPHS = {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
377
|
+
hLine: String.fromCharCode(9472),
|
|
378
|
+
vLine: String.fromCharCode(9474),
|
|
379
|
+
cornerTL: String.fromCharCode(9484),
|
|
380
|
+
cornerTR: String.fromCharCode(9488),
|
|
381
|
+
cornerBL: String.fromCharCode(9492),
|
|
382
|
+
cornerBR: String.fromCharCode(9496),
|
|
383
|
+
teeRight: String.fromCharCode(9500),
|
|
384
|
+
teeLeft: String.fromCharCode(9508),
|
|
385
|
+
teeDown: String.fromCharCode(9516),
|
|
386
|
+
teeUp: String.fromCharCode(9524),
|
|
387
|
+
cross: String.fromCharCode(9532),
|
|
388
|
+
bullet: String.fromCharCode(8226),
|
|
389
|
+
arrow: String.fromCharCode(8594),
|
|
390
|
+
check: String.fromCharCode(10003),
|
|
391
|
+
crossMark: String.fromCharCode(10007),
|
|
392
|
+
warn: String.fromCharCode(9888),
|
|
345
393
|
};
|
|
346
394
|
const ASCII_GLYPHS = {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
395
|
+
hLine: "-",
|
|
396
|
+
vLine: "|",
|
|
397
|
+
cornerTL: "+",
|
|
398
|
+
cornerTR: "+",
|
|
399
|
+
cornerBL: "+",
|
|
400
|
+
cornerBR: "+",
|
|
401
|
+
teeRight: "+",
|
|
402
|
+
teeLeft: "+",
|
|
403
|
+
teeDown: "+",
|
|
404
|
+
teeUp: "+",
|
|
405
|
+
cross: "+",
|
|
406
|
+
bullet: "*",
|
|
407
|
+
arrow: ">",
|
|
408
|
+
check: "+",
|
|
409
|
+
crossMark: "x",
|
|
410
|
+
warn: "!",
|
|
363
411
|
};
|
|
364
412
|
function glyphs() {
|
|
365
|
-
|
|
413
|
+
return isTty() ? PRETTY_GLYPHS : ASCII_GLYPHS;
|
|
366
414
|
}
|
|
367
415
|
function drawTable(headers, rows) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
416
|
+
const g = glyphs();
|
|
417
|
+
const colWidths = headers.map((h, i) =>
|
|
418
|
+
Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)),
|
|
419
|
+
);
|
|
420
|
+
const pad = (text, width) => text.padEnd(width);
|
|
421
|
+
const hLine =
|
|
422
|
+
g.cornerTL + colWidths.map((w) => g.hLine.repeat(w + 2)).join(g.teeDown) + g.cornerTR;
|
|
423
|
+
const headerRow =
|
|
424
|
+
g.vLine +
|
|
425
|
+
headers.map((h, i) => ` ${color(pad(h, colWidths[i]), C$1.bold)} `).join(g.vLine) +
|
|
426
|
+
g.vLine;
|
|
427
|
+
const separator =
|
|
428
|
+
g.teeRight + colWidths.map((w) => g.hLine.repeat(w + 2)).join(g.cross) + g.teeLeft;
|
|
429
|
+
const dataRows = rows.map(
|
|
430
|
+
(row) => g.vLine + row.map((cell, i) => ` ${pad(cell, colWidths[i])} `).join(g.vLine) + g.vLine,
|
|
431
|
+
);
|
|
432
|
+
const bottomLine =
|
|
433
|
+
g.cornerBL + colWidths.map((w) => g.hLine.repeat(w + 2)).join(g.teeUp) + g.cornerBR;
|
|
434
|
+
return [hLine, headerRow, separator, ...dataRows, bottomLine].join("\n");
|
|
383
435
|
}
|
|
384
436
|
function banner(title) {
|
|
385
|
-
|
|
386
|
-
|
|
437
|
+
const line = glyphs().hLine.repeat(Math.max(title.length + 4, 40));
|
|
438
|
+
return `${color(line, C$1.dim)}\n ${color(title, C$1.bold + C$1.cyan)}\n${color(line, C$1.dim)}`;
|
|
387
439
|
}
|
|
388
440
|
function sectionHeader(label) {
|
|
389
|
-
|
|
441
|
+
return `\n${color(`${glyphs().arrow} ${label}`, C$1.bold + C$1.cyan)}`;
|
|
390
442
|
}
|
|
391
443
|
function success(text) {
|
|
392
|
-
|
|
444
|
+
return `${color(`${glyphs().check} ${text}`, C$1.green)}`;
|
|
393
445
|
}
|
|
394
446
|
function failure(text) {
|
|
395
|
-
|
|
447
|
+
return `${color(`${glyphs().crossMark} ${text}`, C$1.red)}`;
|
|
396
448
|
}
|
|
397
449
|
function warning(text) {
|
|
398
|
-
|
|
450
|
+
return `${color(`${glyphs().warn} ${text}`, C$1.yellow)}`;
|
|
399
451
|
}
|
|
400
452
|
function info(text) {
|
|
401
|
-
|
|
453
|
+
return color(text, C$1.dim);
|
|
402
454
|
}
|
|
403
455
|
function keyValue(key, value) {
|
|
404
|
-
|
|
456
|
+
return ` ${color(key, C$1.bold)} ${value}`;
|
|
405
457
|
}
|
|
406
458
|
//#endregion
|
|
407
459
|
//#region src/cli/formatters.ts
|
|
408
460
|
/**
|
|
409
|
-
* Command-specific formatters for the dialekt CLI output.
|
|
410
|
-
*
|
|
411
|
-
* Imports the core utilities from `format.ts` and builds structured
|
|
412
|
-
* pretty / JSON renderers for each dialekt command.
|
|
413
|
-
*/
|
|
461
|
+
* Command-specific formatters for the dialekt CLI output.
|
|
462
|
+
*
|
|
463
|
+
* Imports the core utilities from `format.ts` and builds structured
|
|
464
|
+
* pretty / JSON renderers for each dialekt command.
|
|
465
|
+
*/
|
|
414
466
|
const C = {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
467
|
+
reset: "\x1B[0m",
|
|
468
|
+
bold: "\x1B[1m",
|
|
469
|
+
dim: "\x1B[2m",
|
|
470
|
+
red: "\x1B[31m",
|
|
471
|
+
green: "\x1B[32m",
|
|
472
|
+
yellow: "\x1B[33m",
|
|
473
|
+
blue: "\x1B[34m",
|
|
474
|
+
cyan: "\x1B[36m",
|
|
423
475
|
};
|
|
424
476
|
function formatMissingKeys(entries, format) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
477
|
+
if (format === "json") return JSON.stringify(entries, null, 2) + "\n";
|
|
478
|
+
if (entries.length === 0)
|
|
479
|
+
return success("All translations are complete. No missing keys.") + "\n";
|
|
480
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
481
|
+
for (const e of entries) {
|
|
482
|
+
const byAdapter = grouped.get(e.adapter) ?? /* @__PURE__ */ new Map();
|
|
483
|
+
const byLocale = byAdapter.get(e.locale) ?? /* @__PURE__ */ new Map();
|
|
484
|
+
const keys = byLocale.get(e.resource) ?? [];
|
|
485
|
+
keys.push(e.key);
|
|
486
|
+
byLocale.set(e.resource, keys);
|
|
487
|
+
byAdapter.set(e.locale, byLocale);
|
|
488
|
+
grouped.set(e.adapter, byAdapter);
|
|
489
|
+
}
|
|
490
|
+
const lines = [];
|
|
491
|
+
const g = glyphs();
|
|
492
|
+
const total = entries.length;
|
|
493
|
+
lines.push(sectionHeader(`Missing keys (${total})`));
|
|
494
|
+
for (const [adapter, byLocale] of grouped) {
|
|
495
|
+
let adapterTotal = 0;
|
|
496
|
+
for (const byResource of byLocale.values())
|
|
497
|
+
for (const keys of byResource.values()) adapterTotal += keys.length;
|
|
498
|
+
lines.push(`\n ${color(adapter, C.bold + C.blue)} ${color(`(${adapterTotal})`, C.dim)}`);
|
|
499
|
+
for (const [locale, byResource] of byLocale) {
|
|
500
|
+
let localeTotal = 0;
|
|
501
|
+
for (const keys of byResource.values()) localeTotal += keys.length;
|
|
502
|
+
lines.push(
|
|
503
|
+
` ${color(`${g.arrow} ${locale}`, C.yellow)} ${color(`(${localeTotal})`, C.dim)}`,
|
|
504
|
+
);
|
|
505
|
+
for (const [resource, keys] of byResource) {
|
|
506
|
+
lines.push(` ${color(resource, C.bold)}`);
|
|
507
|
+
for (const key of keys) lines.push(` ${color(g.bullet, C.dim)} ${key}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return lines.join("\n") + "\n";
|
|
456
512
|
}
|
|
457
513
|
function formatUnusedKeys(entries, format) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
514
|
+
if (format === "json") return JSON.stringify(entries, null, 2) + "\n";
|
|
515
|
+
if (entries.length === 0)
|
|
516
|
+
return success("All keys are referenced in source files. No unused keys.") + "\n";
|
|
517
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const e of entries) {
|
|
519
|
+
const byAdapter = grouped.get(e.adapter) ?? /* @__PURE__ */ new Map();
|
|
520
|
+
const byLocale = byAdapter.get(e.locale) ?? /* @__PURE__ */ new Map();
|
|
521
|
+
const keys = byLocale.get(e.resource) ?? [];
|
|
522
|
+
keys.push(e.key);
|
|
523
|
+
byLocale.set(e.resource, keys);
|
|
524
|
+
byAdapter.set(e.locale, byLocale);
|
|
525
|
+
grouped.set(e.adapter, byAdapter);
|
|
526
|
+
}
|
|
527
|
+
const lines = [];
|
|
528
|
+
const g = glyphs();
|
|
529
|
+
const total = entries.length;
|
|
530
|
+
lines.push(sectionHeader(`Unused keys (${total})`));
|
|
531
|
+
for (const [adapter, byLocale] of grouped) {
|
|
532
|
+
let adapterTotal = 0;
|
|
533
|
+
for (const byResource of byLocale.values())
|
|
534
|
+
for (const keys of byResource.values()) adapterTotal += keys.length;
|
|
535
|
+
lines.push(`\n ${color(adapter, C.bold + C.blue)} ${color(`(${adapterTotal})`, C.dim)}`);
|
|
536
|
+
for (const [locale, byResource] of byLocale) {
|
|
537
|
+
let localeTotal = 0;
|
|
538
|
+
for (const keys of byResource.values()) localeTotal += keys.length;
|
|
539
|
+
lines.push(
|
|
540
|
+
` ${color(`${g.arrow} ${locale}`, C.yellow)} ${color(`(${localeTotal})`, C.dim)}`,
|
|
541
|
+
);
|
|
542
|
+
for (const [resource, keys] of byResource) {
|
|
543
|
+
lines.push(` ${color(resource, C.bold)}`);
|
|
544
|
+
for (const key of keys) lines.push(` ${color(g.bullet, C.dim)} ${key}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return lines.join("\n") + "\n";
|
|
489
549
|
}
|
|
490
550
|
function formatValidate(result, format) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
"Adapter",
|
|
504
|
-
"Locale",
|
|
505
|
-
"Resource",
|
|
506
|
-
"Missing"
|
|
507
|
-
], rows));
|
|
508
|
-
lines.push("");
|
|
509
|
-
lines.push(color(`Run ${color("dialekt translate", C.bold + C.cyan)} to fill missing keys.`, C.dim));
|
|
510
|
-
return lines.join("\n") + "\n";
|
|
551
|
+
if (format === "json") return JSON.stringify(result, null, 2) + "\n";
|
|
552
|
+
if (result.passing) return "\n" + success("All translations are up to date.") + "\n";
|
|
553
|
+
const rows = result.entries.map((e) => [e.adapter, e.locale, e.resource, e.count.toString()]);
|
|
554
|
+
const lines = [];
|
|
555
|
+
lines.push(failure(`Missing keys found in ${result.entries.length} resource(s)`));
|
|
556
|
+
lines.push("");
|
|
557
|
+
lines.push(drawTable(["Adapter", "Locale", "Resource", "Missing"], rows));
|
|
558
|
+
lines.push("");
|
|
559
|
+
lines.push(
|
|
560
|
+
color(`Run ${color("dialekt translate", C.bold + C.cyan)} to fill missing keys.`, C.dim),
|
|
561
|
+
);
|
|
562
|
+
return lines.join("\n") + "\n";
|
|
511
563
|
}
|
|
512
564
|
function formatLanguages(entries, format) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
565
|
+
if (format === "json") return JSON.stringify(entries, null, 2) + "\n";
|
|
566
|
+
if (entries.length === 0) return warning("No adapters configured.") + "\n";
|
|
567
|
+
const lines = [];
|
|
568
|
+
const g = glyphs();
|
|
569
|
+
for (const e of entries) {
|
|
570
|
+
lines.push(` ${color(e.adapter, C.bold + C.blue)}`);
|
|
571
|
+
lines.push(` ${color(`${g.arrow}`, C.dim)} ${e.locales.join(color(", ", C.dim))}`);
|
|
572
|
+
}
|
|
573
|
+
return lines.join("\n") + "\n";
|
|
522
574
|
}
|
|
523
575
|
function formatTranslate(result, format) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
576
|
+
if (format === "json") return JSON.stringify(result, null, 2) + "\n";
|
|
577
|
+
if (result.success) {
|
|
578
|
+
const lines = [success(result.message)];
|
|
579
|
+
if (result.stats) {
|
|
580
|
+
lines.push("");
|
|
581
|
+
lines.push(keyValue("Adapters:", result.stats.adaptersProcessed.toString()));
|
|
582
|
+
lines.push(keyValue("Locales:", result.stats.localesTranslated.toString()));
|
|
583
|
+
lines.push(keyValue("Keys:", result.stats.keysTranslated.toString()));
|
|
584
|
+
}
|
|
585
|
+
return lines.join("\n") + "\n";
|
|
586
|
+
}
|
|
587
|
+
return failure(result.message) + "\n";
|
|
536
588
|
}
|
|
537
589
|
function formatAdd(result, format) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
590
|
+
if (format === "json") return JSON.stringify(result, null, 2) + "\n";
|
|
591
|
+
if (result.success) {
|
|
592
|
+
const lines = [success(result.message)];
|
|
593
|
+
if (result.addedResources && result.addedResources.length > 0) {
|
|
594
|
+
lines.push("");
|
|
595
|
+
lines.push(color("Added to:", C.dim));
|
|
596
|
+
for (const r of result.addedResources) lines.push(` ${color(glyphs().bullet, C.dim)} ${r}`);
|
|
597
|
+
}
|
|
598
|
+
return lines.join("\n") + "\n";
|
|
599
|
+
}
|
|
600
|
+
return failure(result.message) + "\n";
|
|
549
601
|
}
|
|
550
602
|
function formatBenchmark(entries, format) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
"Chunks",
|
|
566
|
-
"Total",
|
|
567
|
-
"Avg/Chunk",
|
|
568
|
-
"Attempts"
|
|
569
|
-
], rows));
|
|
570
|
-
return lines.join("\n") + "\n";
|
|
603
|
+
if (format === "json") return JSON.stringify(entries, null, 2) + "\n";
|
|
604
|
+
if (entries.length === 0) return warning("No benchmark data available.") + "\n";
|
|
605
|
+
const lines = [];
|
|
606
|
+
lines.push(banner("Benchmark Results"));
|
|
607
|
+
const rows = entries.map((e) => [
|
|
608
|
+
e.strategyName,
|
|
609
|
+
`${e.succeededChunks}/${e.totalChunks}`,
|
|
610
|
+
`${e.totalDurationMs.toFixed(0)}ms`,
|
|
611
|
+
`${e.averageDurationMsPerChunk.toFixed(1)}ms`,
|
|
612
|
+
e.totalAttempts.toString(),
|
|
613
|
+
]);
|
|
614
|
+
lines.push("");
|
|
615
|
+
lines.push(drawTable(["Strategy", "Chunks", "Total", "Avg/Chunk", "Attempts"], rows));
|
|
616
|
+
return lines.join("\n") + "\n";
|
|
571
617
|
}
|
|
572
618
|
function formatError(message, format) {
|
|
573
|
-
|
|
574
|
-
|
|
619
|
+
if (format === "json") return JSON.stringify({ error: message }, null, 2) + "\n";
|
|
620
|
+
return failure(message) + "\n";
|
|
575
621
|
}
|
|
576
622
|
//#endregion
|
|
577
|
-
export {
|
|
623
|
+
export {
|
|
624
|
+
resolveModel as A,
|
|
625
|
+
runTranslation as C,
|
|
626
|
+
buildUserPrompt as D,
|
|
627
|
+
buildSystemPrompt as E,
|
|
628
|
+
diffKeys as M,
|
|
629
|
+
flattenObject as N,
|
|
630
|
+
TranslationFailedError as O,
|
|
631
|
+
unflattenObject as P,
|
|
632
|
+
computeMissingKeys as S,
|
|
633
|
+
createOneShotStrategy as T,
|
|
634
|
+
sectionHeader as _,
|
|
635
|
+
formatMissingKeys as a,
|
|
636
|
+
ConfigLoadError as b,
|
|
637
|
+
formatValidate as c,
|
|
638
|
+
detectFormat as d,
|
|
639
|
+
drawTable as f,
|
|
640
|
+
keyValue as g,
|
|
641
|
+
info as h,
|
|
642
|
+
formatLanguages as i,
|
|
643
|
+
chunkKeys as j,
|
|
644
|
+
UnknownProviderError as k,
|
|
645
|
+
banner as l,
|
|
646
|
+
glyphs as m,
|
|
647
|
+
formatBenchmark as n,
|
|
648
|
+
formatTranslate as o,
|
|
649
|
+
failure as p,
|
|
650
|
+
formatError as r,
|
|
651
|
+
formatUnusedKeys as s,
|
|
652
|
+
formatAdd as t,
|
|
653
|
+
color as u,
|
|
654
|
+
success as v,
|
|
655
|
+
createToolLoopStrategy as w,
|
|
656
|
+
loadConfig as x,
|
|
657
|
+
warning as y,
|
|
658
|
+
};
|