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
package/dist/cli/main.mjs CHANGED
@@ -1,410 +1,597 @@
1
1
  #!/usr/bin/env node
2
- import { A as resolveModel, C as runTranslation, S as computeMissingKeys, T as createOneShotStrategy, a as formatMissingKeys, c as formatValidate, d as detectFormat, i as formatLanguages, j as chunkKeys, n as formatBenchmark, o as formatTranslate, r as formatError, s as formatUnusedKeys, t as formatAdd, w as createToolLoopStrategy, x as loadConfig } from "../formatters-De4Q-X1d.mjs";
2
+ import {
3
+ A as resolveModel,
4
+ C as runTranslation,
5
+ S as computeMissingKeys,
6
+ T as createOneShotStrategy,
7
+ a as formatMissingKeys,
8
+ c as formatValidate,
9
+ d as detectFormat,
10
+ i as formatLanguages,
11
+ j as chunkKeys,
12
+ n as formatBenchmark,
13
+ o as formatTranslate,
14
+ r as formatError,
15
+ s as formatUnusedKeys,
16
+ t as formatAdd,
17
+ w as createToolLoopStrategy,
18
+ x as loadConfig,
19
+ } from "../formatters-De4Q-X1d.mjs";
3
20
  import { Console, Effect, Option } from "effect";
4
21
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
5
22
  import { Command, Options } from "@effect/cli";
6
23
  //#region src/cli/config-resolution.ts
7
24
  function resolveEffectiveConfig(flags, loaded) {
8
- return {
9
- ...loaded,
10
- sourceLocale: flags.baseLanguage ?? loaded.sourceLocale,
11
- targetLocales: flags.language && flags.language.length > 0 ? flags.language : loaded.targetLocales,
12
- strategy: flags.strategy ?? loaded.strategy,
13
- adapters: flags.adapter ? loaded.adapters.filter((a) => a.name === flags.adapter) : loaded.adapters
14
- };
25
+ return {
26
+ ...loaded,
27
+ sourceLocale: flags.baseLanguage ?? loaded.sourceLocale,
28
+ targetLocales:
29
+ flags.language && flags.language.length > 0 ? flags.language : loaded.targetLocales,
30
+ strategy: flags.strategy ?? loaded.strategy,
31
+ adapters: flags.adapter
32
+ ? loaded.adapters.filter((a) => a.name === flags.adapter)
33
+ : loaded.adapters,
34
+ };
15
35
  }
16
36
  //#endregion
17
37
  //#region src/cli/commands/translate.ts
18
- function runTranslate(flags, configLoader = loadConfig, modelResolver = resolveModel, translationRunner = runTranslation, logger = (msg) => Console.log(msg)) {
19
- return Effect.gen(function* () {
20
- const loaded = yield* configLoader(flags.config);
21
- const effective = resolveEffectiveConfig({
22
- baseLanguage: Option.getOrUndefined(flags.baseLanguage),
23
- language: Option.isSome(flags.language) ? [flags.language.value] : void 0,
24
- adapter: Option.getOrUndefined(flags.adapter),
25
- strategy: Option.getOrUndefined(flags.strategy) === "one-shot" || Option.getOrUndefined(flags.strategy) === "tool-loop-agent" ? Option.getOrUndefined(flags.strategy) : void 0
26
- }, loaded);
27
- const model = yield* modelResolver(flags.fast ? effective.fastModel : effective.model);
28
- const translationStrategy = effective.strategy === "tool-loop-agent" ? createToolLoopStrategy({
29
- model,
30
- retry: effective.retry
31
- }) : createOneShotStrategy({
32
- model,
33
- retry: effective.retry
34
- });
35
- yield* translationRunner({
36
- adapters: effective.adapters,
37
- strategy: translationStrategy,
38
- sourceLocale: effective.sourceLocale,
39
- targetLocales: effective.targetLocales ?? [],
40
- chunking: effective.chunking
41
- });
42
- const format = detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0);
43
- yield* logger(formatTranslate({
44
- success: true,
45
- message: "Translation complete.",
46
- stats: {
47
- adaptersProcessed: effective.adapters.length,
48
- localesTranslated: (effective.targetLocales ?? []).length,
49
- keysTranslated: 0
50
- }
51
- }, format));
52
- });
38
+ function runTranslate(
39
+ flags,
40
+ configLoader = loadConfig,
41
+ modelResolver = resolveModel,
42
+ translationRunner = runTranslation,
43
+ logger = (msg) => Console.log(msg),
44
+ ) {
45
+ return Effect.gen(function* () {
46
+ const loaded = yield* configLoader(flags.config);
47
+ const effective = resolveEffectiveConfig(
48
+ {
49
+ baseLanguage: Option.getOrUndefined(flags.baseLanguage),
50
+ language: Option.isSome(flags.language) ? [flags.language.value] : void 0,
51
+ adapter: Option.getOrUndefined(flags.adapter),
52
+ strategy:
53
+ Option.getOrUndefined(flags.strategy) === "one-shot" ||
54
+ Option.getOrUndefined(flags.strategy) === "tool-loop-agent"
55
+ ? Option.getOrUndefined(flags.strategy)
56
+ : void 0,
57
+ },
58
+ loaded,
59
+ );
60
+ const model = yield* modelResolver(flags.fast ? effective.fastModel : effective.model);
61
+ const translationStrategy =
62
+ effective.strategy === "tool-loop-agent"
63
+ ? createToolLoopStrategy({
64
+ model,
65
+ retry: effective.retry,
66
+ })
67
+ : createOneShotStrategy({
68
+ model,
69
+ retry: effective.retry,
70
+ });
71
+ yield* translationRunner({
72
+ adapters: effective.adapters,
73
+ strategy: translationStrategy,
74
+ sourceLocale: effective.sourceLocale,
75
+ targetLocales: effective.targetLocales ?? [],
76
+ chunking: effective.chunking,
77
+ });
78
+ const format = detectFormat(
79
+ flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0,
80
+ );
81
+ yield* logger(
82
+ formatTranslate(
83
+ {
84
+ success: true,
85
+ message: "Translation complete.",
86
+ stats: {
87
+ adaptersProcessed: effective.adapters.length,
88
+ localesTranslated: (effective.targetLocales ?? []).length,
89
+ keysTranslated: 0,
90
+ },
91
+ },
92
+ format,
93
+ ),
94
+ );
95
+ });
53
96
  }
