@inlang/paraglide-js 2.28.0 → 2.29.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.
Files changed (34) hide show
  1. package/dist/bundler-plugins/unplugin.d.ts +4 -1
  2. package/dist/bundler-plugins/unplugin.js +55 -12
  3. package/dist/bundler-plugins/unplugin.js.map +1 -1
  4. package/dist/bundler-plugins/unplugin.test.js +156 -0
  5. package/dist/bundler-plugins/unplugin.test.js.map +1 -1
  6. package/dist/bundler-plugins/vite-error-response.test.d.ts +1 -0
  7. package/dist/bundler-plugins/vite-error-response.test.js +171 -0
  8. package/dist/bundler-plugins/vite-error-response.test.js.map +1 -0
  9. package/dist/bundler-plugins/vite.d.ts +8 -0
  10. package/dist/bundler-plugins/vite.js +14 -5
  11. package/dist/bundler-plugins/vite.js.map +1 -1
  12. package/dist/compiler/compile-bundle.js +58 -12
  13. package/dist/compiler/compile-bundle.js.map +1 -1
  14. package/dist/compiler/compile-bundle.test.js +208 -26
  15. package/dist/compiler/compile-bundle.test.js.map +1 -1
  16. package/dist/compiler/compile-lifecycle.test.js +83 -0
  17. package/dist/compiler/compile-lifecycle.test.js.map +1 -1
  18. package/dist/compiler/compile-message.d.ts +9 -0
  19. package/dist/compiler/compile-message.js +29 -12
  20. package/dist/compiler/compile-message.js.map +1 -1
  21. package/dist/compiler/compile-project.test.js +157 -0
  22. package/dist/compiler/compile-project.test.js.map +1 -1
  23. package/dist/compiler/compile.js +12 -0
  24. package/dist/compiler/compile.js.map +1 -1
  25. package/dist/compiler/runtime/localize-url.d.ts +8 -4
  26. package/dist/compiler/runtime/localize-url.js +12 -8
  27. package/dist/compiler/runtime/localize-url.js.map +1 -1
  28. package/dist/compiler/runtime/localize-url.test.js +45 -0
  29. package/dist/compiler/runtime/localize-url.test.js.map +1 -1
  30. package/dist/compiler/runtime/type.test.js +41 -0
  31. package/dist/compiler/runtime/type.test.js.map +1 -1
  32. package/dist/services/env-variables/index.js +1 -1
  33. package/dist/services/env-variables/index.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,171 @@
