@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.
- package/dist/bridge/create-bridge.d.ts +3 -7
- package/dist/components/ui/Loader.vue.d.ts +1 -1
- package/dist/composables/useI18nState.d.ts +17 -9
- package/dist/devtools-ui.css +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.es.js +1297 -1293
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +6 -6
- package/dist/index.umd.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/util/Translator.d.ts +11 -11
- package/dist/vite/plugin.mjs +44 -34
- package/dist/vite/plugin.mjs.map +1 -1
- package/package.json +2 -2
- package/vite/plugin.ts +78 -74
package/vite/plugin.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { PluginOption, ViteDevServer, IndexHtmlTransformResult } from 'vite'
|
|
2
|
-
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
3
1
|
import * as fs from 'node:fs'
|
|
4
|
-
import * as path from 'node:path'
|
|
5
2
|
import { readdir } from 'node:fs/promises'
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
|
+
import * as path from 'node:path'
|
|
5
|
+
import type { IndexHtmlTransformResult, PluginOption, ViteDevServer } from 'vite'
|
|
6
6
|
|
|
7
7
|
export interface DevToolsPluginOptions {
|
|
8
8
|
base?: string
|
|
@@ -10,7 +10,7 @@ export interface DevToolsPluginOptions {
|
|
|
10
10
|
injectButton?: boolean
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// Helper function for safe path resolution
|
|
14
14
|
function safeResolvePath(projectRoot: string, filePath: string): string {
|
|
15
15
|
const normalizedFile = filePath.replace(/^\/+/, '').replace(/\/+/g, '/')
|
|
16
16
|
const resolvedPath = path.resolve(projectRoot, normalizedFile)
|
|
@@ -24,7 +24,7 @@ function safeResolvePath(projectRoot: string, filePath: string): string {
|
|
|
24
24
|
return resolvedPath
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// Recursive directory scanning to find JSON files
|
|
28
28
|
async function scanTranslationFiles(dir: string, baseDir: string): Promise<string[]> {
|
|
29
29
|
const files: string[] = []
|
|
30
30
|
try {
|
|
@@ -34,21 +34,19 @@ async function scanTranslationFiles(dir: string, baseDir: string): Promise<strin
|
|
|
34
34
|
if (entry.isDirectory()) {
|
|
35
35
|
const subFiles = await scanTranslationFiles(fullPath, baseDir)
|
|
36
36
|
files.push(...subFiles)
|
|
37
|
-
}
|
|
38
|
-
else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
37
|
+
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
39
38
|
const relativePath = path.relative(baseDir, fullPath)
|
|
40
|
-
files.push(relativePath.replace(/\\/g, '/')) //
|
|
39
|
+
files.push(relativePath.replace(/\\/g, '/')) // Normalize for cross-platform compatibility
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Игнорируем ошибки доступа к директориям
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Ignore directory access errors
|
|
46
44
|
console.warn(`[i18n-devtools] Cannot scan directory ${dir}:`, error)
|
|
47
45
|
}
|
|
48
46
|
return files
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
//
|
|
49
|
+
// Script for button injection
|
|
52
50
|
const BUTTON_INJECTION_SCRIPT = `
|
|
53
51
|
(function() {
|
|
54
52
|
if (typeof window === 'undefined' || document.getElementById('i18n-devtools-button-container')) {
|
|
@@ -84,7 +82,7 @@ const BUTTON_INJECTION_SCRIPT = `
|
|
|
84
82
|
|
|
85
83
|
function hideButton() {
|
|
86
84
|
if (isVisible) {
|
|
87
|
-
//
|
|
85
|
+
// Leave only a small part of the button visible (approximately 40px)
|
|
88
86
|
button.style.transform = 'translateX(calc(100% - 40px))';
|
|
89
87
|
button.style.opacity = '0.3';
|
|
90
88
|
isVisible = false;
|
|
@@ -122,7 +120,7 @@ const BUTTON_INJECTION_SCRIPT = `
|
|
|
122
120
|
clearTimeout(hideTimeout);
|
|
123
121
|
hideTimeout = null;
|
|
124
122
|
}
|
|
125
|
-
//
|
|
123
|
+
// Show the button fully on hover
|
|
126
124
|
if (!isVisible) {
|
|
127
125
|
showButton();
|
|
128
126
|
}
|
|
@@ -137,12 +135,12 @@ const BUTTON_INJECTION_SCRIPT = `
|
|
|
137
135
|
});
|
|
138
136
|
|
|
139
137
|
button.addEventListener('click', function() {
|
|
140
|
-
//
|
|
138
|
+
// Check if the editor is already open
|
|
141
139
|
if (document.getElementById('i18n-devtools-modal')) {
|
|
142
140
|
return;
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
//
|
|
143
|
+
// Open modal window with iframe
|
|
146
144
|
const modal = document.createElement('div');
|
|
147
145
|
modal.id = 'i18n-devtools-modal';
|
|
148
146
|
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;';
|
|
@@ -191,7 +189,7 @@ const BUTTON_INJECTION_SCRIPT = `
|
|
|
191
189
|
|
|
192
190
|
document.addEventListener('mousemove', checkMouseDistance);
|
|
193
191
|
|
|
194
|
-
//
|
|
192
|
+
// Auto-hide after 3 seconds of loading
|
|
195
193
|
setTimeout(() => {
|
|
196
194
|
if (!button.matches(':hover')) {
|
|
197
195
|
hideTimeout = setTimeout(hideButton, 3000);
|
|
@@ -205,11 +203,11 @@ const BUTTON_INJECTION_SCRIPT = `
|
|
|
205
203
|
export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginOption {
|
|
206
204
|
const apiBase = options.base || '/__i18n_api'
|
|
207
205
|
const translationDir = options.translationDir || 'src/locales'
|
|
208
|
-
const injectButton = options.injectButton !== false //
|
|
206
|
+
const injectButton = options.injectButton !== false // Default is true
|
|
209
207
|
|
|
210
208
|
return {
|
|
211
209
|
name: 'i18n-micro-devtools-plugin',
|
|
212
|
-
apply: 'serve', //
|
|
210
|
+
apply: 'serve', // Works only in dev server
|
|
213
211
|
|
|
214
212
|
resolveId(id: string) {
|
|
215
213
|
if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {
|
|
@@ -220,7 +218,7 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
220
218
|
|
|
221
219
|
load(id: string) {
|
|
222
220
|
if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {
|
|
223
|
-
//
|
|
221
|
+
// Return re-export from devtools-ui package
|
|
224
222
|
return `export * from '@i18n-micro/devtools-ui'`
|
|
225
223
|
}
|
|
226
224
|
return null
|
|
@@ -231,21 +229,21 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
231
229
|
return html
|
|
232
230
|
}
|
|
233
231
|
|
|
234
|
-
//
|
|
232
|
+
// Inject script before the closing </body> tag
|
|
235
233
|
const scriptTag = `<script>${BUTTON_INJECTION_SCRIPT}</script>`
|
|
236
234
|
if (html.includes('</body>')) {
|
|
237
235
|
return html.replace('</body>', `${scriptTag}</body>`)
|
|
238
236
|
}
|
|
239
|
-
//
|
|
237
|
+
// If no </body>, append to the end
|
|
240
238
|
return html + scriptTag
|
|
241
239
|
},
|
|
242
240
|
|
|
243
241
|
configureServer(server: ViteDevServer) {
|
|
244
|
-
//
|
|
242
|
+
// Use server.config.root as the source of truth for the project root
|
|
245
243
|
const projectRoot = server.config.root
|
|
246
244
|
|
|
247
|
-
// HTML
|
|
248
|
-
//
|
|
245
|
+
// HTML page for iframe with devtools UI
|
|
246
|
+
// Use virtual module for proper imports
|
|
249
247
|
const devtoolsHtml = `<!DOCTYPE html>
|
|
250
248
|
<html lang="en">
|
|
251
249
|
<head>
|
|
@@ -281,14 +279,14 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
281
279
|
<div class="loading">Loading DevTools...</div>
|
|
282
280
|
</div>
|
|
283
281
|
<script type="module">
|
|
284
|
-
//
|
|
285
|
-
//
|
|
282
|
+
// Import devtools UI
|
|
283
|
+
// Try to import through different paths for compatibility
|
|
286
284
|
let register;
|
|
287
285
|
try {
|
|
288
286
|
const devtoolsModule = await import('/@vite-plugin-i18n-devtools/devtools-ui.js');
|
|
289
287
|
register = devtoolsModule.register;
|
|
290
288
|
} catch (e) {
|
|
291
|
-
// Fallback:
|
|
289
|
+
// Fallback: try to import directly
|
|
292
290
|
try {
|
|
293
291
|
const devtoolsModule = await import('@i18n-micro/devtools-ui');
|
|
294
292
|
register = devtoolsModule.register;
|
|
@@ -299,10 +297,10 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
299
297
|
}
|
|
300
298
|
}
|
|
301
299
|
|
|
302
|
-
//
|
|
300
|
+
// Register custom element
|
|
303
301
|
register();
|
|
304
302
|
|
|
305
|
-
//
|
|
303
|
+
// Create bridge via API
|
|
306
304
|
const bridge = {
|
|
307
305
|
async getLocalesAndTranslations() {
|
|
308
306
|
try {
|
|
@@ -381,14 +379,14 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
381
379
|
</body>
|
|
382
380
|
</html>`
|
|
383
381
|
|
|
384
|
-
// Middleware
|
|
382
|
+
// Middleware for serving the devtools HTML page
|
|
385
383
|
server.middlewares.use('/__i18n_devtools.html', (_req: IncomingMessage, res: ServerResponse) => {
|
|
386
384
|
res.statusCode = 200
|
|
387
385
|
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
388
386
|
res.end(devtoolsHtml)
|
|
389
387
|
})
|
|
390
388
|
|
|
391
|
-
//
|
|
389
|
+
// Endpoint for getting configuration
|
|
392
390
|
server.middlewares.use(`${apiBase}/config`, async (_req: IncomingMessage, res: ServerResponse, next) => {
|
|
393
391
|
if (_req.method !== 'GET') {
|
|
394
392
|
return next()
|
|
@@ -397,25 +395,28 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
397
395
|
try {
|
|
398
396
|
res.statusCode = 200
|
|
399
397
|
res.setHeader('Content-Type', 'application/json')
|
|
400
|
-
res.end(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
398
|
+
res.end(
|
|
399
|
+
JSON.stringify({
|
|
400
|
+
defaultLocale: 'en',
|
|
401
|
+
fallbackLocale: 'en',
|
|
402
|
+
locales: [],
|
|
403
|
+
translationDir,
|
|
404
|
+
}),
|
|
405
|
+
)
|
|
406
|
+
} catch (e) {
|
|
408
407
|
console.error('[i18n-devtools] Config error:', e)
|
|
409
408
|
res.statusCode = 500
|
|
410
409
|
res.setHeader('Content-Type', 'application/json')
|
|
411
|
-
res.end(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
410
|
+
res.end(
|
|
411
|
+
JSON.stringify({
|
|
412
|
+
success: false,
|
|
413
|
+
error: e instanceof Error ? e.message : String(e),
|
|
414
|
+
}),
|
|
415
|
+
)
|
|
415
416
|
}
|
|
416
417
|
})
|
|
417
418
|
|
|
418
|
-
//
|
|
419
|
+
// Endpoint for getting file list
|
|
419
420
|
server.middlewares.use(`${apiBase}/files`, async (req: IncomingMessage, res: ServerResponse, next) => {
|
|
420
421
|
if (req.method !== 'GET') {
|
|
421
422
|
return next()
|
|
@@ -424,7 +425,7 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
424
425
|
try {
|
|
425
426
|
const localesPath = path.resolve(projectRoot, translationDir)
|
|
426
427
|
|
|
427
|
-
//
|
|
428
|
+
// Verify the path is safe
|
|
428
429
|
safeResolvePath(projectRoot, translationDir)
|
|
429
430
|
|
|
430
431
|
if (!fs.existsSync(localesPath)) {
|
|
@@ -436,7 +437,7 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
436
437
|
|
|
437
438
|
const files = await scanTranslationFiles(localesPath, localesPath)
|
|
438
439
|
|
|
439
|
-
//
|
|
440
|
+
// Build directory structure
|
|
440
441
|
const structure: Record<string, unknown> = {}
|
|
441
442
|
for (const file of files) {
|
|
442
443
|
const parts = file.split('/')
|
|
@@ -457,19 +458,20 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
457
458
|
res.statusCode = 200
|
|
458
459
|
res.setHeader('Content-Type', 'application/json')
|
|
459
460
|
res.end(JSON.stringify({ files, structure }))
|
|
460
|
-
}
|
|
461
|
-
catch (e) {
|
|
461
|
+
} catch (e) {
|
|
462
462
|
console.error('[i18n-devtools] Files list error:', e)
|
|
463
463
|
res.statusCode = 500
|
|
464
464
|
res.setHeader('Content-Type', 'application/json')
|
|
465
|
-
res.end(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
465
|
+
res.end(
|
|
466
|
+
JSON.stringify({
|
|
467
|
+
success: false,
|
|
468
|
+
error: e instanceof Error ? e.message : String(e),
|
|
469
|
+
}),
|
|
470
|
+
)
|
|
469
471
|
}
|
|
470
472
|
})
|
|
471
473
|
|
|
472
|
-
//
|
|
474
|
+
// Endpoint for loading a specific file
|
|
473
475
|
server.middlewares.use(`${apiBase}/file`, async (req: IncomingMessage, res: ServerResponse, next) => {
|
|
474
476
|
if (req.method !== 'GET') {
|
|
475
477
|
return next()
|
|
@@ -483,7 +485,7 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
483
485
|
throw new Error('Path parameter is required')
|
|
484
486
|
}
|
|
485
487
|
|
|
486
|
-
//
|
|
488
|
+
// Resolve path relative to translationDir
|
|
487
489
|
const fullPath = path.join(translationDir, filePath)
|
|
488
490
|
const resolvedPath = safeResolvePath(projectRoot, fullPath)
|
|
489
491
|
|
|
@@ -504,26 +506,27 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
504
506
|
res.statusCode = 200
|
|
505
507
|
res.setHeader('Content-Type', 'application/json')
|
|
506
508
|
res.end(JSON.stringify({ success: true, content: parsed, path: filePath }))
|
|
507
|
-
}
|
|
508
|
-
catch (e) {
|
|
509
|
+
} catch (e) {
|
|
509
510
|
console.error('[i18n-devtools] File read error:', e)
|
|
510
511
|
res.statusCode = 500
|
|
511
512
|
res.setHeader('Content-Type', 'application/json')
|
|
512
|
-
res.end(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
513
|
+
res.end(
|
|
514
|
+
JSON.stringify({
|
|
515
|
+
success: false,
|
|
516
|
+
error: e instanceof Error ? e.message : String(e),
|
|
517
|
+
}),
|
|
518
|
+
)
|
|
516
519
|
}
|
|
517
520
|
})
|
|
518
521
|
|
|
519
|
-
//
|
|
522
|
+
// Endpoint for saving a file
|
|
520
523
|
server.middlewares.use(`${apiBase}/save`, async (req: IncomingMessage, res: ServerResponse, next) => {
|
|
521
524
|
if (req.method !== 'POST') {
|
|
522
525
|
return next()
|
|
523
526
|
}
|
|
524
527
|
|
|
525
528
|
try {
|
|
526
|
-
//
|
|
529
|
+
// Read request body
|
|
527
530
|
const buffers: Buffer[] = []
|
|
528
531
|
for await (const chunk of req) {
|
|
529
532
|
buffers.push(chunk)
|
|
@@ -541,37 +544,38 @@ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginO
|
|
|
541
544
|
throw new Error('Invalid data: file and content are required')
|
|
542
545
|
}
|
|
543
546
|
|
|
544
|
-
//
|
|
547
|
+
// Resolve path relative to translationDir if path is not absolute
|
|
545
548
|
const fullPath = file.startsWith(translationDir) ? file : path.join(translationDir, file)
|
|
546
549
|
const filePath = safeResolvePath(projectRoot, fullPath)
|
|
547
550
|
|
|
548
|
-
//
|
|
551
|
+
// Check extension
|
|
549
552
|
if (!filePath.endsWith('.json')) {
|
|
550
553
|
throw new Error('Invalid file: only .json files are allowed')
|
|
551
554
|
}
|
|
552
555
|
|
|
553
|
-
//
|
|
556
|
+
// Create directory if it doesn't exist
|
|
554
557
|
const dir = path.dirname(filePath)
|
|
555
558
|
if (!fs.existsSync(dir)) {
|
|
556
559
|
fs.mkdirSync(dir, { recursive: true })
|
|
557
560
|
}
|
|
558
561
|
|
|
559
|
-
//
|
|
562
|
+
// Write file
|
|
560
563
|
fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf-8')
|
|
561
564
|
|
|
562
|
-
//
|
|
565
|
+
// Successful response
|
|
563
566
|
res.statusCode = 200
|
|
564
567
|
res.setHeader('Content-Type', 'application/json')
|
|
565
568
|
res.end(JSON.stringify({ success: true }))
|
|
566
|
-
}
|
|
567
|
-
catch (e) {
|
|
569
|
+
} catch (e) {
|
|
568
570
|
console.error('[i18n-devtools] Save error:', e)
|
|
569
571
|
res.statusCode = 500
|
|
570
572
|
res.setHeader('Content-Type', 'application/json')
|
|
571
|
-
res.end(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
573
|
+
res.end(
|
|
574
|
+
JSON.stringify({
|
|
575
|
+
success: false,
|
|
576
|
+
error: e instanceof Error ? e.message : String(e),
|
|
577
|
+
}),
|
|
578
|
+
)
|
|
575
579
|
}
|
|
576
580
|
})
|
|
577
581
|
},
|