@i18n-micro/devtools-ui 1.1.0 → 1.2.0

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.
@@ -1,5 +1,5 @@
1
- export type { JSONValue, TranslationContent, LocaleData, I18nDevToolsBridge, } from '../bridge/interface';
2
1
  export type { ModuleOptions } from '@i18n-micro/types';
2
+ export type { I18nDevToolsBridge, JSONValue, LocaleData, TranslationContent, } from '../bridge/interface';
3
3
  export interface TreeNode {
4
4
  name: string;
5
5
  fullPath: string;
@@ -22,41 +22,41 @@ export declare class Translator {
22
22
  */
23
23
  translate(text: string, fromLang: string, toLang: string): Promise<string>;
24
24
  /**
25
- * Очищает текст перед переводом.
26
- * Заменяет специальные конструкции на временные метки.
25
+ * Cleans text before translation.
26
+ * Replaces special constructs with temporary markers.
27
27
  */
28
28
  private clearText;
29
29
  /**
30
- * Восстанавливает текст после перевода.
31
- * Заменяет временные метки на оригинальные значения.
30
+ * Restores text after translation.
31
+ * Replaces temporary markers with original values.
32
32
  */
33
33
  private revertText;
34
34
  /**
35
- * Переводчик Google (платный API).
35
+ * Google Translator (paid API).
36
36
  */
37
37
  private googleTranslate;
38
38
  /**
39
- * Переводчик Yandex (платный API).
39
+ * Yandex Translator (paid API).
40
40
  */
41
41
  private yandexTranslate;
42
42
  /**
43
- * Переводчик DeepL (платный API).
43
+ * DeepL Translator (paid API).
44
44
  */
45
45
  private deeplTranslate;
46
46
  /**
47
- * Переводчик OpenAI (GPT).
47
+ * OpenAI Translator (GPT).
48
48
  */
49
49
  private openaiTranslate;
50
50
  /**
51
- * Переводчик DeepSeek.
51
+ * DeepSeek Translator.
52
52
  */
53
53
  private deepseekTranslate;
54
54
  /**
55
- * Переводчик Google Free (неофициальный API).
55
+ * Google Free Translator (unofficial API).
56
56
  */
57
57
  private googleFreeTranslate;
58
58
  /**
59
- * Переводчик Yandex Cloud.
59
+ * Yandex Cloud Translator.
60
60
  */
61
61
  private yandexCloudTranslate;
62
62
  /**
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
- import * as path from "node:path";
3
2
  import { readdir } from "node:fs/promises";
3
+ import * as path from "node:path";
4
4
  function safeResolvePath(projectRoot, filePath) {
5
5
  const normalizedFile = filePath.replace(/^\/+/, "").replace(/\/+/g, "/");
6
6
  const resolvedPath = path.resolve(projectRoot, normalizedFile);
@@ -65,7 +65,7 @@ const BUTTON_INJECTION_SCRIPT = `
65
65
 
66
66
  function hideButton() {
67
67
  if (isVisible) {
68
- // Оставляем видимой только небольшую часть кнопки (примерно 40px)
68
+ // Leave only a small part of the button visible (approximately 40px)
69
69
  button.style.transform = 'translateX(calc(100% - 40px))';
70
70
  button.style.opacity = '0.3';
71
71
  isVisible = false;
@@ -103,7 +103,7 @@ const BUTTON_INJECTION_SCRIPT = `
103
103
  clearTimeout(hideTimeout);
104
104
  hideTimeout = null;
105
105
  }
106
- // Показываем кнопку полностью при наведении
106
+ // Show the button fully on hover
107
107
  if (!isVisible) {
108
108
  showButton();
109
109
  }
@@ -118,12 +118,12 @@ const BUTTON_INJECTION_SCRIPT = `
118
118
  });
119
119
 
120
120
  button.addEventListener('click', function() {
121
- // Проверяем, не открыт ли уже редактор
121
+ // Check if the editor is already open
122
122
  if (document.getElementById('i18n-devtools-modal')) {
123
123
  return;
124
124
  }
125
125
 
126
- // Открываем модальное окно с iframe
126
+ // Open modal window with iframe
127
127
  const modal = document.createElement('div');
128
128
  modal.id = 'i18n-devtools-modal';
129
129
  modal.style.cssText = 'position: fixed; inset: 0; z-index: 999999; background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(2px); display: flex; align-items: center; justify-content: center; padding: 2rem;';
@@ -172,7 +172,7 @@ const BUTTON_INJECTION_SCRIPT = `
172
172
 
173
173
  document.addEventListener('mousemove', checkMouseDistance);
174
174
 
175
- // Автоматически скрываем через 3 секунды после загрузки
175
+ // Auto-hide after 3 seconds of loading
176
176
  setTimeout(() => {
177
177
  if (!button.matches(':hover')) {
178
178
  hideTimeout = setTimeout(hideButton, 3000);
@@ -189,7 +189,7 @@ function i18nDevToolsPlugin(options = {}) {
189
189
  return {
190
190
  name: "i18n-micro-devtools-plugin",
191
191
  apply: "serve",
192
- // Работает только в dev server
192
+ // Works only in dev server
193
193
  resolveId(id) {
194
194
  if (id === "/@vite-plugin-i18n-devtools/devtools-ui.js") {
195
195
  return id;
@@ -249,14 +249,14 @@ function i18nDevToolsPlugin(options = {}) {
249
249
  <div class="loading">Loading DevTools...</div>
250
250
  </div>
251
251
  <script type="module">
252
- // Импортируем devtools UI
253
- // Пытаемся импортировать через разные пути для совместимости
252
+ // Import devtools UI
253
+ // Try to import through different paths for compatibility
254
254
  let register;
255
255
  try {
256
256
  const devtoolsModule = await import('/@vite-plugin-i18n-devtools/devtools-ui.js');
257
257
  register = devtoolsModule.register;
258
258
  } catch (e) {
259
- // Fallback: пытаемся импортировать напрямую
259
+ // Fallback: try to import directly
260
260
  try {
261
261
  const devtoolsModule = await import('@i18n-micro/devtools-ui');
262
262
  register = devtoolsModule.register;
@@ -267,10 +267,10 @@ function i18nDevToolsPlugin(options = {}) {
267
267
  }
268
268
  }
269
269
 
270
- // Регистрируем custom element
270
+ // Register custom element
271
271
  register();
272
272
 
273
- // Создаем bridge через API
273
+ // Create bridge via API
274
274
  const bridge = {
275
275
  async getLocalesAndTranslations() {
276
276
  try {
@@ -360,20 +360,24 @@ function i18nDevToolsPlugin(options = {}) {
360
360
  try {
361
361
  res.statusCode = 200;
362
362
  res.setHeader("Content-Type", "application/json");
363
- res.end(JSON.stringify({
364
- defaultLocale: "en",
365
- fallbackLocale: "en",
366
- locales: [],
367
- translationDir
368
- }));
363
+ res.end(
364
+ JSON.stringify({
365
+ defaultLocale: "en",
366
+ fallbackLocale: "en",
367
+ locales: [],
368
+ translationDir
369
+ })
370
+ );
369
371
  } catch (e) {
370
372
  console.error("[i18n-devtools] Config error:", e);
371
373
  res.statusCode = 500;
372
374
  res.setHeader("Content-Type", "application/json");
373
- res.end(JSON.stringify({
374
- success: false,
375
- error: e instanceof Error ? e.message : String(e)
376
- }));
375
+ res.end(
376
+ JSON.stringify({
377
+ success: false,
378
+ error: e instanceof Error ? e.message : String(e)
379
+ })
380
+ );
377
381
  }
378
382
  });
379
383
  server.middlewares.use(`${apiBase}/files`, async (req, res, next) => {
@@ -413,10 +417,12 @@ function i18nDevToolsPlugin(options = {}) {
413
417
  console.error("[i18n-devtools] Files list error:", e);
414
418
  res.statusCode = 500;
415
419
  res.setHeader("Content-Type", "application/json");
416
- res.end(JSON.stringify({
417
- success: false,
418
- error: e instanceof Error ? e.message : String(e)
419
- }));
420
+ res.end(
421
+ JSON.stringify({
422
+ success: false,
423
+ error: e instanceof Error ? e.message : String(e)
424
+ })
425
+ );
420
426
  }
421
427
  });
422
428
  server.middlewares.use(`${apiBase}/file`, async (req, res, next) => {
@@ -449,10 +455,12 @@ function i18nDevToolsPlugin(options = {}) {
449
455
  console.error("[i18n-devtools] File read error:", e);
450
456
  res.statusCode = 500;
451
457
  res.setHeader("Content-Type", "application/json");
452
- res.end(JSON.stringify({
453
- success: false,
454
- error: e instanceof Error ? e.message : String(e)
455
- }));
458
+ res.end(
459
+ JSON.stringify({
460
+ success: false,
461
+ error: e instanceof Error ? e.message : String(e)
462
+ })
463
+ );
456
464
  }
457
465
  });
458
466
  server.middlewares.use(`${apiBase}/save`, async (req, res, next) => {
@@ -490,10 +498,12 @@ function i18nDevToolsPlugin(options = {}) {
490
498
  console.error("[i18n-devtools] Save error:", e);
491
499
  res.statusCode = 500;
492
500
  res.setHeader("Content-Type", "application/json");
493
- res.end(JSON.stringify({
494
- success: false,
495
- error: e instanceof Error ? e.message : String(e)
496
- }));
501
+ res.end(
502
+ JSON.stringify({
503
+ success: false,
504
+ error: e instanceof Error ? e.message : String(e)
505
+ })
506
+ );
497
507
  }
498
508
  });
499
509
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","sources":["../../vite/plugin.ts"],"sourcesContent":["import type { PluginOption, ViteDevServer, IndexHtmlTransformResult } from 'vite'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { readdir } from 'node:fs/promises'\n\nexport interface DevToolsPluginOptions {\n base?: string\n translationDir?: string\n injectButton?: boolean\n}\n\n// Вспомогательная функция для безопасного резолва пути\nfunction safeResolvePath(projectRoot: string, filePath: string): string {\n const normalizedFile = filePath.replace(/^\\/+/, '').replace(/\\/+/g, '/')\n const resolvedPath = path.resolve(projectRoot, normalizedFile)\n const normalizedRoot = path.resolve(projectRoot)\n const normalizedFilePath = path.resolve(resolvedPath)\n\n if (!normalizedFilePath.startsWith(normalizedRoot)) {\n throw new Error(`Access denied: Path ${resolvedPath} is outside project root`)\n }\n\n return resolvedPath\n}\n\n// Рекурсивное сканирование директории для поиска JSON файлов\nasync function scanTranslationFiles(dir: string, baseDir: string): Promise<string[]> {\n const files: string[] = []\n try {\n const entries = await readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n const subFiles = await scanTranslationFiles(fullPath, baseDir)\n files.push(...subFiles)\n }\n else if (entry.isFile() && entry.name.endsWith('.json')) {\n const relativePath = path.relative(baseDir, fullPath)\n files.push(relativePath.replace(/\\\\/g, '/')) // Нормализуем для кроссплатформенности\n }\n }\n }\n catch (error) {\n // Игнорируем ошибки доступа к директориям\n console.warn(`[i18n-devtools] Cannot scan directory ${dir}:`, error)\n }\n return files\n}\n\n// Скрипт для инжекции кнопки\nconst BUTTON_INJECTION_SCRIPT = `\n(function() {\n if (typeof window === 'undefined' || document.getElementById('i18n-devtools-button-container')) {\n return;\n }\n\n const container = document.createElement('div');\n container.id = 'i18n-devtools-button-container';\n container.style.cssText = 'position: fixed; bottom: 20px; right: 20px; z-index: 99999; pointer-events: none;';\n document.body.appendChild(container);\n\n const button = document.createElement('button');\n button.id = 'i18n-devtools-button';\n button.type = 'button';\n button.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"display: inline-block; vertical-align: middle; margin-right: 8px;\"><circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\" /><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" /></svg>i18n';\n button.style.cssText = 'cursor: pointer; background: #1e1e1e; color: white; padding: 8px 16px; border-radius: 9999px; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 1px solid #333; font-size: 14px; font-weight: 600; transition: all 0.3s ease; opacity: 0.3; pointer-events: auto; font-family: system-ui, sans-serif;';\n\n let hideTimeout = null;\n let isVisible = true;\n let lastMouseY = window.innerHeight;\n\n function showButton() {\n if (!isVisible) {\n button.style.transform = 'translateX(0)';\n button.style.opacity = '0.3';\n isVisible = true;\n }\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n hideTimeout = null;\n }\n }\n\n function hideButton() {\n if (isVisible) {\n // Оставляем видимой только небольшую часть кнопки (примерно 40px)\n button.style.transform = 'translateX(calc(100% - 40px))';\n button.style.opacity = '0.3';\n isVisible = false;\n }\n }\n\n function checkMouseDistance(e) {\n const buttonRect = button.getBoundingClientRect();\n const buttonCenterX = buttonRect.left + buttonRect.width / 2;\n const buttonCenterY = buttonRect.top + buttonRect.height / 2;\n const distanceX = Math.abs(e.clientX - buttonCenterX);\n const distanceY = Math.abs(e.clientY - buttonCenterY);\n const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);\n\n if (distance > 200 && !button.matches(':hover')) {\n if (!hideTimeout) {\n hideTimeout = setTimeout(hideButton, 1000);\n }\n } else {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n hideTimeout = null;\n }\n if (!isVisible) {\n showButton();\n }\n }\n lastMouseY = e.clientY;\n }\n\n button.addEventListener('mouseenter', function() {\n this.style.opacity = '1';\n this.style.transform = 'translateX(0) translateY(-2px) scale(1.05)';\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n hideTimeout = null;\n }\n // Показываем кнопку полностью при наведении\n if (!isVisible) {\n showButton();\n }\n });\n\n button.addEventListener('mouseleave', function() {\n if (isVisible) {\n this.style.opacity = '0.3';\n this.style.transform = 'translateX(0) scale(1)';\n hideTimeout = setTimeout(hideButton, 3000);\n }\n });\n\n button.addEventListener('click', function() {\n // Проверяем, не открыт ли уже редактор\n if (document.getElementById('i18n-devtools-modal')) {\n return;\n }\n\n // Открываем модальное окно с iframe\n const modal = document.createElement('div');\n modal.id = 'i18n-devtools-modal';\n modal.style.cssText = 'position: fixed; inset: 0; z-index: 999999; background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(2px); display: flex; align-items: center; justify-content: center; padding: 2rem;';\n modal.addEventListener('click', function(e) {\n if (e.target === modal) {\n document.body.removeChild(modal);\n }\n });\n\n const content = document.createElement('div');\n content.style.cssText = 'background: white; width: 90vw; max-width: 1200px; height: 85vh; border-radius: 12px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; overflow: hidden; border: 1px solid #e5e7eb;';\n content.addEventListener('click', function(e) {\n e.stopPropagation();\n });\n\n const header = document.createElement('div');\n header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; flex-shrink: 0;';\n header.innerHTML = '<div style=\"font-weight: 600; color: #334155; display: flex; align-items: center; gap: 8px;\"><svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\" /><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" /></svg> i18n DevTools</div><button id=\"i18n-devtools-close\" style=\"background: transparent; border: none; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; color: #64748b; transition: background 0.2s;\">✕</button>';\n\n const closeBtn = header.querySelector('#i18n-devtools-close');\n closeBtn.addEventListener('mouseenter', function() {\n this.style.background = '#e2e8f0';\n });\n closeBtn.addEventListener('mouseleave', function() {\n this.style.background = 'transparent';\n });\n closeBtn.addEventListener('click', function() {\n document.body.removeChild(modal);\n });\n\n const iframeContainer = document.createElement('div');\n iframeContainer.style.cssText = 'flex: 1; position: relative; overflow: hidden; background: white;';\n\n const iframe = document.createElement('iframe');\n iframe.id = 'i18n-devtools-iframe';\n iframe.style.cssText = 'width: 100%; height: 100%; border: none; background: white;';\n iframe.src = '/__i18n_devtools.html';\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n\n iframeContainer.appendChild(iframe);\n content.appendChild(header);\n content.appendChild(iframeContainer);\n modal.appendChild(content);\n document.body.appendChild(modal);\n });\n\n document.addEventListener('mousemove', checkMouseDistance);\n\n // Автоматически скрываем через 3 секунды после загрузки\n setTimeout(() => {\n if (!button.matches(':hover')) {\n hideTimeout = setTimeout(hideButton, 3000);\n }\n }, 3000);\n\n container.appendChild(button);\n})();\n`\n\nexport function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginOption {\n const apiBase = options.base || '/__i18n_api'\n const translationDir = options.translationDir || 'src/locales'\n const injectButton = options.injectButton !== false // По умолчанию true\n\n return {\n name: 'i18n-micro-devtools-plugin',\n apply: 'serve', // Работает только в dev server\n\n resolveId(id: string) {\n if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {\n return id\n }\n return null\n },\n\n load(id: string) {\n if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {\n // Возвращаем реэкспорт из devtools-ui пакета\n return `export * from '@i18n-micro/devtools-ui'`\n }\n return null\n },\n\n transformIndexHtml(html: string): IndexHtmlTransformResult {\n if (!injectButton) {\n return html\n }\n\n // Инжектируем скрипт перед закрывающим тегом </body>\n const scriptTag = `<script>${BUTTON_INJECTION_SCRIPT}</script>`\n if (html.includes('</body>')) {\n return html.replace('</body>', `${scriptTag}</body>`)\n }\n // Если нет </body>, добавляем в конец\n return html + scriptTag\n },\n\n configureServer(server: ViteDevServer) {\n // Используем server.config.root как источник правды для корня проекта\n const projectRoot = server.config.root\n\n // HTML страница для iframe с devtools UI\n // Используем виртуальный модуль для правильного импорта\n const devtoolsHtml = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>i18n DevTools</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: system-ui, -apple-system, sans-serif;\n overflow: hidden;\n height: 100vh;\n }\n #app {\n width: 100%;\n height: 100%;\n }\n .loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #64748b;\n }\n </style>\n</head>\n<body>\n <div id=\"app\">\n <div class=\"loading\">Loading DevTools...</div>\n </div>\n <script type=\"module\">\n // Импортируем devtools UI\n // Пытаемся импортировать через разные пути для совместимости\n let register;\n try {\n const devtoolsModule = await import('/@vite-plugin-i18n-devtools/devtools-ui.js');\n register = devtoolsModule.register;\n } catch (e) {\n // Fallback: пытаемся импортировать напрямую\n try {\n const devtoolsModule = await import('@i18n-micro/devtools-ui');\n register = devtoolsModule.register;\n } catch (e2) {\n console.error('Failed to load devtools UI:', e2);\n document.getElementById('app').innerHTML = '<div class=\"loading\" style=\"color: #ef4444;\">Failed to load DevTools. Please ensure @i18n-micro/devtools-ui is installed.</div>';\n throw e2;\n }\n }\n\n // Регистрируем custom element\n register();\n\n // Создаем bridge через API\n const bridge = {\n async getLocalesAndTranslations() {\n try {\n const response = await fetch('/__i18n_api/files');\n const data = await response.json();\n const result = {};\n for (const file of data.files || []) {\n try {\n const fileResponse = await fetch('/__i18n_api/file?path=' + encodeURIComponent(file));\n const fileData = await fileResponse.json();\n if (fileData.success && fileData.content) {\n result[file] = fileData.content;\n }\n } catch (e) {\n console.warn('Failed to load file:', file, e);\n }\n }\n return result;\n } catch (e) {\n console.error('Failed to get locales:', e);\n return {};\n }\n },\n\n async getConfigs() {\n try {\n const response = await fetch('/__i18n_api/config');\n return await response.json();\n } catch (e) {\n return {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n locales: [],\n translationDir: '${translationDir}',\n };\n }\n },\n\n async saveTranslation(filePath, content) {\n const response = await fetch('/__i18n_api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: filePath, content }),\n });\n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Unknown error' }));\n throw new Error(error.error || 'Failed to save');\n }\n },\n\n onLocalesUpdate(callback) {\n let interval = null;\n interval = setInterval(async () => {\n try {\n const data = await bridge.getLocalesAndTranslations();\n callback(data);\n } catch (e) {\n console.error('Failed to update locales:', e);\n }\n }, 2000);\n return () => {\n if (interval) {\n clearInterval(interval);\n }\n };\n },\n };\n\n const app = document.getElementById('app');\n app.innerHTML = '';\n const element = document.createElement('i18n-devtools-ui');\n element.bridge = bridge;\n element.style.cssText = 'width: 100%; height: 100%; display: block;';\n app.appendChild(element);\n </script>\n</body>\n</html>`\n\n // Middleware для обслуживания HTML страницы devtools\n server.middlewares.use('/__i18n_devtools.html', (_req: IncomingMessage, res: ServerResponse) => {\n res.statusCode = 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(devtoolsHtml)\n })\n\n // Эндпоинт для получения конфигурации\n server.middlewares.use(`${apiBase}/config`, async (_req: IncomingMessage, res: ServerResponse, next) => {\n if (_req.method !== 'GET') {\n return next()\n }\n\n try {\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n defaultLocale: 'en',\n fallbackLocale: 'en',\n locales: [],\n translationDir,\n }))\n }\n catch (e) {\n console.error('[i18n-devtools] Config error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }))\n }\n })\n\n // Эндпоинт для получения списка файлов\n server.middlewares.use(`${apiBase}/files`, async (req: IncomingMessage, res: ServerResponse, next) => {\n if (req.method !== 'GET') {\n return next()\n }\n\n try {\n const localesPath = path.resolve(projectRoot, translationDir)\n\n // Проверяем, что путь безопасен\n safeResolvePath(projectRoot, translationDir)\n\n if (!fs.existsSync(localesPath)) {\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ files: [], structure: {} }))\n return\n }\n\n const files = await scanTranslationFiles(localesPath, localesPath)\n\n // Строим структуру директорий\n const structure: Record<string, unknown> = {}\n for (const file of files) {\n const parts = file.split('/')\n let current = structure\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!current[part]) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n const last = parts[parts.length - 1]\n if (last !== undefined) {\n current[last] = file\n }\n }\n\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ files, structure }))\n }\n catch (e) {\n console.error('[i18n-devtools] Files list error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }))\n }\n })\n\n // Эндпоинт для загрузки конкретного файла\n server.middlewares.use(`${apiBase}/file`, async (req: IncomingMessage, res: ServerResponse, next) => {\n if (req.method !== 'GET') {\n return next()\n }\n\n try {\n const url = new URL(req.url || '', `http://${req.headers.host}`)\n const filePath = url.searchParams.get('path')\n\n if (!filePath) {\n throw new Error('Path parameter is required')\n }\n\n // Резолвим путь относительно translationDir\n const fullPath = path.join(translationDir, filePath)\n const resolvedPath = safeResolvePath(projectRoot, fullPath)\n\n if (!fs.existsSync(resolvedPath)) {\n res.statusCode = 404\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ success: false, error: 'File not found' }))\n return\n }\n\n if (!resolvedPath.endsWith('.json')) {\n throw new Error('Invalid file: only .json files are allowed')\n }\n\n const content = fs.readFileSync(resolvedPath, 'utf-8')\n const parsed = JSON.parse(content)\n\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ success: true, content: parsed, path: filePath }))\n }\n catch (e) {\n console.error('[i18n-devtools] File read error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }))\n }\n })\n\n // Эндпоинт для сохранения файла\n server.middlewares.use(`${apiBase}/save`, async (req: IncomingMessage, res: ServerResponse, next) => {\n if (req.method !== 'POST') {\n return next()\n }\n\n try {\n // Чтение тела запроса\n const buffers: Buffer[] = []\n for await (const chunk of req) {\n buffers.push(chunk)\n }\n const bodyData = Buffer.concat(buffers).toString()\n\n if (!bodyData) {\n throw new Error('Empty request body')\n }\n\n const body = JSON.parse(bodyData)\n const { file, content } = body\n\n if (!file || !content) {\n throw new Error('Invalid data: file and content are required')\n }\n\n // Резолвим путь относительно translationDir, если путь не абсолютный\n const fullPath = file.startsWith(translationDir) ? file : path.join(translationDir, file)\n const filePath = safeResolvePath(projectRoot, fullPath)\n\n // Проверка расширения\n if (!filePath.endsWith('.json')) {\n throw new Error('Invalid file: only .json files are allowed')\n }\n\n // Создаем директорию, если её нет\n const dir = path.dirname(filePath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n // Записываем файл\n fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf-8')\n\n // Успешный ответ\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ success: true }))\n }\n catch (e) {\n console.error('[i18n-devtools] Save error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }))\n }\n })\n },\n } as PluginOption\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.mjs","sources":["../../vite/plugin.ts"],"sourcesContent":["import * as fs from 'node:fs'\nimport { readdir } from 'node:fs/promises'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport * as path from 'node:path'\nimport type { IndexHtmlTransformResult, PluginOption, ViteDevServer } from 'vite'\n\nexport interface DevToolsPluginOptions {\n base?: string\n translationDir?: string\n injectButton?: boolean\n}\n\n// Helper function for safe path resolution\nfunction safeResolvePath(projectRoot: string, filePath: string): string {\n const normalizedFile = filePath.replace(/^\\/+/, '').replace(/\\/+/g, '/')\n const resolvedPath = path.resolve(projectRoot, normalizedFile)\n const normalizedRoot = path.resolve(projectRoot)\n const normalizedFilePath = path.resolve(resolvedPath)\n\n if (!normalizedFilePath.startsWith(normalizedRoot)) {\n throw new Error(`Access denied: Path ${resolvedPath} is outside project root`)\n }\n\n return resolvedPath\n}\n\n// Recursive directory scanning to find JSON files\nasync function scanTranslationFiles(dir: string, baseDir: string): Promise<string[]> {\n const files: string[] = []\n try {\n const entries = await readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n const subFiles = await scanTranslationFiles(fullPath, baseDir)\n files.push(...subFiles)\n } else if (entry.isFile() && entry.name.endsWith('.json')) {\n const relativePath = path.relative(baseDir, fullPath)\n files.push(relativePath.replace(/\\\\/g, '/')) // Normalize for cross-platform compatibility\n }\n }\n } catch (error) {\n // Ignore directory access errors\n console.warn(`[i18n-devtools] Cannot scan directory ${dir}:`, error)\n }\n return files\n}\n\n// Script for button injection\nconst BUTTON_INJECTION_SCRIPT = `\n(function() {\n if (typeof window === 'undefined' || document.getElementById('i18n-devtools-button-container')) {\n return;\n }\n\n const container = document.createElement('div');\n container.id = 'i18n-devtools-button-container';\n container.style.cssText = 'position: fixed; bottom: 20px; right: 20px; z-index: 99999; pointer-events: none;';\n document.body.appendChild(container);\n\n const button = document.createElement('button');\n button.id = 'i18n-devtools-button';\n button.type = 'button';\n button.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"display: inline-block; vertical-align: middle; margin-right: 8px;\"><circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\" /><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" /></svg>i18n';\n button.style.cssText = 'cursor: pointer; background: #1e1e1e; color: white; padding: 8px 16px; border-radius: 9999px; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 1px solid #333; font-size: 14px; font-weight: 600; transition: all 0.3s ease; opacity: 0.3; pointer-events: auto; font-family: system-ui, sans-serif;';\n\n let hideTimeout = null;\n let isVisible = true;\n let lastMouseY = window.innerHeight;\n\n function showButton() {\n if (!isVisible) {\n button.style.transform = 'translateX(0)';\n button.style.opacity = '0.3';\n isVisible = true;\n }\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n hideTimeout = null;\n }\n }\n\n function hideButton() {\n if (isVisible) {\n // Leave only a small part of the button visible (approximately 40px)\n button.style.transform = 'translateX(calc(100% - 40px))';\n button.style.opacity = '0.3';\n isVisible = false;\n }\n }\n\n function checkMouseDistance(e) {\n const buttonRect = button.getBoundingClientRect();\n const buttonCenterX = buttonRect.left + buttonRect.width / 2;\n const buttonCenterY = buttonRect.top + buttonRect.height / 2;\n const distanceX = Math.abs(e.clientX - buttonCenterX);\n const distanceY = Math.abs(e.clientY - buttonCenterY);\n const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);\n\n if (distance > 200 && !button.matches(':hover')) {\n if (!hideTimeout) {\n hideTimeout = setTimeout(hideButton, 1000);\n }\n } else {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n hideTimeout = null;\n }\n if (!isVisible) {\n showButton();\n }\n }\n lastMouseY = e.clientY;\n }\n\n button.addEventListener('mouseenter', function() {\n this.style.opacity = '1';\n this.style.transform = 'translateX(0) translateY(-2px) scale(1.05)';\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n hideTimeout = null;\n }\n // Show the button fully on hover\n if (!isVisible) {\n showButton();\n }\n });\n\n button.addEventListener('mouseleave', function() {\n if (isVisible) {\n this.style.opacity = '0.3';\n this.style.transform = 'translateX(0) scale(1)';\n hideTimeout = setTimeout(hideButton, 3000);\n }\n });\n\n button.addEventListener('click', function() {\n // Check if the editor is already open\n if (document.getElementById('i18n-devtools-modal')) {\n return;\n }\n\n // Open modal window with iframe\n const modal = document.createElement('div');\n modal.id = 'i18n-devtools-modal';\n modal.style.cssText = 'position: fixed; inset: 0; z-index: 999999; background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(2px); display: flex; align-items: center; justify-content: center; padding: 2rem;';\n modal.addEventListener('click', function(e) {\n if (e.target === modal) {\n document.body.removeChild(modal);\n }\n });\n\n const content = document.createElement('div');\n content.style.cssText = 'background: white; width: 90vw; max-width: 1200px; height: 85vh; border-radius: 12px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; overflow: hidden; border: 1px solid #e5e7eb;';\n content.addEventListener('click', function(e) {\n e.stopPropagation();\n });\n\n const header = document.createElement('div');\n header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; flex-shrink: 0;';\n header.innerHTML = '<div style=\"font-weight: 600; color: #334155; display: flex; align-items: center; gap: 8px;\"><svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\" /><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\" /><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" /></svg> i18n DevTools</div><button id=\"i18n-devtools-close\" style=\"background: transparent; border: none; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; color: #64748b; transition: background 0.2s;\">✕</button>';\n\n const closeBtn = header.querySelector('#i18n-devtools-close');\n closeBtn.addEventListener('mouseenter', function() {\n this.style.background = '#e2e8f0';\n });\n closeBtn.addEventListener('mouseleave', function() {\n this.style.background = 'transparent';\n });\n closeBtn.addEventListener('click', function() {\n document.body.removeChild(modal);\n });\n\n const iframeContainer = document.createElement('div');\n iframeContainer.style.cssText = 'flex: 1; position: relative; overflow: hidden; background: white;';\n\n const iframe = document.createElement('iframe');\n iframe.id = 'i18n-devtools-iframe';\n iframe.style.cssText = 'width: 100%; height: 100%; border: none; background: white;';\n iframe.src = '/__i18n_devtools.html';\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');\n\n iframeContainer.appendChild(iframe);\n content.appendChild(header);\n content.appendChild(iframeContainer);\n modal.appendChild(content);\n document.body.appendChild(modal);\n });\n\n document.addEventListener('mousemove', checkMouseDistance);\n\n // Auto-hide after 3 seconds of loading\n setTimeout(() => {\n if (!button.matches(':hover')) {\n hideTimeout = setTimeout(hideButton, 3000);\n }\n }, 3000);\n\n container.appendChild(button);\n})();\n`\n\nexport function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginOption {\n const apiBase = options.base || '/__i18n_api'\n const translationDir = options.translationDir || 'src/locales'\n const injectButton = options.injectButton !== false // Default is true\n\n return {\n name: 'i18n-micro-devtools-plugin',\n apply: 'serve', // Works only in dev server\n\n resolveId(id: string) {\n if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {\n return id\n }\n return null\n },\n\n load(id: string) {\n if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {\n // Return re-export from devtools-ui package\n return `export * from '@i18n-micro/devtools-ui'`\n }\n return null\n },\n\n transformIndexHtml(html: string): IndexHtmlTransformResult {\n if (!injectButton) {\n return html\n }\n\n // Inject script before the closing </body> tag\n const scriptTag = `<script>${BUTTON_INJECTION_SCRIPT}</script>`\n if (html.includes('</body>')) {\n return html.replace('</body>', `${scriptTag}</body>`)\n }\n // If no </body>, append to the end\n return html + scriptTag\n },\n\n configureServer(server: ViteDevServer) {\n // Use server.config.root as the source of truth for the project root\n const projectRoot = server.config.root\n\n // HTML page for iframe with devtools UI\n // Use virtual module for proper imports\n const devtoolsHtml = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>i18n DevTools</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: system-ui, -apple-system, sans-serif;\n overflow: hidden;\n height: 100vh;\n }\n #app {\n width: 100%;\n height: 100%;\n }\n .loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #64748b;\n }\n </style>\n</head>\n<body>\n <div id=\"app\">\n <div class=\"loading\">Loading DevTools...</div>\n </div>\n <script type=\"module\">\n // Import devtools UI\n // Try to import through different paths for compatibility\n let register;\n try {\n const devtoolsModule = await import('/@vite-plugin-i18n-devtools/devtools-ui.js');\n register = devtoolsModule.register;\n } catch (e) {\n // Fallback: try to import directly\n try {\n const devtoolsModule = await import('@i18n-micro/devtools-ui');\n register = devtoolsModule.register;\n } catch (e2) {\n console.error('Failed to load devtools UI:', e2);\n document.getElementById('app').innerHTML = '<div class=\"loading\" style=\"color: #ef4444;\">Failed to load DevTools. Please ensure @i18n-micro/devtools-ui is installed.</div>';\n throw e2;\n }\n }\n\n // Register custom element\n register();\n\n // Create bridge via API\n const bridge = {\n async getLocalesAndTranslations() {\n try {\n const response = await fetch('/__i18n_api/files');\n const data = await response.json();\n const result = {};\n for (const file of data.files || []) {\n try {\n const fileResponse = await fetch('/__i18n_api/file?path=' + encodeURIComponent(file));\n const fileData = await fileResponse.json();\n if (fileData.success && fileData.content) {\n result[file] = fileData.content;\n }\n } catch (e) {\n console.warn('Failed to load file:', file, e);\n }\n }\n return result;\n } catch (e) {\n console.error('Failed to get locales:', e);\n return {};\n }\n },\n\n async getConfigs() {\n try {\n const response = await fetch('/__i18n_api/config');\n return await response.json();\n } catch (e) {\n return {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n locales: [],\n translationDir: '${translationDir}',\n };\n }\n },\n\n async saveTranslation(filePath, content) {\n const response = await fetch('/__i18n_api/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ file: filePath, content }),\n });\n if (!response.ok) {\n const error = await response.json().catch(() => ({ error: 'Unknown error' }));\n throw new Error(error.error || 'Failed to save');\n }\n },\n\n onLocalesUpdate(callback) {\n let interval = null;\n interval = setInterval(async () => {\n try {\n const data = await bridge.getLocalesAndTranslations();\n callback(data);\n } catch (e) {\n console.error('Failed to update locales:', e);\n }\n }, 2000);\n return () => {\n if (interval) {\n clearInterval(interval);\n }\n };\n },\n };\n\n const app = document.getElementById('app');\n app.innerHTML = '';\n const element = document.createElement('i18n-devtools-ui');\n element.bridge = bridge;\n element.style.cssText = 'width: 100%; height: 100%; display: block;';\n app.appendChild(element);\n </script>\n</body>\n</html>`\n\n // Middleware for serving the devtools HTML page\n server.middlewares.use('/__i18n_devtools.html', (_req: IncomingMessage, res: ServerResponse) => {\n res.statusCode = 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(devtoolsHtml)\n })\n\n // Endpoint for getting configuration\n server.middlewares.use(`${apiBase}/config`, async (_req: IncomingMessage, res: ServerResponse, next) => {\n if (_req.method !== 'GET') {\n return next()\n }\n\n try {\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(\n JSON.stringify({\n defaultLocale: 'en',\n fallbackLocale: 'en',\n locales: [],\n translationDir,\n }),\n )\n } catch (e) {\n console.error('[i18n-devtools] Config error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(\n JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }),\n )\n }\n })\n\n // Endpoint for getting file list\n server.middlewares.use(`${apiBase}/files`, async (req: IncomingMessage, res: ServerResponse, next) => {\n if (req.method !== 'GET') {\n return next()\n }\n\n try {\n const localesPath = path.resolve(projectRoot, translationDir)\n\n // Verify the path is safe\n safeResolvePath(projectRoot, translationDir)\n\n if (!fs.existsSync(localesPath)) {\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ files: [], structure: {} }))\n return\n }\n\n const files = await scanTranslationFiles(localesPath, localesPath)\n\n // Build directory structure\n const structure: Record<string, unknown> = {}\n for (const file of files) {\n const parts = file.split('/')\n let current = structure\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!current[part]) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n const last = parts[parts.length - 1]\n if (last !== undefined) {\n current[last] = file\n }\n }\n\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ files, structure }))\n } catch (e) {\n console.error('[i18n-devtools] Files list error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(\n JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }),\n )\n }\n })\n\n // Endpoint for loading a specific file\n server.middlewares.use(`${apiBase}/file`, async (req: IncomingMessage, res: ServerResponse, next) => {\n if (req.method !== 'GET') {\n return next()\n }\n\n try {\n const url = new URL(req.url || '', `http://${req.headers.host}`)\n const filePath = url.searchParams.get('path')\n\n if (!filePath) {\n throw new Error('Path parameter is required')\n }\n\n // Resolve path relative to translationDir\n const fullPath = path.join(translationDir, filePath)\n const resolvedPath = safeResolvePath(projectRoot, fullPath)\n\n if (!fs.existsSync(resolvedPath)) {\n res.statusCode = 404\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ success: false, error: 'File not found' }))\n return\n }\n\n if (!resolvedPath.endsWith('.json')) {\n throw new Error('Invalid file: only .json files are allowed')\n }\n\n const content = fs.readFileSync(resolvedPath, 'utf-8')\n const parsed = JSON.parse(content)\n\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ success: true, content: parsed, path: filePath }))\n } catch (e) {\n console.error('[i18n-devtools] File read error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(\n JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }),\n )\n }\n })\n\n // Endpoint for saving a file\n server.middlewares.use(`${apiBase}/save`, async (req: IncomingMessage, res: ServerResponse, next) => {\n if (req.method !== 'POST') {\n return next()\n }\n\n try {\n // Read request body\n const buffers: Buffer[] = []\n for await (const chunk of req) {\n buffers.push(chunk)\n }\n const bodyData = Buffer.concat(buffers).toString()\n\n if (!bodyData) {\n throw new Error('Empty request body')\n }\n\n const body = JSON.parse(bodyData)\n const { file, content } = body\n\n if (!file || !content) {\n throw new Error('Invalid data: file and content are required')\n }\n\n // Resolve path relative to translationDir if path is not absolute\n const fullPath = file.startsWith(translationDir) ? file : path.join(translationDir, file)\n const filePath = safeResolvePath(projectRoot, fullPath)\n\n // Check extension\n if (!filePath.endsWith('.json')) {\n throw new Error('Invalid file: only .json files are allowed')\n }\n\n // Create directory if it doesn't exist\n const dir = path.dirname(filePath)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n // Write file\n fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf-8')\n\n // Successful response\n res.statusCode = 200\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ success: true }))\n } catch (e) {\n console.error('[i18n-devtools] Save error:', e)\n res.statusCode = 500\n res.setHeader('Content-Type', 'application/json')\n res.end(\n JSON.stringify({\n success: false,\n error: e instanceof Error ? e.message : String(e),\n }),\n )\n }\n })\n },\n } as PluginOption\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-micro/devtools-ui",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.umd.js",
6
6
  "module": "./dist/index.es.js",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "vue": "^3.5.25",
36
- "@i18n-micro/types": "1.1.0"
36
+ "@i18n-micro/types": "1.1.5"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@tailwindcss/vite": "^4.1.17",