54
- const translateCommand = Command.make("translate", {
55
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
56
- adapter: Options.optional(Options.text("adapter")),
57
- strategy: Options.optional(Options.text("strategy")),
58
- baseLanguage: Options.optional(Options.text("base-language")),
59
- language: Options.optional(Options.text("language")),
60
- name: Options.optional(Options.text("name")),
61
- skipNames: Options.boolean("skip-names"),
62
- skipLanguages: Options.boolean("skip-languages"),
63
- fast: Options.boolean("fast"),
64
- format: Options.optional(Options.text("format"))
65
- }, (flags) => runTranslate(flags));
97
+ const translateCommand = Command.make(
98
+ "translate",
99
+ {
100
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
101
+ adapter: Options.optional(Options.text("adapter")),
102
+ strategy: Options.optional(Options.text("strategy")),
103
+ baseLanguage: Options.optional(Options.text("base-language")),
104
+ language: Options.optional(Options.text("language")),
105
+ name: Options.optional(Options.text("name")),
106
+ skipNames: Options.boolean("skip-names"),
107
+ skipLanguages: Options.boolean("skip-languages"),
108
+ fast: Options.boolean("fast"),
109
+ format: Options.optional(Options.text("format")),
110
+ },
111
+ (flags) => runTranslate(flags),
112
+ );
66
113
  //#endregion
67
114
  //#region src/cli/commands/validate.ts
68
- function runValidate(flags, configLoader = loadConfig, missingKeysComputer = computeMissingKeys, logger = (msg) => Console.log(msg)) {
69
- return Effect.gen(function* () {
70
- const loaded = yield* configLoader(flags.config);
71
- const effective = resolveEffectiveConfig({
72
- baseLanguage: Option.getOrUndefined(flags.baseLanguage),
73
- language: Option.isSome(flags.language) ? [flags.language.value] : void 0,
74
- adapter: Option.getOrUndefined(flags.adapter)
75
- }, loaded);
76
- const entries = [];
77
- for (const a of effective.adapters) {
78
- const locales = yield* a.listLocales();
79
- const sourceLocale = effective.sourceLocale;
80
- const missingEntries = yield* missingKeysComputer(a, sourceLocale, locales.filter((l) => l !== sourceLocale));
81
- for (const entry of missingEntries) entries.push({
82
- adapter: entry.adapter,
83
- locale: entry.locale,
84
- resource: entry.resource.label,
85
- count: entry.missing.length
86
- });
87
- }
88
- const format = detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0);
89
- const passing = entries.length === 0;
90
- yield* logger(formatValidate({
91
- passing,
92
- entries
93
- }, format));
94
- if (!passing) yield* Effect.sync(() => {
95
- process.exitCode = 1;
96
- });
97
- }).pipe(Effect.mapError((e) => e));
115
+ function runValidate(
116
+ flags,
117
+ configLoader = loadConfig,
118
+ missingKeysComputer = computeMissingKeys,
119
+ logger = (msg) => Console.log(msg),
120
+ ) {
121
+ return Effect.gen(function* () {
122
+ const loaded = yield* configLoader(flags.config);
123
+ const effective = resolveEffectiveConfig(
124
+ {
125
+ baseLanguage: Option.getOrUndefined(flags.baseLanguage),
126
+ language: Option.isSome(flags.language) ? [flags.language.value] : void 0,
127
+ adapter: Option.getOrUndefined(flags.adapter),
128
+ },
129
+ loaded,
130
+ );
131
+ const entries = [];
132
+ for (const a of effective.adapters) {
133
+ const locales = yield* a.listLocales();
134
+ const sourceLocale = effective.sourceLocale;
135
+ const missingEntries = yield* missingKeysComputer(
136
+ a,
137
+ sourceLocale,
138
+ locales.filter((l) => l !== sourceLocale),
139
+ );
140
+ for (const entry of missingEntries)
141
+ entries.push({
142
+ adapter: entry.adapter,
143
+ locale: entry.locale,
144
+ resource: entry.resource.label,
145
+ count: entry.missing.length,
146
+ });
147
+ }
148
+ const format = detectFormat(
149
+ flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0,
150
+ );
151
+ const passing = entries.length === 0;
152
+ yield* logger(
153
+ formatValidate(
154
+ {
155
+ passing,
156
+ entries,
157
+ },
158
+ format,
159
+ ),
160
+ );
161
+ if (!passing)
162
+ yield* Effect.sync(() => {
163
+ process.exitCode = 1;
164
+ });
165
+ }).pipe(Effect.mapError((e) => e));
98
166
  }
