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
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect, Either } from
|
|
3
|
-
import { MockLanguageModelV3 } from
|
|
4
|
-
import { createOneShotStrategy } from
|
|
5
|
-
import { TranslationFailedError } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect, Either } from "effect";
|
|
3
|
+
import { MockLanguageModelV3 } from "ai/test";
|
|
4
|
+
import { createOneShotStrategy } from "./one-shot-strategy.js";
|
|
5
|
+
import { TranslationFailedError } from "./types.js";
|
|
6
6
|
|
|
7
|
-
describe(
|
|
8
|
-
it(
|
|
7
|
+
describe("createOneShotStrategy", () => {
|
|
8
|
+
it("returns translated map on success", async () => {
|
|
9
9
|
const model = new MockLanguageModelV3({
|
|
10
10
|
doGenerate: async (options: { prompt: unknown }) => {
|
|
11
11
|
return {
|
|
12
|
-
text:
|
|
12
|
+
text: "",
|
|
13
13
|
content: [
|
|
14
14
|
{
|
|
15
|
-
type:
|
|
16
|
-
text: JSON.stringify({ hello:
|
|
15
|
+
type: "text",
|
|
16
|
+
text: JSON.stringify({ hello: "Hallo", bye: "Tschüss" }),
|
|
17
17
|
},
|
|
18
18
|
],
|
|
19
|
-
finishReason: { unified:
|
|
19
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
20
20
|
usage: {
|
|
21
21
|
inputTokens: { total: 10, noCache: 10, cacheRead: 0, cacheWrite: 0 },
|
|
22
22
|
outputTokens: { total: 4, text: 4, reasoning: 0 },
|
|
23
23
|
},
|
|
24
24
|
response: {
|
|
25
|
-
modelId:
|
|
25
|
+
modelId: "mock",
|
|
26
26
|
timestamp: new Date(),
|
|
27
27
|
},
|
|
28
28
|
request: { body: {} },
|
|
@@ -37,37 +37,37 @@ describe('createOneShotStrategy', () => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
const ctx = {
|
|
40
|
-
sourceLocale:
|
|
41
|
-
targetLocale:
|
|
42
|
-
sourceMap: { hello:
|
|
40
|
+
sourceLocale: "en",
|
|
41
|
+
targetLocale: "de",
|
|
42
|
+
sourceMap: { hello: "Hello", bye: "Bye" },
|
|
43
43
|
targetMap: {},
|
|
44
|
-
keys: [
|
|
44
|
+
keys: ["hello", "bye"],
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const result = await Effect.runPromise(strategy.translateChunk(ctx));
|
|
48
|
-
expect(result).toEqual({ hello:
|
|
48
|
+
expect(result).toEqual({ hello: "Hallo", bye: "Tschüss" });
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
it(
|
|
51
|
+
it("retries when model omits a key and fails after exhaustion", async () => {
|
|
52
52
|
let calls = 0;
|
|
53
53
|
const model = new MockLanguageModelV3({
|
|
54
54
|
doGenerate: async () => {
|
|
55
55
|
calls++;
|
|
56
56
|
return {
|
|
57
|
-
text:
|
|
57
|
+
text: "",
|
|
58
58
|
content: [
|
|
59
59
|
{
|
|
60
|
-
type:
|
|
61
|
-
text: JSON.stringify({ hello:
|
|
60
|
+
type: "text",
|
|
61
|
+
text: JSON.stringify({ hello: "Hallo" }),
|
|
62
62
|
},
|
|
63
63
|
],
|
|
64
|
-
finishReason: { unified:
|
|
64
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
65
65
|
usage: {
|
|
66
66
|
inputTokens: { total: 10, noCache: 10, cacheRead: 0, cacheWrite: 0 },
|
|
67
67
|
outputTokens: { total: 2, text: 2, reasoning: 0 },
|
|
68
68
|
},
|
|
69
69
|
response: {
|
|
70
|
-
modelId:
|
|
70
|
+
modelId: "mock",
|
|
71
71
|
timestamp: new Date(),
|
|
72
72
|
},
|
|
73
73
|
request: { body: {} },
|
|
@@ -82,30 +82,35 @@ describe('createOneShotStrategy', () => {
|
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
const ctx = {
|
|
85
|
-
sourceLocale:
|
|
86
|
-
targetLocale:
|
|
87
|
-
sourceMap: { hello:
|
|
85
|
+
sourceLocale: "en",
|
|
86
|
+
targetLocale: "de",
|
|
87
|
+
sourceMap: { hello: "Hello", bye: "Bye" },
|
|
88
88
|
targetMap: {},
|
|
89
|
-
keys: [
|
|
89
|
+
keys: ["hello", "bye"],
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
const exit = await Effect.runPromise(
|
|
92
|
+
const exit = (await Effect.runPromise(
|
|
93
|
+
Effect.either(strategy.translateChunk(ctx)),
|
|
94
|
+
)) as Either.Either<unknown, TranslationFailedError>;
|
|
93
95
|
expect(calls).toBeGreaterThan(1);
|
|
94
|
-
if (exit._tag ===
|
|
95
|
-
expect(exit.left._tag).toBe(
|
|
96
|
+
if (exit._tag === "Left") {
|
|
97
|
+
expect(exit.left._tag).toBe("TranslationFailedError");
|
|
96
98
|
} else {
|
|
97
|
-
throw new Error(
|
|
99
|
+
throw new Error("Expected Left");
|
|
98
100
|
}
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
it(
|
|
103
|
+
it("handles empty keys", async () => {
|
|
102
104
|
const model = new MockLanguageModelV3({
|
|
103
105
|
doGenerate: async () => ({
|
|
104
|
-
text:
|
|
105
|
-
content: [{ type:
|
|
106
|
-
finishReason: { unified:
|
|
107
|
-
usage: {
|
|
108
|
-
|
|
106
|
+
text: "",
|
|
107
|
+
content: [{ type: "text", text: JSON.stringify({}) }],
|
|
108
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
109
|
+
usage: {
|
|
110
|
+
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
|
111
|
+
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
|
112
|
+
},
|
|
113
|
+
response: { modelId: "mock", timestamp: new Date() },
|
|
109
114
|
request: { body: {} },
|
|
110
115
|
warnings: [],
|
|
111
116
|
}),
|
|
@@ -117,8 +122,8 @@ describe('createOneShotStrategy', () => {
|
|
|
117
122
|
});
|
|
118
123
|
|
|
119
124
|
const ctx = {
|
|
120
|
-
sourceLocale:
|
|
121
|
-
targetLocale:
|
|
125
|
+
sourceLocale: "en",
|
|
126
|
+
targetLocale: "de",
|
|
122
127
|
sourceMap: {},
|
|
123
128
|
targetMap: {},
|
|
124
129
|
keys: [],
|
|
@@ -128,17 +133,20 @@ describe('createOneShotStrategy', () => {
|
|
|
128
133
|
expect(result).toEqual({});
|
|
129
134
|
});
|
|
130
135
|
|
|
131
|
-
it(
|
|
136
|
+
it("handles malformed JSON response by retrying", async () => {
|
|
132
137
|
let calls = 0;
|
|
133
138
|
const model = new MockLanguageModelV3({
|
|
134
139
|
doGenerate: async () => {
|
|
135
140
|
calls++;
|
|
136
141
|
return {
|
|
137
|
-
text:
|
|
138
|
-
content: [{ type:
|
|
139
|
-
finishReason: { unified:
|
|
140
|
-
usage: {
|
|
141
|
-
|
|
142
|
+
text: "",
|
|
143
|
+
content: [{ type: "text", text: calls < 2 ? "not-json" : JSON.stringify({ k: "v" }) }],
|
|
144
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
145
|
+
usage: {
|
|
146
|
+
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
|
147
|
+
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
|
148
|
+
},
|
|
149
|
+
response: { modelId: "mock", timestamp: new Date() },
|
|
142
150
|
request: { body: {} },
|
|
143
151
|
warnings: [],
|
|
144
152
|
};
|
|
@@ -151,26 +159,29 @@ describe('createOneShotStrategy', () => {
|
|
|
151
159
|
});
|
|
152
160
|
|
|
153
161
|
const ctx = {
|
|
154
|
-
sourceLocale:
|
|
155
|
-
targetLocale:
|
|
156
|
-
sourceMap: { k:
|
|
162
|
+
sourceLocale: "en",
|
|
163
|
+
targetLocale: "de",
|
|
164
|
+
sourceMap: { k: "K" },
|
|
157
165
|
targetMap: {},
|
|
158
|
-
keys: [
|
|
166
|
+
keys: ["k"],
|
|
159
167
|
};
|
|
160
168
|
|
|
161
169
|
const result = await Effect.runPromise(strategy.translateChunk(ctx));
|
|
162
|
-
expect(result).toEqual({ k:
|
|
170
|
+
expect(result).toEqual({ k: "v" });
|
|
163
171
|
expect(calls).toBeGreaterThan(1);
|
|
164
172
|
});
|
|
165
173
|
|
|
166
|
-
it(
|
|
174
|
+
it("fails after all retries exhausted", async () => {
|
|
167
175
|
const model = new MockLanguageModelV3({
|
|
168
176
|
doGenerate: async () => ({
|
|
169
|
-
text:
|
|
170
|
-
content: [{ type:
|
|
171
|
-
finishReason: { unified:
|
|
172
|
-
usage: {
|
|
173
|
-
|
|
177
|
+
text: "",
|
|
178
|
+
content: [{ type: "text", text: "not-json" }],
|
|
179
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
180
|
+
usage: {
|
|
181
|
+
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
|
182
|
+
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
|
183
|
+
},
|
|
184
|
+
response: { modelId: "mock", timestamp: new Date() },
|
|
174
185
|
request: { body: {} },
|
|
175
186
|
warnings: [],
|
|
176
187
|
}),
|
|
@@ -182,29 +193,34 @@ describe('createOneShotStrategy', () => {
|
|
|
182
193
|
});
|
|
183
194
|
|
|
184
195
|
const ctx = {
|
|
185
|
-
sourceLocale:
|
|
186
|
-
targetLocale:
|
|
187
|
-
sourceMap: { k:
|
|
196
|
+
sourceLocale: "en",
|
|
197
|
+
targetLocale: "de",
|
|
198
|
+
sourceMap: { k: "K" },
|
|
188
199
|
targetMap: {},
|
|
189
|
-
keys: [
|
|
200
|
+
keys: ["k"],
|
|
190
201
|
};
|
|
191
202
|
|
|
192
|
-
const exit = await Effect.runPromise(
|
|
193
|
-
|
|
194
|
-
|
|
203
|
+
const exit = (await Effect.runPromise(
|
|
204
|
+
Effect.either(strategy.translateChunk(ctx)),
|
|
205
|
+
)) as Either.Either<unknown, TranslationFailedError>;
|
|
206
|
+
if (exit._tag === "Left") {
|
|
207
|
+
expect(exit.left._tag).toBe("TranslationFailedError");
|
|
195
208
|
} else {
|
|
196
|
-
throw new Error(
|
|
209
|
+
throw new Error("Expected Left");
|
|
197
210
|
}
|
|
198
211
|
});
|
|
199
212
|
|
|
200
|
-
it(
|
|
213
|
+
it("handles model returning extra keys (ignores them)", async () => {
|
|
201
214
|
const model = new MockLanguageModelV3({
|
|
202
215
|
doGenerate: async () => ({
|
|
203
|
-
text:
|
|
204
|
-
content: [{ type:
|
|
205
|
-
finishReason: { unified:
|
|
206
|
-
usage: {
|
|
207
|
-
|
|
216
|
+
text: "",
|
|
217
|
+
content: [{ type: "text", text: JSON.stringify({ hello: "Hallo", extra: "ignored" }) }],
|
|
218
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
219
|
+
usage: {
|
|
220
|
+
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
|
221
|
+
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
|
222
|
+
},
|
|
223
|
+
response: { modelId: "mock", timestamp: new Date() },
|
|
208
224
|
request: { body: {} },
|
|
209
225
|
warnings: [],
|
|
210
226
|
}),
|
|
@@ -216,25 +232,28 @@ describe('createOneShotStrategy', () => {
|
|
|
216
232
|
});
|
|
217
233
|
|
|
218
234
|
const ctx = {
|
|
219
|
-
sourceLocale:
|
|
220
|
-
targetLocale:
|
|
221
|
-
sourceMap: { hello:
|
|
235
|
+
sourceLocale: "en",
|
|
236
|
+
targetLocale: "de",
|
|
237
|
+
sourceMap: { hello: "Hello" },
|
|
222
238
|
targetMap: {},
|
|
223
|
-
keys: [
|
|
239
|
+
keys: ["hello"],
|
|
224
240
|
};
|
|
225
241
|
|
|
226
242
|
const result = await Effect.runPromise(strategy.translateChunk(ctx));
|
|
227
|
-
expect(result).toEqual({ hello:
|
|
243
|
+
expect(result).toEqual({ hello: "Hallo" });
|
|
228
244
|
});
|
|
229
245
|
|
|
230
|
-
it(
|
|
246
|
+
it("handles single key translation", async () => {
|
|
231
247
|
const model = new MockLanguageModelV3({
|
|
232
248
|
doGenerate: async () => ({
|
|
233
|
-
text:
|
|
234
|
-
content: [{ type:
|
|
235
|
-
finishReason: { unified:
|
|
236
|
-
usage: {
|
|
237
|
-
|
|
249
|
+
text: "",
|
|
250
|
+
content: [{ type: "text", text: JSON.stringify({ greeting: "Hallo" }) }],
|
|
251
|
+
finishReason: { unified: "stop" as const, raw: "stop" },
|
|
252
|
+
usage: {
|
|
253
|
+
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
|
254
|
+
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
|
255
|
+
},
|
|
256
|
+
response: { modelId: "mock", timestamp: new Date() },
|
|
238
257
|
request: { body: {} },
|
|
239
258
|
warnings: [],
|
|
240
259
|
}),
|
|
@@ -246,14 +265,14 @@ describe('createOneShotStrategy', () => {
|
|
|
246
265
|
});
|
|
247
266
|
|
|
248
267
|
const ctx = {
|
|
249
|
-
sourceLocale:
|
|
250
|
-
targetLocale:
|
|
251
|
-
sourceMap: { greeting:
|
|
268
|
+
sourceLocale: "en",
|
|
269
|
+
targetLocale: "de",
|
|
270
|
+
sourceMap: { greeting: "Hello" },
|
|
252
271
|
targetMap: {},
|
|
253
|
-
keys: [
|
|
272
|
+
keys: ["greeting"],
|
|
254
273
|
};
|
|
255
274
|
|
|
256
275
|
const result = await Effect.runPromise(strategy.translateChunk(ctx));
|
|
257
|
-
expect(result).toEqual({ greeting:
|
|
276
|
+
expect(result).toEqual({ greeting: "Hallo" });
|
|
258
277
|
});
|
|
259
278
|
});
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import { generateText, Output } from
|
|
2
|
-
import { Effect, Schedule } from
|
|
3
|
-
import { z } from
|
|
4
|
-
import type { LanguageModel } from
|
|
5
|
-
import type { TranslationContext, TranslationStrategy } from
|
|
6
|
-
import { TranslationFailedError } from
|
|
7
|
-
import { buildSystemPrompt, buildUserPrompt } from
|
|
1
|
+
import { generateText, Output } from "ai";
|
|
2
|
+
import { Effect, Schedule } from "effect";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { LanguageModel } from "ai";
|
|
5
|
+
import type { TranslationContext, TranslationStrategy } from "./types.js";
|
|
6
|
+
import { TranslationFailedError } from "./types.js";
|
|
7
|
+
import { buildSystemPrompt, buildUserPrompt } from "./prompt.js";
|
|
8
8
|
|
|
9
9
|
async function tryTranslateChunk(
|
|
10
10
|
model: LanguageModel,
|
|
11
11
|
ctx: TranslationContext,
|
|
12
12
|
): Promise<Record<string, string>> {
|
|
13
|
-
const schema = z.object(
|
|
14
|
-
Object.fromEntries(ctx.keys.map((key: string) => [key, z.string()])),
|
|
15
|
-
);
|
|
13
|
+
const schema = z.object(Object.fromEntries(ctx.keys.map((key: string) => [key, z.string()])));
|
|
16
14
|
const { output } = await generateText({
|
|
17
15
|
model,
|
|
18
16
|
system: buildSystemPrompt(ctx.sourceLocale, ctx.targetLocale),
|
|
@@ -21,7 +19,7 @@ async function tryTranslateChunk(
|
|
|
21
19
|
});
|
|
22
20
|
const missing = ctx.keys.filter((key: string) => !(key in output));
|
|
23
21
|
if (missing.length > 0) {
|
|
24
|
-
throw new Error(`Model omitted keys: ${missing.join(
|
|
22
|
+
throw new Error(`Model omitted keys: ${missing.join(", ")}`);
|
|
25
23
|
}
|
|
26
24
|
return output as Record<string, string>;
|
|
27
25
|
}
|
|
@@ -31,7 +29,7 @@ export function createOneShotStrategy(deps: {
|
|
|
31
29
|
retry: { maxAttempts: number; baseDelayMs: number };
|
|
32
30
|
}): TranslationStrategy {
|
|
33
31
|
return {
|
|
34
|
-
name:
|
|
32
|
+
name: "one-shot",
|
|
35
33
|
translateChunk: (ctx: TranslationContext) =>
|
|
36
34
|
Effect.tryPromise({
|
|
37
35
|
try: () => tryTranslateChunk(deps.model, ctx),
|