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.
Files changed (71) hide show
  1. package/README.md +8 -10
  2. package/TESTING.md +29 -29
  3. package/dist/cli/main.d.mts +1 -1
  4. package/dist/cli/main.mjs +549 -362
  5. package/dist/formatters-De4Q-X1d.mjs +516 -435
  6. package/dist/index.d.mts +162 -25
  7. package/dist/index.mjs +119 -34
  8. package/package.json +3 -3
  9. package/pnpm-workspace.yaml +3 -3
  10. package/src/adapter/types.test.ts +57 -57
  11. package/src/adapter/types.ts +7 -4
  12. package/src/benchmark/metrics.test.ts +141 -69
  13. package/src/benchmark/metrics.ts +6 -6
  14. package/src/benchmark/report.test.ts +38 -38
  15. package/src/benchmark/report.ts +6 -6
  16. package/src/benchmark/runner.test.ts +70 -72
  17. package/src/benchmark/runner.ts +4 -4
  18. package/src/cli/commands/add.test.ts +90 -109
  19. package/src/cli/commands/add.ts +40 -28
  20. package/src/cli/commands/benchmark.test.ts +77 -64
  21. package/src/cli/commands/benchmark.ts +64 -41
  22. package/src/cli/commands/languages.test.ts +45 -42
  23. package/src/cli/commands/languages.ts +16 -12
  24. package/src/cli/commands/missing.test.ts +143 -92
  25. package/src/cli/commands/missing.ts +24 -17
  26. package/src/cli/commands/translate.test.ts +79 -79
  27. package/src/cli/commands/translate.ts +41 -31
  28. package/src/cli/commands/unused.test.ts +62 -51
  29. package/src/cli/commands/unused.ts +18 -14
  30. package/src/cli/commands/validate.test.ts +130 -72
  31. package/src/cli/commands/validate.ts +25 -20
  32. package/src/cli/config-resolution.test.ts +169 -49
  33. package/src/cli/config-resolution.ts +5 -7
  34. package/src/cli/format.test.ts +50 -50
  35. package/src/cli/format.ts +57 -60
  36. package/src/cli/formatters.test.ts +128 -106
  37. package/src/cli/formatters.ts +72 -95
  38. package/src/cli/main.ts +13 -13
  39. package/src/config/define-config.test.ts +44 -29
  40. package/src/config/define-config.ts +1 -1
  41. package/src/config/load-config.test.ts +21 -18
  42. package/src/config/load-config.ts +5 -5
  43. package/src/config/types.test.ts +50 -44
  44. package/src/config/types.ts +2 -2
  45. package/src/index.ts +22 -26
  46. package/src/keys/flatten.test.ts +52 -52
  47. package/src/keys/flatten.ts +7 -9
  48. package/src/sdk/file-io.test.ts +47 -59
  49. package/src/sdk/file-io.ts +2 -2
  50. package/src/sdk/node-layer.test.ts +18 -18
  51. package/src/sdk/node-layer.ts +2 -2
  52. package/src/sdk/php-array-reader.test.ts +49 -40
  53. package/src/sdk/php-array-reader.ts +5 -5
  54. package/src/translation/chunking.test.ts +52 -44
  55. package/src/translation/chunking.ts +1 -1
  56. package/src/translation/missing-keys.test.ts +86 -93
  57. package/src/translation/missing-keys.ts +4 -6
  58. package/src/translation/model-registry.test.ts +41 -32
  59. package/src/translation/model-registry.ts +9 -9
  60. package/src/translation/one-shot-strategy.test.ts +105 -86
  61. package/src/translation/one-shot-strategy.ts +10 -12
  62. package/src/translation/orchestrator.test.ts +90 -101
  63. package/src/translation/orchestrator.ts +26 -26
  64. package/src/translation/prompt.test.ts +76 -76
  65. package/src/translation/prompt.ts +2 -2
  66. package/src/translation/tool-loop-strategy.test.ts +134 -107
  67. package/src/translation/tool-loop-strategy.ts +14 -18
  68. package/src/translation/types.test.ts +22 -22
  69. package/src/translation/types.ts +3 -3
  70. package/tsdown.config.ts +3 -3
  71. package/vitest.config.ts +3 -3