99
- const validateCommand = Command.make("validate", {
100
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
101
- adapter: Options.optional(Options.text("adapter")),
102
- baseLanguage: Options.optional(Options.text("base-language")),
103
- language: Options.optional(Options.text("language")),
104
- format: Options.optional(Options.text("format"))
105
- }, (flags) => runValidate(flags));
167
+ const validateCommand = Command.make(
168
+ "validate",
169
+ {
170
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
171
+ adapter: Options.optional(Options.text("adapter")),
172
+ baseLanguage: Options.optional(Options.text("base-language")),
173
+ language: Options.optional(Options.text("language")),
174
+ format: Options.optional(Options.text("format")),
175
+ },
176
+ (flags) => runValidate(flags),
177
+ );
106
178
  //#endregion
107
179
  //#region src/cli/commands/add.ts
108
180
  function parseAddTokens(tokens, errorLogger) {
109
- return Effect.gen(function* () {
110
- const entriesByResource = {};
111
- for (const token of tokens) {
112
- const eqIdx = token.indexOf("=");
113
- if (eqIdx === -1) {
114
- yield* errorLogger(`Invalid token (missing '='): ${token}`);
115
- continue;
116
- }
117
- const key = token.slice(0, eqIdx);
118
- const value = token.slice(eqIdx + 1);
119
- const dotIdx = key.indexOf(".");
120
- if (dotIdx === -1) {
121
- yield* errorLogger(`Invalid key (no resource segment): ${key}`);
122
- continue;
123
- }
124
- const resource = key.slice(0, dotIdx);
125
- const subKey = key.slice(dotIdx + 1);
126
- if (!entriesByResource[resource]) entriesByResource[resource] = {};
127
- entriesByResource[resource][subKey] = value;
128
- }
129
- return entriesByResource;
130
- });
181
+ return Effect.gen(function* () {
182
+ const entriesByResource = {};
183
+ for (const token of tokens) {
184
+ const eqIdx = token.indexOf("=");
185
+ if (eqIdx === -1) {
186
+ yield* errorLogger(`Invalid token (missing '='): ${token}`);
187
+ continue;
188
+ }
189
+ const key = token.slice(0, eqIdx);
190
+ const value = token.slice(eqIdx + 1);
191
+ const dotIdx = key.indexOf(".");
192
+ if (dotIdx === -1) {
193
+ yield* errorLogger(`Invalid key (no resource segment): ${key}`);
194
+ continue;
195
+ }
196
+ const resource = key.slice(0, dotIdx);
197
+ const subKey = key.slice(dotIdx + 1);
198
+ if (!entriesByResource[resource]) entriesByResource[resource] = {};
199
+ entriesByResource[resource][subKey] = value;
200
+ }
201
+ return entriesByResource;
202
+ });
131
203
  }
