@dialekt/adapter-paraglide 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 +37 -0
- package/TESTING.md +5 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +168 -108
- package/package.json +2 -2
- package/src/adapter.test.ts +67 -60
- package/src/adapter.ts +36 -32
- package/src/index.ts +2 -2
- package/src/message-file.test.ts +28 -28
- package/src/message-file.ts +11 -11
- package/src/unused-keys.test.ts +23 -23
- package/src/unused-keys.ts +18 -21
- package/tsconfig.json +1 -3
- package/tsdown.config.ts +3 -3
- package/vitest.config.ts +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<img src="../../resources/icon.svg" align="left" width="175">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
# `@dialekt/adapter-paraglide`
|
|
6
|
+
|
|
7
|
+
Paraglide adapter for [dialekt](https://github.com/mateffy/dialekt). Reads and writes inlang message-format JSON files (`messages/{locale}.json`), scans `.ts`, `.svelte`, and `.vue` source files for `m.key()` references, and reports unused keys.
|
|
8
|
+
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -D @dialekt/adapter-paraglide
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { defineConfig } from "dialekt";
|
|
21
|
+
import { paraglide } from "@dialekt/adapter-paraglide";
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
sourceLocale: "en",
|
|
25
|
+
targetLocales: ["de", "fr", "es"],
|
|
26
|
+
adapters: [
|
|
27
|
+
paraglide({
|
|
28
|
+
messagesDir: "./messages",
|
|
29
|
+
scanPaths: ["./src"],
|
|
30
|
+
}),
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Full documentation
|
|
36
|
+
|
|
37
|
+
See the [dialekt GitHub README](https://github.com/mateffy/dialekt) for commands, translation strategies, and the programmatic API.
|
package/TESTING.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Tested Areas Map
|
|
4
4
|
|
|
5
|
-
| Export
|
|
6
|
-
|
|
7
|
-
| `paraglide()` adapter
|
|
8
|
-
| `readMessageFile` / `writeMessageFile` | `src/message-file.test.ts` | ✅
|
|
9
|
-
| `findUnusedParaglideKeys`
|
|
5
|
+
| Export | Test File | Status |
|
|
6
|
+
| -------------------------------------- | -------------------------- | ------ |
|
|
7
|
+
| `paraglide()` adapter | `src/adapter.test.ts` | ✅ |
|
|
8
|
+
| `readMessageFile` / `writeMessageFile` | `src/message-file.test.ts` | ✅ |
|
|
9
|
+
| `findUnusedParaglideKeys` | `src/unused-keys.test.ts` | ✅ |
|
|
10
10
|
|
|
11
11
|
## Known Coverage Gaps
|
|
12
12
|
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,127 +1,187 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { Path } from "@effect/platform/Path";
|
|
3
3
|
import { FileSystem } from "@effect/platform/FileSystem";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AdapterReadError,
|
|
6
|
+
AdapterWriteError,
|
|
7
|
+
NodePlatformLayer,
|
|
8
|
+
flattenObject,
|
|
9
|
+
unflattenObject,
|
|
10
|
+
} from "dialekt";
|
|
5
11
|
import { FileSystem as FileSystem$1, Path as Path$1 } from "@effect/platform";
|
|
6
12
|
//#region src/message-file.ts
|
|
7
13
|
function readMessageFile(path) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
return Effect.gen(function* () {
|
|
15
|
+
const fs = yield* FileSystem$1.FileSystem;
|
|
16
|
+
if (!(yield* fs.exists(path).pipe(Effect.orElseSucceed(() => false))))
|
|
17
|
+
return {
|
|
18
|
+
translations: {},
|
|
19
|
+
meta: {},
|
|
20
|
+
};
|
|
21
|
+
const content = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(() => "{}"));
|
|
22
|
+
const parsed = yield* Effect.try({
|
|
23
|
+
try: () => JSON.parse(content),
|
|
24
|
+
catch: () => ({}),
|
|
25
|
+
}).pipe(Effect.orElseSucceed(() => ({})));
|
|
26
|
+
const meta = {};
|
|
27
|
+
const translations = {};
|
|
28
|
+
for (const [key, value] of Object.entries(parsed))
|
|
29
|
+
if (key.startsWith("$")) meta[key] = value;
|
|
30
|
+
else if (typeof value === "string") translations[key] = value;
|
|
31
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
32
|
+
const flattened = flattenObject(value);
|
|
33
|
+
for (const [flatKey, flatValue] of Object.entries(flattened))
|
|
34
|
+
if (typeof flatValue === "string") translations[`${key}.${flatKey}`] = flatValue;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
translations,
|
|
38
|
+
meta,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
32
41
|
}
|
|
33
42
|
function writeMessageFile(path, translations, meta) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
return Effect.gen(function* () {
|
|
44
|
+
const fs = yield* FileSystem$1.FileSystem;
|
|
45
|
+
const dir = (yield* Path$1.Path).dirname(path);
|
|
46
|
+
yield* fs.makeDirectory(dir, { recursive: true }).pipe(Effect.orElseSucceed(() => void 0));
|
|
47
|
+
const unflattened = unflattenObject(translations);
|
|
48
|
+
const output = { ...meta };
|
|
49
|
+
for (const [key, value] of Object.entries(unflattened)) output[key] = value;
|
|
50
|
+
yield* fs
|
|
51
|
+
.writeFileString(path, JSON.stringify(output, null, 2) + "\n")
|
|
52
|
+
.pipe(Effect.orElseSucceed(() => void 0));
|
|
53
|
+
});
|
|
43
54
|
}
|
|
44
55
|
//#endregion
|
|
45
56
|
//#region src/unused-keys.ts
|
|
46
57
|
function findUnusedParaglideKeys(scanPaths, keys) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
return Effect.gen(function* () {
|
|
59
|
+
const fs = yield* FileSystem$1.FileSystem;
|
|
60
|
+
const path = yield* Path$1.Path;
|
|
61
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
62
|
+
for (const scanPath of scanPaths) {
|
|
63
|
+
if (!(yield* fs.exists(scanPath).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
64
|
+
const entries = yield* fs
|
|
65
|
+
.readDirectory(scanPath, { recursive: true })
|
|
66
|
+
.pipe(Effect.orElseSucceed(() => []));
|
|
67
|
+
for (const relativePath of entries) {
|
|
68
|
+
if (
|
|
69
|
+
!relativePath.endsWith(".ts") &&
|
|
70
|
+
!relativePath.endsWith(".tsx") &&
|
|
71
|
+
!relativePath.endsWith(".js") &&
|
|
72
|
+
!relativePath.endsWith(".jsx") &&
|
|
73
|
+
!relativePath.endsWith(".svelte") &&
|
|
74
|
+
!relativePath.endsWith(".vue")
|
|
75
|
+
)
|
|
76
|
+
continue;
|
|
77
|
+
const filePath = path.join(scanPath, relativePath);
|
|
78
|
+
const content = yield* fs.readFileString(filePath).pipe(Effect.orElseSucceed(() => ""));
|
|
79
|
+
for (const key of keys)
|
|
80
|
+
if (new RegExp(`\\bm\\.${key}\\b`).test(content)) referenced.add(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return keys.filter((key) => !referenced.has(key));
|
|
84
|
+
}).pipe(
|
|
85
|
+
Effect.mapError(
|
|
86
|
+
(cause) =>
|
|
87
|
+
new AdapterReadError({
|
|
88
|
+
adapter: "paraglide",
|
|
89
|
+
locale: "",
|
|
90
|
+
resource: "messages",
|
|
91
|
+
cause,
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
68
95
|
}
|
|
69
96
|
//#endregion
|
|
70
97
|
//#region src/adapter.ts
|
|
71
98
|
function paraglide(options) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
99
|
+
const { messagesDir, scanPaths = [] } = options;
|
|
100
|
+
return {
|
|
101
|
+
name: "paraglide",
|
|
102
|
+
capabilities: {
|
|
103
|
+
canCreateResource: true,
|
|
104
|
+
unusedKeyDetection: true,
|
|
105
|
+
},
|
|
106
|
+
listLocales: () =>
|
|
107
|
+
Effect.gen(function* () {
|
|
108
|
+
const fs = yield* FileSystem;
|
|
109
|
+
yield* Path;
|
|
110
|
+
if (!(yield* fs.exists(messagesDir).pipe(Effect.orElseSucceed(() => false)))) return [];
|
|
111
|
+
const entries = yield* fs.readDirectory(messagesDir).pipe(Effect.orElseSucceed(() => []));
|
|
112
|
+
const locales = [];
|
|
113
|
+
for (const entry of entries)
|
|
114
|
+
if (entry.endsWith(".json")) locales.push(entry.replace(/\.json$/, ""));
|
|
115
|
+
return locales;
|
|
116
|
+
}).pipe(
|
|
117
|
+
Effect.mapError(
|
|
118
|
+
(cause) =>
|
|
119
|
+
new AdapterReadError({
|
|
120
|
+
adapter: "paraglide",
|
|
121
|
+
locale: "",
|
|
122
|
+
resource: "",
|
|
123
|
+
cause,
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
Effect.provide([NodePlatformLayer]),
|
|
127
|
+
),
|
|
128
|
+
listResources: () =>
|
|
129
|
+
Effect.succeed([
|
|
130
|
+
{
|
|
131
|
+
key: "messages",
|
|
132
|
+
label: "messages",
|
|
133
|
+
},
|
|
134
|
+
]),
|
|
135
|
+
readResource: (locale, resource) =>
|
|
136
|
+
Effect.gen(function* () {
|
|
137
|
+
return (yield* readMessageFile((yield* Path).join(messagesDir, `${locale}.json`)).pipe(
|
|
138
|
+
Effect.mapError(
|
|
139
|
+
(cause) =>
|
|
140
|
+
new AdapterReadError({
|
|
141
|
+
adapter: "paraglide",
|
|
142
|
+
locale,
|
|
143
|
+
resource: resource.key,
|
|
144
|
+
cause,
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
)).translations;
|
|
148
|
+
}).pipe(Effect.provide([NodePlatformLayer])),
|
|
149
|
+
writeResource: (locale, resource, entries) =>
|
|
150
|
+
Effect.gen(function* () {
|
|
151
|
+
const filePath = (yield* Path).join(messagesDir, `${locale}.json`);
|
|
152
|
+
yield* writeMessageFile(
|
|
153
|
+
filePath,
|
|
154
|
+
entries,
|
|
155
|
+
(yield* readMessageFile(filePath).pipe(
|
|
156
|
+
Effect.orElseSucceed(() => ({
|
|
157
|
+
translations: {},
|
|
158
|
+
meta: {},
|
|
159
|
+
})),
|
|
160
|
+
)).meta,
|
|
161
|
+
).pipe(
|
|
162
|
+
Effect.mapError(
|
|
163
|
+
(cause) =>
|
|
164
|
+
new AdapterWriteError({
|
|
165
|
+
adapter: "paraglide",
|
|
166
|
+
locale,
|
|
167
|
+
resource: resource.key,
|
|
168
|
+
cause,
|
|
169
|
+
}),
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
}).pipe(Effect.provide([NodePlatformLayer])),
|
|
173
|
+
findUnusedKeys: (locale, resource) =>
|
|
174
|
+
Effect.gen(function* () {
|
|
175
|
+
const path = yield* Path;
|
|
176
|
+
return yield* findUnusedParaglideKeys(
|
|
177
|
+
scanPaths.length > 0 ? scanPaths : [path.resolve(messagesDir, "..")],
|
|
178
|
+
yield* Effect.gen(function* () {
|
|
179
|
+
const map = yield* paraglide(options).readResource(locale, resource);
|
|
180
|
+
return Object.keys(map);
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}).pipe(Effect.provide([NodePlatformLayer])),
|
|
184
|
+
};
|
|
125
185
|
}
|
|
126
186
|
//#endregion
|
|
127
187
|
export { paraglide };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dialekt/adapter-paraglide",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@effect/platform": "^0.96.0",
|
|
13
13
|
"effect": "^3.21.0",
|
|
14
|
-
"dialekt": "0.1.
|
|
14
|
+
"dialekt": "0.1.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^26.0.1",
|
package/src/adapter.test.ts
CHANGED
|
@@ -1,102 +1,109 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect } from
|
|
3
|
-
import { NodePlatformLayer } from
|
|
4
|
-
import { paraglide } from
|
|
5
|
-
import { mkdirSync, writeFileSync, rmSync } from
|
|
6
|
-
import { join } from
|
|
7
|
-
import { tmpdir } from
|
|
8
|
-
|
|
9
|
-
describe(
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { NodePlatformLayer } from "dialekt";
|
|
4
|
+
import { paraglide } from "./adapter.js";
|
|
5
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
|
|
9
|
+
describe("paraglide adapter", () => {
|
|
10
10
|
const testDir = join(tmpdir(), `paraglide-adapter-test-${Date.now()}`);
|
|
11
11
|
|
|
12
|
-
it(
|
|
12
|
+
it("reads a message resource", async () => {
|
|
13
13
|
mkdirSync(testDir, { recursive: true });
|
|
14
|
-
writeFileSync(join(testDir,
|
|
14
|
+
writeFileSync(join(testDir, "en.json"), JSON.stringify({ greeting: "Hello" }));
|
|
15
15
|
|
|
16
16
|
const adapter = paraglide({ messagesDir: testDir, scanPaths: [testDir] });
|
|
17
|
-
const program = adapter
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const program = adapter
|
|
18
|
+
.readResource("en", { key: "messages", label: "messages" })
|
|
19
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
20
20
|
const result = await Effect.runPromise(program);
|
|
21
|
-
expect(result).toEqual({ greeting:
|
|
21
|
+
expect(result).toEqual({ greeting: "Hello" });
|
|
22
22
|
|
|
23
23
|
rmSync(testDir, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it(
|
|
26
|
+
it("lists locales from filenames", async () => {
|
|
27
27
|
mkdirSync(testDir, { recursive: true });
|
|
28
|
-
writeFileSync(join(testDir,
|
|
29
|
-
writeFileSync(join(testDir,
|
|
28
|
+
writeFileSync(join(testDir, "en.json"), "{}");
|
|
29
|
+
writeFileSync(join(testDir, "de.json"), "{}");
|
|
30
30
|
|
|
31
31
|
const adapter = paraglide({ messagesDir: testDir });
|
|
32
32
|
const program = adapter.listLocales().pipe(Effect.provide(NodePlatformLayer));
|
|
33
33
|
const result = await Effect.runPromise(program);
|
|
34
|
-
expect(result).toContain(
|
|
35
|
-
expect(result).toContain(
|
|
34
|
+
expect(result).toContain("en");
|
|
35
|
+
expect(result).toContain("de");
|
|
36
36
|
|
|
37
37
|
rmSync(testDir, { recursive: true, force: true });
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it("lists exactly one resource", async () => {
|
|
41
41
|
const adapter = paraglide({ messagesDir: testDir });
|
|
42
|
-
const program = adapter.listResources(
|
|
42
|
+
const program = adapter.listResources("en").pipe(Effect.provide(NodePlatformLayer));
|
|
43
43
|
const result = await Effect.runPromise(program);
|
|
44
44
|
expect(result).toHaveLength(1);
|
|
45
|
-
expect(result[0]).toEqual({ key:
|
|
45
|
+
expect(result[0]).toEqual({ key: "messages", label: "messages" });
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
it(
|
|
48
|
+
it("round-trips write and read preserving meta", async () => {
|
|
49
49
|
mkdirSync(testDir, { recursive: true });
|
|
50
50
|
writeFileSync(
|
|
51
|
-
join(testDir,
|
|
52
|
-
JSON.stringify({ $schema:
|
|
51
|
+
join(testDir, "de.json"),
|
|
52
|
+
JSON.stringify({ $schema: "https://inlang.com/schema", greeting: "Hallo" }),
|
|
53
53
|
);
|
|
54
54
|
|
|
55
55
|
const adapter = paraglide({ messagesDir: testDir });
|
|
56
56
|
const writeProgram = adapter
|
|
57
|
-
.writeResource(
|
|
57
|
+
.writeResource(
|
|
58
|
+
"de",
|
|
59
|
+
{ key: "messages", label: "messages" },
|
|
60
|
+
{ greeting: "Hallo!", farewell: "Tschüss" },
|
|
61
|
+
)
|
|
58
62
|
.pipe(Effect.provide(NodePlatformLayer));
|
|
59
63
|
await Effect.runPromise(writeProgram);
|
|
60
64
|
|
|
61
|
-
const readProgram = adapter
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
const readProgram = adapter
|
|
66
|
+
.readResource("de", { key: "messages", label: "messages" })
|
|
67
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
64
68
|
const result = await Effect.runPromise(readProgram);
|
|
65
|
-
expect(result).toEqual({ greeting:
|
|
69
|
+
expect(result).toEqual({ greeting: "Hallo!", farewell: "Tschüss" });
|
|
66
70
|
|
|
67
71
|
rmSync(testDir, { recursive: true, force: true });
|
|
68
72
|
});
|
|
69
73
|
|
|
70
|
-
it(
|
|
74
|
+
it("finds unused keys", async () => {
|
|
71
75
|
mkdirSync(testDir, { recursive: true });
|
|
72
|
-
writeFileSync(
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
writeFileSync(
|
|
77
|
+
join(testDir, "en.json"),
|
|
78
|
+
JSON.stringify({ greeting: "Hello", farewell: "Goodbye" }),
|
|
79
|
+
);
|
|
80
|
+
mkdirSync(join(testDir, "src"), { recursive: true });
|
|
81
|
+
writeFileSync(join(testDir, "src", "page.ts"), "m.greeting();");
|
|
75
82
|
|
|
76
|
-
const adapter = paraglide({ messagesDir: testDir, scanPaths: [join(testDir,
|
|
77
|
-
const program = adapter
|
|
78
|
-
.
|
|
79
|
-
|
|
83
|
+
const adapter = paraglide({ messagesDir: testDir, scanPaths: [join(testDir, "src")] });
|
|
84
|
+
const program = adapter.findUnusedKeys!("en", { key: "messages", label: "messages" }).pipe(
|
|
85
|
+
Effect.provide(NodePlatformLayer),
|
|
86
|
+
);
|
|
80
87
|
const result = await Effect.runPromise(program);
|
|
81
|
-
expect(result).toEqual([
|
|
88
|
+
expect(result).toEqual(["farewell"]);
|
|
82
89
|
|
|
83
90
|
rmSync(testDir, { recursive: true, force: true });
|
|
84
91
|
});
|
|
85
92
|
|
|
86
|
-
it(
|
|
93
|
+
it("returns empty for missing locale file", async () => {
|
|
87
94
|
mkdirSync(testDir, { recursive: true });
|
|
88
95
|
|
|
89
96
|
const adapter = paraglide({ messagesDir: testDir });
|
|
90
|
-
const program = adapter
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const program = adapter
|
|
98
|
+
.readResource("missing", { key: "messages", label: "messages" })
|
|
99
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
93
100
|
const result = await Effect.runPromise(program);
|
|
94
101
|
expect(result).toEqual({});
|
|
95
102
|
|
|
96
103
|
rmSync(testDir, { recursive: true, force: true });
|
|
97
104
|
});
|
|
98
105
|
|
|
99
|
-
it(
|
|
106
|
+
it("handles empty messages directory", async () => {
|
|
100
107
|
mkdirSync(testDir, { recursive: true });
|
|
101
108
|
|
|
102
109
|
const adapter = paraglide({ messagesDir: testDir });
|
|
@@ -107,48 +114,48 @@ describe('paraglide adapter', () => {
|
|
|
107
114
|
rmSync(testDir, { recursive: true, force: true });
|
|
108
115
|
});
|
|
109
116
|
|
|
110
|
-
it(
|
|
117
|
+
it("handles capabilities flags", () => {
|
|
111
118
|
const adapter = paraglide({ messagesDir: testDir });
|
|
112
119
|
expect(adapter.capabilities.canCreateResource).toBe(true);
|
|
113
120
|
expect(adapter.capabilities.unusedKeyDetection).toBe(true);
|
|
114
121
|
});
|
|
115
122
|
|
|
116
|
-
it(
|
|
123
|
+
it("reports adapter name", () => {
|
|
117
124
|
const adapter = paraglide({ messagesDir: testDir });
|
|
118
|
-
expect(adapter.name).toBe(
|
|
125
|
+
expect(adapter.name).toBe("paraglide");
|
|
119
126
|
});
|
|
120
127
|
|
|
121
|
-
it(
|
|
128
|
+
it("ignores non-JSON files", async () => {
|
|
122
129
|
mkdirSync(testDir, { recursive: true });
|
|
123
|
-
writeFileSync(join(testDir,
|
|
124
|
-
writeFileSync(join(testDir,
|
|
130
|
+
writeFileSync(join(testDir, "README.md"), "# Messages");
|
|
131
|
+
writeFileSync(join(testDir, "en.json"), "{}");
|
|
125
132
|
|
|
126
133
|
const adapter = paraglide({ messagesDir: testDir });
|
|
127
134
|
const program = adapter.listLocales().pipe(Effect.provide(NodePlatformLayer));
|
|
128
135
|
const result = await Effect.runPromise(program);
|
|
129
|
-
expect(result).toEqual([
|
|
136
|
+
expect(result).toEqual(["en"]);
|
|
130
137
|
|
|
131
138
|
rmSync(testDir, { recursive: true, force: true });
|
|
132
139
|
});
|
|
133
140
|
|
|
134
|
-
it(
|
|
141
|
+
it("preserves meta keys on write", async () => {
|
|
135
142
|
mkdirSync(testDir, { recursive: true });
|
|
136
143
|
writeFileSync(
|
|
137
|
-
join(testDir,
|
|
138
|
-
JSON.stringify({ $schema:
|
|
144
|
+
join(testDir, "de.json"),
|
|
145
|
+
JSON.stringify({ $schema: "https://inlang.com/schema", module: "messages" }),
|
|
139
146
|
);
|
|
140
147
|
|
|
141
148
|
const adapter = paraglide({ messagesDir: testDir });
|
|
142
149
|
const writeProgram = adapter
|
|
143
|
-
.writeResource(
|
|
150
|
+
.writeResource("de", { key: "messages", label: "messages" }, { hello: "Hallo" })
|
|
144
151
|
.pipe(Effect.provide(NodePlatformLayer));
|
|
145
152
|
await Effect.runPromise(writeProgram);
|
|
146
153
|
|
|
147
|
-
const readProgram = adapter
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
const readProgram = adapter
|
|
155
|
+
.readResource("de", { key: "messages", label: "messages" })
|
|
156
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
150
157
|
const result = await Effect.runPromise(readProgram);
|
|
151
|
-
expect(result).toEqual({ hello:
|
|
158
|
+
expect(result).toEqual({ hello: "Hallo" });
|
|
152
159
|
|
|
153
160
|
rmSync(testDir, { recursive: true, force: true });
|
|
154
161
|
});
|
package/src/adapter.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import { Effect } from
|
|
2
|
-
import { Path } from
|
|
3
|
-
import { FileSystem } from
|
|
4
|
-
import type {
|
|
5
|
-
ResourceRef,
|
|
6
|
-
TranslationAdapter,
|
|
7
|
-
AdapterReadError,
|
|
8
|
-
AdapterWriteError,
|
|
9
|
-
} from 'dialekt';
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Path } from "@effect/platform/Path";
|
|
3
|
+
import { FileSystem } from "@effect/platform/FileSystem";
|
|
4
|
+
import type { ResourceRef, TranslationAdapter, AdapterReadError, AdapterWriteError } from "dialekt";
|
|
10
5
|
import {
|
|
11
6
|
AdapterReadError as AdapterReadErrorClass,
|
|
12
7
|
AdapterWriteError as AdapterWriteErrorClass,
|
|
13
8
|
NodePlatformLayer,
|
|
14
|
-
} from
|
|
15
|
-
import { readMessageFile, writeMessageFile } from
|
|
16
|
-
import { findUnusedParaglideKeys } from
|
|
9
|
+
} from "dialekt";
|
|
10
|
+
import { readMessageFile, writeMessageFile } from "./message-file.js";
|
|
11
|
+
import { findUnusedParaglideKeys } from "./unused-keys.js";
|
|
17
12
|
|
|
18
13
|
export interface ParaglideAdapterOptions {
|
|
19
14
|
readonly messagesDir: string;
|
|
@@ -24,7 +19,7 @@ export function paraglide(options: ParaglideAdapterOptions): TranslationAdapter
|
|
|
24
19
|
const { messagesDir, scanPaths = [] } = options;
|
|
25
20
|
|
|
26
21
|
return {
|
|
27
|
-
name:
|
|
22
|
+
name: "paraglide",
|
|
28
23
|
capabilities: {
|
|
29
24
|
canCreateResource: true,
|
|
30
25
|
unusedKeyDetection: true,
|
|
@@ -36,13 +31,13 @@ export function paraglide(options: ParaglideAdapterOptions): TranslationAdapter
|
|
|
36
31
|
const path = yield* Path;
|
|
37
32
|
const exists = yield* fs.exists(messagesDir).pipe(Effect.orElseSucceed(() => false));
|
|
38
33
|
if (!exists) return [];
|
|
39
|
-
const entries = yield* fs
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
const entries = yield* fs
|
|
35
|
+
.readDirectory(messagesDir)
|
|
36
|
+
.pipe(Effect.orElseSucceed(() => [] as string[]));
|
|
42
37
|
const locales: string[] = [];
|
|
43
38
|
for (const entry of entries) {
|
|
44
|
-
if (entry.endsWith(
|
|
45
|
-
locales.push(entry.replace(/\.json$/,
|
|
39
|
+
if (entry.endsWith(".json")) {
|
|
40
|
+
locales.push(entry.replace(/\.json$/, ""));
|
|
46
41
|
}
|
|
47
42
|
}
|
|
48
43
|
return locales;
|
|
@@ -50,17 +45,16 @@ export function paraglide(options: ParaglideAdapterOptions): TranslationAdapter
|
|
|
50
45
|
Effect.mapError(
|
|
51
46
|
(cause) =>
|
|
52
47
|
new AdapterReadErrorClass({
|
|
53
|
-
adapter:
|
|
54
|
-
locale:
|
|
55
|
-
resource:
|
|
48
|
+
adapter: "paraglide",
|
|
49
|
+
locale: "",
|
|
50
|
+
resource: "",
|
|
56
51
|
cause,
|
|
57
52
|
}) as AdapterReadError,
|
|
58
53
|
),
|
|
59
54
|
Effect.provide([NodePlatformLayer]),
|
|
60
55
|
) as Effect.Effect<readonly string[], AdapterReadError, never>,
|
|
61
56
|
|
|
62
|
-
listResources: () =>
|
|
63
|
-
Effect.succeed([{ key: 'messages', label: 'messages' }]),
|
|
57
|
+
listResources: () => Effect.succeed([{ key: "messages", label: "messages" }]),
|
|
64
58
|
|
|
65
59
|
readResource: (locale, resource) =>
|
|
66
60
|
Effect.gen(function* () {
|
|
@@ -70,7 +64,7 @@ export function paraglide(options: ParaglideAdapterOptions): TranslationAdapter
|
|
|
70
64
|
Effect.mapError(
|
|
71
65
|
(cause) =>
|
|
72
66
|
new AdapterReadErrorClass({
|
|
73
|
-
adapter:
|
|
67
|
+
adapter: "paraglide",
|
|
74
68
|
locale,
|
|
75
69
|
resource: resource.key,
|
|
76
70
|
cause,
|
|
@@ -78,21 +72,27 @@ export function paraglide(options: ParaglideAdapterOptions): TranslationAdapter
|
|
|
78
72
|
),
|
|
79
73
|
);
|
|
80
74
|
return result.translations;
|
|
81
|
-
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
75
|
+
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
76
|
+
Record<string, string>,
|
|
77
|
+
AdapterReadError,
|
|
78
|
+
never
|
|
79
|
+
>,
|
|
82
80
|
|
|
83
81
|
writeResource: (locale, resource, entries) =>
|
|
84
82
|
Effect.gen(function* () {
|
|
85
83
|
const path = yield* Path;
|
|
86
84
|
const filePath = path.join(messagesDir, `${locale}.json`);
|
|
87
|
-
const existing = yield* readMessageFile(filePath).pipe(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const existing = yield* readMessageFile(filePath).pipe(
|
|
86
|
+
Effect.orElseSucceed(() => ({
|
|
87
|
+
translations: {},
|
|
88
|
+
meta: {},
|
|
89
|
+
})),
|
|
90
|
+
);
|
|
91
91
|
yield* writeMessageFile(filePath, entries, existing.meta).pipe(
|
|
92
92
|
Effect.mapError(
|
|
93
93
|
(cause) =>
|
|
94
94
|
new AdapterWriteErrorClass({
|
|
95
|
-
adapter:
|
|
95
|
+
adapter: "paraglide",
|
|
96
96
|
locale,
|
|
97
97
|
resource: resource.key,
|
|
98
98
|
cause,
|
|
@@ -105,12 +105,16 @@ export function paraglide(options: ParaglideAdapterOptions): TranslationAdapter
|
|
|
105
105
|
Effect.gen(function* () {
|
|
106
106
|
const path = yield* Path;
|
|
107
107
|
const adapterScanPaths =
|
|
108
|
-
scanPaths.length > 0 ? scanPaths : [path.resolve(messagesDir,
|
|
108
|
+
scanPaths.length > 0 ? scanPaths : [path.resolve(messagesDir, "..")];
|
|
109
109
|
const keys = yield* Effect.gen(function* () {
|
|
110
110
|
const map = yield* paraglide(options).readResource(locale, resource);
|
|
111
111
|
return Object.keys(map);
|
|
112
112
|
});
|
|
113
113
|
return yield* findUnusedParaglideKeys(adapterScanPaths, keys);
|
|
114
|
-
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
114
|
+
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
115
|
+
readonly string[],
|
|
116
|
+
AdapterReadError,
|
|
117
|
+
never
|
|
118
|
+
>,
|
|
115
119
|
};
|
|
116
120
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { paraglide } from
|
|
2
|
-
export type { ParaglideAdapterOptions } from
|
|
1
|
+
export { paraglide } from "./adapter.js";
|
|
2
|
+
export type { ParaglideAdapterOptions } from "./adapter.js";
|
package/src/message-file.test.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect, Layer } from
|
|
3
|
-
import { FileSystem, Path } from
|
|
4
|
-
import { readMessageFile, writeMessageFile } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
4
|
+
import { readMessageFile, writeMessageFile } from "./message-file.js";
|
|
5
5
|
|
|
6
6
|
function makeFsLayer(files: Record<string, string>) {
|
|
7
7
|
const stub = FileSystem.makeNoop({
|
|
8
8
|
exists: (path) => Effect.succeed(path in files),
|
|
9
9
|
readFileString: (path) =>
|
|
10
|
-
path in files ? Effect.succeed(files[path]!) : Effect.fail(new Error(
|
|
10
|
+
path in files ? Effect.succeed(files[path]!) : Effect.fail(new Error("ENOENT") as never),
|
|
11
11
|
writeFileString: (path, content) => {
|
|
12
12
|
files[path] = content;
|
|
13
13
|
return Effect.void;
|
|
@@ -17,50 +17,50 @@ function makeFsLayer(files: Record<string, string>) {
|
|
|
17
17
|
return Layer.succeed(FileSystem.FileSystem, stub);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
describe(
|
|
21
|
-
it(
|
|
20
|
+
describe("readMessageFile", () => {
|
|
21
|
+
it("reads flat translations and preserves meta keys", async () => {
|
|
22
22
|
const files = {
|
|
23
|
-
|
|
24
|
-
$schema:
|
|
25
|
-
greeting:
|
|
26
|
-
farewell:
|
|
23
|
+
"/messages/en.json": JSON.stringify({
|
|
24
|
+
$schema: "https://inlang.com/schema",
|
|
25
|
+
greeting: "Hello",
|
|
26
|
+
farewell: "Goodbye",
|
|
27
27
|
}),
|
|
28
28
|
};
|
|
29
|
-
const program = readMessageFile(
|
|
29
|
+
const program = readMessageFile("/messages/en.json").pipe(Effect.provide(makeFsLayer(files)));
|
|
30
30
|
const result = await Effect.runPromise(program);
|
|
31
|
-
expect(result.translations).toEqual({ greeting:
|
|
32
|
-
expect(result.meta).toEqual({ $schema:
|
|
31
|
+
expect(result.translations).toEqual({ greeting: "Hello", farewell: "Goodbye" });
|
|
32
|
+
expect(result.meta).toEqual({ $schema: "https://inlang.com/schema" });
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it(
|
|
36
|
-
const program = readMessageFile(
|
|
35
|
+
it("returns empty for missing file", async () => {
|
|
36
|
+
const program = readMessageFile("/messages/missing.json").pipe(Effect.provide(makeFsLayer({})));
|
|
37
37
|
const result = await Effect.runPromise(program);
|
|
38
38
|
expect(result.translations).toEqual({});
|
|
39
39
|
expect(result.meta).toEqual({});
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it(
|
|
42
|
+
it("flattens nested objects", async () => {
|
|
43
43
|
const files = {
|
|
44
|
-
|
|
45
|
-
nav: { home:
|
|
44
|
+
"/messages/en.json": JSON.stringify({
|
|
45
|
+
nav: { home: "Home", about: "About" },
|
|
46
46
|
}),
|
|
47
47
|
};
|
|
48
|
-
const program = readMessageFile(
|
|
48
|
+
const program = readMessageFile("/messages/en.json").pipe(Effect.provide(makeFsLayer(files)));
|
|
49
49
|
const result = await Effect.runPromise(program);
|
|
50
|
-
expect(result.translations).toEqual({
|
|
50
|
+
expect(result.translations).toEqual({ "nav.home": "Home", "nav.about": "About" });
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
describe(
|
|
55
|
-
it(
|
|
54
|
+
describe("writeMessageFile", () => {
|
|
55
|
+
it("round-trips translations and meta", async () => {
|
|
56
56
|
const files: Record<string, string> = {};
|
|
57
57
|
const program = writeMessageFile(
|
|
58
|
-
|
|
59
|
-
{ greeting:
|
|
60
|
-
{ $schema:
|
|
58
|
+
"/messages/de.json",
|
|
59
|
+
{ greeting: "Hallo" },
|
|
60
|
+
{ $schema: "https://inlang.com/schema" },
|
|
61
61
|
).pipe(Effect.provide(makeFsLayer(files)), Effect.provide(Path.layer));
|
|
62
62
|
await Effect.runPromise(program);
|
|
63
|
-
expect(files[
|
|
64
|
-
expect(files[
|
|
63
|
+
expect(files["/messages/de.json"]).toContain("Hallo");
|
|
64
|
+
expect(files["/messages/de.json"]).toContain("$schema");
|
|
65
65
|
});
|
|
66
66
|
});
|
package/src/message-file.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FileSystem, Path } from
|
|
2
|
-
import { Effect } from
|
|
3
|
-
import { flattenObject, unflattenObject } from
|
|
1
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { flattenObject, unflattenObject } from "dialekt";
|
|
4
4
|
|
|
5
5
|
export interface MessageFileResult {
|
|
6
6
|
readonly translations: Record<string, string>;
|
|
@@ -16,7 +16,7 @@ export function readMessageFile(
|
|
|
16
16
|
if (!exists) {
|
|
17
17
|
return { translations: {}, meta: {} };
|
|
18
18
|
}
|
|
19
|
-
const content = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(() =>
|
|
19
|
+
const content = yield* fs.readFileString(path).pipe(Effect.orElseSucceed(() => "{}"));
|
|
20
20
|
const parsed = yield* Effect.try({
|
|
21
21
|
try: () => JSON.parse(content) as Record<string, unknown>,
|
|
22
22
|
catch: () => ({}),
|
|
@@ -26,14 +26,14 @@ export function readMessageFile(
|
|
|
26
26
|
const translations: Record<string, string> = {};
|
|
27
27
|
|
|
28
28
|
for (const [key, value] of Object.entries(parsed)) {
|
|
29
|
-
if (key.startsWith(
|
|
29
|
+
if (key.startsWith("$")) {
|
|
30
30
|
meta[key] = value;
|
|
31
|
-
} else if (typeof value ===
|
|
31
|
+
} else if (typeof value === "string") {
|
|
32
32
|
translations[key] = value;
|
|
33
|
-
} else if (typeof value ===
|
|
33
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
34
34
|
const flattened = flattenObject(value as Record<string, unknown>);
|
|
35
35
|
for (const [flatKey, flatValue] of Object.entries(flattened)) {
|
|
36
|
-
if (typeof flatValue ===
|
|
36
|
+
if (typeof flatValue === "string") {
|
|
37
37
|
translations[`${key}.${flatKey}`] = flatValue;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -61,8 +61,8 @@ export function writeMessageFile(
|
|
|
61
61
|
output[key] = value;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
yield* fs
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
yield* fs
|
|
65
|
+
.writeFileString(path, JSON.stringify(output, null, 2) + "\n")
|
|
66
|
+
.pipe(Effect.orElseSucceed(() => undefined));
|
|
67
67
|
});
|
|
68
68
|
}
|
package/src/unused-keys.test.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect } from
|
|
3
|
-
import { NodePlatformLayer } from
|
|
4
|
-
import { findUnusedParaglideKeys } from
|
|
5
|
-
import { mkdirSync, writeFileSync, rmSync } from
|
|
6
|
-
import { join } from
|
|
7
|
-
import { tmpdir } from
|
|
8
|
-
|
|
9
|
-
describe(
|
|
10
|
-
it(
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { NodePlatformLayer } from "dialekt";
|
|
4
|
+
import { findUnusedParaglideKeys } from "./unused-keys.js";
|
|
5
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
|
|
9
|
+
describe("findUnusedParaglideKeys", () => {
|
|
10
|
+
it("finds keys not referenced in source files", async () => {
|
|
11
11
|
const dir = join(tmpdir(), `paraglide-unused-${Date.now()}`);
|
|
12
|
-
mkdirSync(join(dir,
|
|
13
|
-
writeFileSync(join(dir,
|
|
12
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
13
|
+
writeFileSync(join(dir, "src", "page.tsx"), "const t = m.greeting({ name: 'x' });");
|
|
14
14
|
|
|
15
|
-
const program = findUnusedParaglideKeys([join(dir,
|
|
15
|
+
const program = findUnusedParaglideKeys([join(dir, "src")], ["greeting", "farewell"]).pipe(
|
|
16
16
|
Effect.provide(NodePlatformLayer),
|
|
17
17
|
);
|
|
18
18
|
const result = await Effect.runPromise(program);
|
|
19
|
-
expect(result).toEqual([
|
|
19
|
+
expect(result).toEqual(["farewell"]);
|
|
20
20
|
|
|
21
21
|
rmSync(dir, { recursive: true, force: true });
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
it(
|
|
24
|
+
it("finds m.key reference without call parens", async () => {
|
|
25
25
|
const dir = join(tmpdir(), `paraglide-unused-ref-${Date.now()}`);
|
|
26
|
-
mkdirSync(join(dir,
|
|
27
|
-
writeFileSync(join(dir,
|
|
26
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
27
|
+
writeFileSync(join(dir, "src", "util.ts"), "export const msg = m.greeting;");
|
|
28
28
|
|
|
29
|
-
const program = findUnusedParaglideKeys([join(dir,
|
|
29
|
+
const program = findUnusedParaglideKeys([join(dir, "src")], ["greeting"]).pipe(
|
|
30
30
|
Effect.provide(NodePlatformLayer),
|
|
31
31
|
);
|
|
32
32
|
const result = await Effect.runPromise(program);
|
|
@@ -35,16 +35,16 @@ describe('findUnusedParaglideKeys', () => {
|
|
|
35
35
|
rmSync(dir, { recursive: true, force: true });
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
it(
|
|
38
|
+
it("does not treat prefixes as matches", async () => {
|
|
39
39
|
const dir = join(tmpdir(), `paraglide-unused-prefix-${Date.now()}`);
|
|
40
|
-
mkdirSync(join(dir,
|
|
41
|
-
writeFileSync(join(dir,
|
|
40
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
41
|
+
writeFileSync(join(dir, "src", "page.ts"), "m.greeting();");
|
|
42
42
|
|
|
43
|
-
const program = findUnusedParaglideKeys([join(dir,
|
|
43
|
+
const program = findUnusedParaglideKeys([join(dir, "src")], ["greet", "greeting"]).pipe(
|
|
44
44
|
Effect.provide(NodePlatformLayer),
|
|
45
45
|
);
|
|
46
46
|
const result = await Effect.runPromise(program);
|
|
47
|
-
expect(result).toEqual([
|
|
47
|
+
expect(result).toEqual(["greet"]);
|
|
48
48
|
|
|
49
49
|
rmSync(dir, { recursive: true, force: true });
|
|
50
50
|
});
|
package/src/unused-keys.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { FileSystem, Path } from
|
|
2
|
-
import { Effect } from
|
|
3
|
-
import type { AdapterReadError } from
|
|
4
|
-
import { AdapterReadError as AdapterReadErrorClass } from
|
|
1
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import type { AdapterReadError } from "dialekt";
|
|
4
|
+
import { AdapterReadError as AdapterReadErrorClass } from "dialekt";
|
|
5
5
|
|
|
6
|
-
export function findUnusedParaglideKeys(
|
|
7
|
-
scanPaths: readonly string[],
|
|
8
|
-
keys: readonly string[],
|
|
9
|
-
) {
|
|
6
|
+
export function findUnusedParaglideKeys(scanPaths: readonly string[], keys: readonly string[]) {
|
|
10
7
|
return Effect.gen(function* () {
|
|
11
8
|
const fs = yield* FileSystem.FileSystem;
|
|
12
9
|
const path = yield* Path.Path;
|
|
@@ -17,23 +14,23 @@ export function findUnusedParaglideKeys(
|
|
|
17
14
|
const exists = yield* fs.exists(scanPath).pipe(Effect.orElseSucceed(() => false));
|
|
18
15
|
if (!exists) continue;
|
|
19
16
|
|
|
20
|
-
const entries = yield* fs
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
const entries = yield* fs
|
|
18
|
+
.readDirectory(scanPath, { recursive: true })
|
|
19
|
+
.pipe(Effect.orElseSucceed(() => [] as string[]));
|
|
23
20
|
|
|
24
21
|
for (const relativePath of entries) {
|
|
25
22
|
if (
|
|
26
|
-
!relativePath.endsWith(
|
|
27
|
-
!relativePath.endsWith(
|
|
28
|
-
!relativePath.endsWith(
|
|
29
|
-
!relativePath.endsWith(
|
|
30
|
-
!relativePath.endsWith(
|
|
31
|
-
!relativePath.endsWith(
|
|
23
|
+
!relativePath.endsWith(".ts") &&
|
|
24
|
+
!relativePath.endsWith(".tsx") &&
|
|
25
|
+
!relativePath.endsWith(".js") &&
|
|
26
|
+
!relativePath.endsWith(".jsx") &&
|
|
27
|
+
!relativePath.endsWith(".svelte") &&
|
|
28
|
+
!relativePath.endsWith(".vue")
|
|
32
29
|
) {
|
|
33
30
|
continue;
|
|
34
31
|
}
|
|
35
32
|
const filePath = path.join(scanPath, relativePath);
|
|
36
|
-
const content = yield* fs.readFileString(filePath).pipe(Effect.orElseSucceed(() =>
|
|
33
|
+
const content = yield* fs.readFileString(filePath).pipe(Effect.orElseSucceed(() => ""));
|
|
37
34
|
|
|
38
35
|
for (const key of keys) {
|
|
39
36
|
const pattern = new RegExp(`\\bm\\.${key}\\b`);
|
|
@@ -49,9 +46,9 @@ export function findUnusedParaglideKeys(
|
|
|
49
46
|
Effect.mapError(
|
|
50
47
|
(cause) =>
|
|
51
48
|
new AdapterReadErrorClass({
|
|
52
|
-
adapter:
|
|
53
|
-
locale:
|
|
54
|
-
resource:
|
|
49
|
+
adapter: "paraglide",
|
|
50
|
+
locale: "",
|
|
51
|
+
resource: "messages",
|
|
55
52
|
cause,
|
|
56
53
|
}) as AdapterReadError,
|
|
57
54
|
),
|
package/tsconfig.json
CHANGED
package/tsdown.config.ts
CHANGED
package/vitest.config.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { defineConfig } from
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
|
-
include: [
|
|
6
|
-
environment:
|
|
5
|
+
include: ["src/**/*.test.ts"],
|
|
6
|
+
environment: "node",
|
|
7
7
|
},
|
|
8
8
|
});
|