1
+ import { afterEach, expect, test, vi } from "vitest";
2
+ import fs from "node:fs/promises";
3
+ import nodeFs from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import { createServer } from "vite";
7
+ import { loadProjectInMemory, newProject, saveProjectToDirectory, } from "@inlang/sdk";
8
+ import { paraglideVitePlugin } from "./vite.js";
9
+ const cleanups = [];
10
+ const initialNodeEnv = process.env.NODE_ENV;
11
+ afterEach(async () => {
12
+ for (const cleanup of cleanups.splice(0).reverse())
13
+ await cleanup();
14
+ vi.restoreAllMocks();
15
+ if (initialNodeEnv === undefined)
16
+ delete process.env.NODE_ENV;
17
+ else
18
+ process.env.NODE_ENV = initialNodeEnv;
19
+ });
20
+ async function createFixture(options) {
21
+ process.env.NODE_ENV = "development";
22
+ const root = nodeFs.realpathSync(await fs.mkdtemp(path.join(tmpdir(), "paraglide-error-response-")));
23
+ cleanups.push(() => fs.rm(root, { recursive: true, force: true }));
24
+ const projectDir = path.join(root, "project.inlang");
25
+ const project = await loadProjectInMemory({
26
+ blob: await newProject({ settings: { baseLocale: "en", locales: ["en"] } }),
27
+ });
28
+ try {
29
+ await saveProjectToDirectory({ project, path: projectDir, fs });
30
+ }
31
+ finally {
32
+ await project.close();
33
+ }
34
+ const configFile = path.join(projectDir, "paraglide.config.js");
35
+ await fs.writeFile(configFile, "export default {};");
36
+ await fs.writeFile(path.join(root, "index.html"), "<p>Translation preview</p>");
37
+ const file = options.source === "settings"
38
+ ? path.join(projectDir, "settings.json")
39
+ : configFile;
40
+ const original = await fs.readFile(file, "utf8");
41
+ const invalid = options.source === "settings" ? "{" : "export default { outdir: 42 };";
42
+ if (options.isInitiallyInvalid)
43
+ await fs.writeFile(file, invalid);
44
+ const server = await createServer({
45
+ root,
46
+ configFile: false,
47
+ logLevel: "silent",
48
+ plugins: [
49
+ paraglideVitePlugin({
50
+ project: projectDir,
51
+ outdir: path.join(root, "paraglide"),
52
+ devErrorResponse: options.devErrorResponse,
53
+ experimentalPerLocaleBuild: options.experimentalPerLocaleBuild,
54
+ }),
55
+ ],
56
+ server: { host: "127.0.0.1", port: 0, watch: null },
57
+ optimizeDeps: { noDiscovery: true, include: [] },
58
+ });
59
+ cleanups.push(() => server.close());
60
+ await server.listen();
61
+ const url = server.resolvedUrls.local[0];
62
+ return { server, file, original, invalid, url };
63
+ }
64
+ async function notifyChange(server, file) {
65
+ await server.pluginContainer.watchChange(file, { event: "update" });
66
+ }
67
+ test.each(["settings", "config"])("serves HTTP 500 for invalid %s on startup and recovers without restarting", async (source) => {
68
+ const fixture = await createFixture({
69
+ source,
70
+ devErrorResponse: true,
71
+ isInitiallyInvalid: true,
72
+ });
73
+ const response = await fetch(fixture.url);
74
+ expect(response.status).toBe(500);
75
+ expect(response.headers.get("content-type")).toContain("text/plain");
76
+ expect(response.headers.get("cache-control")).toBe("no-store");
77
+ expect(await response.text()).not.toContain("Translation preview");
78
+ await fs.writeFile(fixture.file, fixture.original);
79
+ await notifyChange(fixture.server, fixture.file);
80
+ const recovered = await fetch(fixture.url);
81
+ expect(recovered.status).toBe(200);
82
+ expect(await recovered.text()).toContain("Translation preview");
83
+ });
84
+ test.each(["settings", "config"])("blocks stale responses after invalid %s and restores them after recovery", async (source) => {
85
+ const fixture = await createFixture({ source, devErrorResponse: true });
86
+ const before = await fetch(fixture.url);
87
+ expect(before.status).toBe(200);
88
+ await before.body?.cancel();
89
+ await fs.writeFile(fixture.file, fixture.invalid);
90
+ await notifyChange(fixture.server, fixture.file);
91
+ const failed = await fetch(fixture.url);
92
+ expect(failed.status).toBe(500);
93
+ expect(await failed.text()).not.toContain("Translation preview");
94
+ await fs.writeFile(fixture.file, fixture.original);
95
+ await notifyChange(fixture.server, fixture.file);
96
+ const recovered = await fetch(fixture.url);
97
+ expect(recovered.status).toBe(200);
98
+ await recovered.body?.cancel();
99
+ });
100
+ test.each(["settings", "config"])("keeps the default HTTP behavior after invalid %s", async (source) => {
101
+ const fixture = await createFixture({ source });
102
+ await fs.writeFile(fixture.file, fixture.invalid);
103
+ await notifyChange(fixture.server, fixture.file);
104
+ const response = await fetch(fixture.url);
105
+ expect(response.status).toBe(200);
106
+ expect(await response.text()).toContain("Translation preview");
107
+ });
108
+ test("preserves error responses with experimental per-locale builds enabled", async () => {
109
+ const fixture = await createFixture({
110
+ source: "config",
111
+ devErrorResponse: true,
112
+ experimentalPerLocaleBuild: true,
113
+ });
114
+ await fs.writeFile(fixture.file, fixture.invalid);
115
+ await notifyChange(fixture.server, fixture.file);
116
+ const response = await fetch(fixture.url);
117
+ expect(response.status).toBe(500);
118
+ await response.body?.cancel();
119
+ await fs.writeFile(fixture.file, fixture.original);
120
+ await notifyChange(fixture.server, fixture.file);
121
+ const recovered = await fetch(fixture.url);
122
+ expect(recovered.status).toBe(200);
123
+ await recovered.body?.cancel();
124
+ });
125
+ test("blocks generated modules while preserving errors and recovery for connected clients", async () => {
126
+ const fixture = await createFixture({
127
+ source: "settings",
128
+ devErrorResponse: true,
129
+ });
130
+ const moduleUrl = new URL("paraglide/runtime.js", fixture.url);
131
+ const before = await fetch(moduleUrl);
132
+ expect(before.status).toBe(200);
133
+ expect(await before.text()).toContain("baseLocale");
134
+ const socketUrl = new URL(fixture.url);
135
+ socketUrl.protocol = "ws:";
136
+ socketUrl.searchParams.set("token", fixture.server.config.webSocketToken);
137
+ const socket = new WebSocket(socketUrl, "vite-hmr");
138
+ cleanups.push(() => {
139
+ if (socket.readyState === WebSocket.CLOSED)
140
+ return Promise.resolve();
141
+ return new Promise((resolve) => {
142
+ socket.addEventListener("close", () => resolve(), { once: true });
143
+ socket.close();
144
+ });
145
+ });
146
+ const messages = [];
147
+ socket.addEventListener("message", (event) => {
148
+ messages.push(JSON.parse(String(event.data)));
149
+ });
150
+ await vi.waitFor(() => {
151
+ expect(messages).toContainEqual({ type: "connected" });
152
+ });
153
+ await fs.writeFile(fixture.file, fixture.invalid);
154
+ await notifyChange(fixture.server, fixture.file);
155
+ await vi.waitFor(() => {
156
+ expect(messages.some((message) => message.type === "error")).toBe(true);
157
+ });
158
+ const failed = await fetch(moduleUrl);
159
+ expect(failed.status).toBe(500);
160
+ const diagnostic = messages.find((message) => message.type === "error");
161
+ expect(await failed.text()).toBe(diagnostic?.err.message);
162
+ await fs.writeFile(fixture.file, fixture.original);
163
+ await notifyChange(fixture.server, fixture.file);
164
+ await vi.waitFor(() => {
165
+ expect(messages).toContainEqual({ type: "full-reload" });
166
+ });
167
+ const recovered = await fetch(moduleUrl);
168
+ expect(recovered.status).toBe(200);
169
+ expect(await recovered.text()).toContain("baseLocale");
170
+ });
171
+ //# sourceMappingURL=vite-error-response.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-error-response.test.js","sourceRoot":"","sources":["../../src/bundler-plugins/vite-error-response.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAuC,MAAM,MAAM,CAAC;AACzE,OAAO,EACN,mBAAmB,EACnB,UAAU,EACV,sBAAsB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAShD,MAAM,QAAQ,GAA+B,EAAE,CAAC;AAChD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE5C,SAAS,CAAC,KAAK,IAAI,EAAE;IACpB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QAAE,MAAM,OAAO,EAAE,CAAC;IACpE,EAAE,CAAC,eAAe,EAAE,CAAC;IACrB,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;;QACzD,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,cAAc,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,aAAa,CAAC,OAAuB;IACnD,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,aAAa,CAAC;IACrC,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAC/B,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,2BAA2B,CAAC,CAAC,CAClE,CAAC;IACF,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC;QACzC,IAAI,EAAE,MAAM,UAAU,CAAC,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;KAC3E,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,MAAM,sBAAsB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC;YAAS,CAAC;QACV,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;IAChE,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,SAAS,CACjB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,EAC7B,4BAA4B,CAC5B,CAAC;IACF,MAAM,IAAI,GACT,OAAO,CAAC,MAAM,KAAK,UAAU;QAC5B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC;QACxC,CAAC,CAAC,UAAU,CAAC;IACf,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,OAAO,GACZ,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gCAAgC,CAAC;IACxE,IAAI,OAAO,CAAC,kBAAkB;QAAE,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;QACjC,IAAI;QACJ,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE;YACR,mBAAmB,CAAC;gBACnB,OAAO,EAAE,UAAU;gBACnB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;gBACpC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;gBAC1C,0BAA0B,EAAE,OAAO,CAAC,0BAA0B;aAC9D,CAAC;SACF;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;QACnD,YAAY,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;KAChD,CAAC,CAAC;IACH,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACpC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;IACtB,MAAM,GAAG,GAAG,MAAM,CAAC,YAAa,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,MAAqB,EAAE,IAAY;IAC9D,MAAM,MAAM,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAU,CAAC,CACzC,2EAA2E,EAC3E,KAAK,EAAE,MAAM,EAAE,EAAE;IAChB,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;QACnC,MAAM;QACN,gBAAgB,EAAE,IAAI;QACtB,kBAAkB,EAAE,IAAI;KACxB,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACrE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/D,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;AACjE,CAAC,CACD,CAAC;AAEF,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAU,CAAC,CACzC,0EAA0E,EAC1E,KAAK,EAAE,MAAM,EAAE,EAAE;IAChB,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACjE,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC,CACD,CAAC;AAEF,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAU,CAAC,CACzC,kDAAkD,EAClD,KAAK,EAAE,MAAM,EAAE,EAAE;IAChB,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAChD,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;AAChE,CAAC,CACD,CAAC;AAEF,IAAI,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;IACxF,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;QACnC,MAAM,EAAE,QAAQ;QAChB,gBAAgB,EAAE,IAAI;QACtB,0BAA0B,EAAE,IAAI;KAChC,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAC9B,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qFAAqF,EAAE,KAAK,IAAI,EAAE;IACtG,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;QACnC,MAAM,EAAE,UAAU;QAClB,gBAAgB,EAAE,IAAI;KACtB,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvC,SAAS,CAAC,QAAQ,GAAG,KAAK,CAAC;IAC3B,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE;QAClB,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QACrE,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACpC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,MAAM,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAC5C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAe,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QACrB,MAAM,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QACrB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IACxE,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QACrB,MAAM,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACzC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;AACxD,CAAC,CAAC,CAAC","sourcesContent":["import { afterEach, expect, test, vi } from \"vitest\";\nimport fs from \"node:fs/promises\";\nimport nodeFs from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { createServer, type HotPayload, type ViteDevServer } from \"vite\";\nimport {\n\tloadProjectInMemory,\n\tnewProject,\n\tsaveProjectToDirectory,\n} from \"@inlang/sdk\";\nimport { paraglideVitePlugin } from \"./vite.js\";\n\ntype FixtureOptions = {\n\tdevErrorResponse?: boolean;\n\texperimentalPerLocaleBuild?: boolean;\n\tsource: \"settings\" | \"config\";\n\tisInitiallyInvalid?: boolean;\n};\n\nconst cleanups: Array<() => Promise<void>> = [];\nconst initialNodeEnv = process.env.NODE_ENV;\n\nafterEach(async () => {\n\tfor (const cleanup of cleanups.splice(0).reverse()) await cleanup();\n\tvi.restoreAllMocks();\n\tif (initialNodeEnv === undefined) delete process.env.NODE_ENV;\n\telse process.env.NODE_ENV = initialNodeEnv;\n});\n\nasync function createFixture(options: FixtureOptions) {\n\tprocess.env.NODE_ENV = \"development\";\n\tconst root = nodeFs.realpathSync(\n\t\tawait fs.mkdtemp(path.join(tmpdir(), \"paraglide-error-response-\"))\n\t);\n\tcleanups.push(() => fs.rm(root, { recursive: true, force: true }));\n\tconst projectDir = path.join(root, \"project.inlang\");\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({ settings: { baseLocale: \"en\", locales: [\"en\"] } }),\n\t});\n\ttry {\n\t\tawait saveProjectToDirectory({ project, path: projectDir, fs });\n\t} finally {\n\t\tawait project.close();\n\t}\n\tconst configFile = path.join(projectDir, \"paraglide.config.js\");\n\tawait fs.writeFile(configFile, \"export default {};\");\n\tawait fs.writeFile(\n\t\tpath.join(root, \"index.html\"),\n\t\t\"<p>Translation preview</p>\"\n\t);\n\tconst file =\n\t\toptions.source === \"settings\"\n\t\t\t? path.join(projectDir, \"settings.json\")\n\t\t\t: configFile;\n\tconst original = await fs.readFile(file, \"utf8\");\n\tconst invalid =\n\t\toptions.source === \"settings\" ? \"{\" : \"export default { outdir: 42 };\";\n\tif (options.isInitiallyInvalid) await fs.writeFile(file, invalid);\n\tconst server = await createServer({\n\t\troot,\n\t\tconfigFile: false,\n\t\tlogLevel: \"silent\",\n\t\tplugins: [\n\t\t\tparaglideVitePlugin({\n\t\t\t\tproject: projectDir,\n\t\t\t\toutdir: path.join(root, \"paraglide\"),\n\t\t\t\tdevErrorResponse: options.devErrorResponse,\n\t\t\t\texperimentalPerLocaleBuild: options.experimentalPerLocaleBuild,\n\t\t\t}),\n\t\t],\n\t\tserver: { host: \"127.0.0.1\", port: 0, watch: null },\n\t\toptimizeDeps: { noDiscovery: true, include: [] },\n\t});\n\tcleanups.push(() => server.close());\n\tawait server.listen();\n\tconst url = server.resolvedUrls!.local[0]!;\n\treturn { server, file, original, invalid, url };\n}\n\nasync function notifyChange(server: ViteDevServer, file: string) {\n\tawait server.pluginContainer.watchChange(file, { event: \"update\" });\n}\n\ntest.each([\"settings\", \"config\"] as const)(\n\t\"serves HTTP 500 for invalid %s on startup and recovers without restarting\",\n\tasync (source) => {\n\t\tconst fixture = await createFixture({\n\t\t\tsource,\n\t\t\tdevErrorResponse: true,\n\t\t\tisInitiallyInvalid: true,\n\t\t});\n\t\tconst response = await fetch(fixture.url);\n\t\texpect(response.status).toBe(500);\n\t\texpect(response.headers.get(\"content-type\")).toContain(\"text/plain\");\n\t\texpect(response.headers.get(\"cache-control\")).toBe(\"no-store\");\n\t\texpect(await response.text()).not.toContain(\"Translation preview\");\n\t\tawait fs.writeFile(fixture.file, fixture.original);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst recovered = await fetch(fixture.url);\n\t\texpect(recovered.status).toBe(200);\n\t\texpect(await recovered.text()).toContain(\"Translation preview\");\n\t}\n);\n\ntest.each([\"settings\", \"config\"] as const)(\n\t\"blocks stale responses after invalid %s and restores them after recovery\",\n\tasync (source) => {\n\t\tconst fixture = await createFixture({ source, devErrorResponse: true });\n\t\tconst before = await fetch(fixture.url);\n\t\texpect(before.status).toBe(200);\n\t\tawait before.body?.cancel();\n\t\tawait fs.writeFile(fixture.file, fixture.invalid);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst failed = await fetch(fixture.url);\n\t\texpect(failed.status).toBe(500);\n\t\texpect(await failed.text()).not.toContain(\"Translation preview\");\n\t\tawait fs.writeFile(fixture.file, fixture.original);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst recovered = await fetch(fixture.url);\n\t\texpect(recovered.status).toBe(200);\n\t\tawait recovered.body?.cancel();\n\t}\n);\n\ntest.each([\"settings\", \"config\"] as const)(\n\t\"keeps the default HTTP behavior after invalid %s\",\n\tasync (source) => {\n\t\tconst fixture = await createFixture({ source });\n\t\tawait fs.writeFile(fixture.file, fixture.invalid);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst response = await fetch(fixture.url);\n\t\texpect(response.status).toBe(200);\n\t\texpect(await response.text()).toContain(\"Translation preview\");\n\t}\n);\n\ntest(\"preserves error responses with experimental per-locale builds enabled\", async () => {\n\tconst fixture = await createFixture({\n\t\tsource: \"config\",\n\t\tdevErrorResponse: true,\n\t\texperimentalPerLocaleBuild: true,\n\t});\n\tawait fs.writeFile(fixture.file, fixture.invalid);\n\tawait notifyChange(fixture.server, fixture.file);\n\tconst response = await fetch(fixture.url);\n\texpect(response.status).toBe(500);\n\tawait response.body?.cancel();\n\tawait fs.writeFile(fixture.file, fixture.original);\n\tawait notifyChange(fixture.server, fixture.file);\n\tconst recovered = await fetch(fixture.url);\n\texpect(recovered.status).toBe(200);\n\tawait recovered.body?.cancel();\n});\n\ntest(\"blocks generated modules while preserving errors and recovery for connected clients\", async () => {\n\tconst fixture = await createFixture({\n\t\tsource: \"settings\",\n\t\tdevErrorResponse: true,\n\t});\n\tconst moduleUrl = new URL(\"paraglide/runtime.js\", fixture.url);\n\tconst before = await fetch(moduleUrl);\n\texpect(before.status).toBe(200);\n\texpect(await before.text()).toContain(\"baseLocale\");\n\tconst socketUrl = new URL(fixture.url);\n\tsocketUrl.protocol = \"ws:\";\n\tsocketUrl.searchParams.set(\"token\", fixture.server.config.webSocketToken);\n\tconst socket = new WebSocket(socketUrl, \"vite-hmr\");\n\tcleanups.push(() => {\n\t\tif (socket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tsocket.addEventListener(\"close\", () => resolve(), { once: true });\n\t\t\tsocket.close();\n\t\t});\n\t});\n\tconst messages: HotPayload[] = [];\n\tsocket.addEventListener(\"message\", (event) => {\n\t\tmessages.push(JSON.parse(String(event.data)) as HotPayload);\n\t});\n\tawait vi.waitFor(() => {\n\t\texpect(messages).toContainEqual({ type: \"connected\" });\n\t});\n\tawait fs.writeFile(fixture.file, fixture.invalid);\n\tawait notifyChange(fixture.server, fixture.file);\n\tawait vi.waitFor(() => {\n\t\texpect(messages.some((message) => message.type === \"error\")).toBe(true);\n\t});\n\tconst failed = await fetch(moduleUrl);\n\texpect(failed.status).toBe(500);\n\tconst diagnostic = messages.find((message) => message.type === \"error\");\n\texpect(await failed.text()).toBe(diagnostic?.err.message);\n\tawait fs.writeFile(fixture.file, fixture.original);\n\tawait notifyChange(fixture.server, fixture.file);\n\tawait vi.waitFor(() => {\n\t\texpect(messages).toContainEqual({ type: \"full-reload\" });\n\t});\n\tconst recovered = await fetch(moduleUrl);\n\texpect(recovered.status).toBe(200);\n\texpect(await recovered.text()).toContain(\"baseLocale\");\n});\n"]}
@@ -1,6 +1,14 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { ParaglidePluginOptions } from "./unplugin.js";
3
3
  export type ParaglideVitePluginOptions = ParaglidePluginOptions & {
4
+ /**
5
+ * Respond with HTTP 500 while the development compiler or its config is
6
+ * invalid, instead of serving the last successful output. Error overlays
7
+ * remain enabled. Set this on the Vite plugin, not in paraglide.config.
8
+ *
9
+ * @default false
10
+ */
11
+ devErrorResponse?: boolean;
4
12
  /**
5
13
  * Emit one compiler-specialized client asset tree per project locale.
6
14
  *
@@ -1,16 +1,25 @@
1
1
  import { createVitePlugin } from "unplugin";
2
2
  import { createPerLocaleBuildPlugins } from "./per-locale-build/index.js";
3
3
  import { unpluginFactory } from "./unplugin.js";
4
- const createCompilerVitePlugin = createVitePlugin(unpluginFactory);
5
- const createPerLocaleCompilerVitePlugin = createVitePlugin((options, meta) => unpluginFactory(options, meta, { outputStructure: "locale-modules" }));
4
+ const createCompilerVitePlugin = createVitePlugin(({ devErrorResponse, ...options }, meta) => unpluginFactory(options, meta, { devErrorResponse }));
5
+ const createPerLocaleCompilerVitePlugin = createVitePlugin(({ devErrorResponse, ...options }, meta) => unpluginFactory(options, meta, {
6
+ outputStructure: "locale-modules",
7
+ devErrorResponse,
8
+ }));
6
9
  export function paraglideVitePlugin(options) {
7
- const { experimentalPerLocaleBuild, ...compilerOptions } = options;
10
+ const { experimentalPerLocaleBuild, devErrorResponse, ...compilerOptions } = options;
8
11
  if (!experimentalPerLocaleBuild) {
9
- return createCompilerVitePlugin(compilerOptions);
12
+ return createCompilerVitePlugin({
13
+ ...compilerOptions,
14
+ devErrorResponse,
15
+ });
10
16
  }
11
17
  return createPerLocaleBuildPlugins({
12
18
  compilerOptions,
13
- createCompilerPlugin: (options) => createPerLocaleCompilerVitePlugin(options),
19
+ createCompilerPlugin: (options) => createPerLocaleCompilerVitePlugin({
20
+ ...options,
21
+ devErrorResponse,
22
+ }),
14
23
  });
15
24
  }
16
25
  //# sourceMappingURL=vite.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/bundler-plugins/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAiBhD,MAAM,wBAAwB,GAAG,gBAAgB,CAAC,eAAe,CAAC,CAAC;AACnE,MAAM,iCAAiC,GACtC,gBAAgB,CAAyB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAC1D,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC,CACrE,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAClC,OAAmC;IAEnC,MAAM,EAAE,0BAA0B,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;IACnE,IAAI,CAAC,0BAA0B,EAAE,CAAC;QACjC,OAAO,wBAAwB,CAAC,eAAe,CAAW,CAAC;IAC5D,CAAC;IAED,OAAO,2BAA2B,CAAC;QAClC,eAAe;QACf,oBAAoB,EAAE,CAAC,OAAO,EAAE,EAAE,CACjC,iCAAiC,CAAC,OAAO,CAAW;KACrD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { createVitePlugin } from \"unplugin\";\nimport type { Plugin } from \"vite\";\nimport type { ParaglidePluginOptions } from \"./unplugin.js\";\nimport { createPerLocaleBuildPlugins } from \"./per-locale-build/index.js\";\nimport { unpluginFactory } from \"./unplugin.js\";\n\nexport type ParaglideVitePluginOptions = ParaglidePluginOptions & {\n\t/**\n\t * Emit one compiler-specialized client asset tree per project locale.\n\t *\n\t * Requires Vite 8+. Builds one untouched Rolldown client environment per\n\t * locale and emits `paraglide-vite-locales.json`. Locale changes need to use\n\t * full-document navigation. Framework SSR graph selection requires a public\n\t * framework integration API.\n\t *\n\t * @experimental\n\t * @default false\n\t */\n\texperimentalPerLocaleBuild?: boolean;\n};\n\nconst createCompilerVitePlugin = createVitePlugin(unpluginFactory);\nconst createPerLocaleCompilerVitePlugin =\n\tcreateVitePlugin<ParaglidePluginOptions>((options, meta) =>\n\t\tunpluginFactory(options, meta, { outputStructure: \"locale-modules\" })\n\t);\n\nexport function paraglideVitePlugin(\n\toptions: ParaglideVitePluginOptions\n): Plugin | Plugin[] {\n\tconst { experimentalPerLocaleBuild, ...compilerOptions } = options;\n\tif (!experimentalPerLocaleBuild) {\n\t\treturn createCompilerVitePlugin(compilerOptions) as Plugin;\n\t}\n\n\treturn createPerLocaleBuildPlugins({\n\t\tcompilerOptions,\n\t\tcreateCompilerPlugin: (options) =>\n\t\t\tcreatePerLocaleCompilerVitePlugin(options) as Plugin,\n\t});\n}\n"]}
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/bundler-plugins/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AA8BhD,MAAM,wBAAwB,GAAG,gBAAgB,CAChD,CAAC,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,CAC1C,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,EAAE,CAAC,CACrD,CAAC;AACF,MAAM,iCAAiC,GACtC,gBAAgB,CACf,CAAC,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,CAC1C,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE;IAC9B,eAAe,EAAE,gBAAgB;IACjC,gBAAgB;CAChB,CAAC,CACH,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAClC,OAAmC;IAEnC,MAAM,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,GAAG,eAAe,EAAE,GACzE,OAAO,CAAC;IACT,IAAI,CAAC,0BAA0B,EAAE,CAAC;QACjC,OAAO,wBAAwB,CAAC;YAC/B,GAAG,eAAe;YAClB,gBAAgB;SAChB,CAAW,CAAC;IACd,CAAC;IAED,OAAO,2BAA2B,CAAC;QAClC,eAAe;QACf,oBAAoB,EAAE,CAAC,OAAO,EAAE,EAAE,CACjC,iCAAiC,CAAC;YACjC,GAAG,OAAO;YACV,gBAAgB;SAChB,CAAW;KACb,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { createVitePlugin } from \"unplugin\";\nimport type { Plugin } from \"vite\";\nimport type { ParaglidePluginOptions } from \"./unplugin.js\";\nimport { createPerLocaleBuildPlugins } from \"./per-locale-build/index.js\";\nimport { unpluginFactory } from \"./unplugin.js\";\n\nexport type ParaglideVitePluginOptions = ParaglidePluginOptions & {\n\t/**\n\t * Respond with HTTP 500 while the development compiler or its config is\n\t * invalid, instead of serving the last successful output. Error overlays\n\t * remain enabled. Set this on the Vite plugin, not in paraglide.config.\n\t *\n\t * @default false\n\t */\n\tdevErrorResponse?: boolean;\n\t/**\n\t * Emit one compiler-specialized client asset tree per project locale.\n\t *\n\t * Requires Vite 8+. Builds one untouched Rolldown client environment per\n\t * locale and emits `paraglide-vite-locales.json`. Locale changes need to use\n\t * full-document navigation. Framework SSR graph selection requires a public\n\t * framework integration API.\n\t *\n\t * @experimental\n\t * @default false\n\t */\n\texperimentalPerLocaleBuild?: boolean;\n};\n\ntype CompilerVitePluginOptions = Omit<\n\tParaglideVitePluginOptions,\n\t\"experimentalPerLocaleBuild\"\n>;\n\nconst createCompilerVitePlugin = createVitePlugin<CompilerVitePluginOptions>(\n\t({ devErrorResponse, ...options }, meta) =>\n\t\tunpluginFactory(options, meta, { devErrorResponse })\n);\nconst createPerLocaleCompilerVitePlugin =\n\tcreateVitePlugin<CompilerVitePluginOptions>(\n\t\t({ devErrorResponse, ...options }, meta) =>\n\t\t\tunpluginFactory(options, meta, {\n\t\t\t\toutputStructure: \"locale-modules\",\n\t\t\t\tdevErrorResponse,\n\t\t\t})\n\t);\n\nexport function paraglideVitePlugin(\n\toptions: ParaglideVitePluginOptions\n): Plugin | Plugin[] {\n\tconst { experimentalPerLocaleBuild, devErrorResponse, ...compilerOptions } =\n\t\toptions;\n\tif (!experimentalPerLocaleBuild) {\n\t\treturn createCompilerVitePlugin({\n\t\t\t...compilerOptions,\n\t\t\tdevErrorResponse,\n\t\t}) as Plugin;\n\t}\n\n\treturn createPerLocaleBuildPlugins({\n\t\tcompilerOptions,\n\t\tcreateCompilerPlugin: (options) =>\n\t\t\tcreatePerLocaleCompilerVitePlugin({\n\t\t\t\t...options,\n\t\t\t\tdevErrorResponse,\n\t\t\t}) as Plugin,\n\t});\n}\n"]}
@@ -22,22 +22,38 @@ export const compileBundle = (args) => {
22
22
  // set the pattern for the language tag
23
23
  compiledMessages[message.locale] = compiledMessage;
24
24
  }