132
- function runAdd(flags, tokens, configLoader = loadConfig, modelResolver = resolveModel, translationRunner = runTranslation, logger = (msg) => Console.log(msg), errorLogger = (msg) => Console.error(msg)) {
133
- return Effect.gen(function* () {
134
- const effective = resolveEffectiveConfig({}, yield* configLoader(flags.config));
135
- const entriesByResource = yield* parseAddTokens(tokens, errorLogger);
136
- const addedResources = [];
137
- for (const adapter of effective.adapters) for (const [resourceKey, entries] of Object.entries(entriesByResource)) {
138
- const resourceRef = {
139
- key: resourceKey,
140
- label: resourceKey
141
- };
142
- yield* adapter.writeResource(effective.sourceLocale, resourceRef, entries);
143
- addedResources.push(`${adapter.name}/${effective.sourceLocale}/${resourceKey}`);
144
- }
145
- const modelConfig = effective.model;
146
- const model = yield* modelResolver(modelConfig);
147
- const translationStrategy = effective.strategy === "tool-loop-agent" ? createToolLoopStrategy({
148
- model,
149
- retry: effective.retry
150
- }) : createOneShotStrategy({
151
- model,
152
- retry: effective.retry
153
- });
154
- yield* translationRunner({
155
- adapters: effective.adapters,
156
- strategy: translationStrategy,
157
- sourceLocale: effective.sourceLocale,
158
- targetLocales: (effective.targetLocales ?? []).filter((l) => l !== effective.sourceLocale),
159
- chunking: effective.chunking
160
- });
161
- const format = detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0);
162
- yield* logger(formatAdd({
163
- success: true,
164
- message: "Add + translate complete.",
165
- addedResources
166
- }, format));
167
- });
204
+ function runAdd(
205
+ flags,
206
+ tokens,
207
+ configLoader = loadConfig,
208
+ modelResolver = resolveModel,
209
+ translationRunner = runTranslation,
210
+ logger = (msg) => Console.log(msg),
211
+ errorLogger = (msg) => Console.error(msg),
212
+ ) {
213
+ return Effect.gen(function* () {
214
+ const effective = resolveEffectiveConfig({}, yield* configLoader(flags.config));
215
+ const entriesByResource = yield* parseAddTokens(tokens, errorLogger);
216
+ const addedResources = [];
217
+ for (const adapter of effective.adapters)
218
+ for (const [resourceKey, entries] of Object.entries(entriesByResource)) {
219
+ const resourceRef = {
220
+ key: resourceKey,
221
+ label: resourceKey,
222
+ };
223
+ yield* adapter.writeResource(effective.sourceLocale, resourceRef, entries);
224
+ addedResources.push(`${adapter.name}/${effective.sourceLocale}/${resourceKey}`);
225
+ }
226
+ const modelConfig = effective.model;
227
+ const model = yield* modelResolver(modelConfig);
228
+ const translationStrategy =
229
+ effective.strategy === "tool-loop-agent"
230
+ ? createToolLoopStrategy({
231
+ model,
232
+ retry: effective.retry,
233
+ })
234
+ : createOneShotStrategy({
235
+ model,
236
+ retry: effective.retry,
237
+ });
238
+ yield* translationRunner({
239
+ adapters: effective.adapters,
240
+ strategy: translationStrategy,
241
+ sourceLocale: effective.sourceLocale,
242
+ targetLocales: (effective.targetLocales ?? []).filter((l) => l !== effective.sourceLocale),
243
+ chunking: effective.chunking,
244
+ });
245
+ const format = detectFormat(
246
+ flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0,
247
+ );
248
+ yield* logger(
249
+ formatAdd(
250
+ {
251
+ success: true,
252
+ message: "Add + translate complete.",
253
+ addedResources,
254
+ },
255
+ format,
256
+ ),
257
+ );
258
+ });
168
259
  }
169
- const addCommand = Command.make("add", {
170
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
171
- create: Options.boolean("create"),
172
- format: Options.optional(Options.text("format"))
173
- }, ({ config, create, format }) => {
174
- const rawTokens = process.argv.slice(3).filter((t) => !t.startsWith("--") && !t.startsWith("-"));
175
- return runAdd({
176
- config,
177
- create,
178
- format
179
- }, rawTokens);
180
- });
260
+ const addCommand = Command.make(
261
+ "add",
262
+ {
263
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
264
+ create: Options.boolean("create"),
265
+ format: Options.optional(Options.text("format")),
266
+ },
267
+ ({ config, create, format }) => {
268
+ const rawTokens = process.argv
269
+ .slice(3)
270
+ .filter((t) => !t.startsWith("--") && !t.startsWith("-"));
271
+ return runAdd(
272
+ {
273
+ config,
274
+ create,
275
+ format,
276
+ },
277
+ rawTokens,
278
+ );
279
+ },
280
+ );
181
281
  //#endregion
182
282
  //#region src/cli/commands/missing.ts
183
- function runMissing(flags, configLoader = loadConfig, missingKeysComputer = computeMissingKeys, logger = (msg) => Console.log(msg)) {
184
- return Effect.gen(function* () {
185
- const loaded = yield* configLoader(flags.config);
186
- const effective = resolveEffectiveConfig({
187
- baseLanguage: Option.getOrUndefined(flags.baseLanguage),
188
- language: Option.isSome(flags.language) ? [flags.language.value] : void 0,
189
- adapter: Option.getOrUndefined(flags.adapter)
190
- }, loaded);
191
- const allEntries = [];
192
- for (const a of effective.adapters) {
193
- const locales = yield* a.listLocales();
194
- const sourceLocale = effective.sourceLocale;
195
- const entries = yield* missingKeysComputer(a, sourceLocale, locales.filter((l) => l !== sourceLocale));
196
- for (const entry of entries) for (const key of entry.missing) allEntries.push({
197
- adapter: entry.adapter,
198
- locale: entry.locale,
199
- resource: entry.resource.label,
200
- key
201
- });
202
- }
203
- yield* logger(formatMissingKeys(allEntries, detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0)));
204
- }).pipe(Effect.mapError((e) => e));
283
+ function runMissing(
284
+ flags,
285
+ configLoader = loadConfig,
286
+ missingKeysComputer = computeMissingKeys,
287
+ logger = (msg) => Console.log(msg),
288
+ ) {
289
+ return Effect.gen(function* () {
290
+ const loaded = yield* configLoader(flags.config);
291
+ const effective = resolveEffectiveConfig(
292
+ {
293
+ baseLanguage: Option.getOrUndefined(flags.baseLanguage),
294
+ language: Option.isSome(flags.language) ? [flags.language.value] : void 0,
295
+ adapter: Option.getOrUndefined(flags.adapter),
296
+ },
297
+ loaded,
298
+ );
299
+ const allEntries = [];
300
+ for (const a of effective.adapters) {
301
+ const locales = yield* a.listLocales();
302
+ const sourceLocale = effective.sourceLocale;
303
+ const entries = yield* missingKeysComputer(
304
+ a,
305
+ sourceLocale,
306
+ locales.filter((l) => l !== sourceLocale),
307
+ );
308
+ for (const entry of entries)
309
+ for (const key of entry.missing)
310
+ allEntries.push({
311
+ adapter: entry.adapter,
312
+ locale: entry.locale,
313
+ resource: entry.resource.label,
314
+ key,
315
+ });
316
+ }
317
+ yield* logger(
318
+ formatMissingKeys(
319
+ allEntries,
320
+ detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0),
321
+ ),
322
+ );
323
+ }).pipe(Effect.mapError((e) => e));
205
324
  }
