@i18n-micro/devtools-ui 1.2.9 → 1.2.15

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 (37) hide show
  1. package/dist/App.ce.vue.d.ts +3 -2
  2. package/dist/bridge/create-bridge.js +69 -76
  3. package/dist/bridge/create-bridge.js.map +1 -1
  4. package/dist/bridge/interface.js +6 -5
  5. package/dist/bridge/interface.js.map +1 -1
  6. package/dist/components/config/ConfigCard.vue.d.ts +9 -12
  7. package/dist/components/config/DiffViewer.vue.d.ts +2 -1
  8. package/dist/components/config/SettingsCard.vue.d.ts +9 -12
  9. package/dist/components/data/LocaleTable.vue.d.ts +2 -1
  10. package/dist/components/data/LocalesList.vue.d.ts +3 -2
  11. package/dist/components/data/StatItem.vue.d.ts +3 -2
  12. package/dist/components/data/Statistics.vue.d.ts +2 -16
  13. package/dist/components/data/TreeItem.vue.d.ts +3 -2
  14. package/dist/components/editor/TranslationControls.vue.d.ts +3 -2
  15. package/dist/components/editor/TranslationEditor.vue.d.ts +3 -2
  16. package/dist/components/layout/ActionBar.vue.d.ts +3 -2
  17. package/dist/components/layout/SplitPane.vue.d.ts +10 -13
  18. package/dist/components/ui/Button.vue.d.ts +8 -12
  19. package/dist/components/ui/Loader.vue.d.ts +3 -2
  20. package/dist/components/ui/Modal.vue.d.ts +11 -13
  21. package/dist/components/ui/Pagination.vue.d.ts +3 -2
  22. package/dist/components/ui/StatusIndicator.vue.d.ts +3 -2
  23. package/dist/components/ui/Tabs.vue.d.ts +3 -2
  24. package/dist/components/ui/icons/GlobeIcon.vue.d.ts +2 -1
  25. package/dist/components/ui/icons/ServerIcon.vue.d.ts +2 -1
  26. package/dist/components/ui/icons/SettingsIcon.vue.d.ts +2 -1
  27. package/dist/devtools-ui.css +3 -1
  28. package/dist/index.es.js +6261 -8230
  29. package/dist/index.es.js.map +1 -1
  30. package/dist/index.umd.js +28 -9
  31. package/dist/index.umd.js.map +1 -1
  32. package/dist/views/ConfigView.vue.d.ts +2 -1
  33. package/dist/views/I18nView.vue.d.ts +2 -3
  34. package/dist/views/SettingsView.vue.d.ts +2 -1
  35. package/dist/vite/plugin.mjs +211 -254
  36. package/dist/vite/plugin.mjs.map +1 -1
  37. package/package.json +13 -13
@@ -1,2 +1,3 @@
1
- declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
1
+ declare const __VLS_export: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
2
3
  export default _default;
@@ -1,4 +1,3 @@
1
- declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
- file: HTMLInputElement;
3
- }, HTMLDivElement>;
1
+ declare const __VLS_export: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
4
3
  export default _default;
@@ -1,2 +1,3 @@
1
- declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
1
+ declare const __VLS_export: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
2
3
  export default _default;
@@ -1,72 +1,63 @@
1
1
  import * as fs from "node:fs";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import * as path from "node:path";
4
+ //#region src/safe-path.ts
4
5
  function rejectInvalidSymlink(linkPath, originalPath) {
5
- const linkTarget = fs.readlinkSync(linkPath);
6
- const absoluteTarget = path.isAbsolute(linkTarget) ? linkTarget : path.resolve(path.dirname(linkPath), linkTarget);
7
- if (!fs.existsSync(absoluteTarget)) {
8
- throw new Error(`Access denied: Path ${originalPath} resolves through an invalid symlink`);
9
- }
6
+ const linkTarget = fs.readlinkSync(linkPath);
7
+ const absoluteTarget = path.isAbsolute(linkTarget) ? linkTarget : path.resolve(path.dirname(linkPath), linkTarget);
8
+ if (!fs.existsSync(absoluteTarget)) throw new Error(`Access denied: Path ${originalPath} resolves through an invalid symlink`);
10
9
  }