@@ -1,28 +1,28 @@
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';
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('createOneShotStrategy', () => {
8
- it('returns translated map on success', async () => {
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: 'text',
16
- text: JSON.stringify({ hello: 'Hallo', bye: 'Tschüss' }),
15
+ type: "text",
16
+ text: JSON.stringify({ hello: "Hallo", bye: "Tschüss" }),
17
17
  },
18
18
  ],
19
- finishReason: { unified: 'stop' as const, raw: 'stop' },
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: 'mock',
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: 'en',
41
- targetLocale: 'de',
42
- sourceMap: { hello: 'Hello', bye: 'Bye' },
40
+ sourceLocale: "en",
41
+ targetLocale: "de",
42
+ sourceMap: { hello: "Hello", bye: "Bye" },
43
43
  targetMap: {},
44
- keys: ['hello', 'bye'],
44
+ keys: ["hello", "bye"],
45
45
  };
46
46
 
47
47
  const result = await Effect.runPromise(strategy.translateChunk(ctx));
48
- expect(result).toEqual({ hello: 'Hallo', bye: 'Tschüss' });
48
+ expect(result).toEqual({ hello: "Hallo", bye: "Tschüss" });
49
49
  });
50
50
 
51
- it('retries when model omits a key and fails after exhaustion', async () => {
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: 'text',
61
- text: JSON.stringify({ hello: 'Hallo' }),
60
+ type: "text",
61
+ text: JSON.stringify({ hello: "Hallo" }),
62
62
  },
63
63
  ],
64
- finishReason: { unified: 'stop' as const, raw: 'stop' },
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: 'mock',
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: 'en',
86
- targetLocale: 'de',
87
- sourceMap: { hello: 'Hello', bye: 'Bye' },
85
+ sourceLocale: "en",
86
+ targetLocale: "de",
87
+ sourceMap: { hello: "Hello", bye: "Bye" },
88
88
  targetMap: {},
89
- keys: ['hello', 'bye'],
89
+ keys: ["hello", "bye"],
90
90
  };
91
91
 
92
- const exit = await Effect.runPromise(Effect.either(strategy.translateChunk(ctx))) as Either.Either<unknown, TranslationFailedError>;
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 === 'Left') {
95
- expect(exit.left._tag).toBe('TranslationFailedError');
96
+ if (exit._tag === "Left") {
97
+ expect(exit.left._tag).toBe("TranslationFailedError");
96
98
  } else {
97
- throw new Error('Expected Left');
99
+ throw new Error("Expected Left");
98
100
  }
99
101
  });
100
102
 