206
- const missingCommand = Command.make("missing", {
207
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
208
- adapter: Options.optional(Options.text("adapter")),
209
- baseLanguage: Options.optional(Options.text("base-language")),
210
- language: Options.optional(Options.text("language")),
211
- format: Options.optional(Options.text("format"))
212
- }, (flags) => runMissing(flags));
325
+ const missingCommand = Command.make(
326
+ "missing",
327
+ {
328
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
329
+ adapter: Options.optional(Options.text("adapter")),
330
+ baseLanguage: Options.optional(Options.text("base-language")),
331
+ language: Options.optional(Options.text("language")),
332
+ format: Options.optional(Options.text("format")),
333
+ },
334
+ (flags) => runMissing(flags),
335
+ );
213
336
  //#endregion
214
337
  //#region src/cli/commands/unused.ts
215
- function runUnused(flags, configLoader = loadConfig, logger = (msg) => Console.log(msg), errorLogger = (msg) => Console.error(msg)) {
216
- return Effect.gen(function* () {
217
- const loaded = yield* configLoader(flags.config);
218
- const effective = resolveEffectiveConfig({
219
- baseLanguage: Option.getOrUndefined(flags.baseLanguage),
220
- adapter: Option.getOrUndefined(flags.adapter)
221
- }, loaded);
222
- const allEntries = [];
223
- for (const a of effective.adapters) {
224
- if (!a.capabilities.unusedKeyDetection) {
225
- yield* errorLogger(formatError(`Adapter '${a.name}' does not support unused-key detection.`, detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0)));
226
- continue;
227
- }
228
- yield* a.listLocales();
229
- const sourceLocale = effective.sourceLocale;
230
- const resources = yield* a.listResources(sourceLocale);
231
- for (const resource of resources) {
232
- const unused = yield* a.findUnusedKeys(sourceLocale, resource);
233
- for (const key of unused) allEntries.push({
234
- adapter: a.name,
235
- locale: sourceLocale,
236
- resource: resource.label,
237
- key
238
- });
239
- }
240
- }
241
- yield* logger(formatUnusedKeys(allEntries, detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0)));
242
- }).pipe(Effect.mapError((e) => e));
338
+ function runUnused(
339
+ flags,
340
+ configLoader = loadConfig,
341
+ logger = (msg) => Console.log(msg),
342
+ errorLogger = (msg) => Console.error(msg),
343
+ ) {
344
+ return Effect.gen(function* () {
345
+ const loaded = yield* configLoader(flags.config);
346
+ const effective = resolveEffectiveConfig(
347
+ {
348
+ baseLanguage: Option.getOrUndefined(flags.baseLanguage),
349
+ adapter: Option.getOrUndefined(flags.adapter),
350
+ },
351
+ loaded,
352
+ );
353
+ const allEntries = [];
354
+ for (const a of effective.adapters) {
355
+ if (!a.capabilities.unusedKeyDetection) {
356
+ yield* errorLogger(
357
+ formatError(
358
+ `Adapter '${a.name}' does not support unused-key detection.`,
359
+ detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0),
360
+ ),
361
+ );
362
+ continue;
363
+ }
364
+ yield* a.listLocales();
365
+ const sourceLocale = effective.sourceLocale;
366
+ const resources = yield* a.listResources(sourceLocale);
367
+ for (const resource of resources) {
368
+ const unused = yield* a.findUnusedKeys(sourceLocale, resource);
369
+ for (const key of unused)
370
+ allEntries.push({
371
+ adapter: a.name,
372
+ locale: sourceLocale,
373
+ resource: resource.label,
374
+ key,
375
+ });
376
+ }
377
+ }
378
+ yield* logger(
379
+ formatUnusedKeys(
380
+ allEntries,
381
+ detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0),
382
+ ),
383
+ );
384
+ }).pipe(Effect.mapError((e) => e));
243
385
  }