10
+ /**
11
+ * Resolve the deepest existing ancestor of `p` through `fs.realpathSync` (so
12
+ * symlinks are followed), then re-append the not-yet-existing tail. Lets us
13
+ * validate both existing reads and to-be-created write targets.
14
+ */
11
15
  function realpathAllowingMissing(p) {
12
- let current = p;
13
- const tail = [];
14
- while (true) {
15
- try {
16
- const stat = fs.lstatSync(current);
17
- if (stat.isSymbolicLink()) {
18
- rejectInvalidSymlink(current, p);
19
- }
20
- break;
21
- } catch (error) {
22
- if (error instanceof Error && error.message.startsWith("Access denied:")) {
23
- throw error;
24
- }
25
- const code = error instanceof Error && "code" in error ? error.code : void 0;
26
- if (code !== "ENOENT" && code !== "ENOTDIR") {
27
- throw error;
28
- }
29
- tail.unshift(path.basename(current));
30
- const parent = path.dirname(current);
31
- if (parent === current) break;
32
- current = parent;
33
- }
34
- }
35
- const realExisting = fs.realpathSync(current);
36
- return tail.length > 0 ? path.join(realExisting, ...tail) : realExisting;
16
+ let current = p;
17
+ const tail = [];
18
+ while (true) try {
19
+ if (fs.lstatSync(current).isSymbolicLink()) rejectInvalidSymlink(current, p);
20
+ break;
21
+ } catch (error) {
22
+ if (error instanceof Error && error.message.startsWith("Access denied:")) throw error;
23
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
24
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
25
+ tail.unshift(path.basename(current));
26
+ const parent = path.dirname(current);
27
+ if (parent === current) break;
28
+ current = parent;
29
+ }
30
+ const realExisting = fs.realpathSync(current);
31
+ return tail.length > 0 ? path.join(realExisting, ...tail) : realExisting;
37
32
  }
38
33
  function safeResolvePath(projectRoot, filePath) {
39
- const normalizedFile = filePath.replace(/^\/+/, "").replace(/\/+/g, "/");
40
- const resolvedPath = path.resolve(projectRoot, normalizedFile);
41
- const realRoot = fs.realpathSync(path.resolve(projectRoot));
42
- const realCandidate = realpathAllowingMissing(resolvedPath);
43
- const rel = path.relative(realRoot, realCandidate);
44
- if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
45
- throw new Error(`Access denied: Path ${resolvedPath} is outside project root`);
46
- }
47
- return resolvedPath;
34
+ const normalizedFile = filePath.replace(/^\/+/, "").replace(/\/+/g, "/");
35
+ const resolvedPath = path.resolve(projectRoot, normalizedFile);
36
+ const realRoot = fs.realpathSync(path.resolve(projectRoot));
37
+ const realCandidate = realpathAllowingMissing(resolvedPath);
38
+ const rel = path.relative(realRoot, realCandidate);
39
+ if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) throw new Error(`Access denied: Path ${resolvedPath} is outside project root`);
40
+ return resolvedPath;
48
41
  }
