@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 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 | 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` | ✅ |
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
@@ -8,4 +8,4 @@ interface LaravelAdapterOptions {
8
8
  }
9
9
  declare function laravel(options: LaravelAdapterOptions): TranslationAdapter;
10
10
  //#endregion
11
- export { type LaravelAdapterOptions, laravel };
11
+ export { type LaravelAdapterOptions, laravel };
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 { AdapterReadError, AdapterWriteError, NodePlatformLayer, flattenObject, readFileIfExists, readPhpArrayAsJson, unflattenObject, writeFileEnsuringDir } from "dialekt";
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
- return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
16
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
8
17
  }
9
18
  function renderPhpArray(value, indent = 0) {
10
- const entries = Object.entries(value);
11
- if (entries.length === 0) return "[]";
12
- const pad = " ".repeat(indent);
13
- const innerPad = " ".repeat(indent + 1);
14
- const lines = ["["];
15
- for (const [key, val] of entries) {
16
- const renderedKey = /^\d+$/.test(key) ? key : phpVarExport(key);
17
- const renderedValue = typeof val === "object" && val !== null && !Array.isArray(val) ? renderPhpArray(val, indent + 1) : typeof val === "string" ? phpVarExport(val) : String(val);
18
- lines.push(`${innerPad}${renderedKey} => ${renderedValue},`);
19
- }
20
- lines.push(`${pad}]`);
21
- return lines.join("\n");
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
- return `<?php\n\nreturn ${renderPhpArray(value, 0)};\n`;
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
- return Effect.gen(function* () {
30
- const fs = yield* FileSystem.FileSystem;
31
- const path = yield* Path$1.Path;
32
- const entries = yield* fs.readDirectory(langDir).pipe(Effect.orElseSucceed(() => []));
33
- const locales = [];
34
- for (const entry of entries) {
35
- const fullPath = path.join(langDir, entry);
36
- const stat = yield* fs.stat(fullPath).pipe(Effect.option);
37
- if (stat._tag === "Some" && stat.value.type === "Directory" && entry !== "vendor" && entry !== "lang") locales.push(entry);
38
- }
39
- return locales;
40
- }).pipe(Effect.mapError((cause) => new AdapterReadError({
41
- adapter: "laravel",
42
- locale: "",
43
- resource: "",
44
- cause
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
- return Effect.gen(function* () {
49
- const fs = yield* FileSystem.FileSystem;
50
- const path = yield* Path$1.Path;
51
- const localeDir = path.join(langDir, locale);
52
- const refs = [];
53
- const entries = yield* fs.readDirectory(localeDir).pipe(Effect.orElseSucceed(() => []));
54
- for (const entry of entries) if (entry.endsWith(".php")) {
55
- const domain = entry.replace(/\.php$/, "");
56
- refs.push({
57
- key: domain,
58
- label: domain
59
- });
60
- }
61
- const jsonPath = path.join(langDir, `${locale}.json`);
62
- if (yield* fs.exists(jsonPath).pipe(Effect.orElseSucceed(() => false))) refs.push({
63
- key: "json",
64
- label: `${locale}.json`
65
- });
66
- return refs;
67
- }).pipe(Effect.mapError((cause) => new AdapterReadError({
68
- adapter: "laravel",
69
- locale,
70
- resource: "",
71
- cause
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
- return Effect.gen(function* () {
78
- const fs = yield* FileSystem.FileSystem;
79
- const path = yield* Path$1.Path;
80
- const referenced = /* @__PURE__ */ new Set();
81
- for (const scanPath of scanPaths) {
82
- if (!(yield* fs.exists(scanPath).pipe(Effect.orElseSucceed(() => false)))) continue;
83
- const entries = yield* fs.readDirectory(scanPath, { recursive: true }).pipe(Effect.orElseSucceed(() => []));
84
- for (const relativePath of entries) {
85
- if (!relativePath.endsWith(".php") && !relativePath.endsWith(".blade.php")) continue;
86
- const filePath = path.join(scanPath, relativePath);
87
- const content = yield* fs.readFileString(filePath).pipe(Effect.orElseSucceed(() => ""));
88
- const quotedStrings = [];
89
- for (const pattern of [/'((?:[^'\\]|\\.)*)'/g, /"((?:[^"\\]|\\.)*)"/g]) {
90
- let m;
91
- while ((m = pattern.exec(content)) !== null) if (m[1] !== void 0) quotedStrings.push(m[1]);
92
- }
93
- for (const str of quotedStrings) for (const key of keys) if (str === `${domain}.${key}`) referenced.add(key);
94
- }
95
- }
96
- return keys.filter((key) => !referenced.has(key));
97
- }).pipe(Effect.mapError((cause) => new AdapterReadError({
98
- adapter: "laravel",
99
- locale: "",
100
- resource: domain,
101
- cause
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
- const { langDir, scanPaths = [] } = options;
108
- return {
109
- name: "laravel",
110
- capabilities: {
111
- canCreateResource: true,
112
- unusedKeyDetection: true
113
- },
114
- listLocales: () => listLaravelLocales(langDir).pipe(Effect.provide(NodePlatformLayer)),
115
- listResources: (locale) => listLaravelResources(langDir, locale).pipe(Effect.provide(NodePlatformLayer)),
116
- readResource: (locale, resource) => Effect.gen(function* () {
117
- const path = yield* Path;
118
- if (resource.key === "json") {
119
- const content = yield* readFileIfExists(path.join(langDir, `${locale}.json`)).pipe(Effect.mapError((cause) => new AdapterReadError({
120
- adapter: "laravel",
121
- locale,
122
- resource: resource.key,
123
- cause
124
- })));
125
- if (content === null) return {};
126
- return yield* Effect.try({
127
- try: () => JSON.parse(content),
128
- catch: (cause) => new AdapterReadError({
129
- adapter: "laravel",
130
- locale,
131
- resource: resource.key,
132
- cause
133
- })
134
- });
135
- }
136
- return flattenObject(yield* readPhpArrayAsJson(path.join(langDir, locale, `${resource.key}.php`)).pipe(Effect.catchTag("PhpExecutionError", () => Effect.succeed({})), Effect.mapError((cause) => new AdapterReadError({
137
- adapter: "laravel",
138
- locale,
139
- resource: resource.key,
140
- cause
141
- }))));
142
- }).pipe(Effect.provide([NodePlatformLayer])),
143
- writeResource: (locale, resource, entries) => Effect.gen(function* () {
144
- const path = yield* Path;
145
- if (resource.key === "json") {
146
- yield* writeFileEnsuringDir(path.join(langDir, `${locale}.json`), JSON.stringify(entries, null, 2)).pipe(Effect.mapError((cause) => new AdapterWriteError({
147
- adapter: "laravel",
148
- locale,
149
- resource: resource.key,
150
- cause
151
- })));
152
- return;
153
- }
154
- yield* writeFileEnsuringDir(path.join(langDir, locale, `${resource.key}.php`), renderPhpFile(unflattenObject(entries))).pipe(Effect.mapError((cause) => new AdapterWriteError({
155
- adapter: "laravel",
156
- locale,
157
- resource: resource.key,
158
- cause
159
- })));
160
- }).pipe(Effect.provide([NodePlatformLayer])),
161
- findUnusedKeys: (locale, resource) => Effect.gen(function* () {
162
- const path = yield* Path;
163
- const adapterScanPaths = scanPaths.length > 0 ? scanPaths : [path.resolve(langDir, "..")];
164
- const keys = yield* Effect.gen(function* () {
165
- const map = yield* laravel(options).readResource(locale, resource);
166
- return Object.keys(map);
167
- });
168
- return yield* findUnusedLaravelKeys(adapterScanPaths, resource.key, keys);
169
- }).pipe(Effect.provide([NodePlatformLayer]))
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.0",
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.0"
14
+ "dialekt": "0.1.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/node": "^26.0.1",