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