101
- it('handles empty keys', async () => {
103
+ it("handles empty keys", async () => {
102
104
  const model = new MockLanguageModelV3({
103
105
  doGenerate: async () => ({
104
- text: '',
105
- content: [{ type: 'text', text: JSON.stringify({}) }],
106
- finishReason: { unified: 'stop' as const, raw: 'stop' },
107
- usage: { inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 }, outputTokens: { total: 1, text: 1, reasoning: 0 } },
108
- response: { modelId: 'mock', timestamp: new Date() },
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: 'en',
121
- targetLocale: 'de',
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('handles malformed JSON response by retrying', async () => {
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: 'text', text: calls < 2 ? 'not-json' : JSON.stringify({ k: 'v' }) }],
139
- finishReason: { unified: 'stop' as const, raw: 'stop' },
140
- usage: { inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 }, outputTokens: { total: 1, text: 1, reasoning: 0 } },
141
- response: { modelId: 'mock', timestamp: new Date() },
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: 'en',
155
- targetLocale: 'de',
156
- sourceMap: { k: 'K' },
162
+ sourceLocale: "en",
163
+ targetLocale: "de",
164
+ sourceMap: { k: "K" },
157
165
  targetMap: {},
158
- keys: ['k'],
166
+ keys: ["k"],
159
167
  };
160
168
 
161
169
  const result = await Effect.runPromise(strategy.translateChunk(ctx));
162
- expect(result).toEqual({ k: 'v' });
170
+ expect(result).toEqual({ k: "v" });
163
171
  expect(calls).toBeGreaterThan(1);
164
172
  });
165
173
 
166
- it('fails after all retries exhausted', async () => {
174
+ it("fails after all retries exhausted", async () => {
167
175
  const model = new MockLanguageModelV3({
168
176
  doGenerate: async () => ({
169
- text: '',
170
- content: [{ type: 'text', text: 'not-json' }],
171
- finishReason: { unified: 'stop' as const, raw: 'stop' },
172
- usage: { inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 }, outputTokens: { total: 1, text: 1, reasoning: 0 } },
173
- response: { modelId: 'mock', timestamp: new Date() },
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: 'en',
186
- targetLocale: 'de',
187
- sourceMap: { k: 'K' },
196
+ sourceLocale: "en",
197
+ targetLocale: "de",
198
+ sourceMap: { k: "K" },
188
199
  targetMap: {},
189
- keys: ['k'],
200
+ keys: ["k"],
190
201
  };
191
202
 
192
- const exit = await Effect.runPromise(Effect.either(strategy.translateChunk(ctx))) as Either.Either<unknown, TranslationFailedError>;
193
- if (exit._tag === 'Left') {
194
- expect(exit.left._tag).toBe('TranslationFailedError');
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('Expected Left');
209
+ throw new Error("Expected Left");
197
210
  }
198
211
  });
199
212
 
200
- it('handles model returning extra keys (ignores them)', async () => {
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: 'text', text: JSON.stringify({ hello: 'Hallo', extra: 'ignored' }) }],
205
- finishReason: { unified: 'stop' as const, raw: 'stop' },
206
- usage: { inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 }, outputTokens: { total: 1, text: 1, reasoning: 0 } },
207
- response: { modelId: 'mock', timestamp: new Date() },
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: 'en',
220
- targetLocale: 'de',
221
- sourceMap: { hello: 'Hello' },
235
+ sourceLocale: "en",
236
+ targetLocale: "de",
237
+ sourceMap: { hello: "Hello" },
222
238
  targetMap: {},
223
- keys: ['hello'],
239
+ keys: ["hello"],
224
240
  };
225
241
 
226
242
  const result = await Effect.runPromise(strategy.translateChunk(ctx));
227
- expect(result).toEqual({ hello: 'Hallo' });
243
+ expect(result).toEqual({ hello: "Hallo" });
228
244
  });
229
245
 
230
- it('handles single key translation', async () => {
246
+ it("handles single key translation", async () => {
231
247
  const model = new MockLanguageModelV3({
232
248
  doGenerate: async () => ({
233
- text: '',
234
- content: [{ type: 'text', text: JSON.stringify({ greeting: 'Hallo' }) }],
235
- finishReason: { unified: 'stop' as const, raw: 'stop' },
236
- usage: { inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 }, outputTokens: { total: 1, text: 1, reasoning: 0 } },
237
- response: { modelId: 'mock', timestamp: new Date() },
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: 'en',
250
- targetLocale: 'de',
251
- sourceMap: { greeting: 'Hello' },
268
+ sourceLocale: "en",
269
+ targetLocale: "de",
270
+ sourceMap: { greeting: "Hello" },
252
271
  targetMap: {},
253
- keys: ['greeting'],
272
+ keys: ["greeting"],
254
273
  };
255
274
 
256
275
  const result = await Effect.runPromise(strategy.translateChunk(ctx));
257
- expect(result).toEqual({ greeting: 'Hallo' });
276
+ expect(result).toEqual({ greeting: "Hallo" });
258
277
  });
259
278
  });
@@ -1,18 +1,16 @@
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';
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: 'one-shot',
32
+ name: "one-shot",
35
33
  translateChunk: (ctx: TranslationContext) =>
36
34
  Effect.tryPromise({
37
35
  try: () => tryTranslateChunk(deps.model, ctx),