@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/src/adapter.test.ts
CHANGED
|
@@ -1,178 +1,180 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect, Either } from
|
|
3
|
-
import { NodePlatformLayer } from
|
|
4
|
-
import { laravel } from
|
|
5
|
-
import { mkdirSync, writeFileSync, rmSync } from
|
|
6
|
-
import { join } from
|
|
7
|
-
import { tmpdir } from
|
|
8
|
-
import { execSync } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect, Either } from "effect";
|
|
3
|
+
import { NodePlatformLayer } from "dialekt";
|
|
4
|
+
import { laravel } from "./adapter.js";
|
|
5
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
9
|
|
|
10
10
|
function hasPhpBinary(): boolean {
|
|
11
11
|
try {
|
|
12
|
-
execSync(
|
|
12
|
+
execSync("php -v", { stdio: "ignore" });
|
|
13
13
|
return true;
|
|
14
14
|
} catch {
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
describe(
|
|
19
|
+
describe("laravel adapter", () => {
|
|
20
20
|
const testDir = join(tmpdir(), `laravel-adapter-test-${Date.now()}`);
|
|
21
21
|
|
|
22
|
-
it.skipIf(!hasPhpBinary())(
|
|
23
|
-
mkdirSync(join(testDir,
|
|
22
|
+
it.skipIf(!hasPhpBinary())("reads a PHP domain resource", async () => {
|
|
23
|
+
mkdirSync(join(testDir, "en"), { recursive: true });
|
|
24
24
|
writeFileSync(
|
|
25
|
-
join(testDir,
|
|
25
|
+
join(testDir, "en", "validation.php"),
|
|
26
26
|
"<?php return ['email' => 'Email address'];",
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
30
|
-
const program = adapter
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const program = adapter
|
|
31
|
+
.readResource("en", { key: "validation", label: "validation" })
|
|
32
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
33
33
|
const result = await Effect.runPromise(program);
|
|
34
|
-
expect(result).toEqual({ email:
|
|
34
|
+
expect(result).toEqual({ email: "Email address" });
|
|
35
35
|
|
|
36
36
|
rmSync(testDir, { recursive: true, force: true });
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it.skipIf(!hasPhpBinary())(
|
|
40
|
-
mkdirSync(join(testDir,
|
|
39
|
+
it.skipIf(!hasPhpBinary())("returns {} for a missing resource", async () => {
|
|
40
|
+
mkdirSync(join(testDir, "en"), { recursive: true });
|
|
41
41
|
|
|
42
42
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
43
|
-
const program = adapter
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const program = adapter
|
|
44
|
+
.readResource("en", { key: "missing", label: "missing" })
|
|
45
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
46
46
|
const result = await Effect.runPromise(program);
|
|
47
47
|
expect(result).toEqual({});
|
|
48
48
|
|
|
49
49
|
rmSync(testDir, { recursive: true, force: true });
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it.skipIf(!hasPhpBinary())(
|
|
53
|
-
mkdirSync(join(testDir,
|
|
52
|
+
it.skipIf(!hasPhpBinary())("round-trips write and read", async () => {
|
|
53
|
+
mkdirSync(join(testDir, "de"), { recursive: true });
|
|
54
54
|
|
|
55
55
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
56
56
|
const writeProgram = adapter
|
|
57
|
-
.writeResource(
|
|
57
|
+
.writeResource("de", { key: "auth", label: "auth" }, { login: "Anmelden" })
|
|
58
58
|
.pipe(Effect.provide(NodePlatformLayer));
|
|
59
59
|
await Effect.runPromise(writeProgram);
|
|
60
60
|
|
|
61
|
-
const readProgram = adapter
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const readProgram = adapter
|
|
62
|
+
.readResource("de", { key: "auth", label: "auth" })
|
|
63
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
64
64
|
const result = await Effect.runPromise(readProgram);
|
|
65
|
-
expect(result).toEqual({ login:
|
|
65
|
+
expect(result).toEqual({ login: "Anmelden" });
|
|
66
66
|
|
|
67
67
|
rmSync(testDir, { recursive: true, force: true });
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it(
|
|
70
|
+
it("reads a JSON locale resource", async () => {
|
|
71
71
|
mkdirSync(testDir, { recursive: true });
|
|
72
|
-
writeFileSync(join(testDir,
|
|
72
|
+
writeFileSync(join(testDir, "en.json"), JSON.stringify({ greeting: "Hello" }));
|
|
73
73
|
|
|
74
74
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
75
|
-
const program = adapter
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const program = adapter
|
|
76
|
+
.readResource("en", { key: "json", label: "en.json" })
|
|
77
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
78
78
|
const result = await Effect.runPromise(program);
|
|
79
|
-
expect(result).toEqual({ greeting:
|
|
79
|
+
expect(result).toEqual({ greeting: "Hello" });
|
|
80
80
|
|
|
81
81
|
rmSync(testDir, { recursive: true, force: true });
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it(
|
|
85
|
-
mkdirSync(join(testDir,
|
|
86
|
-
mkdirSync(join(testDir,
|
|
87
|
-
writeFileSync(join(testDir,
|
|
84
|
+
it("lists locales and resources", async () => {
|
|
85
|
+
mkdirSync(join(testDir, "en"), { recursive: true });
|
|
86
|
+
mkdirSync(join(testDir, "de"), { recursive: true });
|
|
87
|
+
writeFileSync(join(testDir, "en", "validation.php"), "<?php return [];");
|
|
88
88
|
|
|
89
89
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
90
|
-
const locales = await Effect.runPromise(
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const locales = await Effect.runPromise(
|
|
91
|
+
adapter.listLocales().pipe(Effect.provide(NodePlatformLayer)),
|
|
92
|
+
);
|
|
93
|
+
expect(locales).toContain("en");
|
|
94
|
+
expect(locales).toContain("de");
|
|
93
95
|
|
|
94
96
|
const resources = await Effect.runPromise(
|
|
95
|
-
adapter.listResources(
|
|
97
|
+
adapter.listResources("en").pipe(Effect.provide(NodePlatformLayer)),
|
|
96
98
|
);
|
|
97
|
-
expect(resources.map((r) => r.key)).toContain(
|
|
99
|
+
expect(resources.map((r) => r.key)).toContain("validation");
|
|
98
100
|
|
|
99
101
|
rmSync(testDir, { recursive: true, force: true });
|
|
100
102
|
});
|
|
101
103
|
|
|
102
|
-
it(
|
|
103
|
-
mkdirSync(join(testDir,
|
|
104
|
-
writeFileSync(join(testDir,
|
|
105
|
-
mkdirSync(join(testDir,
|
|
106
|
-
writeFileSync(join(testDir,
|
|
104
|
+
it("finds unused keys", async () => {
|
|
105
|
+
mkdirSync(join(testDir, "en"), { recursive: true });
|
|
106
|
+
writeFileSync(join(testDir, "en", "validation.php"), "<?php return ['email' => 'Email'];");
|
|
107
|
+
mkdirSync(join(testDir, "views"), { recursive: true });
|
|
108
|
+
writeFileSync(join(testDir, "views", "page.blade.php"), "__('validation.email')");
|
|
107
109
|
|
|
108
|
-
const adapter = laravel({ langDir: testDir, scanPaths: [join(testDir,
|
|
109
|
-
const program = adapter
|
|
110
|
-
.
|
|
111
|
-
|
|
110
|
+
const adapter = laravel({ langDir: testDir, scanPaths: [join(testDir, "views")] });
|
|
111
|
+
const program = adapter.findUnusedKeys!("en", { key: "validation", label: "validation" }).pipe(
|
|
112
|
+
Effect.provide(NodePlatformLayer),
|
|
113
|
+
);
|
|
112
114
|
const result = await Effect.runPromise(program);
|
|
113
115
|
expect(result).toEqual([]);
|
|
114
116
|
|
|
115
117
|
rmSync(testDir, { recursive: true, force: true });
|
|
116
118
|
});
|
|
117
119
|
|
|
118
|
-
it.skipIf(!hasPhpBinary())(
|
|
119
|
-
mkdirSync(join(testDir,
|
|
120
|
+
it.skipIf(!hasPhpBinary())("reads nested PHP arrays", async () => {
|
|
121
|
+
mkdirSync(join(testDir, "en"), { recursive: true });
|
|
120
122
|
writeFileSync(
|
|
121
|
-
join(testDir,
|
|
123
|
+
join(testDir, "en", "nested.php"),
|
|
122
124
|
"<?php return ['validation' => ['email' => 'Email', 'required' => 'Required']];",
|
|
123
125
|
);
|
|
124
126
|
|
|
125
127
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
126
|
-
const program = adapter
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
const program = adapter
|
|
129
|
+
.readResource("en", { key: "nested", label: "nested" })
|
|
130
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
129
131
|
const result = await Effect.runPromise(program);
|
|
130
132
|
expect(result).toEqual({
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
"validation.email": "Email",
|
|
134
|
+
"validation.required": "Required",
|
|
133
135
|
});
|
|
134
136
|
|
|
135
137
|
rmSync(testDir, { recursive: true, force: true });
|
|
136
138
|
});
|
|
137
139
|
|
|
138
|
-
it(
|
|
140
|
+
it("returns empty for missing JSON locale file", async () => {
|
|
139
141
|
mkdirSync(testDir, { recursive: true });
|
|
140
142
|
|
|
141
143
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
142
|
-
const program = adapter
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
const program = adapter
|
|
145
|
+
.readResource("en", { key: "json", label: "en.json" })
|
|
146
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
145
147
|
const result = await Effect.runPromise(program);
|
|
146
148
|
expect(result).toEqual({});
|
|
147
149
|
|
|
148
150
|
rmSync(testDir, { recursive: true, force: true });
|
|
149
151
|
});
|
|
150
152
|
|
|
151
|
-
it(
|
|
153
|
+
it("handles capabilities flags", () => {
|
|
152
154
|
const adapter = laravel({ langDir: testDir });
|
|
153
155
|
expect(adapter.capabilities.canCreateResource).toBe(true);
|
|
154
156
|
expect(adapter.capabilities.unusedKeyDetection).toBe(true);
|
|
155
157
|
});
|
|
156
158
|
|
|
157
|
-
it(
|
|
159
|
+
it("reports adapter name", () => {
|
|
158
160
|
const adapter = laravel({ langDir: testDir });
|
|
159
|
-
expect(adapter.name).toBe(
|
|
161
|
+
expect(adapter.name).toBe("laravel");
|
|
160
162
|
});
|
|
161
163
|
|
|
162
|
-
it.skipIf(!hasPhpBinary())(
|
|
164
|
+
it.skipIf(!hasPhpBinary())("writes JSON locale files", async () => {
|
|
163
165
|
mkdirSync(testDir, { recursive: true });
|
|
164
166
|
|
|
165
167
|
const adapter = laravel({ langDir: testDir, scanPaths: [testDir] });
|
|
166
168
|
const writeProgram = adapter
|
|
167
|
-
.writeResource(
|
|
169
|
+
.writeResource("en", { key: "json", label: "en.json" }, { greeting: "Hello" })
|
|
168
170
|
.pipe(Effect.provide(NodePlatformLayer));
|
|
169
171
|
await Effect.runPromise(writeProgram);
|
|
170
172
|
|
|
171
|
-
const readProgram = adapter
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
const readProgram = adapter
|
|
174
|
+
.readResource("en", { key: "json", label: "en.json" })
|
|
175
|
+
.pipe(Effect.provide(NodePlatformLayer));
|
|
174
176
|
const result = await Effect.runPromise(readProgram);
|
|
175
|
-
expect(result).toEqual({ greeting:
|
|
177
|
+
expect(result).toEqual({ greeting: "Hello" });
|
|
176
178
|
|
|
177
179
|
rmSync(testDir, { recursive: true, force: true });
|
|
178
180
|
});
|
package/src/adapter.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { Effect } from
|
|
2
|
-
import { Path } from
|
|
3
|
-
import type {
|
|
4
|
-
ResourceRef,
|
|
5
|
-
TranslationAdapter,
|
|
6
|
-
AdapterReadError,
|
|
7
|
-
AdapterWriteError,
|
|
8
|
-
} from 'dialekt';
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Path } from "@effect/platform/Path";
|
|
3
|
+
import type { ResourceRef, TranslationAdapter, AdapterReadError, AdapterWriteError } from "dialekt";
|
|
9
4
|
import {
|
|
10
5
|
AdapterReadError as AdapterReadErrorClass,
|
|
11
6
|
AdapterWriteError as AdapterWriteErrorClass,
|
|
@@ -15,10 +10,10 @@ import {
|
|
|
15
10
|
readPhpArrayAsJson,
|
|
16
11
|
readFileIfExists,
|
|
17
12
|
writeFileEnsuringDir,
|
|
18
|
-
} from
|
|
19
|
-
import { renderPhpFile } from
|
|
20
|
-
import { listLaravelLocales, listLaravelResources } from
|
|
21
|
-
import { findUnusedLaravelKeys } from
|
|
13
|
+
} from "dialekt";
|
|
14
|
+
import { renderPhpFile } from "./php-array-writer.js";
|
|
15
|
+
import { listLaravelLocales, listLaravelResources } from "./resources.js";
|
|
16
|
+
import { findUnusedLaravelKeys } from "./unused-keys.js";
|
|
22
17
|
|
|
23
18
|
export interface LaravelAdapterOptions {
|
|
24
19
|
readonly langDir: string;
|
|
@@ -30,7 +25,7 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
30
25
|
const { langDir, scanPaths = [] } = options;
|
|
31
26
|
|
|
32
27
|
return {
|
|
33
|
-
name:
|
|
28
|
+
name: "laravel",
|
|
34
29
|
capabilities: {
|
|
35
30
|
canCreateResource: true,
|
|
36
31
|
unusedKeyDetection: true,
|
|
@@ -38,20 +33,21 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
38
33
|
|
|
39
34
|
listLocales: () => listLaravelLocales(langDir).pipe(Effect.provide(NodePlatformLayer)),
|
|
40
35
|
|
|
41
|
-
listResources: (locale) =>
|
|
36
|
+
listResources: (locale) =>
|
|
37
|
+
listLaravelResources(langDir, locale).pipe(Effect.provide(NodePlatformLayer)),
|
|
42
38
|
|
|
43
39
|
readResource: (locale, resource) =>
|
|
44
40
|
Effect.gen(function* () {
|
|
45
41
|
const path = yield* Path;
|
|
46
42
|
|
|
47
|
-
if (resource.key ===
|
|
43
|
+
if (resource.key === "json") {
|
|
48
44
|
// JSON locale file
|
|
49
45
|
const filePath = path.join(langDir, `${locale}.json`);
|
|
50
46
|
const content = yield* readFileIfExists(filePath).pipe(
|
|
51
47
|
Effect.mapError(
|
|
52
48
|
(cause) =>
|
|
53
49
|
new AdapterReadErrorClass({
|
|
54
|
-
adapter:
|
|
50
|
+
adapter: "laravel",
|
|
55
51
|
locale,
|
|
56
52
|
resource: resource.key,
|
|
57
53
|
cause,
|
|
@@ -63,7 +59,7 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
63
59
|
try: () => JSON.parse(content) as Record<string, string>,
|
|
64
60
|
catch: (cause) =>
|
|
65
61
|
new AdapterReadErrorClass({
|
|
66
|
-
adapter:
|
|
62
|
+
adapter: "laravel",
|
|
67
63
|
locale,
|
|
68
64
|
resource: resource.key,
|
|
69
65
|
cause,
|
|
@@ -74,11 +70,11 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
74
70
|
// PHP domain file
|
|
75
71
|
const filePath = path.join(langDir, locale, `${resource.key}.php`);
|
|
76
72
|
const result = yield* readPhpArrayAsJson(filePath).pipe(
|
|
77
|
-
Effect.catchTag(
|
|
73
|
+
Effect.catchTag("PhpExecutionError", () => Effect.succeed({})),
|
|
78
74
|
Effect.mapError(
|
|
79
75
|
(cause) =>
|
|
80
76
|
new AdapterReadErrorClass({
|
|
81
|
-
adapter:
|
|
77
|
+
adapter: "laravel",
|
|
82
78
|
locale,
|
|
83
79
|
resource: resource.key,
|
|
84
80
|
cause,
|
|
@@ -86,20 +82,24 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
86
82
|
),
|
|
87
83
|
);
|
|
88
84
|
return flattenObject(result);
|
|
89
|
-
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
85
|
+
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
86
|
+
Record<string, string>,
|
|
87
|
+
AdapterReadError,
|
|
88
|
+
never
|
|
89
|
+
>,
|
|
90
90
|
|
|
91
91
|
writeResource: (locale, resource, entries) =>
|
|
92
92
|
Effect.gen(function* () {
|
|
93
93
|
const path = yield* Path;
|
|
94
94
|
|
|
95
|
-
if (resource.key ===
|
|
95
|
+
if (resource.key === "json") {
|
|
96
96
|
// JSON locale file
|
|
97
97
|
const filePath = path.join(langDir, `${locale}.json`);
|
|
98
98
|
yield* writeFileEnsuringDir(filePath, JSON.stringify(entries, null, 2)).pipe(
|
|
99
99
|
Effect.mapError(
|
|
100
100
|
(cause) =>
|
|
101
101
|
new AdapterWriteErrorClass({
|
|
102
|
-
adapter:
|
|
102
|
+
adapter: "laravel",
|
|
103
103
|
locale,
|
|
104
104
|
resource: resource.key,
|
|
105
105
|
cause,
|
|
@@ -116,7 +116,7 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
116
116
|
Effect.mapError(
|
|
117
117
|
(cause) =>
|
|
118
118
|
new AdapterWriteErrorClass({
|
|
119
|
-
adapter:
|
|
119
|
+
adapter: "laravel",
|
|
120
120
|
locale,
|
|
121
121
|
resource: resource.key,
|
|
122
122
|
cause,
|
|
@@ -128,13 +128,16 @@ export function laravel(options: LaravelAdapterOptions): TranslationAdapter {
|
|
|
128
128
|
findUnusedKeys: (locale, resource) =>
|
|
129
129
|
Effect.gen(function* () {
|
|
130
130
|
const path = yield* Path;
|
|
131
|
-
const adapterScanPaths =
|
|
132
|
-
scanPaths.length > 0 ? scanPaths : [path.resolve(langDir, '..')];
|
|
131
|
+
const adapterScanPaths = scanPaths.length > 0 ? scanPaths : [path.resolve(langDir, "..")];
|
|
133
132
|
const keys = yield* Effect.gen(function* () {
|
|
134
133
|
const map = yield* laravel(options).readResource(locale, resource);
|
|
135
134
|
return Object.keys(map);
|
|
136
135
|
});
|
|
137
136
|
return yield* findUnusedLaravelKeys(adapterScanPaths, resource.key, keys);
|
|
138
|
-
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
137
|
+
}).pipe(Effect.provide([NodePlatformLayer])) as Effect.Effect<
|
|
138
|
+
readonly string[],
|
|
139
|
+
AdapterReadError,
|
|
140
|
+
never
|
|
141
|
+
>,
|
|
139
142
|
};
|
|
140
143
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { laravel } from
|
|
2
|
-
export type { LaravelAdapterOptions } from
|
|
1
|
+
export { laravel } from "./adapter.js";
|
|
2
|
+
export type { LaravelAdapterOptions } from "./adapter.js";
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { renderPhpArray, renderPhpFile } from
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { renderPhpArray, renderPhpFile } from "./php-array-writer.js";
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
6
|
-
expect(renderPhpArray({})).toBe(
|
|
4
|
+
describe("renderPhpArray", () => {
|
|
5
|
+
it("renders an empty array", () => {
|
|
6
|
+
expect(renderPhpArray({})).toBe("[]");
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
it(
|
|
10
|
-
const result = renderPhpArray({ hello:
|
|
9
|
+
it("renders flat key-value pairs", () => {
|
|
10
|
+
const result = renderPhpArray({ hello: "Hello" });
|
|
11
11
|
expect(result).toContain("'hello' => 'Hello'");
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it(
|
|
15
|
-
const result = renderPhpArray({ validation: { email:
|
|
14
|
+
it("renders nested objects", () => {
|
|
15
|
+
const result = renderPhpArray({ validation: { email: "Email address" } });
|
|
16
16
|
expect(result).toContain("'validation' => [");
|
|
17
17
|
expect(result).toContain(" 'email' => 'Email address',");
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it(
|
|
20
|
+
it("escapes single quotes", () => {
|
|
21
21
|
const result = renderPhpArray({ key: "it's" });
|
|
22
22
|
expect(result).toContain("'it\\'s'");
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it(
|
|
26
|
-
const result = renderPhpArray({ key:
|
|
25
|
+
it("escapes backslashes", () => {
|
|
26
|
+
const result = renderPhpArray({ key: "a\\b" });
|
|
27
27
|
expect(result).toContain("'a\\\\b'");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it(
|
|
31
|
-
const result = renderPhpArray({ key:
|
|
32
|
-
expect(result).toContain(
|
|
30
|
+
it("preserves unicode literally", () => {
|
|
31
|
+
const result = renderPhpArray({ key: "Héllo 🌍 — 日本語" });
|
|
32
|
+
expect(result).toContain("Héllo 🌍 — 日本語");
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it(
|
|
36
|
-
const result = renderPhpArray({ key:
|
|
35
|
+
it("preserves empty string", () => {
|
|
36
|
+
const result = renderPhpArray({ key: "" });
|
|
37
37
|
expect(result).toContain("'key' => '',");
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it("preserves double quotes without escaping", () => {
|
|
41
41
|
const result = renderPhpArray({ key: 'He said "hi"' });
|
|
42
42
|
expect(result).toContain('"hi"');
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
it(
|
|
46
|
-
const result = renderPhpArray({ 0:
|
|
45
|
+
it("renders numeric keys without quotes", () => {
|
|
46
|
+
const result = renderPhpArray({ 0: "first", 1: "second" });
|
|
47
47
|
expect(result).toContain("0 => 'first'");
|
|
48
48
|
expect(result).toContain("1 => 'second'");
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
describe(
|
|
53
|
-
it(
|
|
54
|
-
const result = renderPhpFile({ hello:
|
|
55
|
-
expect(result.startsWith(
|
|
56
|
-
expect(result.endsWith(
|
|
52
|
+
describe("renderPhpFile", () => {
|
|
53
|
+
it("wraps in PHP tags", () => {
|
|
54
|
+
const result = renderPhpFile({ hello: "World" });
|
|
55
|
+
expect(result.startsWith("<?php\n\nreturn ")).toBe(true);
|
|
56
|
+
expect(result.endsWith(";\n")).toBe(true);
|
|
57
57
|
});
|
|
58
58
|
});
|
package/src/php-array-writer.ts
CHANGED
|
@@ -1,36 +1,33 @@
|
|
|
1
1
|
function phpVarExport(value: string): string {
|
|
2
2
|
// Wrap in single quotes; escape backslashes and single quotes only.
|
|
3
3
|
// This matches PHP var_export() semantics for plain strings.
|
|
4
|
-
const escaped = value.replace(/\\/g,
|
|
4
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
5
5
|
return `'${escaped}'`;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function renderPhpArray(
|
|
9
|
-
value: Record<string, unknown>,
|
|
10
|
-
indent = 0,
|
|
11
|
-
): string {
|
|
8
|
+
export function renderPhpArray(value: Record<string, unknown>, indent = 0): string {
|
|
12
9
|
const entries = Object.entries(value);
|
|
13
10
|
if (entries.length === 0) {
|
|
14
|
-
return
|
|
11
|
+
return "[]";
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
const pad =
|
|
18
|
-
const innerPad =
|
|
19
|
-
const lines: string[] = [
|
|
14
|
+
const pad = " ".repeat(indent);
|
|
15
|
+
const innerPad = " ".repeat(indent + 1);
|
|
16
|
+
const lines: string[] = ["["];
|
|
20
17
|
|
|
21
18
|
for (const [key, val] of entries) {
|
|
22
19
|
const renderedKey = /^\d+$/.test(key) ? key : phpVarExport(key);
|
|
23
20
|
const renderedValue =
|
|
24
|
-
typeof val ===
|
|
21
|
+
typeof val === "object" && val !== null && !Array.isArray(val)
|
|
25
22
|
? renderPhpArray(val as Record<string, unknown>, indent + 1)
|
|
26
|
-
: typeof val ===
|
|
23
|
+
: typeof val === "string"
|
|
27
24
|
? phpVarExport(val)
|
|
28
25
|
: String(val);
|
|
29
26
|
lines.push(`${innerPad}${renderedKey} => ${renderedValue},`);
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
lines.push(`${pad}]`);
|
|
33
|
-
return lines.join(
|
|
30
|
+
return lines.join("\n");
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
export function renderPhpFile(value: Record<string, unknown>): string {
|
package/src/resources.test.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import { Effect } from
|
|
3
|
-
import { FileSystem, Path } from
|
|
4
|
-
import { NodePlatformLayer } from
|
|
5
|
-
import { listLaravelLocales, listLaravelResources } from
|
|
6
|
-
import { mkdirSync, writeFileSync, rmSync } from
|
|
7
|
-
import { join } from
|
|
8
|
-
import { tmpdir } from
|
|
9
|
-
|
|
10
|
-
describe(
|
|
11
|
-
it(
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
4
|
+
import { NodePlatformLayer } from "dialekt";
|
|
5
|
+
import { listLaravelLocales, listLaravelResources } from "./resources.js";
|
|
6
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
|
|
10
|
+
describe("listLaravelLocales", () => {
|
|
11
|
+
it("detects locales from directory structure", async () => {
|
|
12
12
|
const dir = join(tmpdir(), `laravel-locales-${Date.now()}`);
|
|
13
|
-
mkdirSync(join(dir,
|
|
14
|
-
mkdirSync(join(dir,
|
|
15
|
-
mkdirSync(join(dir,
|
|
13
|
+
mkdirSync(join(dir, "en"), { recursive: true });
|
|
14
|
+
mkdirSync(join(dir, "de"), { recursive: true });
|
|
15
|
+
mkdirSync(join(dir, "es"), { recursive: true });
|
|
16
16
|
|
|
17
17
|
const program = listLaravelLocales(dir).pipe(Effect.provide(NodePlatformLayer));
|
|
18
18
|
const result = await Effect.runPromise(program);
|
|
19
|
-
expect(result).toContain(
|
|
20
|
-
expect(result).toContain(
|
|
21
|
-
expect(result).toContain(
|
|
19
|
+
expect(result).toContain("en");
|
|
20
|
+
expect(result).toContain("de");
|
|
21
|
+
expect(result).toContain("es");
|
|
22
22
|
|
|
23
23
|
rmSync(dir, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it(
|
|
26
|
+
it("excludes vendor and lang directories", async () => {
|
|
27
27
|
const dir = join(tmpdir(), `laravel-locales-exclude-${Date.now()}`);
|
|
28
|
-
mkdirSync(join(dir,
|
|
29
|
-
mkdirSync(join(dir,
|
|
30
|
-
mkdirSync(join(dir,
|
|
28
|
+
mkdirSync(join(dir, "en"), { recursive: true });
|
|
29
|
+
mkdirSync(join(dir, "vendor"), { recursive: true });
|
|
30
|
+
mkdirSync(join(dir, "lang"), { recursive: true });
|
|
31
31
|
|
|
32
32
|
const program = listLaravelLocales(dir).pipe(Effect.provide(NodePlatformLayer));
|
|
33
33
|
const result = await Effect.runPromise(program);
|
|
34
|
-
expect(result).toContain(
|
|
35
|
-
expect(result).not.toContain(
|
|
36
|
-
expect(result).not.toContain(
|
|
34
|
+
expect(result).toContain("en");
|
|
35
|
+
expect(result).not.toContain("vendor");
|
|
36
|
+
expect(result).not.toContain("lang");
|
|
37
37
|
|
|
38
38
|
rmSync(dir, { recursive: true, force: true });
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
describe(
|
|
43
|
-
it(
|
|
42
|
+
describe("listLaravelResources", () => {
|
|
43
|
+
it("lists PHP domain files and JSON file", async () => {
|
|
44
44
|
const dir = join(tmpdir(), `laravel-resources-${Date.now()}`);
|
|
45
|
-
mkdirSync(join(dir,
|
|
46
|
-
writeFileSync(join(dir,
|
|
47
|
-
writeFileSync(join(dir,
|
|
48
|
-
writeFileSync(join(dir,
|
|
45
|
+
mkdirSync(join(dir, "en"), { recursive: true });
|
|
46
|
+
writeFileSync(join(dir, "en", "validation.php"), "<?php return [];");
|
|
47
|
+
writeFileSync(join(dir, "en", "auth.php"), "<?php return [];");
|
|
48
|
+
writeFileSync(join(dir, "en.json"), "{}");
|
|
49
49
|
|
|
50
|
-
const program = listLaravelResources(dir,
|
|
50
|
+
const program = listLaravelResources(dir, "en").pipe(Effect.provide(NodePlatformLayer));
|
|
51
51
|
const result = await Effect.runPromise(program);
|
|
52
52
|
const keys = result.map((r) => r.key);
|
|
53
|
-
expect(keys).toContain(
|
|
54
|
-
expect(keys).toContain(
|
|
55
|
-
expect(keys).toContain(
|
|
53
|
+
expect(keys).toContain("validation");
|
|
54
|
+
expect(keys).toContain("auth");
|
|
55
|
+
expect(keys).toContain("json");
|
|
56
56
|
|
|
57
57
|
rmSync(dir, { recursive: true, force: true });
|
|
58
58
|
});
|