dialekt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -0
- package/TESTING.md +66 -0
- package/dist/cli/main.d.mts +1 -0
- package/dist/cli/main.mjs +412 -0
- package/dist/formatters-De4Q-X1d.mjs +577 -0
- package/dist/index.d.mts +329 -0
- package/dist/index.mjs +60 -0
- package/package.json +39 -0
- package/pnpm-workspace.yaml +7 -0
- package/src/adapter/types.test.ts +98 -0
- package/src/adapter/types.ts +73 -0
- package/src/benchmark/metrics.test.ts +180 -0
- package/src/benchmark/metrics.ts +69 -0
- package/src/benchmark/report.test.ts +129 -0
- package/src/benchmark/report.ts +21 -0
- package/src/benchmark/runner.test.ts +162 -0
- package/src/benchmark/runner.ts +27 -0
- package/src/cli/commands/add.test.ts +267 -0
- package/src/cli/commands/add.ts +123 -0
- package/src/cli/commands/benchmark.test.ts +346 -0
- package/src/cli/commands/benchmark.ts +148 -0
- package/src/cli/commands/languages.test.ts +127 -0
- package/src/cli/commands/languages.ts +42 -0
- package/src/cli/commands/missing.test.ts +256 -0
- package/src/cli/commands/missing.ts +88 -0
- package/src/cli/commands/translate.test.ts +384 -0
- package/src/cli/commands/translate.ts +106 -0
- package/src/cli/commands/unused.test.ts +192 -0
- package/src/cli/commands/unused.ts +87 -0
- package/src/cli/commands/validate.test.ts +245 -0
- package/src/cli/commands/validate.ts +96 -0
- package/src/cli/config-resolution.test.ts +99 -0
- package/src/cli/config-resolution.ts +29 -0
- package/src/cli/format.test.ts +117 -0
- package/src/cli/format.ts +205 -0
- package/src/cli/formatters.test.ts +186 -0
- package/src/cli/formatters.ts +350 -0
- package/src/cli/main.ts +31 -0
- package/src/config/define-config.test.ts +66 -0
- package/src/config/define-config.ts +5 -0
- package/src/config/load-config.test.ts +35 -0
- package/src/config/load-config.ts +21 -0
- package/src/config/types.test.ts +101 -0
- package/src/config/types.ts +28 -0
- package/src/index.ts +56 -0
- package/src/keys/flatten.test.ts +111 -0
- package/src/keys/flatten.ts +41 -0
- package/src/sdk/file-io.test.ts +139 -0
- package/src/sdk/file-io.ts +21 -0
- package/src/sdk/node-layer.test.ts +54 -0
- package/src/sdk/node-layer.ts +10 -0
- package/src/sdk/php-array-reader.test.ts +114 -0
- package/src/sdk/php-array-reader.ts +26 -0
- package/src/translation/chunking.test.ts +118 -0
- package/src/translation/chunking.ts +57 -0
- package/src/translation/missing-keys.test.ts +179 -0
- package/src/translation/missing-keys.ts +36 -0
- package/src/translation/model-registry.test.ts +54 -0
- package/src/translation/model-registry.ts +43 -0
- package/src/translation/one-shot-strategy.test.ts +259 -0
- package/src/translation/one-shot-strategy.ts +48 -0
- package/src/translation/orchestrator.test.ts +276 -0
- package/src/translation/orchestrator.ts +83 -0
- package/src/translation/prompt.test.ts +149 -0
- package/src/translation/prompt.ts +42 -0
- package/src/translation/tool-loop-strategy.test.ts +279 -0
- package/src/translation/tool-loop-strategy.ts +68 -0
- package/src/translation/types.test.ts +37 -0
- package/src/translation/types.ts +21 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.ts +7 -0
- package/vitest.config.ts +8 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { NodeContext } from "@effect/platform-node";
|
|
3
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
4
|
+
import { LanguageModel } from "ai";
|
|
5
|
+
import { CommandExecutor } from "@effect/platform/CommandExecutor";
|
|
6
|
+
|
|
7
|
+
//#region src/adapter/types.d.ts
|
|
8
|
+
/** Opaque adapter-specific identifier for one resource within a locale. */
|
|
9
|
+
interface ResourceRef {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
}
|
|
13
|
+
declare const AdapterReadError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
14
|
+
readonly _tag: "AdapterReadError";
|
|
15
|
+
} & Readonly<A>;
|
|
16
|
+
declare class AdapterReadError extends AdapterReadError_base<{
|
|
17
|
+
readonly adapter: string;
|
|
18
|
+
readonly locale: string;
|
|
19
|
+
readonly resource: string;
|
|
20
|
+
readonly cause: unknown;
|
|
21
|
+
}> {}
|
|
22
|
+
declare const AdapterWriteError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
23
|
+
readonly _tag: "AdapterWriteError";
|
|
24
|
+
} & Readonly<A>;
|
|
25
|
+
declare class AdapterWriteError extends AdapterWriteError_base<{
|
|
26
|
+
readonly adapter: string;
|
|
27
|
+
readonly locale: string;
|
|
28
|
+
readonly resource: string;
|
|
29
|
+
readonly cause: unknown;
|
|
30
|
+
}> {}
|
|
31
|
+
interface TranslationAdapter {
|
|
32
|
+
/** Stable adapter name, e.g. "laravel", "paraglide". Used in CLI --adapter flag and error messages. */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** Which optional features this adapter instance supports (see Feature flags below). */
|
|
35
|
+
readonly capabilities: AdapterCapabilities;
|
|
36
|
+
/** Auto-detect configured locales (e.g. subdirectories of a lang dir), or return the user-configured list. */
|
|
37
|
+
listLocales(): Effect.Effect<readonly string[], AdapterReadError>;
|
|
38
|
+
/** List the resources available for a given locale (e.g. domain files present for "en"). */
|
|
39
|
+
listResources(locale: string): Effect.Effect<readonly ResourceRef[], AdapterReadError>;
|
|
40
|
+
/** Read one resource, flattened to dot-notation key → string value. Returns {} if the resource does not exist. */
|
|
41
|
+
readResource(locale: string, resource: ResourceRef): Effect.Effect<Record<string, string>, AdapterReadError>;
|
|
42
|
+
/** Write a full flattened key→value map back to a resource, unflattening as needed. Creates the resource if absent and `create` capability allows it. */
|
|
43
|
+
writeResource(locale: string, resource: ResourceRef, entries: Record<string, string>): Effect.Effect<void, AdapterWriteError>;
|
|
44
|
+
/**
|
|
45
|
+
* Returns translation keys present in the resource but never referenced
|
|
46
|
+
* anywhere in the project's source code. Only called by the CLI's `unused`
|
|
47
|
+
* command when `capabilities.unusedKeyDetection` is `true` — present as an
|
|
48
|
+
* optional method (not required on every adapter) because some future
|
|
49
|
+
* adapter format may have no reliable "is this key referenced" heuristic
|
|
50
|
+
* (e.g. a flat gettext catalog with no consistent call-site convention).
|
|
51
|
+
*
|
|
52
|
+
* Deliberately minimal contract: the adapter receives no scan-path
|
|
53
|
+
* guidance, no shared "grep helper", and no hint about what a "reference"
|
|
54
|
+
* looks like in its ecosystem. It owns the entire strategy internally —
|
|
55
|
+
* Laravel scans for `__('domain.key')`-shaped calls in PHP/Blade files;
|
|
56
|
+
* Paraglide scans for `m.messageName(...)` calls in JS/TS files. Each
|
|
57
|
+
* adapter's own constructor options (`LaravelAdapterOptions`,
|
|
58
|
+
* `ParaglideAdapterOptions`) carry whatever scan-path configuration that
|
|
59
|
+
* adapter's own heuristic needs — core never sees or validates those
|
|
60
|
+
* options.
|
|
61
|
+
*/
|
|
62
|
+
findUnusedKeys?(locale: string, resource: ResourceRef): Effect.Effect<readonly string[], AdapterReadError>;
|
|
63
|
+
}
|
|
64
|
+
interface AdapterCapabilities {
|
|
65
|
+
readonly canCreateResource: boolean;
|
|
66
|
+
readonly unusedKeyDetection: boolean;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/config/types.d.ts
|
|
70
|
+
interface ModelConfig {
|
|
71
|
+
readonly provider: string;
|
|
72
|
+
readonly modelId: string;
|
|
73
|
+
}
|
|
74
|
+
interface ChunkingConfig {
|
|
75
|
+
readonly maxTokens: number;
|
|
76
|
+
readonly charsPerToken: number;
|
|
77
|
+
readonly concurrency: number;
|
|
78
|
+
}
|
|
79
|
+
interface RetryConfig {
|
|
80
|
+
readonly maxAttempts: number;
|
|
81
|
+
readonly baseDelayMs: number;
|
|
82
|
+
}
|
|
83
|
+
interface DialektConfig {
|
|
84
|
+
readonly sourceLocale: string;
|
|
85
|
+
readonly targetLocales: readonly string[] | null;
|
|
86
|
+
readonly strategy: 'one-shot' | 'tool-loop-agent';
|
|
87
|
+
readonly model: ModelConfig;
|
|
88
|
+
readonly fastModel: ModelConfig;
|
|
89
|
+
readonly chunking: ChunkingConfig;
|
|
90
|
+
readonly retry: RetryConfig;
|
|
91
|
+
readonly adapters: readonly TranslationAdapter[];
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/config/define-config.d.ts
|
|
95
|
+
declare function defineConfig(config: DialektConfig): DialektConfig;
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/keys/flatten.d.ts
|
|
98
|
+
declare function flattenObject(input: Readonly<Record<string, unknown>>, prefix?: string): Record<string, string>;
|
|
99
|
+
declare function unflattenObject(input: Readonly<Record<string, string>>): Record<string, unknown>;
|
|
100
|
+
declare function diffKeys(source: Readonly<Record<string, string>>, target: Readonly<Record<string, string>>): string[];
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/translation/chunking.d.ts
|
|
103
|
+
interface ChunkingConfig$1 {
|
|
104
|
+
readonly maxTokens: number;
|
|
105
|
+
readonly charsPerToken: number;
|
|
106
|
+
}
|
|
107
|
+
declare function chunkKeys(keys: readonly string[], sourceMap: Readonly<Record<string, string>>, targetMap: Readonly<Record<string, string>>, config: ChunkingConfig$1): string[][];
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/sdk/node-layer.d.ts
|
|
110
|
+
/**
|
|
111
|
+
* The only file in this package (besides cli/main.ts) permitted to know
|
|
112
|
+
* this is running on Node.js. Provides FileSystem, Path, and
|
|
113
|
+
* CommandExecutor. Swapping to Bun/Deno later means swapping this one
|
|
114
|
+
* import for @effect/platform-bun's equivalent — nothing else changes.
|
|
115
|
+
*/
|
|
116
|
+
declare const NodePlatformLayer: Layer.Layer<NodeContext.NodeContext, never, never>;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/sdk/file-io.d.ts
|
|
119
|
+
declare function readFileIfExists(path: string): Effect.Effect<string | null, import("@effect/platform/Error").PlatformError, FileSystem.FileSystem>;
|
|
120
|
+
declare function writeFileEnsuringDir(path: string, content: string): Effect.Effect<void, import("@effect/platform/Error").PlatformError, FileSystem.FileSystem | Path.Path>;
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/sdk/php-array-reader.d.ts
|
|
123
|
+
declare const PhpExecutionError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
124
|
+
readonly _tag: "PhpExecutionError";
|
|
125
|
+
} & Readonly<A>;
|
|
126
|
+
declare class PhpExecutionError extends PhpExecutionError_base<{
|
|
127
|
+
readonly path: string;
|
|
128
|
+
readonly cause: unknown;
|
|
129
|
+
}> {}
|
|
130
|
+
declare function readPhpArrayAsJson(absolutePath: string): Effect.Effect<Record<string, unknown>, PhpExecutionError, CommandExecutor>;
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/translation/model-registry.d.ts
|
|
133
|
+
declare const UnknownProviderError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
134
|
+
readonly _tag: "UnknownProviderError";
|
|
135
|
+
} & Readonly<A>;
|
|
136
|
+
declare class UnknownProviderError extends UnknownProviderError_base<{
|
|
137
|
+
readonly provider: string;
|
|
138
|
+
}> {}
|
|
139
|
+
interface ModelConfig$1 {
|
|
140
|
+
readonly provider: string;
|
|
141
|
+
readonly modelId: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The one file in the entire codebase allowed to import AI SDK provider packages.
|
|
145
|
+
*/
|
|
146
|
+
declare function resolveModel(config: ModelConfig$1): Effect.Effect<LanguageModel, UnknownProviderError>;
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/translation/types.d.ts
|
|
149
|
+
interface TranslationContext {
|
|
150
|
+
readonly sourceLocale: string;
|
|
151
|
+
readonly targetLocale: string;
|
|
152
|
+
readonly sourceMap: Record<string, string>;
|
|
153
|
+
readonly targetMap: Record<string, string>;
|
|
154
|
+
readonly keys: readonly string[];
|
|
155
|
+
}
|
|
156
|
+
declare const TranslationFailedError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
157
|
+
readonly _tag: "TranslationFailedError";
|
|
158
|
+
} & Readonly<A>;
|
|
159
|
+
declare class TranslationFailedError extends TranslationFailedError_base<{
|
|
160
|
+
readonly keys: readonly string[];
|
|
161
|
+
readonly cause: unknown;
|
|
162
|
+
}> {}
|
|
163
|
+
interface TranslationStrategy {
|
|
164
|
+
readonly name: 'one-shot' | 'tool-loop-agent';
|
|
165
|
+
translateChunk(ctx: TranslationContext): Effect.Effect<Record<string, string>, TranslationFailedError>;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/translation/one-shot-strategy.d.ts
|
|
169
|
+
declare function createOneShotStrategy(deps: {
|
|
170
|
+
model: LanguageModel;
|
|
171
|
+
retry: {
|
|
172
|
+
maxAttempts: number;
|
|
173
|
+
baseDelayMs: number;
|
|
174
|
+
};
|
|
175
|
+
}): TranslationStrategy;
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/translation/tool-loop-strategy.d.ts
|
|
178
|
+
declare function createToolLoopStrategy(deps: {
|
|
179
|
+
model: LanguageModel;
|
|
180
|
+
retry: {
|
|
181
|
+
maxAttempts: number;
|
|
182
|
+
baseDelayMs: number;
|
|
183
|
+
};
|
|
184
|
+
}): TranslationStrategy;
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/translation/orchestrator.d.ts
|
|
187
|
+
interface TranslationRunConfig {
|
|
188
|
+
readonly adapters: readonly TranslationAdapter[];
|
|
189
|
+
readonly strategy: TranslationStrategy;
|
|
190
|
+
readonly sourceLocale: string;
|
|
191
|
+
readonly targetLocales: readonly string[];
|
|
192
|
+
readonly chunking: ChunkingConfig;
|
|
193
|
+
}
|
|
194
|
+
declare function runTranslation(config: TranslationRunConfig): Effect.Effect<undefined, AdapterReadError | AdapterWriteError | TranslationFailedError, never>;
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/translation/prompt.d.ts
|
|
197
|
+
declare function buildSystemPrompt(from: string, to: string): string;
|
|
198
|
+
declare function buildUserPrompt(ctx: TranslationContext): string;
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/translation/missing-keys.d.ts
|
|
201
|
+
interface MissingKeyEntry$1 {
|
|
202
|
+
readonly adapter: string;
|
|
203
|
+
readonly locale: string;
|
|
204
|
+
readonly resource: ResourceRef;
|
|
205
|
+
readonly missing: readonly string[];
|
|
206
|
+
}
|
|
207
|
+
declare function computeMissingKeys(adapter: TranslationAdapter, sourceLocale: string, targetLocales: readonly string[]): Effect.Effect<readonly MissingKeyEntry$1[], AdapterReadError>;
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/config/load-config.d.ts
|
|
210
|
+
declare const ConfigLoadError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
|
|
211
|
+
readonly _tag: "ConfigLoadError";
|
|
212
|
+
} & Readonly<A>;
|
|
213
|
+
declare class ConfigLoadError extends ConfigLoadError_base<{
|
|
214
|
+
readonly path: string;
|
|
215
|
+
readonly cause: unknown;
|
|
216
|
+
}> {}
|
|
217
|
+
declare function loadConfig(configPath: string): Effect.Effect<DialektConfig, ConfigLoadError>;
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/cli/format.d.ts
|
|
220
|
+
/**
|
|
221
|
+
* Terminal formatting core utilities for the dialekt CLI output.
|
|
222
|
+
*
|
|
223
|
+
* Two output modes:
|
|
224
|
+
* - `pretty` — lush human-readable output with colours and grouping (TTY only)
|
|
225
|
+
* - `json` — single compact JSON document for AI agents / machines
|
|
226
|
+
*
|
|
227
|
+
* stdout is the data contract in every mode; status / banners go to stderr.
|
|
228
|
+
* All decoration is gated behind `isTTY` so the output is never mojibake-prone
|
|
229
|
+
* when piped or consumed by another process.
|
|
230
|
+
*/
|
|
231
|
+
type OutputFormat = 'pretty' | 'json';
|
|
232
|
+
/**
|
|
233
|
+
* Resolves the output format from explicit flag and environment.
|
|
234
|
+
* Precedence: explicit `--format` > auto-detection.
|
|
235
|
+
*
|
|
236
|
+
* Auto-detection picks `json` when stdout is not a TTY or an agent env var
|
|
237
|
+
* is present; otherwise `pretty`.
|
|
238
|
+
*/
|
|
239
|
+
declare function detectFormat(explicit?: OutputFormat | undefined): OutputFormat;
|
|
240
|
+
/** Wraps text in ANSI codes only when stdout is a TTY; otherwise returns it bare. */
|
|
241
|
+
declare function color(text: string, ...codes: string[]): string;
|
|
242
|
+
interface Glyphs {
|
|
243
|
+
hLine: string;
|
|
244
|
+
vLine: string;
|
|
245
|
+
cornerTL: string;
|
|
246
|
+
cornerTR: string;
|
|
247
|
+
cornerBL: string;
|
|
248
|
+
cornerBR: string;
|
|
249
|
+
teeRight: string;
|
|
250
|
+
teeLeft: string;
|
|
251
|
+
teeDown: string;
|
|
252
|
+
teeUp: string;
|
|
253
|
+
cross: string;
|
|
254
|
+
bullet: string;
|
|
255
|
+
arrow: string;
|
|
256
|
+
check: string;
|
|
257
|
+
crossMark: string;
|
|
258
|
+
warn: string;
|
|
259
|
+
}
|
|
260
|
+
declare function glyphs(): Glyphs;
|
|
261
|
+
declare function drawTable(headers: readonly string[], rows: readonly (readonly string[])[]): string;
|
|
262
|
+
declare function banner(title: string): string;
|
|
263
|
+
declare function sectionHeader(label: string): string;
|
|
264
|
+
declare function success(text: string): string;
|
|
265
|
+
declare function failure(text: string): string;
|
|
266
|
+
declare function warning(text: string): string;
|
|
267
|
+
declare function info(text: string): string;
|
|
268
|
+
declare function keyValue(key: string, value: string): string;
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/cli/formatters.d.ts
|
|
271
|
+
interface MissingKeyEntry {
|
|
272
|
+
readonly adapter: string;
|
|
273
|
+
readonly locale: string;
|
|
274
|
+
readonly resource: string;
|
|
275
|
+
readonly key: string;
|
|
276
|
+
}
|
|
277
|
+
declare function formatMissingKeys(entries: readonly MissingKeyEntry[], format: OutputFormat): string;
|
|
278
|
+
interface UnusedKeyEntry {
|
|
279
|
+
readonly adapter: string;
|
|
280
|
+
readonly locale: string;
|
|
281
|
+
readonly resource: string;
|
|
282
|
+
readonly key: string;
|
|
283
|
+
}
|
|
284
|
+
declare function formatUnusedKeys(entries: readonly UnusedKeyEntry[], format: OutputFormat): string;
|
|
285
|
+
interface ValidateEntry {
|
|
286
|
+
readonly adapter: string;
|
|
287
|
+
readonly locale: string;
|
|
288
|
+
readonly resource: string;
|
|
289
|
+
readonly count: number;
|
|
290
|
+
}
|
|
291
|
+
interface ValidateResult {
|
|
292
|
+
readonly passing: boolean;
|
|
293
|
+
readonly entries: readonly ValidateEntry[];
|
|
294
|
+
}
|
|
295
|
+
declare function formatValidate(result: ValidateResult, format: OutputFormat): string;
|
|
296
|
+
interface LanguageEntry {
|
|
297
|
+
readonly adapter: string;
|
|
298
|
+
readonly locales: readonly string[];
|
|
299
|
+
}
|
|
300
|
+
declare function formatLanguages(entries: readonly LanguageEntry[], format: OutputFormat): string;
|
|
301
|
+
interface TranslateResult {
|
|
302
|
+
readonly success: boolean;
|
|
303
|
+
readonly message: string;
|
|
304
|
+
readonly stats?: {
|
|
305
|
+
readonly adaptersProcessed: number;
|
|
306
|
+
readonly localesTranslated: number;
|
|
307
|
+
readonly keysTranslated: number;
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
declare function formatTranslate(result: TranslateResult, format: OutputFormat): string;
|
|
311
|
+
interface AddResult {
|
|
312
|
+
readonly success: boolean;
|
|
313
|
+
readonly message: string;
|
|
314
|
+
readonly addedResources?: readonly string[];
|
|
315
|
+
}
|
|
316
|
+
declare function formatAdd(result: AddResult, format: OutputFormat): string;
|
|
317
|
+
interface BenchmarkEntry {
|
|
318
|
+
readonly strategyName: string;
|
|
319
|
+
readonly totalChunks: number;
|
|
320
|
+
readonly succeededChunks: number;
|
|
321
|
+
readonly failedChunks: number;
|
|
322
|
+
readonly totalDurationMs: number;
|
|
323
|
+
readonly averageDurationMsPerChunk: number;
|
|
324
|
+
readonly totalAttempts: number;
|
|
325
|
+
}
|
|
326
|
+
declare function formatBenchmark(entries: readonly BenchmarkEntry[], format: OutputFormat): string;
|
|
327
|
+
declare function formatError(message: string, format: OutputFormat): string;
|
|
328
|
+
//#endregion
|
|
329
|
+
export { type AdapterCapabilities, AdapterReadError, AdapterWriteError, type AddResult, type BenchmarkEntry, type ChunkingConfig, ConfigLoadError, type DialektConfig, type LanguageEntry, type MissingKeyEntry, type ModelConfig, NodePlatformLayer, type OutputFormat, PhpExecutionError, type ResourceRef, type RetryConfig, type TranslateResult, type TranslationAdapter, type TranslationContext, TranslationFailedError, type TranslationStrategy, UnknownProviderError, type UnusedKeyEntry, type ValidateEntry, type ValidateResult, banner, buildSystemPrompt, buildUserPrompt, chunkKeys, color, computeMissingKeys, createOneShotStrategy, createToolLoopStrategy, defineConfig, detectFormat, diffKeys, drawTable, failure, flattenObject, formatAdd, formatBenchmark, formatError, formatLanguages, formatMissingKeys, formatTranslate, formatUnusedKeys, formatValidate, glyphs, info, keyValue, loadConfig, readFileIfExists, readPhpArrayAsJson, resolveModel, runTranslation, sectionHeader, success, unflattenObject, warning, writeFileEnsuringDir };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { A as resolveModel, C as runTranslation, D as buildUserPrompt, E as buildSystemPrompt, M as diffKeys, N as flattenObject, O as TranslationFailedError, P as unflattenObject, S as computeMissingKeys, T as createOneShotStrategy, _ as sectionHeader, a as formatMissingKeys, b as ConfigLoadError, c as formatValidate, d as detectFormat, f as drawTable, g as keyValue, h as info, i as formatLanguages, j as chunkKeys, k as UnknownProviderError, l as banner, m as glyphs, n as formatBenchmark, o as formatTranslate, p as failure, r as formatError, s as formatUnusedKeys, t as formatAdd, u as color, v as success, w as createToolLoopStrategy, x as loadConfig, y as warning } from "./formatters-De4Q-X1d.mjs";
|
|
2
|
+
import { Data, Effect } from "effect";
|
|
3
|
+
import { NodeContext } from "@effect/platform-node";
|
|
4
|
+
import { Command, FileSystem, Path } from "@effect/platform";
|
|
5
|
+
//#region src/config/define-config.ts
|
|
6
|
+
function defineConfig(config) {
|
|
7
|
+
return config;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/adapter/types.ts
|
|
11
|
+
var AdapterReadError = class extends Data.TaggedError("AdapterReadError") {};
|
|
12
|
+
var AdapterWriteError = class extends Data.TaggedError("AdapterWriteError") {};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/sdk/node-layer.ts
|
|
15
|
+
/**
|
|
16
|
+
* The only file in this package (besides cli/main.ts) permitted to know
|
|
17
|
+
* this is running on Node.js. Provides FileSystem, Path, and
|
|
18
|
+
* CommandExecutor. Swapping to Bun/Deno later means swapping this one
|
|
19
|
+
* import for @effect/platform-bun's equivalent — nothing else changes.
|
|
20
|
+
*/
|
|
21
|
+
const NodePlatformLayer = NodeContext.layer;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/sdk/file-io.ts
|
|
24
|
+
function readFileIfExists(path) {
|
|
25
|
+
return Effect.gen(function* () {
|
|
26
|
+
const fs = yield* FileSystem.FileSystem;
|
|
27
|
+
if (!(yield* fs.exists(path))) return null;
|
|
28
|
+
return yield* fs.readFileString(path);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function writeFileEnsuringDir(path, content) {
|
|
32
|
+
return Effect.gen(function* () {
|
|
33
|
+
const fs = yield* FileSystem.FileSystem;
|
|
34
|
+
const dir = (yield* Path.Path).dirname(path);
|
|
35
|
+
yield* fs.makeDirectory(dir, { recursive: true });
|
|
36
|
+
yield* fs.writeFileString(path, content);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/sdk/php-array-reader.ts
|
|
41
|
+
var PhpExecutionError = class extends Data.TaggedError("PhpExecutionError") {};
|
|
42
|
+
const DUMP_SCRIPT = "echo json_encode(is_array($v = require $argv[1]) ? $v : [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);";
|
|
43
|
+
function readPhpArrayAsJson(absolutePath) {
|
|
44
|
+
return Effect.gen(function* () {
|
|
45
|
+
const cmd = Command.make("php", "-r", DUMP_SCRIPT, "--", absolutePath);
|
|
46
|
+
const output = yield* Command.string(cmd).pipe(Effect.mapError((cause) => new PhpExecutionError({
|
|
47
|
+
path: absolutePath,
|
|
48
|
+
cause
|
|
49
|
+
})));
|
|
50
|
+
return yield* Effect.try({
|
|
51
|
+
try: () => JSON.parse(output),
|
|
52
|
+
catch: (cause) => new PhpExecutionError({
|
|
53
|
+
path: absolutePath,
|
|
54
|
+
cause
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { AdapterReadError, AdapterWriteError, ConfigLoadError, NodePlatformLayer, PhpExecutionError, TranslationFailedError, UnknownProviderError, banner, buildSystemPrompt, buildUserPrompt, chunkKeys, color, computeMissingKeys, createOneShotStrategy, createToolLoopStrategy, defineConfig, detectFormat, diffKeys, drawTable, failure, flattenObject, formatAdd, formatBenchmark, formatError, formatLanguages, formatMissingKeys, formatTranslate, formatUnusedKeys, formatValidate, glyphs, info, keyValue, loadConfig, readFileIfExists, readPhpArrayAsJson, resolveModel, runTranslation, sectionHeader, success, unflattenObject, warning, writeFileEnsuringDir };
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dialekt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"dialekt": "./dist/cli/main.mjs"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@ai-sdk/anthropic": "^4.0.4",
|
|
16
|
+
"@ai-sdk/google": "^4.0.3",
|
|
17
|
+
"@ai-sdk/openai": "^4.0.4",
|
|
18
|
+
"dialekt": "link:",
|
|
19
|
+
"@effect/cli": "^0.75.0",
|
|
20
|
+
"@effect/platform": "^0.96.0",
|
|
21
|
+
"@effect/platform-node": "^0.107.0",
|
|
22
|
+
"ai": "^7.0.0",
|
|
23
|
+
"effect": "^3.21.0",
|
|
24
|
+
"jiti": "^2.7.0",
|
|
25
|
+
"zod": "^3.24.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@effect/vitest": "^0.29.0",
|
|
29
|
+
"@types/node": "^26.0.1",
|
|
30
|
+
"tsdown": "^0.22.3",
|
|
31
|
+
"typescript": "^5.8.0",
|
|
32
|
+
"vitest": "^4.0.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { AdapterReadError, AdapterWriteError } from './types.js';
|
|
3
|
+
|
|
4
|
+
describe('AdapterReadError', () => {
|
|
5
|
+
it('carries adapter/locale/resource/cause', () => {
|
|
6
|
+
const err = new AdapterReadError({
|
|
7
|
+
adapter: 'laravel',
|
|
8
|
+
locale: 'en',
|
|
9
|
+
resource: 'validation',
|
|
10
|
+
cause: new Error('boom'),
|
|
11
|
+
});
|
|
12
|
+
expect(err._tag).toBe('AdapterReadError');
|
|
13
|
+
expect(err.adapter).toBe('laravel');
|
|
14
|
+
expect(err.locale).toBe('en');
|
|
15
|
+
expect(err.resource).toBe('validation');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('carries string cause', () => {
|
|
19
|
+
const err = new AdapterReadError({
|
|
20
|
+
adapter: 'paraglide',
|
|
21
|
+
locale: 'de',
|
|
22
|
+
resource: 'messages',
|
|
23
|
+
cause: 'file not found',
|
|
24
|
+
});
|
|
25
|
+
expect(err._tag).toBe('AdapterReadError');
|
|
26
|
+
expect(err.cause).toBe('file not found');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('preserves Error cause identity', () => {
|
|
30
|
+
const cause = new Error('disk full');
|
|
31
|
+
const err = new AdapterReadError({
|
|
32
|
+
adapter: 'laravel',
|
|
33
|
+
locale: 'fr',
|
|
34
|
+
resource: 'auth',
|
|
35
|
+
cause,
|
|
36
|
+
});
|
|
37
|
+
expect(err.cause).toBe(cause);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('accepts empty locale and resource', () => {
|
|
41
|
+
const err = new AdapterReadError({
|
|
42
|
+
adapter: 'test',
|
|
43
|
+
locale: '',
|
|
44
|
+
resource: '',
|
|
45
|
+
cause: 'unknown',
|
|
46
|
+
});
|
|
47
|
+
expect(err.locale).toBe('');
|
|
48
|
+
expect(err.resource).toBe('');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('accepts various adapter names', () => {
|
|
52
|
+
for (const name of ['laravel', 'paraglide', 'symfony', 'custom']) {
|
|
53
|
+
const err = new AdapterReadError({
|
|
54
|
+
adapter: name,
|
|
55
|
+
locale: 'en',
|
|
56
|
+
resource: 'x',
|
|
57
|
+
cause: 'test',
|
|
58
|
+
});
|
|
59
|
+
expect(err.adapter).toBe(name);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('AdapterWriteError', () => {
|
|
65
|
+
it('carries adapter/locale/resource/cause', () => {
|
|
66
|
+
const err = new AdapterWriteError({
|
|
67
|
+
adapter: 'paraglide',
|
|
68
|
+
locale: 'de',
|
|
69
|
+
resource: 'messages',
|
|
70
|
+
cause: 'disk full',
|
|
71
|
+
});
|
|
72
|
+
expect(err._tag).toBe('AdapterWriteError');
|
|
73
|
+
expect(err.adapter).toBe('paraglide');
|
|
74
|
+
expect(err.locale).toBe('de');
|
|
75
|
+
expect(err.resource).toBe('messages');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('carries Error cause', () => {
|
|
79
|
+
const cause = new Error('permission denied');
|
|
80
|
+
const err = new AdapterWriteError({
|
|
81
|
+
adapter: 'laravel',
|
|
82
|
+
locale: 'en',
|
|
83
|
+
resource: 'validation',
|
|
84
|
+
cause,
|
|
85
|
+
});
|
|
86
|
+
expect(err.cause).toBe(cause);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('accepts empty strings', () => {
|
|
90
|
+
const err = new AdapterWriteError({
|
|
91
|
+
adapter: '',
|
|
92
|
+
locale: '',
|
|
93
|
+
resource: '',
|
|
94
|
+
cause: '',
|
|
95
|
+
});
|
|
96
|
+
expect(err.adapter).toBe('');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Data, Effect } from 'effect';
|
|
2
|
+
|
|
3
|
+
/** Opaque adapter-specific identifier for one resource within a locale. */
|
|
4
|
+
export interface ResourceRef {
|
|
5
|
+
readonly key: string; // e.g. Laravel domain "validation", or "messages" for Paraglide
|
|
6
|
+
readonly label: string; // human-readable, for CLI output
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class AdapterReadError extends Data.TaggedError('AdapterReadError')<{
|
|
10
|
+
readonly adapter: string;
|
|
11
|
+
readonly locale: string;
|
|
12
|
+
readonly resource: string;
|
|
13
|
+
readonly cause: unknown;
|
|
14
|
+
}> {}
|
|
15
|
+
|
|
16
|
+
export class AdapterWriteError extends Data.TaggedError('AdapterWriteError')<{
|
|
17
|
+
readonly adapter: string;
|
|
18
|
+
readonly locale: string;
|
|
19
|
+
readonly resource: string;
|
|
20
|
+
readonly cause: unknown;
|
|
21
|
+
}> {}
|
|
22
|
+
|
|
23
|
+
export interface TranslationAdapter {
|
|
24
|
+
/** Stable adapter name, e.g. "laravel", "paraglide". Used in CLI --adapter flag and error messages. */
|
|
25
|
+
readonly name: string;
|
|
26
|
+
|
|
27
|
+
/** Which optional features this adapter instance supports (see Feature flags below). */
|
|
28
|
+
readonly capabilities: AdapterCapabilities;
|
|
29
|
+
|
|
30
|
+
/** Auto-detect configured locales (e.g. subdirectories of a lang dir), or return the user-configured list. */
|
|
31
|
+
listLocales(): Effect.Effect<readonly string[], AdapterReadError>;
|
|
32
|
+
|
|
33
|
+
/** List the resources available for a given locale (e.g. domain files present for "en"). */
|
|
34
|
+
listResources(locale: string): Effect.Effect<readonly ResourceRef[], AdapterReadError>;
|
|
35
|
+
|
|
36
|
+
/** Read one resource, flattened to dot-notation key → string value. Returns {} if the resource does not exist. */
|
|
37
|
+
readResource(locale: string, resource: ResourceRef): Effect.Effect<Record<string, string>, AdapterReadError>;
|
|
38
|
+
|
|
39
|
+
/** Write a full flattened key→value map back to a resource, unflattening as needed. Creates the resource if absent and `create` capability allows it. */
|
|
40
|
+
writeResource(
|
|
41
|
+
locale: string,
|
|
42
|
+
resource: ResourceRef,
|
|
43
|
+
entries: Record<string, string>,
|
|
44
|
+
): Effect.Effect<void, AdapterWriteError>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns translation keys present in the resource but never referenced
|
|
48
|
+
* anywhere in the project's source code. Only called by the CLI's `unused`
|
|
49
|
+
* command when `capabilities.unusedKeyDetection` is `true` — present as an
|
|
50
|
+
* optional method (not required on every adapter) because some future
|
|
51
|
+
* adapter format may have no reliable "is this key referenced" heuristic
|
|
52
|
+
* (e.g. a flat gettext catalog with no consistent call-site convention).
|
|
53
|
+
*
|
|
54
|
+
* Deliberately minimal contract: the adapter receives no scan-path
|
|
55
|
+
* guidance, no shared "grep helper", and no hint about what a "reference"
|
|
56
|
+
* looks like in its ecosystem. It owns the entire strategy internally —
|
|
57
|
+
* Laravel scans for `__('domain.key')`-shaped calls in PHP/Blade files;
|
|
58
|
+
* Paraglide scans for `m.messageName(...)` calls in JS/TS files. Each
|
|
59
|
+
* adapter's own constructor options (`LaravelAdapterOptions`,
|
|
60
|
+
* `ParaglideAdapterOptions`) carry whatever scan-path configuration that
|
|
61
|
+
* adapter's own heuristic needs — core never sees or validates those
|
|
62
|
+
* options.
|
|
63
|
+
*/
|
|
64
|
+
findUnusedKeys?(
|
|
65
|
+
locale: string,
|
|
66
|
+
resource: ResourceRef,
|
|
67
|
+
): Effect.Effect<readonly string[], AdapterReadError>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AdapterCapabilities {
|
|
71
|
+
readonly canCreateResource: boolean; // can writeResource() create a brand-new file?
|
|
72
|
+
readonly unusedKeyDetection: boolean; // is findUnusedKeys implemented?
|
|
73
|
+
}
|