25
- const inlinePlainTextByLocale = args.inlineMessages !== true ||
26
- args.experimentalStaticLocale === perLocaleBuildStaticLocaleExpression
27
- ? undefined
28
- : findPlainTextMessages(args.bundle);
25
+ const inlineMessages = args.inlineMessages === true &&
26
+ args.experimentalStaticLocale !== perLocaleBuildStaticLocaleExpression;
27
+ const inlinePlainTextByLocale = inlineMessages
28
+ ? findPlainTextMessages(args.bundle)
29
+ : undefined;
30
+ const inlineCandidates = inlineMessages
31
+ ? findInlineBodies(compiledMessages, inlinePlainTextByLocale ?? {})
32
+ : {};
29
33
  const unreferencedMessageLocales = findUnreferencedMessageLocales({
30
- inlinePlainTextByLocale: inlinePlainTextByLocale ?? Object.create(null),
34
+ inlinedLocales: [
35
+ ...Object.keys(inlinePlainTextByLocale ?? {}),
36
+ ...Object.keys(inlineCandidates),
37
+ ],
31
38
  compiledMessages,
32
39
  fallbackMap: args.fallbackMap,
33
40
  hasMarkup,
34
41
  });
42
+ // A fallback target stays a message function that the missing-locale alias
43
+ // calls, so its dispatcher branch calls it too instead of repeating the body.
44
+ const inlineBodiesByLocale = Object.create(null);
45
+ for (const locale of unreferencedMessageLocales) {
46
+ const body = inlineCandidates[locale];
47
+ if (body)
48
+ inlineBodiesByLocale[locale] = body;
49
+ }
35
50
  return {
36
51
  bundle: compileBundleFunction({
37
52
  bundle: args.bundle,
38
53
  availableLocales: Object.keys(args.fallbackMap),
39
54
  messageReferenceExpression: args.messageReferenceExpression,
40
55
  inlinePlainTextByLocale,
56
+ inlineBodiesByLocale,
41
57
  settings: args.settings,
42
58
  matchTypes,
43
59
  hasMarkup,
@@ -74,6 +90,20 @@ function findPlainTextMessages(bundle) {
74
90
  }
75
91
  return result;
76
92
  }
93
+ /**
94
+ * Finds the messages with inputs whose function body the dispatcher can run
95
+ * in place of calling the message function. Markup messages keep their `.parts`
96
+ * function, and messages that are plain text are returned as a string instead.
97
+ */
98
+ function findInlineBodies(compiledMessages, inlinePlainTextByLocale) {
99
+ const result = Object.create(null);
100
+ for (const [locale, message] of Object.entries(compiledMessages)) {
101
+ if (message.inline && inlinePlainTextByLocale[locale] === undefined) {
102
+ result[locale] = message.inline;
103
+ }
104
+ }
105
+ return result;
106
+ }
77
107
  function findUnreferencedMessageLocales(args) {
78
108
  if (args.hasMarkup)
79
109
  return [];
@@ -94,7 +124,7 @@ function findUnreferencedMessageLocales(args) {
94
124
  fallbackLocale = args.fallbackMap[fallbackLocale];
95
125
  }
96
126
  }
97
- return Object.keys(args.inlinePlainTextByLocale).filter((locale) => !fallbackTargets.has(locale));
127
+ return args.inlinedLocales.filter((locale) => !fallbackTargets.has(locale));
98
128
  }
99
129
  const compileBundleFunction = (args) => {
100
130
  const inputs = args.bundle.declarations.filter((decl) => decl.type === "input-variable");
@@ -109,7 +139,15 @@ const compileBundleFunction = (args) => {
109
139
  baseLocale,
110
140
  ]
111
141
  : args.availableLocales;
112
- const allStringBranchesInline = dispatchLocales.every((locale) => args.inlinePlainTextByLocale?.[locale] !== undefined);
142
+ // Inlined bodies read their inputs as `i`.
143
+ const inputsName = Object.keys(args.inlineBodiesByLocale ?? {}).length
144
+ ? "i"
145
+ : "inputs";
146
+ const anyBranchReadsInputs = dispatchLocales.some((locale) => {
147
+ if (args.inlinePlainTextByLocale?.[locale] !== undefined)
148
+ return false;
149
+ return args.inlineBodiesByLocale?.[locale]?.readsInput ?? true;
150
+ });
113
151
  const inputType = args.inputTypeAliasName;
114
152
  const localesUnion = args.availableLocales.length === 0
115
153
  ? "never"
@@ -132,7 +170,15 @@ const compileBundleFunction = (args) => {
132
170
  if (inlineText !== undefined) {
133
171
  return `${prefix}${condition}return /** @type {LocalizedString} */ (${JSON.stringify(inlineText)})`;
134
172
  }
135
- return `${prefix}${condition}return ${messageRef}(inputs)`;
173
+ const inlineBody = args.inlineBodiesByLocale?.[locale];
174
+ if (inlineBody) {
175
+ const [onlyStatement] = inlineBody.statements;
176
+ return inlineBody.statements.length === 1 &&
177
+ onlyStatement?.startsWith("return ")
178
+ ? `${prefix}${condition}${onlyStatement}`
179
+ : `${prefix}${condition}{\n${continuationIndent}\t${inlineBody.statements.join(`\n${continuationIndent}\t`)}\n${continuationIndent}}`;
180
+ }
181
+ return `${prefix}${condition}return ${messageRef}(${inputsName})`;
136
182
  }
137
183
  // A locale's message has .parts() only if its own variants have
138
184
  // markup. Fallbacks and messages without markup are typed without
@@ -159,7 +205,7 @@ ${englishMatchTableDoc}${jsDocBundleFunctionTypes({
159
205
  if (!args.experimentalMiddlewareLocaleSplitting) {
160
206
  return "";
161
207
  }
162
- return `\n${indent}if (experimentalMiddlewareLocaleSplitting && isServer === false) {\n${indent}\treturn /** @type {any} */ (globalThis).__paraglide.ssr.${safeBundleId}(inputs)\n${indent}}`;
208
+ return `\n${indent}if (experimentalMiddlewareLocaleSplitting && isServer === false) {\n${indent}\treturn /** @type {any} */ (globalThis).__paraglide.ssr.${safeBundleId}(${inputsName})\n${indent}}`;
163
209
  };
164
210
  const clientPartsMiddlewareGuard = (indent) => {
165
211
  if (!args.experimentalMiddlewareLocaleSplitting) {
@@ -187,13 +233,13 @@ ${englishMatchTableDoc}${jsDocBundleFunctionTypes({
187
233
  }
188
234
  return `${indent}const locale = ${localeExpression}${overrideWarning}${maybeTrackMessageCall(indent)}`;
189
235
  };
190
- const keepUnusedInputsParameter = (indent) => allStringBranchesInline && !args.experimentalMiddlewareLocaleSplitting
191
- ? `\n${indent}void inputs;`
236
+ const keepUnusedInputsParameter = (indent) => !anyBranchReadsInputs && !args.experimentalMiddlewareLocaleSplitting
237
+ ? `\n${indent}void ${inputsName};`
192
238
  : "";
193
239
  let code = "";
194
240
  if (!args.hasMarkup) {
195
241
  code = `${commonJsDoc}
196
- ${isSafeBundleId ? "export " : ""}const ${safeBundleId} = /** @type {(${bundleFunctionType}) & ${messageMetadataType}} */ ((inputs${hasInputs ? "" : " = {}"}, options = {}) => {${clientMiddlewareGuard("\t")}${keepUnusedInputsParameter("\t")}
242
+ ${isSafeBundleId ? "export " : ""}const ${safeBundleId} = /** @type {(${bundleFunctionType}) & ${messageMetadataType}} */ ((${inputsName}${hasInputs ? "" : " = {}"}, options = {}) => {${clientMiddlewareGuard("\t")}${keepUnusedInputsParameter("\t")}
197
243
  ${localeResolutionStatement("\t")}
198
244
  ${compileLocaleReturnStatements("string", "\t")}${!isFullyTranslated
199
245
  ? `\n return /** @type {LocalizedString} */ ("${args.bundle.id}")`