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,98 +1,98 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { AdapterReadError, AdapterWriteError } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AdapterReadError, AdapterWriteError } from "./types.js";
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
4
|
+
describe("AdapterReadError", () => {
|
|
5
|
+
it("carries adapter/locale/resource/cause", () => {
|
|
6
6
|
const err = new AdapterReadError({
|
|
7
|
-
adapter:
|
|
8
|
-
locale:
|
|
9
|
-
resource:
|
|
10
|
-
cause: new Error(
|
|
7
|
+
adapter: "laravel",
|
|
8
|
+
locale: "en",
|
|
9
|
+
resource: "validation",
|
|
10
|
+
cause: new Error("boom"),
|
|
11
11
|
});
|
|
12
|
-
expect(err._tag).toBe(
|
|
13
|
-
expect(err.adapter).toBe(
|
|
14
|
-
expect(err.locale).toBe(
|
|
15
|
-
expect(err.resource).toBe(
|
|
12
|
+
expect(err._tag).toBe("AdapterReadError");
|
|
13
|
+
expect(err.adapter).toBe("laravel");
|
|
14
|
+
expect(err.locale).toBe("en");
|
|
15
|
+
expect(err.resource).toBe("validation");
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it("carries string cause", () => {
|
|
19
19
|
const err = new AdapterReadError({
|
|
20
|
-
adapter:
|
|
21
|
-
locale:
|
|
22
|
-
resource:
|
|
23
|
-
cause:
|
|
20
|
+
adapter: "paraglide",
|
|
21
|
+
locale: "de",
|
|
22
|
+
resource: "messages",
|
|
23
|
+
cause: "file not found",
|
|
24
24
|
});
|
|
25
|
-
expect(err._tag).toBe(
|
|
26
|
-
expect(err.cause).toBe(
|
|
25
|
+
expect(err._tag).toBe("AdapterReadError");
|
|
26
|
+
expect(err.cause).toBe("file not found");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it(
|
|
30
|
-
const cause = new Error(
|
|
29
|
+
it("preserves Error cause identity", () => {
|
|
30
|
+
const cause = new Error("disk full");
|
|
31
31
|
const err = new AdapterReadError({
|
|
32
|
-
adapter:
|
|
33
|
-
locale:
|
|
34
|
-
resource:
|
|
32
|
+
adapter: "laravel",
|
|
33
|
+
locale: "fr",
|
|
34
|
+
resource: "auth",
|
|
35
35
|
cause,
|
|
36
36
|
});
|
|
37
37
|
expect(err.cause).toBe(cause);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it("accepts empty locale and resource", () => {
|
|
41
41
|
const err = new AdapterReadError({
|
|
42
|
-
adapter:
|
|
43
|
-
locale:
|
|
44
|
-
resource:
|
|
45
|
-
cause:
|
|
42
|
+
adapter: "test",
|
|
43
|
+
locale: "",
|
|
44
|
+
resource: "",
|
|
45
|
+
cause: "unknown",
|
|
46
46
|
});
|
|
47
|
-
expect(err.locale).toBe(
|
|
48
|
-
expect(err.resource).toBe(
|
|
47
|
+
expect(err.locale).toBe("");
|
|
48
|
+
expect(err.resource).toBe("");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
it(
|
|
52
|
-
for (const name of [
|
|
51
|
+
it("accepts various adapter names", () => {
|
|
52
|
+
for (const name of ["laravel", "paraglide", "symfony", "custom"]) {
|
|
53
53
|
const err = new AdapterReadError({
|
|
54
54
|
adapter: name,
|
|
55
|
-
locale:
|
|
56
|
-
resource:
|
|
57
|
-
cause:
|
|
55
|
+
locale: "en",
|
|
56
|
+
resource: "x",
|
|
57
|
+
cause: "test",
|
|
58
58
|
});
|
|
59
59
|
expect(err.adapter).toBe(name);
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
describe(
|
|
65
|
-
it(
|
|
64
|
+
describe("AdapterWriteError", () => {
|
|
65
|
+
it("carries adapter/locale/resource/cause", () => {
|
|
66
66
|
const err = new AdapterWriteError({
|
|
67
|
-
adapter:
|
|
68
|
-
locale:
|
|
69
|
-
resource:
|
|
70
|
-
cause:
|
|
67
|
+
adapter: "paraglide",
|
|
68
|
+
locale: "de",
|
|
69
|
+
resource: "messages",
|
|
70
|
+
cause: "disk full",
|
|
71
71
|
});
|
|
72
|
-
expect(err._tag).toBe(
|
|
73
|
-
expect(err.adapter).toBe(
|
|
74
|
-
expect(err.locale).toBe(
|
|
75
|
-
expect(err.resource).toBe(
|
|
72
|
+
expect(err._tag).toBe("AdapterWriteError");
|
|
73
|
+
expect(err.adapter).toBe("paraglide");
|
|
74
|
+
expect(err.locale).toBe("de");
|
|
75
|
+
expect(err.resource).toBe("messages");
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it(
|
|
79
|
-
const cause = new Error(
|
|
78
|
+
it("carries Error cause", () => {
|
|
79
|
+
const cause = new Error("permission denied");
|
|
80
80
|
const err = new AdapterWriteError({
|
|
81
|
-
adapter:
|
|
82
|
-
locale:
|
|
83
|
-
resource:
|
|
81
|
+
adapter: "laravel",
|
|
82
|
+
locale: "en",
|
|
83
|
+
resource: "validation",
|
|
84
84
|
cause,
|
|
85
85
|
});
|
|
86
86
|
expect(err.cause).toBe(cause);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
it(
|
|
89
|
+
it("accepts empty strings", () => {
|
|
90
90
|
const err = new AdapterWriteError({
|
|
91
|
-
adapter:
|
|
92
|
-
locale:
|
|
93
|
-
resource:
|
|
94
|
-
cause:
|
|
91
|
+
adapter: "",
|
|
92
|
+
locale: "",
|
|
93
|
+
resource: "",
|
|
94
|
+
cause: "",
|
|
95
95
|
});
|
|
96
|
-
expect(err.adapter).toBe(
|
|
96
|
+
expect(err.adapter).toBe("");
|
|
97
97
|
});
|
|
98
98
|
});
|
package/src/adapter/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect } from
|
|
1
|
+
import { Data, Effect } from "effect";
|
|
2
2
|
|
|
3
3
|
/** Opaque adapter-specific identifier for one resource within a locale. */
|
|
4
4
|
export interface ResourceRef {
|
|
@@ -6,14 +6,14 @@ export interface ResourceRef {
|
|
|
6
6
|
readonly label: string; // human-readable, for CLI output
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export class AdapterReadError extends Data.TaggedError(
|
|
9
|
+
export class AdapterReadError extends Data.TaggedError("AdapterReadError")<{
|
|
10
10
|
readonly adapter: string;
|
|
11
11
|
readonly locale: string;
|
|
12
12
|
readonly resource: string;
|
|
13
13
|
readonly cause: unknown;
|
|
14
14
|
}> {}
|
|
15
15
|
|
|
16
|
-
export class AdapterWriteError extends Data.TaggedError(
|
|
16
|
+
export class AdapterWriteError extends Data.TaggedError("AdapterWriteError")<{
|
|
17
17
|
readonly adapter: string;
|
|
18
18
|
readonly locale: string;
|
|
19
19
|
readonly resource: string;
|
|
@@ -34,7 +34,10 @@ export interface TranslationAdapter {
|
|
|
34
34
|
listResources(locale: string): Effect.Effect<readonly ResourceRef[], AdapterReadError>;
|
|
35
35
|
|
|
36
36
|
/** Read one resource, flattened to dot-notation key → string value. Returns {} if the resource does not exist. */
|
|
37
|
-
readResource(
|
|
37
|
+
readResource(
|
|
38
|
+
locale: string,
|
|
39
|
+
resource: ResourceRef,
|
|
40
|
+
): Effect.Effect<Record<string, string>, AdapterReadError>;
|
|
38
41
|
|
|
39
42
|
/** Write a full flattened key→value map back to a resource, unflattening as needed. Creates the resource if absent and `create` capability allows it. */
|
|
40
43
|
writeResource(
|
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect } from
|
|
3
|
-
import { summarizeBenchmarkResults, runBenchmarkedChunk } from
|
|
4
|
-
import type { ChunkBenchmarkResult } from
|
|
5
|
-
import type { TranslationStrategy, TranslationContext } from
|
|
6
|
-
import { TranslationFailedError } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { summarizeBenchmarkResults, runBenchmarkedChunk } from "./metrics.js";
|
|
4
|
+
import type { ChunkBenchmarkResult } from "./metrics.js";
|
|
5
|
+
import type { TranslationStrategy, TranslationContext } from "../translation/types.js";
|
|
6
|
+
import { TranslationFailedError } from "../translation/types.js";
|
|
7
7
|
|
|
8
|
-
describe(
|
|
9
|
-
it(
|
|
8
|
+
describe("summarizeBenchmarkResults", () => {
|
|
9
|
+
it("computes totals and averages correctly", () => {
|
|
10
10
|
const results = [
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
{
|
|
12
|
+
strategyName: "one-shot" as const,
|
|
13
|
+
chunkKeyCount: 2,
|
|
14
|
+
durationMs: 100,
|
|
15
|
+
attemptCount: 1,
|
|
16
|
+
succeeded: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
strategyName: "one-shot" as const,
|
|
20
|
+
chunkKeyCount: 2,
|
|
21
|
+
durationMs: 200,
|
|
22
|
+
attemptCount: 1,
|
|
23
|
+
succeeded: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
strategyName: "one-shot" as const,
|
|
27
|
+
chunkKeyCount: 1,
|
|
28
|
+
durationMs: 300,
|
|
29
|
+
attemptCount: 1,
|
|
30
|
+
succeeded: false,
|
|
31
|
+
errorMessage: "oops",
|
|
32
|
+
},
|
|
14
33
|
];
|
|
15
34
|
const summary = summarizeBenchmarkResults(results);
|
|
16
35
|
expect(summary.totalChunks).toBe(3);
|
|
@@ -21,7 +40,7 @@ describe('summarizeBenchmarkResults', () => {
|
|
|
21
40
|
expect(summary.totalAttempts).toBe(3);
|
|
22
41
|
});
|
|
23
42
|
|
|
24
|
-
it(
|
|
43
|
+
it("handles empty results", () => {
|
|
25
44
|
const summary = summarizeBenchmarkResults([]);
|
|
26
45
|
expect(summary.totalChunks).toBe(0);
|
|
27
46
|
expect(summary.succeededChunks).toBe(0);
|
|
@@ -29,12 +48,18 @@ describe('summarizeBenchmarkResults', () => {
|
|
|
29
48
|
expect(summary.totalDurationMs).toBe(0);
|
|
30
49
|
expect(summary.averageDurationMsPerChunk).toBe(0);
|
|
31
50
|
expect(summary.totalAttempts).toBe(0);
|
|
32
|
-
expect(summary.strategyName).toBe(
|
|
51
|
+
expect(summary.strategyName).toBe("one-shot");
|
|
33
52
|
});
|
|
34
53
|
|
|
35
|
-
it(
|
|
54
|
+
it("handles single result", () => {
|
|
36
55
|
const results = [
|
|
37
|
-
{
|
|
56
|
+
{
|
|
57
|
+
strategyName: "tool-loop-agent" as const,
|
|
58
|
+
chunkKeyCount: 5,
|
|
59
|
+
durationMs: 150,
|
|
60
|
+
attemptCount: 2,
|
|
61
|
+
succeeded: true,
|
|
62
|
+
},
|
|
38
63
|
];
|
|
39
64
|
const summary = summarizeBenchmarkResults(results);
|
|
40
65
|
expect(summary.totalChunks).toBe(1);
|
|
@@ -44,10 +69,24 @@ describe('summarizeBenchmarkResults', () => {
|
|
|
44
69
|
expect(summary.totalAttempts).toBe(2);
|
|
45
70
|
});
|
|
46
71
|
|
|
47
|
-
it(
|
|
72
|
+
it("handles all failures", () => {
|
|
48
73
|
const results = [
|
|
49
|
-
{
|
|
50
|
-
|
|
74
|
+
{
|
|
75
|
+
strategyName: "one-shot" as const,
|
|
76
|
+
chunkKeyCount: 1,
|
|
77
|
+
durationMs: 50,
|
|
78
|
+
attemptCount: 1,
|
|
79
|
+
succeeded: false,
|
|
80
|
+
errorMessage: "a",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
strategyName: "one-shot" as const,
|
|
84
|
+
chunkKeyCount: 1,
|
|
85
|
+
durationMs: 60,
|
|
86
|
+
attemptCount: 1,
|
|
87
|
+
succeeded: false,
|
|
88
|
+
errorMessage: "b",
|
|
89
|
+
},
|
|
51
90
|
];
|
|
52
91
|
const summary = summarizeBenchmarkResults(results);
|
|
53
92
|
expect(summary.succeededChunks).toBe(0);
|
|
@@ -55,27 +94,51 @@ describe('summarizeBenchmarkResults', () => {
|
|
|
55
94
|
expect(summary.totalDurationMs).toBe(110);
|
|
56
95
|
});
|
|
57
96
|
|
|
58
|
-
it(
|
|
97
|
+
it("handles zero-duration results", () => {
|
|
59
98
|
const results = [
|
|
60
|
-
{
|
|
99
|
+
{
|
|
100
|
+
strategyName: "one-shot" as const,
|
|
101
|
+
chunkKeyCount: 1,
|
|
102
|
+
durationMs: 0,
|
|
103
|
+
attemptCount: 1,
|
|
104
|
+
succeeded: true,
|
|
105
|
+
},
|
|
61
106
|
];
|
|
62
107
|
const summary = summarizeBenchmarkResults(results);
|
|
63
108
|
expect(summary.totalDurationMs).toBe(0);
|
|
64
109
|
expect(summary.averageDurationMsPerChunk).toBe(0);
|
|
65
110
|
});
|
|
66
111
|
|
|
67
|
-
it(
|
|
112
|
+
it("preserves strategyName from first result", () => {
|
|
68
113
|
const results = [
|
|
69
|
-
{
|
|
114
|
+
{
|
|
115
|
+
strategyName: "tool-loop-agent" as const,
|
|
116
|
+
chunkKeyCount: 1,
|
|
117
|
+
durationMs: 100,
|
|
118
|
+
attemptCount: 1,
|
|
119
|
+
succeeded: true,
|
|
120
|
+
},
|
|
70
121
|
];
|
|
71
122
|
const summary = summarizeBenchmarkResults(results);
|
|
72
|
-
expect(summary.strategyName).toBe(
|
|
123
|
+
expect(summary.strategyName).toBe("tool-loop-agent");
|
|
73
124
|
});
|
|
74
125
|
|
|
75
|
-
it(
|
|
126
|
+
it("handles very large durations without overflow", () => {
|
|
76
127
|
const results = [
|
|
77
|
-
{
|
|
78
|
-
|
|
128
|
+
{
|
|
129
|
+
strategyName: "one-shot" as const,
|
|
130
|
+
chunkKeyCount: 1,
|
|
131
|
+
durationMs: 1_000_000,
|
|
132
|
+
attemptCount: 1,
|
|
133
|
+
succeeded: true,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
strategyName: "one-shot" as const,
|
|
137
|
+
chunkKeyCount: 1,
|
|
138
|
+
durationMs: 2_000_000,
|
|
139
|
+
attemptCount: 1,
|
|
140
|
+
succeeded: true,
|
|
141
|
+
},
|
|
79
142
|
];
|
|
80
143
|
const summary = summarizeBenchmarkResults(results);
|
|
81
144
|
expect(summary.totalDurationMs).toBe(3_000_000);
|
|
@@ -83,98 +146,107 @@ describe('summarizeBenchmarkResults', () => {
|
|
|
83
146
|
});
|
|
84
147
|
});
|
|
85
148
|
|
|
86
|
-
describe(
|
|
87
|
-
it(
|
|
149
|
+
describe("runBenchmarkedChunk", () => {
|
|
150
|
+
it("measures duration and returns success", async () => {
|
|
88
151
|
const strategy: TranslationStrategy = {
|
|
89
|
-
name:
|
|
90
|
-
translateChunk: () => Effect.succeed({ hello:
|
|
152
|
+
name: "one-shot",
|
|
153
|
+
translateChunk: () => Effect.succeed({ hello: "Hallo" }),
|
|
91
154
|
};
|
|
92
155
|
const ctx: TranslationContext = {
|
|
93
|
-
sourceLocale:
|
|
94
|
-
targetLocale:
|
|
95
|
-
sourceMap: { hello:
|
|
156
|
+
sourceLocale: "en",
|
|
157
|
+
targetLocale: "de",
|
|
158
|
+
sourceMap: { hello: "Hello" },
|
|
96
159
|
targetMap: {},
|
|
97
|
-
keys: [
|
|
160
|
+
keys: ["hello"],
|
|
98
161
|
};
|
|
99
|
-
const result = await Effect.runPromise(
|
|
162
|
+
const result = (await Effect.runPromise(
|
|
163
|
+
runBenchmarkedChunk(strategy, ctx),
|
|
164
|
+
)) as ChunkBenchmarkResult;
|
|
100
165
|
expect(result.succeeded).toBe(true);
|
|
101
166
|
expect(result.chunkKeyCount).toBe(1);
|
|
102
167
|
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
103
|
-
expect(result.strategyName).toBe(
|
|
168
|
+
expect(result.strategyName).toBe("one-shot");
|
|
104
169
|
});
|
|
105
170
|
|
|
106
|
-
it(
|
|
171
|
+
it("records failure without propagating", async () => {
|
|
107
172
|
const strategy: TranslationStrategy = {
|
|
108
|
-
name:
|
|
109
|
-
translateChunk: () =>
|
|
110
|
-
Effect.fail(new TranslationFailedError({ keys: ['a'], cause: 'boom' })),
|
|
173
|
+
name: "tool-loop-agent",
|
|
174
|
+
translateChunk: () => Effect.fail(new TranslationFailedError({ keys: ["a"], cause: "boom" })),
|
|
111
175
|
};
|
|
112
176
|
const ctx: TranslationContext = {
|
|
113
|
-
sourceLocale:
|
|
114
|
-
targetLocale:
|
|
115
|
-
sourceMap: { a:
|
|
177
|
+
sourceLocale: "en",
|
|
178
|
+
targetLocale: "de",
|
|
179
|
+
sourceMap: { a: "A" },
|
|
116
180
|
targetMap: {},
|
|
117
|
-
keys: [
|
|
181
|
+
keys: ["a"],
|
|
118
182
|
};
|
|
119
|
-
const result = await Effect.runPromise(
|
|
183
|
+
const result = (await Effect.runPromise(
|
|
184
|
+
runBenchmarkedChunk(strategy, ctx),
|
|
185
|
+
)) as ChunkBenchmarkResult;
|
|
120
186
|
expect(result.succeeded).toBe(false);
|
|
121
|
-
expect(result.errorMessage).toBe(
|
|
187
|
+
expect(result.errorMessage).toBe("boom");
|
|
122
188
|
expect(result.chunkKeyCount).toBe(1);
|
|
123
189
|
});
|
|
124
190
|
|
|
125
|
-
it(
|
|
191
|
+
it("handles empty key list", async () => {
|
|
126
192
|
const strategy: TranslationStrategy = {
|
|
127
|
-
name:
|
|
193
|
+
name: "one-shot",
|
|
128
194
|
translateChunk: () => Effect.succeed({}),
|
|
129
195
|
};
|
|
130
196
|
const ctx: TranslationContext = {
|
|
131
|
-
sourceLocale:
|
|
132
|
-
targetLocale:
|
|
197
|
+
sourceLocale: "en",
|
|
198
|
+
targetLocale: "de",
|
|
133
199
|
sourceMap: {},
|
|
134
200
|
targetMap: {},
|
|
135
201
|
keys: [],
|
|
136
202
|
};
|
|
137
|
-
const result = await Effect.runPromise(
|
|
203
|
+
const result = (await Effect.runPromise(
|
|
204
|
+
runBenchmarkedChunk(strategy, ctx),
|
|
205
|
+
)) as ChunkBenchmarkResult;
|
|
138
206
|
expect(result.succeeded).toBe(true);
|
|
139
207
|
expect(result.chunkKeyCount).toBe(0);
|
|
140
208
|
});
|
|
141
209
|
|
|
142
|
-
it(
|
|
210
|
+
it("measures duration for slow strategies", async () => {
|
|
143
211
|
const strategy: TranslationStrategy = {
|
|
144
|
-
name:
|
|
212
|
+
name: "tool-loop-agent",
|
|
145
213
|
translateChunk: () =>
|
|
146
214
|
Effect.gen(function* () {
|
|
147
|
-
yield* Effect.sleep(
|
|
148
|
-
return { k:
|
|
215
|
+
yield* Effect.sleep("50 millis");
|
|
216
|
+
return { k: "v" };
|
|
149
217
|
}),
|
|
150
218
|
};
|
|
151
219
|
const ctx: TranslationContext = {
|
|
152
|
-
sourceLocale:
|
|
153
|
-
targetLocale:
|
|
154
|
-
sourceMap: { k:
|
|
220
|
+
sourceLocale: "en",
|
|
221
|
+
targetLocale: "de",
|
|
222
|
+
sourceMap: { k: "K" },
|
|
155
223
|
targetMap: {},
|
|
156
|
-
keys: [
|
|
224
|
+
keys: ["k"],
|
|
157
225
|
};
|
|
158
|
-
const result = await Effect.runPromise(
|
|
226
|
+
const result = (await Effect.runPromise(
|
|
227
|
+
runBenchmarkedChunk(strategy, ctx),
|
|
228
|
+
)) as ChunkBenchmarkResult;
|
|
159
229
|
expect(result.succeeded).toBe(true);
|
|
160
230
|
expect(result.durationMs).toBeGreaterThanOrEqual(30);
|
|
161
231
|
});
|
|
162
232
|
|
|
163
|
-
it(
|
|
233
|
+
it("captures Error cause in errorMessage", async () => {
|
|
164
234
|
const strategy: TranslationStrategy = {
|
|
165
|
-
name:
|
|
235
|
+
name: "one-shot",
|
|
166
236
|
translateChunk: () =>
|
|
167
|
-
Effect.fail(new TranslationFailedError({ keys: [
|
|
237
|
+
Effect.fail(new TranslationFailedError({ keys: ["x"], cause: new Error("deep error") })),
|
|
168
238
|
};
|
|
169
239
|
const ctx: TranslationContext = {
|
|
170
|
-
sourceLocale:
|
|
171
|
-
targetLocale:
|
|
172
|
-
sourceMap: { x:
|
|
240
|
+
sourceLocale: "en",
|
|
241
|
+
targetLocale: "de",
|
|
242
|
+
sourceMap: { x: "X" },
|
|
173
243
|
targetMap: {},
|
|
174
|
-
keys: [
|
|
244
|
+
keys: ["x"],
|
|
175
245
|
};
|
|
176
|
-
const result = await Effect.runPromise(
|
|
246
|
+
const result = (await Effect.runPromise(
|
|
247
|
+
runBenchmarkedChunk(strategy, ctx),
|
|
248
|
+
)) as ChunkBenchmarkResult;
|
|
177
249
|
expect(result.succeeded).toBe(false);
|
|
178
|
-
expect(result.errorMessage).toContain(
|
|
250
|
+
expect(result.errorMessage).toContain("deep error");
|
|
179
251
|
});
|
|
180
252
|
});
|
package/src/benchmark/metrics.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Effect } from
|
|
2
|
-
import type { TranslationStrategy, TranslationContext } from
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { TranslationStrategy, TranslationContext } from "../translation/types.js";
|
|
3
3
|
|
|
4
4
|
export interface ChunkBenchmarkResult {
|
|
5
|
-
readonly strategyName:
|
|
5
|
+
readonly strategyName: "one-shot" | "tool-loop-agent";
|
|
6
6
|
readonly chunkKeyCount: number;
|
|
7
7
|
readonly durationMs: number;
|
|
8
8
|
readonly attemptCount: number;
|
|
@@ -11,7 +11,7 @@ export interface ChunkBenchmarkResult {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface StrategyBenchmarkSummary {
|
|
14
|
-
readonly strategyName:
|
|
14
|
+
readonly strategyName: "one-shot" | "tool-loop-agent";
|
|
15
15
|
readonly totalChunks: number;
|
|
16
16
|
readonly succeededChunks: number;
|
|
17
17
|
readonly failedChunks: number;
|
|
@@ -29,7 +29,7 @@ export function summarizeBenchmarkResults(
|
|
|
29
29
|
const totalDurationMs = results.reduce((sum, r) => sum + r.durationMs, 0);
|
|
30
30
|
const totalAttempts = results.reduce((sum, r) => sum + r.attemptCount, 0);
|
|
31
31
|
return {
|
|
32
|
-
strategyName: results[0]?.strategyName ??
|
|
32
|
+
strategyName: results[0]?.strategyName ?? "one-shot",
|
|
33
33
|
totalChunks,
|
|
34
34
|
succeededChunks,
|
|
35
35
|
failedChunks,
|
|
@@ -47,7 +47,7 @@ export function runBenchmarkedChunk(
|
|
|
47
47
|
const start = Date.now();
|
|
48
48
|
const result = yield* Effect.either(strategy.translateChunk(ctx));
|
|
49
49
|
const durationMs = Date.now() - start;
|
|
50
|
-
if (result._tag ===
|
|
50
|
+
if (result._tag === "Right") {
|
|
51
51
|
return {
|
|
52
52
|
strategyName: strategy.name,
|
|
53
53
|
chunkKeyCount: ctx.keys.length,
|