@dialekt/adapter-laravel 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 +6 -6
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +222 -147
- package/package.json +2 -2
- package/src/adapter.test.ts +76 -74
- package/src/adapter.ts +29 -26
- package/src/index.ts +2 -2
- package/src/php-array-writer.test.ts +25 -25
- package/src/php-array-writer.ts +9 -12
- package/src/resources.test.ts +34 -34
- package/src/resources.ts +22 -22
- package/src/unused-keys.test.ts +29 -41
- package/src/unused-keys.ts +12 -15
- 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-laravel`
|
|
6
|
+
|
|
7
|
+
Laravel adapter for [dialekt](https://github.com/mateffy/dialekt). Reads and writes PHP array translation files (`lang/{locale}/{resource}.php`) and JSON string files (`lang/{locale}.json`), scans Blade views and PHP controllers for `__()`, `@lang()`, and `trans()` calls, and reports unused keys.
|
|
8
|
+
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -D @dialekt/adapter-laravel
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { defineConfig } from "dialekt";
|
|
21
|
+
import { laravel } from "@dialekt/adapter-laravel";
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
sourceLocale: "en",
|
|
25
|
+
targetLocales: ["de", "fr"],
|
|
26
|
+
adapters: [
|
|
27
|
+
laravel({
|
|
28
|
+
langDir: "./lang",
|
|
29
|
+
scanPaths: ["./app", "./resources/views"],
|
|
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,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Tested Areas Map
|
|
4
4
|
|
|
5
|
-
| Export
|
|
6
|
-
|
|
7
|
-
| `laravel()` adapter
|
|
8
|
-
| `renderPhpArray` / `renderPhpFile`
|
|
9
|
-
| `listLaravelLocales` / `listLaravelResources` | `src/resources.test.ts`
|
|
10
|
-
| `findUnusedLaravelKeys`
|
|
5
|
+
| Export | Test File | Status |
|
|
6
|
+
| --------------------------------------------- | ------------------------------ | ------ |
|
|
7
|
+
| `laravel()` adapter | `src/adapter.test.ts` | ✅ |
|
|
8
|
+
| `renderPhpArray` / `renderPhpFile` | `src/php-array-writer.test.ts` | ✅ |
|
|
9
|
+
| `listLaravelLocales` / `listLaravelResources` | `src/resources.test.ts` | ✅ |
|
|
10
|
+
| `findUnusedLaravelKeys` | `src/unused-keys.test.ts` | ✅ |
|
|
11
11
|
|
|
12
12
|
## Known Coverage Gaps
|
|
13
13
|
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,173 +1,248 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { Path } from "@effect/platform/Path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AdapterReadError,
|
|
5
|
+
AdapterWriteError,
|
|
6
|
+
NodePlatformLayer,
|
|
7
|
+
flattenObject,
|
|
8
|
+
readFileIfExists,
|
|
9
|
+
readPhpArrayAsJson,
|
|
10
|
+
unflattenObject,
|
|
11
|
+
writeFileEnsuringDir,
|
|
12
|
+
} from "dialekt";
|
|
4
13
|
import { FileSystem, Path as Path$1 } from "@effect/platform";
|
|
5
14
|
//#region src/php-array-writer.ts
|
|
6
15
|
function phpVarExport(value) {
|
|
7
|
-
|
|
16
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
8
17
|
}
|
|
9
18
|
function renderPhpArray(value, indent = 0) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const entries = Object.entries(value);
|
|
20
|
+
if (entries.length === 0) return "[]";
|
|
21
|
+
const pad = " ".repeat(indent);
|
|
22
|
+
const innerPad = " ".repeat(indent + 1);
|
|
23
|
+
const lines = ["["];
|
|
24
|
+
for (const [key, val] of entries) {
|
|
25
|
+
const renderedKey = /^\d+$/.test(key) ? key : phpVarExport(key);
|
|
26
|
+
const renderedValue =
|
|
27
|
+
typeof val === "object" && val !== null && !Array.isArray(val)
|
|
28
|
+
? renderPhpArray(val, indent + 1)
|
|
29
|
+
: typeof val === "string"
|
|
30
|
+
? phpVarExport(val)
|
|
31
|
+
: String(val);
|
|
32
|
+
lines.push(`${innerPad}${renderedKey} => ${renderedValue},`);
|
|
33
|
+
}
|
|
34
|
+
lines.push(`${pad}]`);
|
|
35
|
+
return lines.join("\n");
|
|
22
36
|
}
|
|
23
37
|
function renderPhpFile(value) {
|
|
24
|
-
|
|
38
|
+
return `<?php\n\nreturn ${renderPhpArray(value, 0)};\n`;
|
|
25
39
|
}
|
|
26
40
|
//#endregion
|
|
27
41
|
//#region src/resources.ts
|
|
28
42
|
function listLaravelLocales(langDir) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
return Effect.gen(function* () {
|
|
44
|
+
const fs = yield* FileSystem.FileSystem;
|
|
45
|
+
const path = yield* Path$1.Path;
|
|
46
|
+
const entries = yield* fs.readDirectory(langDir).pipe(Effect.orElseSucceed(() => []));
|
|
47
|
+
const locales = [];
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const fullPath = path.join(langDir, entry);
|
|
50
|
+
const stat = yield* fs.stat(fullPath).pipe(Effect.option);
|
|
51
|
+
if (
|
|
52
|
+
stat._tag === "Some" &&
|
|
53
|
+
stat.value.type === "Directory" &&
|
|
54
|
+
entry !== "vendor" &&
|
|
55
|
+
entry !== "lang"
|
|
56
|
+
)
|
|
57
|
+
locales.push(entry);
|
|
58
|
+
}
|
|
59
|
+
return locales;
|
|
60
|
+
}).pipe(
|
|
61
|
+
Effect.mapError(
|
|
62
|
+
(cause) =>
|
|
63
|
+
new AdapterReadError({
|
|
64
|
+
adapter: "laravel",
|
|
65
|
+
locale: "",
|
|
66
|
+
resource: "",
|
|
67
|
+
cause,
|
|
68
|
+
}),
|
|
69
|
+
),
|
|
70
|
+
);
|
|
46
71
|
}
|
|
47
72
|
function listLaravelResources(langDir, locale) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
return Effect.gen(function* () {
|
|
74
|
+
const fs = yield* FileSystem.FileSystem;
|
|
75
|
+
const path = yield* Path$1.Path;
|
|
76
|
+
const localeDir = path.join(langDir, locale);
|
|
77
|
+
const refs = [];
|
|
78
|
+
const entries = yield* fs.readDirectory(localeDir).pipe(Effect.orElseSucceed(() => []));
|
|
79
|
+
for (const entry of entries)
|
|
80
|
+
if (entry.endsWith(".php")) {
|
|
81
|
+
const domain = entry.replace(/\.php$/, "");
|
|
82
|
+
refs.push({
|
|
83
|
+
key: domain,
|
|
84
|
+
label: domain,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const jsonPath = path.join(langDir, `${locale}.json`);
|
|
88
|
+
if (yield* fs.exists(jsonPath).pipe(Effect.orElseSucceed(() => false)))
|
|
89
|
+
refs.push({
|
|
90
|
+
key: "json",
|
|
91
|
+
label: `${locale}.json`,
|
|
92
|
+
});
|
|
93
|
+
return refs;
|
|
94
|
+
}).pipe(
|
|
95
|
+
Effect.mapError(
|
|
96
|
+
(cause) =>
|
|
97
|
+
new AdapterReadError({
|
|
98
|
+
adapter: "laravel",
|
|
99
|
+
locale,
|
|
100
|
+
resource: "",
|
|
101
|
+
cause,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
73
105
|
}
|
|
74
106
|
//#endregion
|
|
75
107
|
//#region src/unused-keys.ts
|
|
76
108
|
function findUnusedLaravelKeys(scanPaths, domain, keys) {
|
|
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
|
-
|
|
109
|
+
return Effect.gen(function* () {
|
|
110
|
+
const fs = yield* FileSystem.FileSystem;
|
|
111
|
+
const path = yield* Path$1.Path;
|
|
112
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
113
|
+
for (const scanPath of scanPaths) {
|
|
114
|
+
if (!(yield* fs.exists(scanPath).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
115
|
+
const entries = yield* fs
|
|
116
|
+
.readDirectory(scanPath, { recursive: true })
|
|
117
|
+
.pipe(Effect.orElseSucceed(() => []));
|
|
118
|
+
for (const relativePath of entries) {
|
|
119
|
+
if (!relativePath.endsWith(".php") && !relativePath.endsWith(".blade.php")) continue;
|
|
120
|
+
const filePath = path.join(scanPath, relativePath);
|
|
121
|
+
const content = yield* fs.readFileString(filePath).pipe(Effect.orElseSucceed(() => ""));
|
|
122
|
+
const quotedStrings = [];
|
|
123
|
+
for (const pattern of [/'((?:[^'\\]|\\.)*)'/g, /"((?:[^"\\]|\\.)*)"/g]) {
|
|
124
|
+
let m;
|
|
125
|
+
while ((m = pattern.exec(content)) !== null)
|
|
126
|
+
if (m[1] !== void 0) quotedStrings.push(m[1]);
|
|
127
|
+
}
|
|
128
|
+
for (const str of quotedStrings)
|
|
129
|
+
for (const key of keys) if (str === `${domain}.${key}`) referenced.add(key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return keys.filter((key) => !referenced.has(key));
|
|
133
|
+
}).pipe(
|
|
134
|
+
Effect.mapError(
|
|
135
|
+
(cause) =>
|
|
136
|
+
new AdapterReadError({
|
|
137
|
+
adapter: "laravel",
|
|
138
|
+
locale: "",
|
|
139
|
+
resource: domain,
|
|
140
|
+
cause,
|
|
141
|
+
}),
|
|
142
|
+
),
|
|
143
|
+
);
|
|
103
144
|
}
|
|
104
145
|
//#endregion
|
|
105
146
|
//#region src/adapter.ts
|
|
106
147
|
function laravel(options) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
148
|
+
const { langDir, scanPaths = [] } = options;
|
|
149
|
+
return {
|
|
150
|
+
name: "laravel",
|
|
151
|
+
capabilities: {
|
|
152
|
+
canCreateResource: true,
|
|
153
|
+
unusedKeyDetection: true,
|
|
154
|
+
},
|
|
155
|
+
listLocales: () => listLaravelLocales(langDir).pipe(Effect.provide(NodePlatformLayer)),
|
|
156
|
+
listResources: (locale) =>
|
|
157
|
+
listLaravelResources(langDir, locale).pipe(Effect.provide(NodePlatformLayer)),
|
|
158
|
+
readResource: (locale, resource) =>
|
|
159
|
+
Effect.gen(function* () {
|
|
160
|
+
const path = yield* Path;
|
|
161
|
+
if (resource.key === "json") {
|
|
162
|
+
const content = yield* readFileIfExists(path.join(langDir, `${locale}.json`)).pipe(
|
|
163
|
+
Effect.mapError(
|
|
164
|
+
(cause) =>
|
|
165
|
+
new AdapterReadError({
|
|
166
|
+
adapter: "laravel",
|
|
167
|
+
locale,
|
|
168
|
+
resource: resource.key,
|
|
169
|
+
cause,
|
|
170
|
+
}),
|
|
171
|
+
),
|
|
172
|
+
);
|
|
173
|
+
if (content === null) return {};
|
|
174
|
+
return yield* Effect.try({
|
|
175
|
+
try: () => JSON.parse(content),
|
|
176
|
+
catch: (cause) =>
|
|
177
|
+
new AdapterReadError({
|
|
178
|
+
adapter: "laravel",
|
|
179
|
+
locale,
|
|
180
|
+
resource: resource.key,
|
|
181
|
+
cause,
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return flattenObject(
|
|
186
|
+
yield* readPhpArrayAsJson(path.join(langDir, locale, `${resource.key}.php`)).pipe(
|
|
187
|
+
Effect.catchTag("PhpExecutionError", () => Effect.succeed({})),
|
|
188
|
+
Effect.mapError(
|
|
189
|
+
(cause) =>
|
|
190
|
+
new AdapterReadError({
|
|
191
|
+
adapter: "laravel",
|
|
192
|
+
locale,
|
|
193
|
+
resource: resource.key,
|
|
194
|
+
cause,
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
}).pipe(Effect.provide([NodePlatformLayer])),
|
|
200
|
+
writeResource: (locale, resource, entries) =>
|
|
201
|
+
Effect.gen(function* () {
|
|
202
|
+
const path = yield* Path;
|
|
203
|
+
if (resource.key === "json") {
|
|
204
|
+
yield* writeFileEnsuringDir(
|
|
205
|
+
path.join(langDir, `${locale}.json`),
|
|
206
|
+
JSON.stringify(entries, null, 2),
|
|
207
|
+
).pipe(
|
|
208
|
+
Effect.mapError(
|
|
209
|
+
(cause) =>
|
|
210
|
+
new AdapterWriteError({
|
|
211
|
+
adapter: "laravel",
|
|
212
|
+
locale,
|
|
213
|
+
resource: resource.key,
|
|
214
|
+
cause,
|
|
215
|
+
}),
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
yield* writeFileEnsuringDir(
|
|
221
|
+
path.join(langDir, locale, `${resource.key}.php`),
|
|
222
|
+
renderPhpFile(unflattenObject(entries)),
|
|
223
|
+
).pipe(
|
|
224
|
+
Effect.mapError(
|
|
225
|
+
(cause) =>
|
|
226
|
+
new AdapterWriteError({
|
|
227
|
+
adapter: "laravel",
|
|
228
|
+
locale,
|
|
229
|
+
resource: resource.key,
|
|
230
|
+
cause,
|
|
231
|
+
}),
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
}).pipe(Effect.provide([NodePlatformLayer])),
|
|
235
|
+
findUnusedKeys: (locale, resource) =>
|
|
236
|
+
Effect.gen(function* () {
|
|
237
|
+
const path = yield* Path;
|
|
238
|
+
const adapterScanPaths = scanPaths.length > 0 ? scanPaths : [path.resolve(langDir, "..")];
|
|
239
|
+
const keys = yield* Effect.gen(function* () {
|
|
240
|
+
const map = yield* laravel(options).readResource(locale, resource);
|
|
241
|
+
return Object.keys(map);
|
|
242
|
+
});
|
|
243
|
+
return yield* findUnusedLaravelKeys(adapterScanPaths, resource.key, keys);
|
|
244
|
+
}).pipe(Effect.provide([NodePlatformLayer])),
|
|
245
|
+
};
|
|
171
246
|
}
|
|
172
247
|
//#endregion
|
|
173
248
|
export { laravel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dialekt/adapter-laravel",
|
|
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",
|