@linguanahq/vite 0.1.1 → 0.1.2

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/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ import { type SourceAdapter, type StrictPolicy } from "@linguanahq/build-core";
2
2
  import { type Plugin } from "vite";
3
3
  export type LinguanaViteAdapter = SourceAdapter;
4
4
  export type LinguanaViteOptions = {
5
+ /** Live development preview. The dashboard can disable it; false always opts out. */
6
+ hotReload?: boolean;
7
+ /** Per-preview request ceiling in USD micros, additionally limited by the project and organization. */
8
+ previewMaxChargeMicros?: number;
5
9
  adapter: LinguanaViteAdapter;
6
10
  projectId: string;
7
11
  environment?: string;
package/dist/index.js CHANGED
@@ -1,14 +1,401 @@
1
1
  // src/index.ts
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
3
4
  import {
4
- BuildSession
5
+ BuildSession as BuildSession2
5
6
  } from "@linguanahq/build-core";
6
7
  import {
7
8
  createFilter,
8
9
  normalizePath,
9
10
  version as viteVersion
10
11
  } from "vite";
11
- var VERSION = "0.1.1";
12
+
13
+ // src/preview.ts
14
+ import {
15
+ BuildSession
16
+ } from "@linguanahq/build-core";
17
+ import { contentHash } from "@linguanahq/catalog";
18
+ var messageKey = (message) => `${message.id}:${message.contentHash}`;
19
+ var terminal = /* @__PURE__ */ new Set([
20
+ "succeeded",
21
+ "failed",
22
+ "canceled",
23
+ "review_required"
24
+ ]);
25
+ var LocalPreview = class {
26
+ files = /* @__PURE__ */ new Map();
27
+ locales = /* @__PURE__ */ new Set();
28
+ states = /* @__PURE__ */ new Map();
29
+ jobs = /* @__PURE__ */ new Map();
30
+ config;
31
+ configAt = 0;
32
+ timer;
33
+ running = false;
34
+ dirty = false;
35
+ disposed = false;
36
+ generation = 0;
37
+ retrySequence = 0;
38
+ controllers = /* @__PURE__ */ new Set();
39
+ options;
40
+ constructor(options) {
41
+ this.options = options;
42
+ }
43
+ collect(file, result) {
44
+ if (result) this.files.set(file, structuredClone(result));
45
+ else this.files.delete(file);
46
+ this.prune();
47
+ this.schedule(250);
48
+ }
49
+ watch(locales) {
50
+ this.locales = new Set(
51
+ locales.filter((locale) => locale !== this.options.session.sourceLocale).slice(0, 10)
52
+ );
53
+ for (const locale of this.locales) this.state(locale);
54
+ this.schedule(0);
55
+ }
56
+ refresh() {
57
+ this.retrySequence += 1;
58
+ for (const job of this.jobs.values())
59
+ if (["failed", "canceled", "review_required"].includes(job.status)) {
60
+ for (const key of job.keys)
61
+ this.state(job.locale).submitted.delete(key);
62
+ this.jobs.delete(job.id);
63
+ }
64
+ for (const state of this.states.values()) {
65
+ state.status = "ready";
66
+ state.message = void 0;
67
+ }
68
+ this.configAt = 0;
69
+ this.schedule(0);
70
+ }
71
+ dispose() {
72
+ this.disposed = true;
73
+ clearTimeout(this.timer);
74
+ for (const controller of this.controllers) controller.abort();
75
+ this.controllers.clear();
76
+ this.files.clear();
77
+ this.jobs.clear();
78
+ this.states.clear();
79
+ }
80
+ state(locale) {
81
+ let state = this.states.get(locale);
82
+ if (!state) {
83
+ state = { submitted: /* @__PURE__ */ new Set(), entries: /* @__PURE__ */ new Map(), status: "ready" };
84
+ this.states.set(locale, state);
85
+ }
86
+ return state;
87
+ }
88
+ messages() {
89
+ const messages = /* @__PURE__ */ new Map();
90
+ const conflicts = /* @__PURE__ */ new Set();
91
+ for (const file of this.files.values())
92
+ for (const message of file.messages) {
93
+ if (messages.has(message.id) && messages.get(message.id)?.contentHash !== message.contentHash)
94
+ conflicts.add(message.id);
95
+ messages.set(message.id, message);
96
+ }
97
+ for (const id of conflicts) messages.delete(id);
98
+ return messages;
99
+ }
100
+ prune() {
101
+ const current = this.messages();
102
+ const keys = new Set([...current.values()].map(messageKey));
103
+ for (const [locale, state] of this.states) {
104
+ for (const [id, entry] of state.entries)
105
+ if (current.get(id)?.contentHash !== entry.contentHash)
106
+ state.entries.delete(id);
107
+ for (const key of state.submitted)
108
+ if (!keys.has(key)) state.submitted.delete(key);
109
+ this.emit(locale);
110
+ }
111
+ for (const [id, job] of this.jobs)
112
+ if (![...job.keys].some((key) => keys.has(key))) this.jobs.delete(id);
113
+ }
114
+ emit(locale) {
115
+ if (this.disposed || !this.locales.has(locale)) return;
116
+ const state = this.state(locale);
117
+ this.options.emit({
118
+ projectId: this.options.session.projectId,
119
+ environment: this.options.session.environment,
120
+ locale,
121
+ entries: [...state.entries.values()],
122
+ sources: [...this.messages().values()].map(({ id, source }) => ({
123
+ id,
124
+ source
125
+ })),
126
+ status: state.status,
127
+ total: this.messages().size,
128
+ message: state.message
129
+ });
130
+ }
131
+ schedule(ms) {
132
+ if (this.disposed) return;
133
+ if (this.running) {
134
+ this.dirty = true;
135
+ return;
136
+ }
137
+ clearTimeout(this.timer);
138
+ this.timer = setTimeout(() => {
139
+ void this.tick();
140
+ }, ms);
141
+ this.timer.unref?.();
142
+ }
143
+ async request(path, init = {}) {
144
+ const controller = new AbortController();
145
+ this.controllers.add(controller);
146
+ const timeout = setTimeout(() => controller.abort(), 1e4);
147
+ try {
148
+ const response = await (this.options.fetch ?? fetch)(
149
+ `${this.options.apiUrl.replace(/\/$/, "")}${path}`,
150
+ {
151
+ ...init,
152
+ signal: controller.signal,
153
+ headers: {
154
+ "content-type": "application/json",
155
+ authorization: `Bearer ${this.options.token}`,
156
+ ...init.headers
157
+ }
158
+ }
159
+ );
160
+ const body = await response.json();
161
+ if (!response.ok)
162
+ throw Object.assign(
163
+ new Error(body.message ?? "Preview request failed."),
164
+ { code: body.code ?? `HTTP_${response.status}` }
165
+ );
166
+ return body;
167
+ } finally {
168
+ clearTimeout(timeout);
169
+ this.controllers.delete(controller);
170
+ }
171
+ }
172
+ async tick() {
173
+ if (this.running || this.disposed || !this.locales.size) return;
174
+ this.running = true;
175
+ this.dirty = false;
176
+ try {
177
+ if (Date.now() - this.configAt >= 5e3) {
178
+ const config2 = await this.request(
179
+ "/api/v1/preview/config"
180
+ );
181
+ if (config2.projectId !== this.options.session.projectId || config2.environment !== "development" || config2.sourceLocale !== this.options.session.sourceLocale)
182
+ throw new Error("Preview configuration does not match this project.");
183
+ if (this.config && this.config.policyKey !== config2.policyKey) {
184
+ this.generation += 1;
185
+ this.jobs.clear();
186
+ for (const state of this.states.values()) {
187
+ state.submitted.clear();
188
+ state.entries.clear();
189
+ state.status = "ready";
190
+ }
191
+ }
192
+ this.config = config2;
193
+ this.configAt = Date.now();
194
+ }
195
+ if (!this.config?.hotReload) {
196
+ for (const locale of this.locales) {
197
+ const state = this.state(locale);
198
+ state.entries.clear();
199
+ state.status = "disabled";
200
+ this.emit(locale);
201
+ }
202
+ return;
203
+ }
204
+ const config = this.config;
205
+ for (const locale of this.locales) {
206
+ const state = this.state(locale);
207
+ if (!config.locales.includes(locale)) {
208
+ state.status = "disabled";
209
+ state.message = "Enable this language in the dashboard.";
210
+ this.emit(locale);
211
+ continue;
212
+ }
213
+ if (state.status === "disabled") state.status = "ready";
214
+ const missing = [...this.messages().values()].filter(
215
+ (message) => !state.submitted.has(messageKey(message))
216
+ );
217
+ const outstanding = [...this.jobs.values()].filter(
218
+ (job) => !terminal.has(job.status)
219
+ ).length;
220
+ if (missing.length && outstanding < 2 && !["budget_blocked", "failed"].includes(state.status)) {
221
+ const batch = [];
222
+ let characters = 0;
223
+ for (const message of missing) {
224
+ const size = [...message.source].length;
225
+ if (batch.length && (batch.length >= 20 || characters + size > 2500))
226
+ break;
227
+ if (size > 12e3) {
228
+ state.status = "failed";
229
+ state.message = "A source string exceeds the 12,000-character preview limit.";
230
+ break;
231
+ }
232
+ batch.push(message);
233
+ characters += size;
234
+ }
235
+ if (batch.length) await this.enqueue(locale, batch, config);
236
+ }
237
+ }
238
+ const now = Date.now();
239
+ const due = [...this.jobs.values()].filter(
240
+ (job) => this.locales.has(job.locale) && now - job.lastPoll >= (terminal.has(job.status) ? 5e3 : 600)
241
+ );
242
+ await Promise.all(due.slice(0, 8).map((job) => this.poll(job)));
243
+ for (const locale of this.locales) this.emit(locale);
244
+ } catch (error) {
245
+ if (!this.disposed)
246
+ for (const locale of this.locales) {
247
+ const state = this.state(locale);
248
+ const code = error.code ?? "PREVIEW_UNAVAILABLE";
249
+ state.status = /CAP|BILLING|BUDGET/.test(code) ? "budget_blocked" : "failed";
250
+ state.message = `${code}. Check the dashboard, then retry preview.`;
251
+ this.emit(locale);
252
+ }
253
+ } finally {
254
+ this.running = false;
255
+ this.schedule(this.dirty ? 250 : 750);
256
+ }
257
+ }
258
+ async enqueue(locale, batch, config) {
259
+ const session = new BuildSession(this.options.session);
260
+ session.collect({ code: "", map: null, messages: batch, diagnostics: [] });
261
+ const manifest = session.manifest();
262
+ manifest.sourceRevision = "linguana-local-preview-v1";
263
+ const controller = new AbortController();
264
+ this.controllers.add(controller);
265
+ const timeout = setTimeout(() => controller.abort(), 15e3);
266
+ let registration;
267
+ try {
268
+ registration = await session.upload(manifest, {
269
+ apiUrl: this.options.apiUrl,
270
+ token: this.options.token,
271
+ preview: true,
272
+ signal: controller.signal
273
+ });
274
+ } finally {
275
+ clearTimeout(timeout);
276
+ this.controllers.delete(controller);
277
+ }
278
+ if (this.disposed || !this.locales.has(locale)) return;
279
+ const created = await this.request(`/api/v1/manifests/${registration.manifestId}/translations`, {
280
+ method: "POST",
281
+ headers: {
282
+ "idempotency-key": contentHash({
283
+ manifest: registration.manifestId,
284
+ locale,
285
+ policy: config.policyKey,
286
+ preview: true,
287
+ retry: this.retrySequence
288
+ })
289
+ },
290
+ body: JSON.stringify({
291
+ preview: true,
292
+ previewRetry: this.retrySequence > 0,
293
+ targetLocales: [locale],
294
+ maxChargeMicros: Math.min(
295
+ config.maxChargeMicros,
296
+ this.options.maxChargeMicros ?? config.maxChargeMicros
297
+ )
298
+ })
299
+ });
300
+ const keys = new Set(batch.map(messageKey));
301
+ for (const job of created.jobs)
302
+ this.jobs.set(job.id, {
303
+ id: job.id,
304
+ locale,
305
+ keys,
306
+ status: "queued",
307
+ lastPoll: 0
308
+ });
309
+ const state = this.state(locale);
310
+ for (const key of keys) state.submitted.add(key);
311
+ state.status = "translating";
312
+ state.message = void 0;
313
+ this.emit(locale);
314
+ }
315
+ async poll(job) {
316
+ job.lastPoll = Date.now();
317
+ const generation = this.generation;
318
+ const update = await this.request(`/api/v1/preview/jobs/${job.id}`);
319
+ if (generation !== this.generation || this.disposed || !this.locales.has(job.locale))
320
+ return;
321
+ if (update.projectId !== this.options.session.projectId || update.environment !== this.options.session.environment || update.locale !== job.locale)
322
+ throw new Error("Invalid preview response scope.");
323
+ job.status = update.status;
324
+ const state = this.state(job.locale);
325
+ const current = this.messages();
326
+ for (const entry of update.entries)
327
+ if (current.get(entry.id)?.contentHash === entry.contentHash && current.get(entry.id)?.source === entry.source)
328
+ state.entries.set(entry.id, entry);
329
+ if (update.status === "disabled") {
330
+ state.entries.clear();
331
+ state.status = "disabled";
332
+ } else if (["failed", "canceled"].includes(update.status)) {
333
+ state.status = /CAP|BILLING/.test(update.errorCode ?? "") ? "budget_blocked" : "failed";
334
+ state.message = update.errorCode ?? "Translation failed. Retry from the preview bar.";
335
+ } else if (update.status === "review_required")
336
+ state.status = "review_required";
337
+ else if (!["failed", "budget_blocked", "review_required"].includes(state.status))
338
+ state.status = [...this.jobs.values()].some(
339
+ (other) => other.locale === job.locale && !terminal.has(other.status)
340
+ ) ? "translating" : "ready";
341
+ }
342
+ };
343
+
344
+ // src/preview-client.ts
345
+ function previewClientSource(coreModule, projectId, sourceLocale) {
346
+ return `
347
+ import { installDevelopmentPreview } from ${JSON.stringify(coreModule)};
348
+ const hot = import.meta.hot;
349
+ if (hot && typeof document !== "undefined") {
350
+ let scopes = [], original = false, hidden = false, host;
351
+ const bridge = installDevelopmentPreview({ watch(next) {
352
+ scopes = next.filter(scope => scope.projectId === ${JSON.stringify(projectId)} && scope.environment === "development" && scope.locale !== ${JSON.stringify(sourceLocale)});
353
+ hot.send("linguana:preview:watch", { locales: scopes.map(scope => scope.locale) });
354
+ if (host) host.hidden = hidden || scopes.length === 0;
355
+ } });
356
+ let disposed = false, configTimer;
357
+ const configScope = { projectId: ${JSON.stringify(projectId)}, environment: "development" };
358
+ const refreshConfig = async () => {
359
+ try { await bridge.refreshConfig(configScope); }
360
+ finally { if (!disposed) configTimer = setTimeout(refreshConfig, 5000); }
361
+ };
362
+ void refreshConfig();
363
+ const mount = () => {
364
+ host = document.createElement("div");
365
+ host.setAttribute("data-linguana-preview", "");
366
+ host.style.cssText = "position:fixed;inset:auto 12px 12px auto;z-index:2147483000;max-width:calc(100vw - 24px)";
367
+ const root = host.attachShadow({ mode: "open" });
368
+ root.innerHTML = '<style>:host{color-scheme:light dark}aside{display:flex;flex-wrap:wrap;gap:9px;align-items:center;max-width:520px;padding:10px 12px;border:1px solid #b7c8bc;border-radius:10px;background:#f7fbf8;color:#193327;box-shadow:0 3px 18px #0002;font:12px/1.4 system-ui,sans-serif;text-align:left;direction:ltr}strong{font-weight:650}span{flex:1;min-width:90px}button{cursor:pointer;border:1px solid #b7c8bc;border-radius:5px;background:transparent;color:inherit;font:inherit;padding:4px 7px}button:focus-visible{outline:2px solid #19653b;outline-offset:2px}@media(prefers-color-scheme:dark){aside{background:#17271e;color:#eaf5ed;border-color:#4e6858}button{border-color:#4e6858}}</style><aside aria-label="Linguana local preview"><strong>Local preview</strong><span role="status" aria-live="polite">Connecting\u2026</span><button type="button" data-original aria-pressed="false">Show original</button><button type="button" data-retry>Refresh</button><button type="button" data-hide aria-label="Hide preview controls">\xD7</button></aside>';
369
+ root.querySelector('[data-original]').onclick = (event) => {
370
+ original = !original; bridge.original(original);
371
+ event.currentTarget.textContent = original ? 'Show translation' : 'Show original';
372
+ event.currentTarget.setAttribute('aria-pressed', String(original));
373
+ };
374
+ root.querySelector('[data-retry]').onclick = () => { void bridge.refreshConfig(configScope); hot.send('linguana:preview:refresh', {}); };
375
+ root.querySelector('[data-hide]').onclick = () => { hidden = true; host.hidden = true; };
376
+ document.body.append(host); host.hidden = hidden || scopes.length === 0;
377
+ };
378
+ if (document.body) mount(); else document.addEventListener('DOMContentLoaded', mount, { once: true });
379
+ const onUpdate = (update) => {
380
+ if (update.projectId !== ${JSON.stringify(projectId)} || update.environment !== 'development') return;
381
+ bridge.update(update);
382
+ const label = host?.shadowRoot?.querySelector('[role="status"]');
383
+ const statuses = { ready: 'Up to date', translating: 'Translating', disabled: 'Off in dashboard', failed: 'Preview unavailable', budget_blocked: 'Spending limit reached', review_required: 'Needs review' };
384
+ if (label) {
385
+ label.textContent = update.locale + ' \xB7 ' + (statuses[update.status] || 'Waiting') + ' \xB7 ' + update.entries.length + '/' + update.total;
386
+ label.title = update.message || 'Validated draft wording. Publishing is a separate action.';
387
+ }
388
+ };
389
+ const reconnect = () => { void bridge.refreshConfig(configScope); hot.send('linguana:preview:watch', { locales: scopes.map(scope => scope.locale) }); };
390
+ hot.on('linguana:preview:update', onUpdate);
391
+ hot.on('vite:ws:connect', reconnect);
392
+ hot.dispose(() => { disposed = true; clearTimeout(configTimer); bridge.dispose(); host?.remove(); document.removeEventListener('DOMContentLoaded', mount); hot.off('linguana:preview:update', onUpdate); hot.off('vite:ws:connect', reconnect); });
393
+ }
394
+ `;
395
+ }
396
+
397
+ // src/index.ts
398
+ var VERSION = "0.1.2";
12
399
  var OFFICIAL_PLUGINS = {
13
400
  react: [
14
401
  "vite:react-babel",
@@ -33,6 +420,20 @@ function linguana(options) {
33
420
  throw new Error(
34
421
  "translation.maxChargeMicros must be a positive safe integer."
35
422
  );
423
+ if (options.previewMaxChargeMicros !== void 0 && (!Number.isSafeInteger(options.previewMaxChargeMicros) || options.previewMaxChargeMicros <= 0))
424
+ throw new Error("previewMaxChargeMicros must be a positive safe integer.");
425
+ const previewEnabled = options.hotReload !== false && environment === "development" && Boolean(options.token) && options.upload !== false;
426
+ const virtualPreview = "virtual:linguana-preview";
427
+ const virtualId = `\0${virtualPreview}`;
428
+ let preview;
429
+ let isServe = false;
430
+ let base = "/";
431
+ const coreEntry = normalizePath(
432
+ fileURLToPath(import.meta.resolve("@linguanahq/core"))
433
+ );
434
+ const previewEntry = normalizePath(
435
+ fileURLToPath(import.meta.resolve("@linguanahq/core/preview"))
436
+ );
36
437
  let root = process.cwd();
37
438
  let session;
38
439
  let filter;
@@ -41,13 +442,99 @@ function linguana(options) {
41
442
  const plugin = {
42
443
  name: "linguana:vite",
43
444
  enforce: "pre",
445
+ config(_, context) {
446
+ if (previewEnabled && context.command === "serve")
447
+ return {
448
+ optimizeDeps: {
449
+ exclude: [
450
+ "@linguanahq/core",
451
+ "@linguanahq/react",
452
+ "@linguanahq/vue",
453
+ "@linguanahq/solid",
454
+ "@linguanahq/svelte"
455
+ ]
456
+ }
457
+ };
458
+ },
459
+ resolveId(id) {
460
+ if (isServe && previewEnabled && id === virtualPreview) return virtualId;
461
+ },
462
+ load(id) {
463
+ if (isServe && previewEnabled && id === virtualId)
464
+ return previewClientSource(
465
+ `${base}@fs/${previewEntry}`,
466
+ options.projectId,
467
+ sourceLocale
468
+ );
469
+ },
470
+ transformIndexHtml() {
471
+ if (isServe && previewEnabled)
472
+ return [
473
+ {
474
+ tag: "script",
475
+ attrs: {
476
+ type: "module",
477
+ src: `${base}@id/__x00__${virtualPreview}`
478
+ },
479
+ injectTo: "head"
480
+ }
481
+ ];
482
+ },
483
+ configureServer(server) {
484
+ if (!previewEnabled || !options.token) return;
485
+ const clients = /* @__PURE__ */ new Map();
486
+ preview = new LocalPreview({
487
+ session: session.options,
488
+ apiUrl: options.apiUrl ?? "https://api.linguana.dev",
489
+ token: options.token,
490
+ maxChargeMicros: options.previewMaxChargeMicros,
491
+ emit(update) {
492
+ for (const [client, locales] of clients)
493
+ if (locales.has(update.locale))
494
+ client.send("linguana:preview:update", update);
495
+ }
496
+ });
497
+ const watch = (data, client) => {
498
+ if (!Array.isArray(data?.locales)) return;
499
+ const locales = data.locales.filter(
500
+ (locale) => typeof locale === "string" && /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/.test(locale)
501
+ ).slice(0, 10);
502
+ if (!clients.has(client))
503
+ client.socket.once("close", () => {
504
+ clients.delete(client);
505
+ preview?.watch([
506
+ ...new Set([...clients.values()].flatMap((value) => [...value]))
507
+ ]);
508
+ });
509
+ clients.set(client, new Set(locales));
510
+ preview?.watch([
511
+ ...new Set([...clients.values()].flatMap((value) => [...value]))
512
+ ]);
513
+ };
514
+ const refresh = () => preview?.refresh();
515
+ const remove = (file) => preview?.collect(normalizePath(file), null);
516
+ server.ws.on("linguana:preview:watch", watch);
517
+ server.ws.on("linguana:preview:refresh", refresh);
518
+ server.watcher.on("unlink", remove);
519
+ server.httpServer?.once("close", () => {
520
+ preview?.dispose();
521
+ server.ws.off("linguana:preview:watch", watch);
522
+ server.ws.off("linguana:preview:refresh", refresh);
523
+ server.watcher.off("unlink", remove);
524
+ });
525
+ },
526
+ closeBundle() {
527
+ preview?.dispose();
528
+ },
44
529
  configResolved(config) {
45
530
  root = config.root;
531
+ isServe = config.command === "serve";
532
+ base = config.base;
46
533
  filter = createFilter(
47
534
  options.include ?? options.adapter.extensions.map((extension) => `**/*${extension}`),
48
535
  options.exclude ?? ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"]
49
536
  );
50
- session = new BuildSession({
537
+ session = new BuildSession2({
51
538
  projectId: options.projectId,
52
539
  environment,
53
540
  sourceLocale,
@@ -93,8 +580,9 @@ function linguana(options) {
93
580
  root,
94
581
  sourceLocale
95
582
  });
583
+ preview?.collect(cleanId, result);
96
584
  if (!result) return null;
97
- session.collect(result);
585
+ if (!isServe) session.collect(result);
98
586
  return {
99
587
  code: result.code,
100
588
  map: result.map
@@ -102,6 +590,9 @@ function linguana(options) {
102
590
  };
103
591
  },
104
592
  async transform(code, id, transformOptions) {
593
+ if (isServe && previewEnabled && !transformOptions?.ssr && normalizePath(id.split("?")[0] ?? id) === coreEntry)
594
+ return { code: `import "${virtualPreview}";
595
+ ${code}`, map: null };
105
596
  return processSource(code, id, Boolean(transformOptions?.ssr));
106
597
  },
107
598
  async writeBundle() {
@@ -0,0 +1,2 @@
1
+ /** This module is emitted only by Vite's development server. */
2
+ export declare function previewClientSource(coreModule: string, projectId: string, sourceLocale: string): string;
@@ -0,0 +1,41 @@
1
+ import { type SourceTransformResult } from "@linguanahq/build-core";
2
+ export type PreviewConfig = {
3
+ projectId: string;
4
+ environment: string;
5
+ sourceLocale: string;
6
+ hotReload: boolean;
7
+ maxChargeMicros: number;
8
+ locales: string[];
9
+ policyKey: string;
10
+ };
11
+ /** Owns only dev-server memory. Every paid task is durably enqueued by the authenticated API. */
12
+ export declare class LocalPreview {
13
+ private files;
14
+ private locales;
15
+ private states;
16
+ private jobs;
17
+ private config?;
18
+ private configAt;
19
+ private timer?;
20
+ private running;
21
+ private dirty;
22
+ private disposed;
23
+ private generation;
24
+ private retrySequence;
25
+ private controllers;
26
+ private options;
27
+ constructor(options: LocalPreview["options"]);
28
+ collect(file: string, result: SourceTransformResult | null): void;
29
+ watch(locales: string[]): void;
30
+ refresh(): void;
31
+ dispose(): void;
32
+ private state;
33
+ private messages;
34
+ private prune;
35
+ private emit;
36
+ private schedule;
37
+ private request;
38
+ private tick;
39
+ private enqueue;
40
+ private poll;
41
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/react.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/react.ts
2
- import { transformSource } from "@linguanahq/compiler";
2
+ import { transformSource } from "@linguanahq/compiler/transform";
3
3
  function react() {
4
4
  return {
5
5
  id: "react",
6
- version: "0.1.1",
6
+ version: "0.1.2",
7
7
  extensions: [".jsx", ".tsx"],
8
8
  runtimeModule: "@linguanahq/react/compiler-runtime",
9
9
  async transform(input) {
package/dist/solid.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/solid.ts
2
- import { transformSource } from "@linguanahq/compiler";
2
+ import { transformSource } from "@linguanahq/compiler/transform";
3
3
  function solid() {
4
4
  return {
5
5
  id: "solid",
6
- version: "0.1.1",
6
+ version: "0.1.2",
7
7
  extensions: [".jsx", ".tsx"],
8
8
  runtimeModule: "@linguanahq/solid/compiler-runtime",
9
9
  async transform(input) {
package/dist/svelte.js CHANGED
@@ -116,7 +116,7 @@ function position(code, offset) {
116
116
  function svelte() {
117
117
  return {
118
118
  id: "svelte",
119
- version: "0.1.1",
119
+ version: "0.1.2",
120
120
  extensions: [".svelte"],
121
121
  runtimeModule: "@linguanahq/svelte/compiler-runtime",
122
122
  async transform(input) {
package/dist/vue.js CHANGED
@@ -113,7 +113,7 @@ function unsafeDiagnostic(item) {
113
113
  function vue() {
114
114
  return {
115
115
  id: "vue",
116
- version: "0.1.1",
116
+ version: "0.1.2",
117
117
  extensions: [".vue"],
118
118
  runtimeModule: "@linguanahq/vue/compiler-runtime",
119
119
  async transform(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linguanahq/vite",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Linguana vite integration",
6
6
  "exports": {
@@ -37,10 +37,11 @@
37
37
  "node": ">=22.12.0"
38
38
  },
39
39
  "dependencies": {
40
- "@linguanahq/build-core": "0.1.1",
41
- "@linguanahq/catalog": "0.1.1",
42
- "@linguanahq/compiler": "0.1.1",
43
- "magic-string": "^0.30.21"
40
+ "@linguanahq/build-core": "0.1.2",
41
+ "@linguanahq/catalog": "0.1.2",
42
+ "@linguanahq/compiler": "0.1.2",
43
+ "magic-string": "^0.30.21",
44
+ "@linguanahq/core": "0.1.2"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "vite": ">=7 <9",