244
- const unusedCommand = Command.make("unused", {
245
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
246
- adapter: Options.optional(Options.text("adapter")),
247
- baseLanguage: Options.optional(Options.text("base-language")),
248
- format: Options.optional(Options.text("format"))
249
- }, (flags) => runUnused(flags));
386
+ const unusedCommand = Command.make(
387
+ "unused",
388
+ {
389
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
390
+ adapter: Options.optional(Options.text("adapter")),
391
+ baseLanguage: Options.optional(Options.text("base-language")),
392
+ format: Options.optional(Options.text("format")),
393
+ },
394
+ (flags) => runUnused(flags),
395
+ );
250
396
  //#endregion
251
397
  //#region src/cli/commands/languages.ts
252
398
  function runLanguages(flags, configLoader = loadConfig, logger = (msg) => Console.log(msg)) {
253
- return Effect.gen(function* () {
254
- const effective = resolveEffectiveConfig({}, yield* configLoader(flags.config));
255
- const entries = [];
256
- for (const adapter of effective.adapters) {
257
- const locales = yield* adapter.listLocales();
258
- entries.push({
259
- adapter: adapter.name,
260
- locales
261
- });
262
- }
263
- yield* logger(formatLanguages(entries, detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0)));
264
- }).pipe(Effect.mapError((e) => e));
399
+ return Effect.gen(function* () {
400
+ const effective = resolveEffectiveConfig({}, yield* configLoader(flags.config));
401
+ const entries = [];
402
+ for (const adapter of effective.adapters) {
403
+ const locales = yield* adapter.listLocales();
404
+ entries.push({
405
+ adapter: adapter.name,
406
+ locales,
407
+ });
408
+ }
409
+ yield* logger(
410
+ formatLanguages(
411
+ entries,
412
+ detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0),
413
+ ),
414
+ );
415
+ }).pipe(Effect.mapError((e) => e));
265
416
  }
266
- const languagesCommand = Command.make("languages", {
267
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
268
- format: Options.optional(Options.text("format"))
269
- }, (flags) => runLanguages(flags));
417
+ const languagesCommand = Command.make(
418
+ "languages",
419
+ {
420
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
421
+ format: Options.optional(Options.text("format")),
422
+ },
423
+ (flags) => runLanguages(flags),
424
+ );
270
425
  //#endregion
271
426
  //#region src/benchmark/metrics.ts
272
427
  function summarizeBenchmarkResults(results) {
273
- const totalChunks = results.length;
274
- const succeededChunks = results.filter((r) => r.succeeded).length;
275
- const failedChunks = totalChunks - succeededChunks;
276
- const totalDurationMs = results.reduce((sum, r) => sum + r.durationMs, 0);
277
- const totalAttempts = results.reduce((sum, r) => sum + r.attemptCount, 0);
278
- return {
279
- strategyName: results[0]?.strategyName ?? "one-shot",
280
- totalChunks,
281
- succeededChunks,
282
- failedChunks,
283
- totalDurationMs,
284
- averageDurationMsPerChunk: totalChunks > 0 ? totalDurationMs / totalChunks : 0,
285
- totalAttempts
286
- };
428
+ const totalChunks = results.length;
429
+ const succeededChunks = results.filter((r) => r.succeeded).length;
430
+ const failedChunks = totalChunks - succeededChunks;
431
+ const totalDurationMs = results.reduce((sum, r) => sum + r.durationMs, 0);
432
+ const totalAttempts = results.reduce((sum, r) => sum + r.attemptCount, 0);
433
+ return {
434
+ strategyName: results[0]?.strategyName ?? "one-shot",
435
+ totalChunks,
436
+ succeededChunks,
437
+ failedChunks,
438
+ totalDurationMs,
439
+ averageDurationMsPerChunk: totalChunks > 0 ? totalDurationMs / totalChunks : 0,
440
+ totalAttempts,
441
+ };
287
442
  }
288
443
  function runBenchmarkedChunk(strategy, ctx) {
289
- return Effect.gen(function* () {
290
- const start = Date.now();
291
- const result = yield* Effect.either(strategy.translateChunk(ctx));
292
- const durationMs = Date.now() - start;
293
- if (result._tag === "Right") return {
294
- strategyName: strategy.name,
295
- chunkKeyCount: ctx.keys.length,
296
- durationMs,
297
- attemptCount: 1,
298
- succeeded: true,
299
- errorMessage: void 0
300
- };
301
- return {
302
- strategyName: strategy.name,
303
- chunkKeyCount: ctx.keys.length,
304
- durationMs,
305
- attemptCount: 1,
306
- succeeded: false,
307
- errorMessage: String(result.left.cause)
308
- };
309
- });
444
+ return Effect.gen(function* () {
445
+ const start = Date.now();
446
+ const result = yield* Effect.either(strategy.translateChunk(ctx));
447
+ const durationMs = Date.now() - start;
448
+ if (result._tag === "Right")
449
+ return {
450
+ strategyName: strategy.name,
451
+ chunkKeyCount: ctx.keys.length,
452
+ durationMs,
453
+ attemptCount: 1,
454
+ succeeded: true,
455
+ errorMessage: void 0,
456
+ };
457
+ return {
458
+ strategyName: strategy.name,
459
+ chunkKeyCount: ctx.keys.length,
460
+ durationMs,
461
+ attemptCount: 1,
462
+ succeeded: false,
463
+ errorMessage: String(result.left.cause),
464
+ };
465
+ });
310
466
  }