42
+ //#endregion
43
+ //#region vite/plugin.ts
49
44
  async function scanTranslationFiles(dir, baseDir) {
50
- const files = [];
51
- try {
52
- const entries = await readdir(dir, { withFileTypes: true });
53
- const subdirFiles = await Promise.all(
54
- entries.filter((entry) => entry.isDirectory()).map((entry) => scanTranslationFiles(path.join(dir, entry.name), baseDir))
55
- );
56
- files.push(...subdirFiles.flat());
57
- for (const entry of entries) {
58
- if (entry.isFile() && entry.name.endsWith(".json")) {
59
- const fullPath = path.join(dir, entry.name);
60
- const relativePath = path.relative(baseDir, fullPath);
61
- files.push(relativePath.replace(/\\/g, "/"));
62
- }
63
- }
64
- } catch (error) {
65
- console.warn(`[i18n-devtools] Cannot scan directory ${dir}:`, error);
66
- }
67
- return files;
45
+ const files = [];
46
+ try {
47
+ const entries = await readdir(dir, { withFileTypes: true });
48
+ const subdirFiles = await Promise.all(entries.filter((entry) => entry.isDirectory()).map((entry) => scanTranslationFiles(path.join(dir, entry.name), baseDir)));
49
+ files.push(...subdirFiles.flat());
50
+ for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".json")) {
51
+ const fullPath = path.join(dir, entry.name);
52
+ const relativePath = path.relative(baseDir, fullPath);
53
+ files.push(relativePath.replace(/\\/g, "/"));
54
+ }
55
+ } catch (error) {
56
+ console.warn(`[i18n-devtools] Cannot scan directory ${dir}:`, error);
57
+ }
58
+ return files;
68
59
  }
