dialekt 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -10
- package/TESTING.md +29 -29
- package/dist/cli/main.d.mts +1 -1
- package/dist/cli/main.mjs +549 -362
- package/dist/formatters-De4Q-X1d.mjs +516 -435
- package/dist/index.d.mts +162 -25
- package/dist/index.mjs +119 -34
- package/package.json +3 -3
- package/pnpm-workspace.yaml +3 -3
- package/src/adapter/types.test.ts +57 -57
- package/src/adapter/types.ts +7 -4
- package/src/benchmark/metrics.test.ts +141 -69
- package/src/benchmark/metrics.ts +6 -6
- package/src/benchmark/report.test.ts +38 -38
- package/src/benchmark/report.ts +6 -6
- package/src/benchmark/runner.test.ts +70 -72
- package/src/benchmark/runner.ts +4 -4
- package/src/cli/commands/add.test.ts +90 -109
- package/src/cli/commands/add.ts +40 -28
- package/src/cli/commands/benchmark.test.ts +77 -64
- package/src/cli/commands/benchmark.ts +64 -41
- package/src/cli/commands/languages.test.ts +45 -42
- package/src/cli/commands/languages.ts +16 -12
- package/src/cli/commands/missing.test.ts +143 -92
- package/src/cli/commands/missing.ts +24 -17
- package/src/cli/commands/translate.test.ts +79 -79
- package/src/cli/commands/translate.ts +41 -31
- package/src/cli/commands/unused.test.ts +62 -51
- package/src/cli/commands/unused.ts +18 -14
- package/src/cli/commands/validate.test.ts +130 -72
- package/src/cli/commands/validate.ts +25 -20
- package/src/cli/config-resolution.test.ts +169 -49
- package/src/cli/config-resolution.ts +5 -7
- package/src/cli/format.test.ts +50 -50
- package/src/cli/format.ts +57 -60
- package/src/cli/formatters.test.ts +128 -106
- package/src/cli/formatters.ts +72 -95
- package/src/cli/main.ts +13 -13
- package/src/config/define-config.test.ts +44 -29
- package/src/config/define-config.ts +1 -1
- package/src/config/load-config.test.ts +21 -18
- package/src/config/load-config.ts +5 -5
- package/src/config/types.test.ts +50 -44
- package/src/config/types.ts +2 -2
- package/src/index.ts +22 -26
- package/src/keys/flatten.test.ts +52 -52
- package/src/keys/flatten.ts +7 -9
- package/src/sdk/file-io.test.ts +47 -59
- package/src/sdk/file-io.ts +2 -2
- package/src/sdk/node-layer.test.ts +18 -18
- package/src/sdk/node-layer.ts +2 -2
- package/src/sdk/php-array-reader.test.ts +49 -40
- package/src/sdk/php-array-reader.ts +5 -5
- package/src/translation/chunking.test.ts +52 -44
- package/src/translation/chunking.ts +1 -1
- package/src/translation/missing-keys.test.ts +86 -93
- package/src/translation/missing-keys.ts +4 -6
- package/src/translation/model-registry.test.ts +41 -32
- package/src/translation/model-registry.ts +9 -9
- package/src/translation/one-shot-strategy.test.ts +105 -86
- package/src/translation/one-shot-strategy.ts +10 -12
- package/src/translation/orchestrator.test.ts +90 -101
- package/src/translation/orchestrator.ts +26 -26
- package/src/translation/prompt.test.ts +76 -76
- package/src/translation/prompt.ts +2 -2
- package/src/translation/tool-loop-strategy.test.ts +134 -107
- package/src/translation/tool-loop-strategy.ts +14 -18
- package/src/translation/types.test.ts +22 -22
- package/src/translation/types.ts +3 -3
- package/tsdown.config.ts +3 -3
- package/vitest.config.ts +3 -3
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect, Option } from
|
|
3
|
-
import { runLanguages, languagesCommand } from
|
|
4
|
-
import type { DialektConfig } from
|
|
5
|
-
import type { TranslationAdapter } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect, Option } from "effect";
|
|
3
|
+
import { runLanguages, languagesCommand } from "./languages.js";
|
|
4
|
+
import type { DialektConfig } from "../../config/types.js";
|
|
5
|
+
import type { TranslationAdapter } from "../../adapter/types.js";
|
|
6
6
|
|
|
7
|
-
describe(
|
|
7
|
+
describe("runLanguages", () => {
|
|
8
8
|
const baseConfig: DialektConfig = {
|
|
9
|
-
sourceLocale:
|
|
10
|
-
targetLocales: [
|
|
11
|
-
strategy:
|
|
12
|
-
model: { provider:
|
|
13
|
-
fastModel: { provider:
|
|
9
|
+
sourceLocale: "en",
|
|
10
|
+
targetLocales: ["de", "fr"],
|
|
11
|
+
strategy: "one-shot",
|
|
12
|
+
model: { provider: "openai", modelId: "gpt-4o" },
|
|
13
|
+
fastModel: { provider: "openai", modelId: "gpt-4o-mini" },
|
|
14
14
|
chunking: { maxTokens: 3000, charsPerToken: 3.0, concurrency: 3 },
|
|
15
15
|
retry: { maxAttempts: 3, baseDelayMs: 1000 },
|
|
16
16
|
adapters: [],
|
|
@@ -20,21 +20,24 @@ describe('runLanguages', () => {
|
|
|
20
20
|
return {
|
|
21
21
|
name: opts.name,
|
|
22
22
|
capabilities: { canCreateResource: true, unusedKeyDetection: false },
|
|
23
|
-
listLocales: () => Effect.succeed(opts.locales ?? [
|
|
23
|
+
listLocales: () => Effect.succeed(opts.locales ?? ["en", "de"]),
|
|
24
24
|
listResources: () => Effect.succeed([]),
|
|
25
25
|
readResource: () => Effect.succeed({}),
|
|
26
26
|
writeResource: () => Effect.void,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
it(
|
|
30
|
+
it("logs locales for each adapter", async () => {
|
|
31
31
|
const logs: string[] = [];
|
|
32
|
-
const laravel = makeAdapter({ name:
|
|
33
|
-
const paraglide = makeAdapter({ name:
|
|
34
|
-
const config = {
|
|
32
|
+
const laravel = makeAdapter({ name: "laravel", locales: ["en", "de", "fr"] });
|
|
33
|
+
const paraglide = makeAdapter({ name: "paraglide", locales: ["en", "es"] });
|
|
34
|
+
const config = {
|
|
35
|
+
...baseConfig,
|
|
36
|
+
adapters: [laravel, paraglide] as unknown as DialektConfig["adapters"],
|
|
37
|
+
};
|
|
35
38
|
|
|
36
39
|
const program = runLanguages(
|
|
37
|
-
{ config:
|
|
40
|
+
{ config: "./config.ts" },
|
|
38
41
|
() => Effect.succeed(config),
|
|
39
42
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
40
43
|
);
|
|
@@ -43,17 +46,17 @@ describe('runLanguages', () => {
|
|
|
43
46
|
expect(logs).toHaveLength(1);
|
|
44
47
|
const parsed = JSON.parse(logs[0]!);
|
|
45
48
|
expect(parsed).toHaveLength(2);
|
|
46
|
-
expect(parsed).toContainEqual({ adapter:
|
|
47
|
-
expect(parsed).toContainEqual({ adapter:
|
|
49
|
+
expect(parsed).toContainEqual({ adapter: "laravel", locales: ["en", "de", "fr"] });
|
|
50
|
+
expect(parsed).toContainEqual({ adapter: "paraglide", locales: ["en", "es"] });
|
|
48
51
|
});
|
|
49
52
|
|
|
50
|
-
it(
|
|
53
|
+
it("handles single adapter with single locale", async () => {
|
|
51
54
|
const logs: string[] = [];
|
|
52
|
-
const adapter = makeAdapter({ name:
|
|
53
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
55
|
+
const adapter = makeAdapter({ name: "mono", locales: ["en"] });
|
|
56
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
54
57
|
|
|
55
58
|
const program = runLanguages(
|
|
56
|
-
{ config:
|
|
59
|
+
{ config: "./config.ts" },
|
|
57
60
|
() => Effect.succeed(config),
|
|
58
61
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
59
62
|
);
|
|
@@ -61,15 +64,15 @@ describe('runLanguages', () => {
|
|
|
61
64
|
await Effect.runPromise(program);
|
|
62
65
|
expect(logs).toHaveLength(1);
|
|
63
66
|
const parsed = JSON.parse(logs[0]!);
|
|
64
|
-
expect(parsed).toEqual([{ adapter:
|
|
67
|
+
expect(parsed).toEqual([{ adapter: "mono", locales: ["en"] }]);
|
|
65
68
|
});
|
|
66
69
|
|
|
67
|
-
it(
|
|
70
|
+
it("handles empty adapter list", async () => {
|
|
68
71
|
const logs: string[] = [];
|
|
69
72
|
const config = { ...baseConfig, adapters: [] };
|
|
70
73
|
|
|
71
74
|
const program = runLanguages(
|
|
72
|
-
{ config:
|
|
75
|
+
{ config: "./config.ts" },
|
|
73
76
|
() => Effect.succeed(config),
|
|
74
77
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
75
78
|
);
|
|
@@ -80,23 +83,23 @@ describe('runLanguages', () => {
|
|
|
80
83
|
expect(parsed).toEqual([]);
|
|
81
84
|
});
|
|
82
85
|
|
|
83
|
-
it(
|
|
86
|
+
it("fails when configLoader fails", async () => {
|
|
84
87
|
const program = runLanguages(
|
|
85
|
-
{ config:
|
|
86
|
-
() => Effect.fail(new Error(
|
|
88
|
+
{ config: "./missing.ts" },
|
|
89
|
+
() => Effect.fail(new Error("Config not found")),
|
|
87
90
|
() => Effect.void,
|
|
88
91
|
);
|
|
89
92
|
|
|
90
|
-
await expect(Effect.runPromise(program)).rejects.toThrow(
|
|
93
|
+
await expect(Effect.runPromise(program)).rejects.toThrow("Config not found");
|
|
91
94
|
});
|
|
92
95
|
|
|
93
|
-
it(
|
|
96
|
+
it("handles adapter with empty locales", async () => {
|
|
94
97
|
const logs: string[] = [];
|
|
95
|
-
const adapter = makeAdapter({ name:
|
|
96
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
98
|
+
const adapter = makeAdapter({ name: "empty", locales: [] });
|
|
99
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
97
100
|
|
|
98
101
|
const program = runLanguages(
|
|
99
|
-
{ config:
|
|
102
|
+
{ config: "./config.ts" },
|
|
100
103
|
() => Effect.succeed(config),
|
|
101
104
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
102
105
|
);
|
|
@@ -104,24 +107,24 @@ describe('runLanguages', () => {
|
|
|
104
107
|
await Effect.runPromise(program);
|
|
105
108
|
expect(logs).toHaveLength(1);
|
|
106
109
|
const parsed = JSON.parse(logs[0]!);
|
|
107
|
-
expect(parsed).toEqual([{ adapter:
|
|
110
|
+
expect(parsed).toEqual([{ adapter: "empty", locales: [] }]);
|
|
108
111
|
});
|
|
109
112
|
|
|
110
|
-
it(
|
|
113
|
+
it("outputs pretty when --format pretty is passed", async () => {
|
|
111
114
|
const logs: string[] = [];
|
|
112
|
-
const adapter = makeAdapter({ name:
|
|
113
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
115
|
+
const adapter = makeAdapter({ name: "test", locales: ["en", "de"] });
|
|
116
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
114
117
|
|
|
115
118
|
const program = runLanguages(
|
|
116
|
-
{ config:
|
|
119
|
+
{ config: "./config.ts", format: Option.some("pretty") },
|
|
117
120
|
() => Effect.succeed(config),
|
|
118
121
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
119
122
|
);
|
|
120
123
|
|
|
121
124
|
await Effect.runPromise(program);
|
|
122
125
|
expect(logs).toHaveLength(1);
|
|
123
|
-
expect(logs[0]).toContain(
|
|
124
|
-
expect(logs[0]).toContain(
|
|
125
|
-
expect(logs[0]).toContain(
|
|
126
|
+
expect(logs[0]).toContain("test");
|
|
127
|
+
expect(logs[0]).toContain("en");
|
|
128
|
+
expect(logs[0]).toContain("de");
|
|
126
129
|
});
|
|
127
130
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Command, Options } from
|
|
2
|
-
import { Effect, Console, Option } from
|
|
3
|
-
import { loadConfig } from
|
|
4
|
-
import { resolveEffectiveConfig } from
|
|
5
|
-
import { detectFormat, type OutputFormat } from
|
|
6
|
-
import { formatLanguages } from
|
|
7
|
-
import type { DialektConfig } from
|
|
8
|
-
import type { TranslationAdapter } from
|
|
1
|
+
import { Command, Options } from "@effect/cli";
|
|
2
|
+
import { Effect, Console, Option } from "effect";
|
|
3
|
+
import { loadConfig } from "../../config/load-config.js";
|
|
4
|
+
import { resolveEffectiveConfig } from "../config-resolution.js";
|
|
5
|
+
import { detectFormat, type OutputFormat } from "../format.js";
|
|
6
|
+
import { formatLanguages } from "../formatters.js";
|
|
7
|
+
import type { DialektConfig } from "../../config/types.js";
|
|
8
|
+
import type { TranslationAdapter } from "../../adapter/types.js";
|
|
9
9
|
|
|
10
10
|
export interface LanguagesFlags {
|
|
11
11
|
readonly config: string;
|
|
@@ -36,7 +36,11 @@ export function runLanguages(
|
|
|
36
36
|
}).pipe(Effect.mapError((e) => e as never)) as Effect.Effect<void, never, never>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export const languagesCommand = Command.make(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
export const languagesCommand = Command.make(
|
|
40
|
+
"languages",
|
|
41
|
+
{
|
|
42
|
+
config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
|
|
43
|
+
format: Options.optional(Options.text("format")),
|
|
44
|
+
},
|
|
45
|
+
(flags) => runLanguages(flags),
|
|
46
|
+
);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect, Option } from
|
|
3
|
-
import { runMissing, missingCommand } from
|
|
4
|
-
import type { DialektConfig } from
|
|
5
|
-
import type { TranslationAdapter, ResourceRef } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect, Option } from "effect";
|
|
3
|
+
import { runMissing, missingCommand } from "./missing.js";
|
|
4
|
+
import type { DialektConfig } from "../../config/types.js";
|
|
5
|
+
import type { TranslationAdapter, ResourceRef } from "../../adapter/types.js";
|
|
6
6
|
|
|
7
|
-
describe(
|
|
7
|
+
describe("runMissing", () => {
|
|
8
8
|
const baseConfig: DialektConfig = {
|
|
9
|
-
sourceLocale:
|
|
10
|
-
targetLocales: [
|
|
11
|
-
strategy:
|
|
12
|
-
model: { provider:
|
|
13
|
-
fastModel: { provider:
|
|
9
|
+
sourceLocale: "en",
|
|
10
|
+
targetLocales: ["de", "fr"],
|
|
11
|
+
strategy: "one-shot",
|
|
12
|
+
model: { provider: "openai", modelId: "gpt-4o" },
|
|
13
|
+
fastModel: { provider: "openai", modelId: "gpt-4o-mini" },
|
|
14
14
|
chunking: { maxTokens: 3000, charsPerToken: 3.0, concurrency: 3 },
|
|
15
15
|
retry: { maxAttempts: 3, baseDelayMs: 1000 },
|
|
16
16
|
adapters: [],
|
|
@@ -24,26 +24,29 @@ describe('runMissing', () => {
|
|
|
24
24
|
return {
|
|
25
25
|
name: opts.name,
|
|
26
26
|
capabilities: { canCreateResource: true, unusedKeyDetection: false },
|
|
27
|
-
listLocales: () => Effect.succeed(opts.locales ?? [
|
|
28
|
-
listResources: () => Effect.succeed([{ key:
|
|
27
|
+
listLocales: () => Effect.succeed(opts.locales ?? ["en", "de"]),
|
|
28
|
+
listResources: () => Effect.succeed([{ key: "messages", label: "messages" }]),
|
|
29
29
|
readResource: () => Effect.succeed({}),
|
|
30
30
|
writeResource: () => Effect.void,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
it(
|
|
34
|
+
it("logs missing keys in default format", async () => {
|
|
35
35
|
const logs: string[] = [];
|
|
36
|
-
const adapter = makeAdapter({ name:
|
|
37
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
38
|
-
const resource: ResourceRef = { key:
|
|
36
|
+
const adapter = makeAdapter({ name: "test" });
|
|
37
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
38
|
+
const resource: ResourceRef = { key: "messages", label: "messages" };
|
|
39
39
|
|
|
40
40
|
const program = runMissing(
|
|
41
|
-
{
|
|
41
|
+
{
|
|
42
|
+
config: "./config.ts",
|
|
43
|
+
adapter: Option.none(),
|
|
44
|
+
baseLanguage: Option.none(),
|
|
45
|
+
language: Option.none(),
|
|
46
|
+
},
|
|
42
47
|
() => Effect.succeed(config),
|
|
43
48
|
() =>
|
|
44
|
-
Effect.succeed([
|
|
45
|
-
{ adapter: 'test', locale: 'de', resource, missing: ['hello', 'bye'] },
|
|
46
|
-
]),
|
|
49
|
+
Effect.succeed([{ adapter: "test", locale: "de", resource, missing: ["hello", "bye"] }]),
|
|
47
50
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
48
51
|
);
|
|
49
52
|
|
|
@@ -51,66 +54,76 @@ describe('runMissing', () => {
|
|
|
51
54
|
expect(logs).toHaveLength(1);
|
|
52
55
|
const parsed = JSON.parse(logs[0]!);
|
|
53
56
|
expect(parsed).toEqual([
|
|
54
|
-
{ adapter:
|
|
55
|
-
{ adapter:
|
|
57
|
+
{ adapter: "test", locale: "de", resource: "messages", key: "hello" },
|
|
58
|
+
{ adapter: "test", locale: "de", resource: "messages", key: "bye" },
|
|
56
59
|
]);
|
|
57
60
|
});
|
|
58
61
|
|
|
59
|
-
it(
|
|
62
|
+
it("outputs JSON when --format json is passed", async () => {
|
|
60
63
|
const logs: string[] = [];
|
|
61
|
-
const adapter = makeAdapter({ name:
|
|
62
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
63
|
-
const resource: ResourceRef = { key:
|
|
64
|
+
const adapter = makeAdapter({ name: "test" });
|
|
65
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
66
|
+
const resource: ResourceRef = { key: "messages", label: "messages" };
|
|
64
67
|
|
|
65
68
|
const program = runMissing(
|
|
66
|
-
{
|
|
69
|
+
{
|
|
70
|
+
config: "./config.ts",
|
|
71
|
+
adapter: Option.none(),
|
|
72
|
+
baseLanguage: Option.none(),
|
|
73
|
+
language: Option.none(),
|
|
74
|
+
format: Option.some("json"),
|
|
75
|
+
},
|
|
67
76
|
() => Effect.succeed(config),
|
|
68
|
-
() =>
|
|
69
|
-
Effect.succeed([
|
|
70
|
-
{ adapter: 'test', locale: 'de', resource, missing: ['hello'] },
|
|
71
|
-
]),
|
|
77
|
+
() => Effect.succeed([{ adapter: "test", locale: "de", resource, missing: ["hello"] }]),
|
|
72
78
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
73
79
|
);
|
|
74
80
|
|
|
75
81
|
await Effect.runPromise(program);
|
|
76
82
|
expect(logs).toHaveLength(1);
|
|
77
83
|
const parsed = JSON.parse(logs[0]!);
|
|
78
|
-
expect(parsed).toEqual([
|
|
79
|
-
{ adapter: 'test', locale: 'de', resource: 'messages', key: 'hello' },
|
|
80
|
-
]);
|
|
84
|
+
expect(parsed).toEqual([{ adapter: "test", locale: "de", resource: "messages", key: "hello" }]);
|
|
81
85
|
});
|
|
82
86
|
|
|
83
|
-
it(
|
|
87
|
+
it("outputs pretty when --format pretty is passed", async () => {
|
|
84
88
|
const logs: string[] = [];
|
|
85
|
-
const adapter = makeAdapter({ name:
|
|
86
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
87
|
-
const resource: ResourceRef = { key:
|
|
89
|
+
const adapter = makeAdapter({ name: "test" });
|
|
90
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
91
|
+
const resource: ResourceRef = { key: "messages", label: "messages" };
|
|
88
92
|
|
|
89
93
|
const program = runMissing(
|
|
90
|
-
{
|
|
94
|
+
{
|
|
95
|
+
config: "./config.ts",
|
|
96
|
+
adapter: Option.none(),
|
|
97
|
+
baseLanguage: Option.none(),
|
|
98
|
+
language: Option.none(),
|
|
99
|
+
format: Option.some("pretty"),
|
|
100
|
+
},
|
|
91
101
|
() => Effect.succeed(config),
|
|
92
102
|
() =>
|
|
93
|
-
Effect.succeed([
|
|
94
|
-
{ adapter: 'test', locale: 'de', resource, missing: ['hello', 'bye'] },
|
|
95
|
-
]),
|
|
103
|
+
Effect.succeed([{ adapter: "test", locale: "de", resource, missing: ["hello", "bye"] }]),
|
|
96
104
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
97
105
|
);
|
|
98
106
|
|
|
99
107
|
await Effect.runPromise(program);
|
|
100
108
|
expect(logs).toHaveLength(1);
|
|
101
|
-
expect(logs[0]).toContain(
|
|
102
|
-
expect(logs[0]).toContain(
|
|
103
|
-
expect(logs[0]).toContain(
|
|
104
|
-
expect(logs[0]).toContain(
|
|
109
|
+
expect(logs[0]).toContain("test");
|
|
110
|
+
expect(logs[0]).toContain("de");
|
|
111
|
+
expect(logs[0]).toContain("hello");
|
|
112
|
+
expect(logs[0]).toContain("bye");
|
|
105
113
|
});
|
|
106
114
|
|
|
107
|
-
it(
|
|
115
|
+
it("returns empty JSON array when nothing is missing", async () => {
|
|
108
116
|
const logs: string[] = [];
|
|
109
|
-
const adapter = makeAdapter({ name:
|
|
110
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
117
|
+
const adapter = makeAdapter({ name: "test" });
|
|
118
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
111
119
|
|
|
112
120
|
const program = runMissing(
|
|
113
|
-
{
|
|
121
|
+
{
|
|
122
|
+
config: "./config.ts",
|
|
123
|
+
adapter: Option.none(),
|
|
124
|
+
baseLanguage: Option.none(),
|
|
125
|
+
language: Option.none(),
|
|
126
|
+
},
|
|
114
127
|
() => Effect.succeed(config),
|
|
115
128
|
() => Effect.succeed([]),
|
|
116
129
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
@@ -121,19 +134,24 @@ describe('runMissing', () => {
|
|
|
121
134
|
expect(JSON.parse(logs[0]!)).toEqual([]);
|
|
122
135
|
});
|
|
123
136
|
|
|
124
|
-
it(
|
|
137
|
+
it("handles multiple adapters and multiple locales", async () => {
|
|
125
138
|
const logs: string[] = [];
|
|
126
|
-
const a1 = makeAdapter({ name:
|
|
127
|
-
const a2 = makeAdapter({ name:
|
|
128
|
-
const config = { ...baseConfig, adapters: [a1, a2] as unknown as DialektConfig[
|
|
129
|
-
const resource: ResourceRef = { key:
|
|
139
|
+
const a1 = makeAdapter({ name: "a1" });
|
|
140
|
+
const a2 = makeAdapter({ name: "a2", locales: ["en", "fr"] });
|
|
141
|
+
const config = { ...baseConfig, adapters: [a1, a2] as unknown as DialektConfig["adapters"] };
|
|
142
|
+
const resource: ResourceRef = { key: "messages", label: "messages" };
|
|
130
143
|
|
|
131
144
|
const program = runMissing(
|
|
132
|
-
{
|
|
145
|
+
{
|
|
146
|
+
config: "./config.ts",
|
|
147
|
+
adapter: Option.none(),
|
|
148
|
+
baseLanguage: Option.none(),
|
|
149
|
+
language: Option.none(),
|
|
150
|
+
},
|
|
133
151
|
() => Effect.succeed(config),
|
|
134
152
|
(a) =>
|
|
135
153
|
Effect.succeed([
|
|
136
|
-
{ adapter: a.name, locale: a.name ===
|
|
154
|
+
{ adapter: a.name, locale: a.name === "a1" ? "de" : "fr", resource, missing: ["k1"] },
|
|
137
155
|
]),
|
|
138
156
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
139
157
|
);
|
|
@@ -142,18 +160,23 @@ describe('runMissing', () => {
|
|
|
142
160
|
expect(logs).toHaveLength(1);
|
|
143
161
|
const parsed = JSON.parse(logs[0]!);
|
|
144
162
|
expect(parsed).toHaveLength(2);
|
|
145
|
-
expect(parsed).toContainEqual({ adapter:
|
|
146
|
-
expect(parsed).toContainEqual({ adapter:
|
|
163
|
+
expect(parsed).toContainEqual({ adapter: "a1", locale: "de", resource: "messages", key: "k1" });
|
|
164
|
+
expect(parsed).toContainEqual({ adapter: "a2", locale: "fr", resource: "messages", key: "k1" });
|
|
147
165
|
});
|
|
148
166
|
|
|
149
|
-
it(
|
|
167
|
+
it("filters adapters by --adapter flag", async () => {
|
|
150
168
|
let queriedAdapter: string | undefined;
|
|
151
|
-
const a1 = makeAdapter({ name:
|
|
152
|
-
const a2 = makeAdapter({ name:
|
|
153
|
-
const config = { ...baseConfig, adapters: [a1, a2] as unknown as DialektConfig[
|
|
169
|
+
const a1 = makeAdapter({ name: "a1" });
|
|
170
|
+
const a2 = makeAdapter({ name: "a2" });
|
|
171
|
+
const config = { ...baseConfig, adapters: [a1, a2] as unknown as DialektConfig["adapters"] };
|
|
154
172
|
|
|
155
173
|
const program = runMissing(
|
|
156
|
-
{
|
|
174
|
+
{
|
|
175
|
+
config: "./config.ts",
|
|
176
|
+
adapter: Option.some("a2"),
|
|
177
|
+
baseLanguage: Option.none(),
|
|
178
|
+
language: Option.none(),
|
|
179
|
+
},
|
|
157
180
|
() => Effect.succeed(config),
|
|
158
181
|
(a) =>
|
|
159
182
|
Effect.sync(() => {
|
|
@@ -164,47 +187,62 @@ describe('runMissing', () => {
|
|
|
164
187
|
);
|
|
165
188
|
|
|
166
189
|
await Effect.runPromise(program);
|
|
167
|
-
expect(queriedAdapter).toBe(
|
|
190
|
+
expect(queriedAdapter).toBe("a2");
|
|
168
191
|
});
|
|
169
192
|
|
|
170
|
-
it(
|
|
193
|
+
it("fails when configLoader fails", async () => {
|
|
171
194
|
const program = runMissing(
|
|
172
|
-
{
|
|
173
|
-
|
|
195
|
+
{
|
|
196
|
+
config: "./missing.ts",
|
|
197
|
+
adapter: Option.none(),
|
|
198
|
+
baseLanguage: Option.none(),
|
|
199
|
+
language: Option.none(),
|
|
200
|
+
},
|
|
201
|
+
() => Effect.fail(new Error("Config not found")),
|
|
174
202
|
() => Effect.succeed([]),
|
|
175
203
|
() => Effect.void,
|
|
176
204
|
);
|
|
177
205
|
|
|
178
|
-
await expect(Effect.runPromise(program)).rejects.toThrow(
|
|
206
|
+
await expect(Effect.runPromise(program)).rejects.toThrow("Config not found");
|
|
179
207
|
});
|
|
180
208
|
|
|
181
|
-
it(
|
|
209
|
+
it("fails when listLocales fails", async () => {
|
|
182
210
|
const adapter: TranslationAdapter = {
|
|
183
|
-
name:
|
|
211
|
+
name: "broken",
|
|
184
212
|
capabilities: { canCreateResource: true, unusedKeyDetection: false },
|
|
185
|
-
listLocales: () => Effect.fail(new Error(
|
|
213
|
+
listLocales: () => Effect.fail(new Error("disk error") as never),
|
|
186
214
|
listResources: () => Effect.succeed([]),
|
|
187
215
|
readResource: () => Effect.succeed({}),
|
|
188
216
|
writeResource: () => Effect.void,
|
|
189
217
|
};
|
|
190
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
218
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
191
219
|
|
|
192
220
|
const program = runMissing(
|
|
193
|
-
{
|
|
221
|
+
{
|
|
222
|
+
config: "./config.ts",
|
|
223
|
+
adapter: Option.none(),
|
|
224
|
+
baseLanguage: Option.none(),
|
|
225
|
+
language: Option.none(),
|
|
226
|
+
},
|
|
194
227
|
() => Effect.succeed(config),
|
|
195
228
|
() => Effect.succeed([]),
|
|
196
229
|
() => Effect.void,
|
|
197
230
|
);
|
|
198
231
|
|
|
199
|
-
await expect(Effect.runPromise(program)).rejects.toThrow(
|
|
232
|
+
await expect(Effect.runPromise(program)).rejects.toThrow("disk error");
|
|
200
233
|
});
|
|
201
234
|
|
|
202
|
-
it(
|
|
235
|
+
it("handles empty adapter list", async () => {
|
|
203
236
|
const logs: string[] = [];
|
|
204
237
|
const config = { ...baseConfig, adapters: [] };
|
|
205
238
|
|
|
206
239
|
const program = runMissing(
|
|
207
|
-
{
|
|
240
|
+
{
|
|
241
|
+
config: "./config.ts",
|
|
242
|
+
adapter: Option.none(),
|
|
243
|
+
baseLanguage: Option.none(),
|
|
244
|
+
language: Option.none(),
|
|
245
|
+
},
|
|
208
246
|
() => Effect.succeed(config),
|
|
209
247
|
() => Effect.succeed([]),
|
|
210
248
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
@@ -215,13 +253,18 @@ describe('runMissing', () => {
|
|
|
215
253
|
expect(JSON.parse(logs[0]!)).toEqual([]);
|
|
216
254
|
});
|
|
217
255
|
|
|
218
|
-
it(
|
|
256
|
+
it("handles single-locale adapter (no targets)", async () => {
|
|
219
257
|
const logs: string[] = [];
|
|
220
|
-
const adapter = makeAdapter({ name:
|
|
221
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
258
|
+
const adapter = makeAdapter({ name: "mono", locales: ["en"] });
|
|
259
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
222
260
|
|
|
223
261
|
const program = runMissing(
|
|
224
|
-
{
|
|
262
|
+
{
|
|
263
|
+
config: "./config.ts",
|
|
264
|
+
adapter: Option.none(),
|
|
265
|
+
baseLanguage: Option.none(),
|
|
266
|
+
language: Option.none(),
|
|
267
|
+
},
|
|
225
268
|
() => Effect.succeed(config),
|
|
226
269
|
() => Effect.succeed([]),
|
|
227
270
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
@@ -232,25 +275,33 @@ describe('runMissing', () => {
|
|
|
232
275
|
expect(JSON.parse(logs[0]!)).toEqual([]);
|
|
233
276
|
});
|
|
234
277
|
|
|
235
|
-
it(
|
|
278
|
+
it("produces valid JSON array even with many entries", async () => {
|
|
236
279
|
const logs: string[] = [];
|
|
237
|
-
const adapter = makeAdapter({ name:
|
|
238
|
-
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig[
|
|
239
|
-
const resource: ResourceRef = { key:
|
|
280
|
+
const adapter = makeAdapter({ name: "test" });
|
|
281
|
+
const config = { ...baseConfig, adapters: [adapter] as unknown as DialektConfig["adapters"] };
|
|
282
|
+
const resource: ResourceRef = { key: "messages", label: "messages" };
|
|
240
283
|
|
|
241
284
|
const program = runMissing(
|
|
242
|
-
{
|
|
285
|
+
{
|
|
286
|
+
config: "./config.ts",
|
|
287
|
+
adapter: Option.none(),
|
|
288
|
+
baseLanguage: Option.none(),
|
|
289
|
+
language: Option.none(),
|
|
290
|
+
format: Option.some("json"),
|
|
291
|
+
},
|
|
243
292
|
() => Effect.succeed(config),
|
|
244
|
-
() =>
|
|
245
|
-
Effect.succeed([
|
|
246
|
-
{ adapter: 'test', locale: 'de', resource, missing: ['a', 'b', 'c'] },
|
|
247
|
-
]),
|
|
293
|
+
() => Effect.succeed([{ adapter: "test", locale: "de", resource, missing: ["a", "b", "c"] }]),
|
|
248
294
|
(msg) => Effect.sync(() => logs.push(msg)),
|
|
249
295
|
);
|
|
250
296
|
|
|
251
297
|
await Effect.runPromise(program);
|
|
252
298
|
const parsed = JSON.parse(logs[0]!);
|
|
253
299
|
expect(parsed).toHaveLength(3);
|
|
254
|
-
expect(parsed[0]).toMatchObject({
|
|
300
|
+
expect(parsed[0]).toMatchObject({
|
|
301
|
+
adapter: "test",
|
|
302
|
+
locale: "de",
|
|
303
|
+
resource: "messages",
|
|
304
|
+
key: "a",
|
|
305
|
+
});
|
|
255
306
|
});
|
|
256
307
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Command, Options } from
|
|
2
|
-
import { Effect, Console, Option } from
|
|
3
|
-
import { loadConfig } from
|
|
4
|
-
import { resolveEffectiveConfig } from
|
|
5
|
-
import { computeMissingKeys } from
|
|
6
|
-
import { detectFormat, type OutputFormat } from
|
|
7
|
-
import { formatMissingKeys } from
|
|
8
|
-
import type { DialektConfig } from
|
|
9
|
-
import type { TranslationAdapter } from
|
|
1
|
+
import { Command, Options } from "@effect/cli";
|
|
2
|
+
import { Effect, Console, Option } from "effect";
|
|
3
|
+
import { loadConfig } from "../../config/load-config.js";
|
|
4
|
+
import { resolveEffectiveConfig } from "../config-resolution.js";
|
|
5
|
+
import { computeMissingKeys } from "../../translation/missing-keys.js";
|
|
6
|
+
import { detectFormat, type OutputFormat } from "../format.js";
|
|
7
|
+
import { formatMissingKeys } from "../formatters.js";
|
|
8
|
+
import type { DialektConfig } from "../../config/types.js";
|
|
9
|
+
import type { TranslationAdapter } from "../../adapter/types.js";
|
|
10
10
|
|
|
11
11
|
export interface MissingFlags {
|
|
12
12
|
readonly config: string;
|
|
@@ -30,7 +30,10 @@ export function runMissing(
|
|
|
30
30
|
adapter: TranslationAdapter,
|
|
31
31
|
sourceLocale: string,
|
|
32
32
|
targetLocales: readonly string[],
|
|
33
|
-
) => Effect.Effect<
|
|
33
|
+
) => Effect.Effect<
|
|
34
|
+
readonly MissingKeysEntry[],
|
|
35
|
+
unknown
|
|
36
|
+
> = computeMissingKeys as unknown as typeof missingKeysComputer,
|
|
34
37
|
logger: (msg: string) => Effect.Effect<void> = (msg: string) => Console.log(msg),
|
|
35
38
|
): Effect.Effect<void, never> {
|
|
36
39
|
return Effect.gen(function* () {
|
|
@@ -79,10 +82,14 @@ export function runMissing(
|
|
|
79
82
|
}).pipe(Effect.mapError((e) => e as never)) as Effect.Effect<void, never, never>;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
export const missingCommand = Command.make(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
export const missingCommand = Command.make(
|
|
86
|
+
"missing",
|
|
87
|
+
{
|
|
88
|
+
config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
|
|
89
|
+
adapter: Options.optional(Options.text("adapter")),
|
|
90
|
+
baseLanguage: Options.optional(Options.text("base-language")),
|
|
91
|
+
language: Options.optional(Options.text("language")),
|
|
92
|
+
format: Options.optional(Options.text("format")),
|
|
93
|
+
},
|
|
94
|
+
(flags) => runMissing(flags),
|
|
95
|
+
);
|