311
467
  //#endregion
312
468
  //#region src/benchmark/runner.ts
313
469
  function runBenchmark(config) {
314
- return Effect.gen(function* () {
315
- const summaries = [];
316
- for (const strategy of config.strategies) {
317
- const results = yield* Effect.forEach(config.chunks, (chunk) => runBenchmarkedChunk(strategy, chunk), { concurrency: config.concurrency });
318
- summaries.push(summarizeBenchmarkResults(results));
319
- }
320
- return summaries;
321
- });
470
+ return Effect.gen(function* () {
471
+ const summaries = [];
472
+ for (const strategy of config.strategies) {
473
+ const results = yield* Effect.forEach(
474
+ config.chunks,
475
+ (chunk) => runBenchmarkedChunk(strategy, chunk),
476
+ { concurrency: config.concurrency },
477
+ );
478
+ summaries.push(summarizeBenchmarkResults(results));
479
+ }
480
+ return summaries;
481
+ });
322
482
  }
323
483
  //#endregion
324
484
  //#region src/cli/commands/benchmark.ts
325
485
  function runBenchmarkCommand(flags, deps) {
326
- return Effect.gen(function* () {
327
- yield* deps.errorLogger(formatError("Warning: This will make real API calls to the configured model provider(s) and may incur cost.", detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0)));
328
- const loaded = yield* deps.configLoader(flags.config);
329
- const effective = resolveEffectiveConfig({ adapter: Option.getOrUndefined(flags.adapter) }, loaded);
330
- const strategyNames = Option.getOrElse(flags.strategies, () => "one-shot,tool-loop-agent").split(",").map((s) => s.trim());
331
- const model = yield* deps.modelResolver(effective.model);
332
- const strategyList = strategyNames.map((name) => name === "tool-loop-agent" ? createToolLoopStrategy({
333
- model,
334
- retry: effective.retry
335
- }) : createOneShotStrategy({
336
- model,
337
- retry: effective.retry
338
- }));
339
- const allChunks = [];
340
- for (const a of effective.adapters) {
341
- const locales = yield* a.listLocales();
342
- const sourceLocale = effective.sourceLocale;
343
- const targets = locales.filter((l) => l !== sourceLocale);
344
- const missingEntries = yield* deps.missingKeysComputer(a, sourceLocale, targets);
345
- for (const entry of missingEntries) {
346
- const sourceMap = yield* a.readResource(sourceLocale, entry.resource);
347
- const targetMap = yield* a.readResource(entry.locale, entry.resource);
348
- const chunks = chunkKeys(entry.missing, sourceMap, targetMap, {
349
- maxTokens: effective.chunking.maxTokens,
350
- charsPerToken: effective.chunking.charsPerToken
351
- });
352
- for (const keys of chunks) allChunks.push({
353
- sourceLocale,
354
- targetLocale: entry.locale,
355
- sourceMap,
356
- targetMap,
357
- keys
358
- });
359
- }
360
- }
361
- const sampled = allChunks.slice(0, Option.getOrElse(flags.sampleSize, () => 20));
362
- const summaries = yield* deps.benchmarkRunner({
363
- strategies: strategyList,
364
- chunks: sampled,
365
- concurrency: effective.chunking.concurrency
366
- });
367
- const format = detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0);
368
- const entries = summaries.map((s) => ({
369
- strategyName: s.strategyName,
370
- totalChunks: s.totalChunks,
371
- succeededChunks: s.succeededChunks,
372
- failedChunks: s.failedChunks,
373
- totalDurationMs: s.totalDurationMs,
374
- averageDurationMsPerChunk: s.averageDurationMsPerChunk,
375
- totalAttempts: s.totalAttempts
376
- }));
377
- yield* deps.logger(formatBenchmark(entries, format));
378
- });
486
+ return Effect.gen(function* () {
487
+ yield* deps.errorLogger(
488
+ formatError(
489
+ "Warning: This will make real API calls to the configured model provider(s) and may incur cost.",
490
+ detectFormat(flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0),
491
+ ),
492
+ );
493
+ const loaded = yield* deps.configLoader(flags.config);
494
+ const effective = resolveEffectiveConfig(
495
+ { adapter: Option.getOrUndefined(flags.adapter) },
496
+ loaded,
497
+ );
498
+ const strategyNames = Option.getOrElse(flags.strategies, () => "one-shot,tool-loop-agent")
499
+ .split(",")
500
+ .map((s) => s.trim());
501
+ const model = yield* deps.modelResolver(effective.model);
502
+ const strategyList = strategyNames.map((name) =>
503
+ name === "tool-loop-agent"
504
+ ? createToolLoopStrategy({
505
+ model,
506
+ retry: effective.retry,
507
+ })
508
+ : createOneShotStrategy({
509
+ model,
510
+ retry: effective.retry,
511
+ }),
512
+ );
513
+ const allChunks = [];
514
+ for (const a of effective.adapters) {
515
+ const locales = yield* a.listLocales();
516
+ const sourceLocale = effective.sourceLocale;
517
+ const targets = locales.filter((l) => l !== sourceLocale);
518
+ const missingEntries = yield* deps.missingKeysComputer(a, sourceLocale, targets);
519
+ for (const entry of missingEntries) {
520
+ const sourceMap = yield* a.readResource(sourceLocale, entry.resource);
521
+ const targetMap = yield* a.readResource(entry.locale, entry.resource);
522
+ const chunks = chunkKeys(entry.missing, sourceMap, targetMap, {
523
+ maxTokens: effective.chunking.maxTokens,
524
+ charsPerToken: effective.chunking.charsPerToken,
525
+ });
526
+ for (const keys of chunks)
527
+ allChunks.push({
528
+ sourceLocale,
529
+ targetLocale: entry.locale,
530
+ sourceMap,
531
+ targetMap,
532
+ keys,
533
+ });
534
+ }
535
+ }
536
+ const sampled = allChunks.slice(
537
+ 0,
538
+ Option.getOrElse(flags.sampleSize, () => 20),
539
+ );
540
+ const summaries = yield* deps.benchmarkRunner({
541
+ strategies: strategyList,
542
+ chunks: sampled,
543
+ concurrency: effective.chunking.concurrency,
544
+ });
545
+ const format = detectFormat(
546
+ flags.format !== void 0 ? Option.getOrUndefined(flags.format) : void 0,
547
+ );
548
+ const entries = summaries.map((s) => ({
549
+ strategyName: s.strategyName,
550
+ totalChunks: s.totalChunks,
551
+ succeededChunks: s.succeededChunks,
552
+ failedChunks: s.failedChunks,
553
+ totalDurationMs: s.totalDurationMs,
554
+ averageDurationMsPerChunk: s.averageDurationMsPerChunk,
555
+ totalAttempts: s.totalAttempts,
556
+ }));
557
+ yield* deps.logger(formatBenchmark(entries, format));
558
+ });
379
559
  }