69
- const BUTTON_INJECTION_SCRIPT = `
60
+ var BUTTON_INJECTION_SCRIPT = `
70
61
  (function() {
71
62
  if (typeof window === 'undefined' || document.getElementById('i18n-devtools-button-container')) {
72
63
  return;
@@ -219,38 +210,29 @@ const BUTTON_INJECTION_SCRIPT = `
219
210
  })();
220
211
  `;
221
212
  function i18nDevToolsPlugin(options = {}) {
222
- const apiBase = options.base || "/__i18n_api";
223
- const translationDir = options.translationDir || "src/locales";
224
- const injectButton = options.injectButton !== false;
225
- return {
226
- name: "i18n-micro-devtools-plugin",
227
- apply: "serve",
228
- // Works only in dev server
229
- resolveId(id) {
230
- if (id === "/@vite-plugin-i18n-devtools/devtools-ui.js") {
231
- return id;
232
- }
233
- return null;
234
- },
235
- load(id) {
236
- if (id === "/@vite-plugin-i18n-devtools/devtools-ui.js") {
237
- return `export * from '@i18n-micro/devtools-ui'`;
238
- }
239
- return null;
240
- },
241
- transformIndexHtml(html) {
242
- if (!injectButton) {
243
- return html;
244
- }
245
- const scriptTag = `<script>${BUTTON_INJECTION_SCRIPT}<\/script>`;
246
- if (html.includes("</body>")) {
247
- return html.replace("</body>", `${scriptTag}</body>`);
248
- }
249
- return html + scriptTag;
250
- },
251
- configureServer(server) {
252
- const projectRoot = server.config.root;
253
- const devtoolsHtml = `<!DOCTYPE html>
213
+ const apiBase = options.base || "/__i18n_api";
214
+ const translationDir = options.translationDir || "src/locales";
215
+ const injectButton = options.injectButton !== false;
216
+ return {
217
+ name: "i18n-micro-devtools-plugin",
218
+ apply: "serve",
219
+ resolveId(id) {
220
+ if (id === "/@vite-plugin-i18n-devtools/devtools-ui.js") return id;
221
+ return null;
222
+ },
223
+ load(id) {
224
+ if (id === "/@vite-plugin-i18n-devtools/devtools-ui.js") return `export * from '@i18n-micro/devtools-ui'`;
225
+ return null;
226
+ },
227
+ transformIndexHtml(html) {
228
+ if (!injectButton) return html;
229
+ const scriptTag = `<script>${BUTTON_INJECTION_SCRIPT}<\/script>`;
230
+ if (html.includes("</body>")) return html.replace("</body>", `${scriptTag}</body>`);
231
+ return html + scriptTag;
232
+ },
233
+ configureServer(server) {
234
+ const projectRoot = server.config.root;
235
+ const devtoolsHtml = `<!DOCTYPE html>
254
236
  <html lang="en">
255
237
  <head>
256
238
  <meta charset="UTF-8">
@@ -384,168 +366,143 @@ function i18nDevToolsPlugin(options = {}) {
384
366
  <\/script>
385
367
  </body>
386
368
  </html>`;
387
- server.middlewares.use("/__i18n_devtools.html", (_req, res) => {
388
- res.statusCode = 200;
389
- res.setHeader("Content-Type", "text/html; charset=utf-8");
390
- res.end(devtoolsHtml);
391
- });
392
- server.middlewares.use(`${apiBase}/config`, async (_req, res, next) => {
393
- if (_req.method !== "GET") {
394
- return next();
395
- }
396
- try {
397
- res.statusCode = 200;
398
- res.setHeader("Content-Type", "application/json");
399
- res.end(
400
- JSON.stringify({
401
- defaultLocale: "en",
402
- fallbackLocale: "en",
403
- locales: [],
404
- translationDir
405
- })
406
- );
407
- } catch (e) {
408
- console.error("[i18n-devtools] Config error:", e);
409
- res.statusCode = 500;
410
- res.setHeader("Content-Type", "application/json");
411
- res.end(
412
- JSON.stringify({
413
- success: false,
414
- error: e instanceof Error ? e.message : String(e)
415
- })
416
- );
417
- }
418
- });
419
- server.middlewares.use(`${apiBase}/files`, async (req, res, next) => {
420
- if (req.method !== "GET") {
421
- return next();
422
- }
423
- try {
424
- const localesPath = path.resolve(projectRoot, translationDir);
425
- safeResolvePath(projectRoot, translationDir);
426
- if (!fs.existsSync(localesPath)) {
427
- res.statusCode = 200;
428
- res.setHeader("Content-Type", "application/json");
429
- res.end(JSON.stringify({ files: [], structure: {} }));
430
- return;
431
- }
432
- const files = await scanTranslationFiles(localesPath, localesPath);
433
- const structure = {};
434
- for (const file of files) {
435
- const parts = file.split("/");
436
- let current = structure;
437
- for (let i = 0; i < parts.length - 1; i++) {
438
- const part = parts[i];
439
- if (!current[part]) {
440
- current[part] = {};
441
- }
442
- current = current[part];
443
- }
444
- const last = parts[parts.length - 1];
445
- if (last !== void 0) {
446
- current[last] = file;
447
- }
448
- }
449
- res.statusCode = 200;
450
- res.setHeader("Content-Type", "application/json");
451
- res.end(JSON.stringify({ files, structure }));
452
- } catch (e) {
453
- console.error("[i18n-devtools] Files list error:", e);
454
- res.statusCode = 500;
455
- res.setHeader("Content-Type", "application/json");
456
- res.end(
457
- JSON.stringify({
458
- success: false,
459
- error: e instanceof Error ? e.message : String(e)
460
- })
461
- );
462
- }
463
- });
464
- server.middlewares.use(`${apiBase}/file`, async (req, res, next) => {
465
- if (req.method !== "GET") {
466
- return next();
467
- }
468
- try {
469
- const url = new URL(req.url || "", `http://${req.headers.host}`);
470
- const filePath = url.searchParams.get("path");
471
- if (!filePath) {
472
- throw new Error("Path parameter is required");
473
- }
474
- const fullPath = path.join(translationDir, filePath);
475
- const resolvedPath = safeResolvePath(projectRoot, fullPath);
476
- if (!fs.existsSync(resolvedPath)) {
477
- res.statusCode = 404;
478
- res.setHeader("Content-Type", "application/json");
479
- res.end(JSON.stringify({ success: false, error: "File not found" }));
480
- return;
481
- }
482
- if (!resolvedPath.endsWith(".json")) {
483
- throw new Error("Invalid file: only .json files are allowed");
484
- }
485
- const content = fs.readFileSync(resolvedPath, "utf-8");
486
- const parsed = JSON.parse(content);
487
- res.statusCode = 200;
488
- res.setHeader("Content-Type", "application/json");
489
- res.end(JSON.stringify({ success: true, content: parsed, path: filePath }));
490
- } catch (e) {
491
- console.error("[i18n-devtools] File read error:", e);
492
- res.statusCode = 500;
493
- res.setHeader("Content-Type", "application/json");
494
- res.end(
495
- JSON.stringify({
496
- success: false,
497
- error: e instanceof Error ? e.message : String(e)
498
- })
499
- );
500
- }
501
- });
502
- server.middlewares.use(`${apiBase}/save`, async (req, res, next) => {
503
- if (req.method !== "POST") {
504
- return next();
505
- }
506
- try {
507
- const buffers = [];
508
- for await (const chunk of req) {
509
- buffers.push(chunk);
510
- }
511
- const bodyData = Buffer.concat(buffers).toString();
512
- if (!bodyData) {
513
- throw new Error("Empty request body");
514
- }
515
- const body = JSON.parse(bodyData);
516
- const { file, content } = body;
517
- if (!file || !content) {
518
- throw new Error("Invalid data: file and content are required");
519
- }
520
- const fullPath = file.startsWith(translationDir) ? file : path.join(translationDir, file);
521
- const filePath = safeResolvePath(projectRoot, fullPath);
522
- if (!filePath.endsWith(".json")) {
523
- throw new Error("Invalid file: only .json files are allowed");
524
- }
525
- const dir = path.dirname(filePath);
526
- if (!fs.existsSync(dir)) {
527
- fs.mkdirSync(dir, { recursive: true });
528
- }
529
- fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
530
- res.statusCode = 200;
531
- res.setHeader("Content-Type", "application/json");
532
- res.end(JSON.stringify({ success: true }));
533
- } catch (e) {
534
- console.error("[i18n-devtools] Save error:", e);
535
- res.statusCode = 500;
536
- res.setHeader("Content-Type", "application/json");
537
- res.end(
538
- JSON.stringify({
539
- success: false,
540
- error: e instanceof Error ? e.message : String(e)
541
- })
542
- );
543
- }
544
- });
545
- }
546
- };
369
+ server.middlewares.use("/__i18n_devtools.html", (_req, res) => {
370
+ res.statusCode = 200;
371
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
372
+ res.end(devtoolsHtml);
373
+ });
374
+ server.middlewares.use(`${apiBase}/config`, async (_req, res, next) => {
375
+ if (_req.method !== "GET") return next();
376
+ try {
377
+ res.statusCode = 200;
378
+ res.setHeader("Content-Type", "application/json");
379
+ res.end(JSON.stringify({
380
+ defaultLocale: "en",
381
+ fallbackLocale: "en",
382
+ locales: [],
383
+ translationDir
384
+ }));
385
+ } catch (e) {
386
+ console.error("[i18n-devtools] Config error:", e);
387
+ res.statusCode = 500;
388
+ res.setHeader("Content-Type", "application/json");
389
+ res.end(JSON.stringify({
390
+ success: false,
391
+ error: e instanceof Error ? e.message : String(e)
392
+ }));
393
+ }
394
+ });
395
+ server.middlewares.use(`${apiBase}/files`, async (req, res, next) => {
396
+ if (req.method !== "GET") return next();
397
+ try {
398
+ const localesPath = path.resolve(projectRoot, translationDir);
399
+ safeResolvePath(projectRoot, translationDir);
400
+ if (!fs.existsSync(localesPath)) {
401
+ res.statusCode = 200;
402
+ res.setHeader("Content-Type", "application/json");
403
+ res.end(JSON.stringify({
404
+ files: [],
405
+ structure: {}
406
+ }));
407
+ return;
408
+ }
409
+ const files = await scanTranslationFiles(localesPath, localesPath);
410
+ const structure = {};
411
+ for (const file of files) {
412
+ const parts = file.split("/");
413
+ let current = structure;
414
+ for (let i = 0; i < parts.length - 1; i++) {
415
+ const part = parts[i];
416
+ if (!current[part]) current[part] = {};
417
+ current = current[part];
418
+ }
419
+ const last = parts[parts.length - 1];
420
+ if (last !== void 0) current[last] = file;
421
+ }
422
+ res.statusCode = 200;
423
+ res.setHeader("Content-Type", "application/json");
424
+ res.end(JSON.stringify({
425
+ files,
426
+ structure
427
+ }));
428
+ } catch (e) {
429
+ console.error("[i18n-devtools] Files list error:", e);
430
+ res.statusCode = 500;
431
+ res.setHeader("Content-Type", "application/json");
432
+ res.end(JSON.stringify({
433
+ success: false,
434
+ error: e instanceof Error ? e.message : String(e)
435
+ }));
436
+ }
437
+ });
438
+ server.middlewares.use(`${apiBase}/file`, async (req, res, next) => {
439
+ if (req.method !== "GET") return next();
440
+ try {
441
+ const filePath = new URL(req.url || "", `http://${req.headers.host}`).searchParams.get("path");
442
+ if (!filePath) throw new Error("Path parameter is required");
443
+ const fullPath = path.join(translationDir, filePath);
444
+ const resolvedPath = safeResolvePath(projectRoot, fullPath);
445
+ if (!fs.existsSync(resolvedPath)) {
446
+ res.statusCode = 404;
447
+ res.setHeader("Content-Type", "application/json");
448
+ res.end(JSON.stringify({
449
+ success: false,
450
+ error: "File not found"
451
+ }));
452
+ return;
453
+ }
454
+ if (!resolvedPath.endsWith(".json")) throw new Error("Invalid file: only .json files are allowed");
455
+ const content = fs.readFileSync(resolvedPath, "utf-8");
456
+ const parsed = JSON.parse(content);
457
+ res.statusCode = 200;
458
+ res.setHeader("Content-Type", "application/json");
459
+ res.end(JSON.stringify({
460
+ success: true,
461
+ content: parsed,
462
+ path: filePath
463
+ }));
464
+ } catch (e) {
465
+ console.error("[i18n-devtools] File read error:", e);
466
+ res.statusCode = 500;
467
+ res.setHeader("Content-Type", "application/json");
468
+ res.end(JSON.stringify({
469
+ success: false,
470
+ error: e instanceof Error ? e.message : String(e)
471
+ }));
472
+ }
473
+ });
474
+ server.middlewares.use(`${apiBase}/save`, async (req, res, next) => {
475
+ if (req.method !== "POST") return next();
476
+ try {
477
+ const buffers = [];
478
+ for await (const chunk of req) buffers.push(chunk);
479
+ const bodyData = Buffer.concat(buffers).toString();
480
+ if (!bodyData) throw new Error("Empty request body");
481
+ const { file, content } = JSON.parse(bodyData);
482
+ if (!file || !content) throw new Error("Invalid data: file and content are required");
483
+ const fullPath = file.startsWith(translationDir) ? file : path.join(translationDir, file);
484
+ const filePath = safeResolvePath(projectRoot, fullPath);
485
+ if (!filePath.endsWith(".json")) throw new Error("Invalid file: only .json files are allowed");
486
+ const dir = path.dirname(filePath);
487
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
488
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
489
+ res.statusCode = 200;
490
+ res.setHeader("Content-Type", "application/json");
491
+ res.end(JSON.stringify({ success: true }));
492
+ } catch (e) {
493
+ console.error("[i18n-devtools] Save error:", e);
494
+ res.statusCode = 500;
495
+ res.setHeader("Content-Type", "application/json");
496
+ res.end(JSON.stringify({
497
+ success: false,
498
+ error: e instanceof Error ? e.message : String(e)
499
+ }));
500
+ }
501
+ });
502
+ }
503
+ };
547
504
  }
548
- export {
549
- i18nDevToolsPlugin
550
- };
551
- //# sourceMappingURL=plugin.mjs.map
505
+ //#endregion
506
+ export { i18nDevToolsPlugin };
507
+
508
+ //# sourceMappingURL=plugin.mjs.map