380
- const benchmarkCommand = Command.make("benchmark", {
381
- config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
382
- adapter: Options.optional(Options.text("adapter")),
383
- strategies: Options.optional(Options.text("strategies")),
384
- sampleSize: Options.optional(Options.integer("sample-size")),
385
- format: Options.optional(Options.text("format"))
386
- }, (flags) => runBenchmarkCommand(flags, {
387
- configLoader: loadConfig,
388
- modelResolver: resolveModel,
389
- missingKeysComputer: computeMissingKeys,
390
- benchmarkRunner: runBenchmark,
391
- logger: (msg) => Console.log(msg),
392
- errorLogger: (msg) => Console.error(msg)
393
- }));
560
+ const benchmarkCommand = Command.make(
561
+ "benchmark",
562
+ {
563
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
564
+ adapter: Options.optional(Options.text("adapter")),
565
+ strategies: Options.optional(Options.text("strategies")),
566
+ sampleSize: Options.optional(Options.integer("sample-size")),
567
+ format: Options.optional(Options.text("format")),
568
+ },
569
+ (flags) =>
570
+ runBenchmarkCommand(flags, {
571
+ configLoader: loadConfig,
572
+ modelResolver: resolveModel,
573
+ missingKeysComputer: computeMissingKeys,
574
+ benchmarkRunner: runBenchmark,
575
+ logger: (msg) => Console.log(msg),
576
+ errorLogger: (msg) => Console.error(msg),
577
+ }),
578
+ );
394
579
  //#endregion
395
580
  //#region src/cli/main.ts
396
- const rootCommand = Command.make("dialekt").pipe(Command.withSubcommands([
397
- translateCommand,
398
- validateCommand,
399
- addCommand,
400
- missingCommand,
401
- unusedCommand,
402
- languagesCommand,
403
- benchmarkCommand
404
- ]));
581
+ const rootCommand = Command.make("dialekt").pipe(
582
+ Command.withSubcommands([
583
+ translateCommand,
584
+ validateCommand,
585
+ addCommand,
586
+ missingCommand,
587
+ unusedCommand,
588
+ languagesCommand,
589
+ benchmarkCommand,
590
+ ]),
591
+ );
405
592
  const cli = Command.run(rootCommand, {
406
- name: "dialekt",
407
- version: "0.1.0"
593
+ name: "dialekt",
594
+ version: "0.1.0",
408
595
  });
409
596
  const program = Effect.provide(cli(process.argv), NodeContext.layer);
410
597
  NodeRuntime.runMain(program);