ai-localize-reporting 2.0.0 → 2.0.3

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.
@@ -0,0 +1,831 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ai-localize Dashboard &mdash; Preview</title>
7
+ <style>
8
+ /* ── Reset & Variables ────────────────────────────────────────────────────── */
9
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
+
11
+ :root {
12
+ --sidebar-w: 240px;
13
+ --accent: #6366f1;
14
+ --accent-hover: #4f46e5;
15
+ --ok: #22c55e;
16
+ --warn: #f59e0b;
17
+ --err: #ef4444;
18
+ --info: #3b82f6;
19
+ --neutral: #6b7280;
20
+
21
+ --bg: #f8fafc;
22
+ --bg-surface: #ffffff;
23
+ --bg-elevated: #f1f5f9;
24
+ --border: #e2e8f0;
25
+ --border-subtle: #f1f5f9;
26
+ --text: #0f172a;
27
+ --text-muted: #64748b;
28
+ --text-subtle: #94a3b8;
29
+ --shadow-sm: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
30
+ --shadow-md: 0 4px 12px rgba(0,0,0,.08), 0 2px 4px rgba(0,0,0,.04);
31
+ --shadow-lg: 0 8px 24px rgba(0,0,0,.12);
32
+ --radius: 10px;
33
+ --radius-sm: 6px;
34
+ --radius-lg: 14px;
35
+ --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif;
36
+ --font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", ui-monospace, monospace;
37
+ --transition: 0.18s ease;
38
+ }
39
+
40
+ [data-theme="dark"] {
41
+ --bg: #0c0e14;
42
+ --bg-surface: #161b27;
43
+ --bg-elevated: #1e2535;
44
+ --border: #2d3748;
45
+ --border-subtle: #1e2535;
46
+ --text: #f1f5f9;
47
+ --text-muted: #94a3b8;
48
+ --text-subtle: #64748b;
49
+ --shadow-sm: 0 1px 3px rgba(0,0,0,.3);
50
+ --shadow-md: 0 4px 12px rgba(0,0,0,.4);
51
+ --shadow-lg: 0 8px 24px rgba(0,0,0,.5);
52
+ --accent: #818cf8;
53
+ --accent-hover: #a5b4fc;
54
+ }
55
+
56
+ html { scroll-behavior: smooth; }
57
+ body {
58
+ font-family: var(--font);
59
+ background: var(--bg);
60
+ color: var(--text);
61
+ line-height: 1.6;
62
+ font-size: 14px;
63
+ display: flex;
64
+ min-height: 100vh;
65
+ }
66
+
67
+ /* ── Sidebar ─────────────────────────────────────────────────────────────── */
68
+ .sidebar {
69
+ position: fixed;
70
+ top: 0; left: 0; bottom: 0;
71
+ width: var(--sidebar-w);
72
+ background: var(--bg-surface);
73
+ border-right: 1px solid var(--border);
74
+ display: flex;
75
+ flex-direction: column;
76
+ overflow-y: auto;
77
+ overflow-x: hidden;
78
+ z-index: 100;
79
+ transition: width var(--transition);
80
+ }
81
+ .sidebar-collapsed { width: 52px; }
82
+ .sidebar-collapsed .sidebar-brand,
83
+ .sidebar-collapsed .sidebar-meta,
84
+ .sidebar-collapsed .nav-label,
85
+ .sidebar-collapsed .nav-count,
86
+ .sidebar-collapsed .nav-group-label { display: none; }
87
+ .sidebar-collapsed .nav-item { justify-content:center;padding:10px; }
88
+ .sidebar-collapsed .sidebar-logo { display:none; }
89
+ .sidebar-collapsed .sidebar-header { justify-content:center;padding:10px 6px; }
90
+ .sidebar-collapsed .sidebar-toggle { display:flex !important;align-items:center;justify-content:center;width:36px;height:36px;border-radius:6px;background:var(--bg-elevated);border:1px solid var(--border);color:var(--accent) !important;font-size:18px;margin:0 auto; }
91
+
92
+ .sidebar-header {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 10px;
96
+ padding: 18px 16px 12px;
97
+ border-bottom: 1px solid var(--border);
98
+ flex-shrink: 0;
99
+ }
100
+ .sidebar-logo { font-size: 22px; flex-shrink: 0; }
101
+ .sidebar-brand { font-weight: 700; font-size: 15px; color: var(--accent); flex: 1; }
102
+ .sidebar-toggle {
103
+ background: none; border: none; cursor: pointer;
104
+ font-size: 18px; color: var(--text-muted); padding: 2px 4px;
105
+ border-radius: var(--radius-sm);
106
+ transition: color var(--transition);
107
+ }
108
+ .sidebar-toggle:hover { color: var(--accent); }
109
+
110
+ .sidebar-meta {
111
+ padding: 10px 16px;
112
+ border-bottom: 1px solid var(--border);
113
+ flex-shrink: 0;
114
+ }
115
+ .sidebar-framework { margin-bottom: 4px; }
116
+ .sidebar-date { font-size: 11px; color: var(--text-subtle); }
117
+
118
+ .nav-group { padding: 8px 0; border-bottom: 1px solid var(--border-subtle); }
119
+ .nav-group-label {
120
+ padding: 6px 16px 3px;
121
+ font-size: 10px;
122
+ font-weight: 700;
123
+ letter-spacing: .08em;
124
+ text-transform: uppercase;
125
+ color: var(--text-subtle);
126
+ }
127
+ .nav-item {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 10px;
131
+ padding: 8px 16px;
132
+ text-decoration: none;
133
+ color: var(--text-muted);
134
+ font-size: 13px;
135
+ font-weight: 500;
136
+ border-radius: 0;
137
+ border: none;
138
+ background: none;
139
+ cursor: pointer;
140
+ width: 100%;
141
+ transition: background var(--transition), color var(--transition);
142
+ }
143
+ .nav-item:hover { background: var(--bg-elevated); color: var(--text); }
144
+ .nav-active { background: var(--bg-elevated) !important; color: var(--accent) !important; font-weight: 600; border-left: 3px solid var(--accent); }
145
+ .nav-alert { color: var(--warn) !important; }
146
+ .nav-icon { font-size: 16px; flex-shrink: 0; width: 20px; text-align: center; }
147
+ .nav-label { flex: 1; }
148
+ .nav-count {
149
+ font-size: 11px; font-weight: 700;
150
+ background: var(--bg-elevated);
151
+ padding: 1px 7px; border-radius: 10px;
152
+ min-width: 24px; text-align: center;
153
+ color: var(--text-muted);
154
+ }
155
+ .nav-btn { font-size: 12px; }
156
+
157
+ /* ── Main content ────────────────────────────────────────────────────────── */
158
+ .main {
159
+ margin-left: var(--sidebar-w);
160
+ flex: 1;
161
+ min-width: 0;
162
+ padding: 24px 28px 48px;
163
+ transition: margin-left var(--transition);
164
+ }
165
+ .main-expanded { margin-left: 52px; }
166
+
167
+ /* ── Theme toggle ────────────────────────────────────────────────────────── */
168
+ .theme-toggle {
169
+ position: fixed;
170
+ top: 14px; right: 14px;
171
+ z-index: 200;
172
+ background: var(--bg-surface);
173
+ border: 1px solid var(--border);
174
+ border-radius: 50%;
175
+ width: 38px; height: 38px;
176
+ display: flex; align-items: center; justify-content: center;
177
+ font-size: 18px;
178
+ cursor: pointer;
179
+ box-shadow: var(--shadow-md);
180
+ transition: box-shadow var(--transition);
181
+ }
182
+ .theme-toggle:hover { box-shadow: var(--shadow-lg); }
183
+
184
+ /* ── Page header ─────────────────────────────────────────────────────────── */
185
+ .page-header {
186
+ display: flex;
187
+ align-items: flex-start;
188
+ justify-content: space-between;
189
+ margin-bottom: 28px;
190
+ gap: 16px;
191
+ flex-wrap: wrap;
192
+ }
193
+ .page-title {
194
+ font-size: 22px;
195
+ font-weight: 700;
196
+ color: var(--text);
197
+ margin-bottom: 8px;
198
+ letter-spacing: -.02em;
199
+ }
200
+ .page-meta {
201
+ display: flex;
202
+ flex-wrap: wrap;
203
+ gap: 8px;
204
+ align-items: center;
205
+ }
206
+ .meta-chip { display: inline-flex; align-items: center; }
207
+ .meta-item {
208
+ font-size: 12px;
209
+ color: var(--text-muted);
210
+ background: var(--bg-elevated);
211
+ padding: 3px 10px;
212
+ border-radius: 20px;
213
+ border: 1px solid var(--border);
214
+ }
215
+ .page-header-right { display: flex; gap: 8px; flex-shrink: 0; }
216
+
217
+ /* ── Sections ────────────────────────────────────────────────────────────── */
218
+ .section { margin-bottom: 32px; }
219
+ .section-title-row {
220
+ display: flex; align-items: center; justify-content: space-between;
221
+ margin-bottom: 14px;
222
+ }
223
+ .section-title {
224
+ font-size: 17px;
225
+ font-weight: 700;
226
+ color: var(--text);
227
+ display: flex; align-items: center; gap: 10px;
228
+ }
229
+ .section-count {
230
+ font-size: 12px;
231
+ font-weight: 700;
232
+ padding: 2px 10px;
233
+ border-radius: 20px;
234
+ }
235
+ .count-ok { background: #dcfce7; color: #15803d; }
236
+ .count-warn { background: #fef3c7; color: #92400e; }
237
+ .count-err { background: #fee2e2; color: #991b1b; }
238
+ .count-neutral { background: var(--bg-elevated); color: var(--text-muted); }
239
+ [data-theme="dark"] .count-ok { background: #14532d; color: #86efac; }
240
+ [data-theme="dark"] .count-warn { background: #451a03; color: #fcd34d; }
241
+ [data-theme="dark"] .count-err { background: #450a0a; color: #fca5a5; }
242
+
243
+ /* ── Stat cards ──────────────────────────────────────────────────────────── */
244
+ .stats-grid {
245
+ display: grid;
246
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
247
+ gap: 14px;
248
+ margin-bottom: 20px;
249
+ }
250
+ .stat-card {
251
+ background: var(--bg-surface);
252
+ border: 1px solid var(--border);
253
+ border-radius: var(--radius-lg);
254
+ padding: 18px 16px 14px;
255
+ box-shadow: var(--shadow-sm);
256
+ position: relative;
257
+ overflow: hidden;
258
+ transition: box-shadow var(--transition), transform var(--transition);
259
+ }
260
+ .stat-card:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
261
+ .stat-card::before {
262
+ content: '';
263
+ position: absolute;
264
+ top: 0; left: 0; right: 0;
265
+ height: 3px;
266
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
267
+ }
268
+ .stat-ok::before { background: var(--ok); }
269
+ .stat-warn::before { background: var(--warn); }
270
+ .stat-err::before { background: var(--err); }
271
+ .stat-info::before { background: var(--info); }
272
+ .stat-neutral::before { background: var(--neutral); }
273
+
274
+ .stat-icon { font-size: 22px; margin-bottom: 8px; }
275
+ .stat-value {
276
+ font-size: 28px;
277
+ font-weight: 800;
278
+ line-height: 1;
279
+ margin-bottom: 4px;
280
+ color: var(--text);
281
+ letter-spacing: -.03em;
282
+ }
283
+ .stat-ok .stat-value { color: var(--ok); }
284
+ .stat-warn .stat-value { color: var(--warn); }
285
+ .stat-err .stat-value { color: var(--err); }
286
+ .stat-info .stat-value { color: var(--info); }
287
+ .stat-neutral .stat-value { color: var(--neutral); }
288
+
289
+ .stat-label {
290
+ font-size: 12px;
291
+ font-weight: 700;
292
+ color: var(--text);
293
+ text-transform: uppercase;
294
+ letter-spacing: .04em;
295
+ margin-bottom: 3px;
296
+ }
297
+ .stat-hint { font-size: 11px; color: var(--text-subtle); }
298
+
299
+ /* ── Asset summary row ───────────────────────────────────────────────────── */
300
+ .asset-summary-row {
301
+ display: grid;
302
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
303
+ gap: 12px;
304
+ margin-bottom: 16px;
305
+ }
306
+
307
+ /* ── Accordion ───────────────────────────────────────────────────────────── */
308
+ .accordion {
309
+ background: var(--bg-surface);
310
+ border: 1px solid var(--border);
311
+ border-radius: var(--radius);
312
+ overflow: hidden;
313
+ box-shadow: var(--shadow-sm);
314
+ margin-bottom: 8px;
315
+ }
316
+ .accordion-trigger {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 12px;
320
+ width: 100%;
321
+ padding: 14px 18px;
322
+ background: none;
323
+ border: none;
324
+ cursor: pointer;
325
+ text-align: left;
326
+ transition: background var(--transition);
327
+ }
328
+ .accordion-trigger:hover { background: var(--bg-elevated); }
329
+ .accordion-icon { font-size: 12px; flex-shrink: 0; }
330
+ .severity-ok { color: var(--ok); }
331
+ .severity-warn { color: var(--warn); }
332
+ .severity-err { color: var(--err); }
333
+ .severity-info { color: var(--info); }
334
+ .accordion-title {
335
+ font-size: 14px;
336
+ font-weight: 600;
337
+ color: var(--text);
338
+ flex: 1;
339
+ }
340
+ .accordion-subtitle { font-size: 12px; color: var(--text-muted); margin-right: 12px; }
341
+ .accordion-chevron {
342
+ font-size: 18px;
343
+ color: var(--text-muted);
344
+ transition: transform var(--transition);
345
+ }
346
+ .accordion-open .accordion-chevron { transform: rotate(180deg); }
347
+ .accordion-panel { border-top: 1px solid var(--border); }
348
+ .accordion-body { padding: 18px; }
349
+
350
+ /* ── Charts ──────────────────────────────────────────────────────────────── */
351
+ .charts-grid {
352
+ display: grid;
353
+ grid-template-columns: 180px 1fr 1fr;
354
+ gap: 16px;
355
+ margin-bottom: 4px;
356
+ }
357
+ @media (max-width: 900px) { .charts-grid { grid-template-columns: 1fr; } }
358
+
359
+ .chart-card {
360
+ background: var(--bg-surface);
361
+ border: 1px solid var(--border);
362
+ border-radius: var(--radius);
363
+ padding: 18px;
364
+ box-shadow: var(--shadow-sm);
365
+ }
366
+ .chart-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 14px; }
367
+ .chart-body { min-height: 80px; }
368
+ .chart-donut-wrap { display: flex; justify-content: center; align-items: center; }
369
+ .chart-caption { font-size: 11px; color: var(--text-muted); margin-top: 10px; text-align: center; }
370
+ .donut-chart { width: 120px; height: 120px; }
371
+
372
+ .bar-chart { display: flex; flex-direction: column; gap: 6px; }
373
+ .bar-row { display: flex; align-items: center; gap: 8px; }
374
+ .bar-label { font-size: 11px; color: var(--text-muted); width: 120px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
375
+ .bar-track { flex: 1; height: 8px; background: var(--bg-elevated); border-radius: 4px; overflow: hidden; }
376
+ .bar-fill { height: 100%; background: var(--accent); border-radius: 4px; min-width: 2px; transition: width 0.5s ease; }
377
+ .bar-value { font-size: 11px; font-weight: 700; color: var(--text-muted); width: 36px; text-align: right; flex-shrink: 0; }
378
+
379
+ /* ── Info banners ────────────────────────────────────────────────────────── */
380
+ .info-banner {
381
+ border-radius: var(--radius);
382
+ padding: 14px 18px;
383
+ font-size: 13px;
384
+ margin-bottom: 16px;
385
+ border-left: 4px solid;
386
+ }
387
+ .info-banner ul { margin: 8px 0 0 18px; }
388
+ .info-banner li { margin-bottom: 4px; }
389
+ .info-banner-blue { background: #eff6ff; border-color: var(--info); color: #1e40af; }
390
+ .info-banner-green { background: #f0fdf4; border-color: var(--ok); color: #166534; }
391
+ [data-theme="dark"] .info-banner-blue { background: #1e3a5f; color: #bfdbfe; }
392
+ [data-theme="dark"] .info-banner-green { background: #14532d; color: #bbf7d0; }
393
+
394
+ /* ── Insight legend ──────────────────────────────────────────────────────── */
395
+ .insight-legend {
396
+ font-size: 13px;
397
+ color: var(--text-muted);
398
+ background: var(--bg-elevated);
399
+ border-left: 3px solid var(--accent);
400
+ padding: 10px 14px;
401
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
402
+ margin-bottom: 14px;
403
+ }
404
+ .insight-legend code {
405
+ background: var(--bg-surface);
406
+ border: 1px solid var(--border);
407
+ padding: 1px 6px;
408
+ border-radius: 4px;
409
+ font-family: var(--font-mono);
410
+ font-size: 12px;
411
+ }
412
+
413
+ /* ── Chip filter row ─────────────────────────────────────────────────────── */
414
+ .chip-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; align-items: center; font-size: 12px; color: var(--text-muted); }
415
+ .chip {
416
+ display: inline-flex; align-items: center; gap: 4px;
417
+ padding: 3px 10px; border-radius: 14px;
418
+ font-size: 12px; font-weight: 600;
419
+ cursor: pointer; border: 1px solid transparent;
420
+ transition: box-shadow var(--transition);
421
+ }
422
+ .chip:hover { box-shadow: 0 0 0 2px var(--accent); }
423
+ .chip-red { background: #fee2e2; color: #991b1b; }
424
+ [data-theme="dark"] .chip-red { background: #450a0a; color: #fca5a5; }
425
+
426
+ /* ── Tables ──────────────────────────────────────────────────────────────── */
427
+ .table-wrapper {
428
+ background: var(--bg-surface);
429
+ border: 1px solid var(--border);
430
+ border-radius: var(--radius);
431
+ overflow: hidden;
432
+ box-shadow: var(--shadow-sm);
433
+ }
434
+ .table-controls {
435
+ display: flex;
436
+ align-items: center;
437
+ gap: 10px;
438
+ padding: 12px 16px;
439
+ border-bottom: 1px solid var(--border);
440
+ flex-wrap: wrap;
441
+ }
442
+ .search-box {
443
+ display: flex;
444
+ align-items: center;
445
+ gap: 6px;
446
+ background: var(--bg-elevated);
447
+ border: 1px solid var(--border);
448
+ border-radius: var(--radius-sm);
449
+ padding: 5px 10px;
450
+ flex: 1;
451
+ min-width: 180px;
452
+ max-width: 320px;
453
+ }
454
+ .search-icon { font-size: 13px; color: var(--text-subtle); flex-shrink: 0; }
455
+ .table-search {
456
+ border: none;
457
+ background: transparent;
458
+ outline: none;
459
+ font-size: 13px;
460
+ color: var(--text);
461
+ width: 100%;
462
+ font-family: var(--font);
463
+ }
464
+ .table-meta { font-size: 12px; color: var(--text-muted); flex: 1; min-width: 80px; }
465
+ .table-actions { display: flex; gap: 6px; }
466
+ .table-scroll { overflow-x: auto; }
467
+
468
+ table {
469
+ width: 100%;
470
+ border-collapse: collapse;
471
+ font-size: 13px;
472
+ }
473
+ thead th {
474
+ background: var(--bg-elevated);
475
+ color: var(--text-muted);
476
+ padding: 10px 14px;
477
+ text-align: left;
478
+ font-size: 11px;
479
+ font-weight: 700;
480
+ letter-spacing: .04em;
481
+ text-transform: uppercase;
482
+ border-bottom: 1px solid var(--border);
483
+ white-space: nowrap;
484
+ position: sticky;
485
+ top: 0;
486
+ z-index: 1;
487
+ }
488
+ th.sortable { cursor: pointer; user-select: none; }
489
+ th.sortable:hover { background: var(--border); }
490
+ .sort-icon { font-size: 11px; opacity: .5; }
491
+ tbody td {
492
+ padding: 9px 14px;
493
+ border-bottom: 1px solid var(--border-subtle);
494
+ vertical-align: top;
495
+ color: var(--text);
496
+ }
497
+ tbody tr:last-child td { border-bottom: none; }
498
+ tbody tr:hover td { background: var(--bg-elevated); }
499
+
500
+ .table-pagination {
501
+ display: flex;
502
+ align-items: center;
503
+ gap: 4px;
504
+ padding: 10px 16px;
505
+ border-top: 1px solid var(--border);
506
+ flex-wrap: wrap;
507
+ }
508
+ .pag-btn {
509
+ background: var(--bg-elevated);
510
+ border: 1px solid var(--border);
511
+ border-radius: var(--radius-sm);
512
+ padding: 4px 9px;
513
+ font-size: 12px;
514
+ cursor: pointer;
515
+ color: var(--text);
516
+ transition: background var(--transition);
517
+ }
518
+ .pag-btn:hover:not(.pag-disabled) { background: var(--accent); color: white; border-color: var(--accent); }
519
+ .pag-active { background: var(--accent) !important; color: white !important; border-color: var(--accent) !important; font-weight: 700; }
520
+ .pag-disabled { opacity: .4; cursor: not-allowed; }
521
+ .pag-info { font-size: 11px; color: var(--text-muted); margin-left: 6px; }
522
+
523
+ /* ── Code & path styles ──────────────────────────────────────────────────── */
524
+ .path-code {
525
+ font-family: var(--font-mono);
526
+ font-size: 11px;
527
+ color: var(--text-muted);
528
+ background: var(--bg-elevated);
529
+ padding: 1px 6px;
530
+ border-radius: 4px;
531
+ word-break: break-all;
532
+ }
533
+ .key-code {
534
+ font-family: var(--font-mono);
535
+ font-size: 11px;
536
+ color: var(--accent);
537
+ background: var(--bg-elevated);
538
+ padding: 1px 6px;
539
+ border-radius: 4px;
540
+ word-break: break-all;
541
+ }
542
+ .key-list { display: flex; flex-wrap: wrap; gap: 4px; }
543
+ .text-preview { font-size: 12px; word-break: break-word; max-width: 260px; display: inline-block; }
544
+ .line-num {
545
+ font-family: var(--font-mono);
546
+ font-size: 12px;
547
+ color: var(--text-subtle);
548
+ background: var(--bg-elevated);
549
+ padding: 1px 6px;
550
+ border-radius: 4px;
551
+ }
552
+ .file-size { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
553
+ .detail-text { font-size: 12px; color: var(--text-muted); }
554
+ .muted { color: var(--text-subtle); font-size: 12px; }
555
+ .cdn-link { font-size: 11px; color: var(--accent); text-decoration: none; }
556
+ .cdn-link:hover { text-decoration: underline; }
557
+
558
+ /* ── Badges ──────────────────────────────────────────────────────────────── */
559
+ .badge {
560
+ display: inline-flex;
561
+ align-items: center;
562
+ padding: 2px 8px;
563
+ border-radius: 12px;
564
+ font-size: 11px;
565
+ font-weight: 600;
566
+ white-space: nowrap;
567
+ line-height: 1.5;
568
+ }
569
+ .badge-blue { background: #dbeafe; color: #1d4ed8; }
570
+ .badge-red { background: #fee2e2; color: #b91c1c; }
571
+ .badge-green { background: #dcfce7; color: #15803d; }
572
+ .badge-orange { background: #ffedd5; color: #c2410c; }
573
+ .badge-grey { background: var(--bg-elevated); color: var(--text-muted); border: 1px solid var(--border); }
574
+ .badge-purple { background: #ede9fe; color: #6d28d9; }
575
+ .badge-yellow { background: #fef9c3; color: #a16207; }
576
+ .badge-teal { background: #ccfbf1; color: #0f766e; }
577
+ [data-theme="dark"] .badge-blue { background: #1e3a5f; color: #93c5fd; }
578
+ [data-theme="dark"] .badge-red { background: #450a0a; color: #fca5a5; }
579
+ [data-theme="dark"] .badge-green { background: #14532d; color: #86efac; }
580
+ [data-theme="dark"] .badge-orange { background: #431407; color: #fdba74; }
581
+ [data-theme="dark"] .badge-purple { background: #2e1065; color: #c4b5fd; }
582
+ [data-theme="dark"] .badge-yellow { background: #422006; color: #fde047; }
583
+ [data-theme="dark"] .badge-teal { background: #042f2e; color: #5eead4; }
584
+
585
+ /* ── Buttons ─────────────────────────────────────────────────────────────── */
586
+ .btn {
587
+ display: inline-flex;
588
+ align-items: center;
589
+ gap: 5px;
590
+ padding: 7px 14px;
591
+ font-size: 13px;
592
+ font-weight: 600;
593
+ border-radius: var(--radius-sm);
594
+ border: 1px solid var(--border);
595
+ background: var(--bg-surface);
596
+ color: var(--text);
597
+ cursor: pointer;
598
+ transition: background var(--transition), box-shadow var(--transition);
599
+ font-family: var(--font);
600
+ white-space: nowrap;
601
+ }
602
+ .btn:hover { background: var(--bg-elevated); box-shadow: var(--shadow-sm); }
603
+ .btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
604
+ .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
605
+ .btn-sm { padding: 4px 10px; font-size: 11px; }
606
+
607
+ /* ── AI Insights ─────────────────────────────────────────────────────────── */
608
+ .insight-banner {
609
+ background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
610
+ border: 1px solid #c4b5fd;
611
+ border-radius: var(--radius);
612
+ padding: 12px 18px;
613
+ font-size: 13px;
614
+ color: #5b21b6;
615
+ margin-bottom: 16px;
616
+ }
617
+ [data-theme="dark"] .insight-banner { background: linear-gradient(135deg, #1e1b4b 0%, #2e1065 100%); color: #c4b5fd; border-color: #4c1d95; }
618
+ .insights-grid {
619
+ display: grid;
620
+ grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
621
+ gap: 16px;
622
+ }
623
+ .insight-card {
624
+ background: var(--bg-surface);
625
+ border: 1px solid var(--border);
626
+ border-radius: var(--radius);
627
+ padding: 18px;
628
+ box-shadow: var(--shadow-sm);
629
+ }
630
+ .insight-heading {
631
+ font-size: 14px;
632
+ font-weight: 700;
633
+ color: var(--text);
634
+ margin-bottom: 4px;
635
+ }
636
+ .insight-count { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; }
637
+ .insight-item { font-size: 13px; padding: 8px 0; }
638
+ .insight-ok { color: var(--ok); font-weight: 500; }
639
+ .insight-list { padding-left: 18px; list-style: disc; }
640
+ .insight-list li { margin-bottom: 6px; font-size: 13px; color: var(--text-muted); }
641
+ .insight-ns {
642
+ font-family: var(--font-mono);
643
+ font-size: 12px;
644
+ color: var(--accent);
645
+ background: var(--bg-elevated);
646
+ padding: 1px 6px;
647
+ border-radius: 4px;
648
+ margin-right: 6px;
649
+ }
650
+
651
+ /* ── Empty state ─────────────────────────────────────────────────────────── */
652
+ .empty-state-card {
653
+ background: var(--bg-elevated);
654
+ border: 1px solid var(--border);
655
+ border-radius: var(--radius);
656
+ padding: 20px 24px;
657
+ font-size: 13px;
658
+ color: var(--text-muted);
659
+ text-align: center;
660
+ }
661
+ .empty-state-card code {
662
+ font-family: var(--font-mono);
663
+ background: var(--bg-surface);
664
+ border: 1px solid var(--border);
665
+ padding: 1px 6px;
666
+ border-radius: 4px;
667
+ font-size: 12px;
668
+ }
669
+
670
+ /* ── Overflow notice ─────────────────────────────────────────────────────── */
671
+ .overflow-notice {
672
+ font-size: 12px;
673
+ color: var(--text-subtle);
674
+ text-align: center;
675
+ padding: 8px;
676
+ background: var(--bg-elevated);
677
+ border-top: 1px solid var(--border);
678
+ border-radius: 0 0 var(--radius) var(--radius);
679
+ font-style: italic;
680
+ }
681
+
682
+ /* ── Footer ──────────────────────────────────────────────────────────────── */
683
+ .page-footer {
684
+ margin-top: 48px;
685
+ padding-top: 18px;
686
+ border-top: 1px solid var(--border);
687
+ font-size: 12px;
688
+ color: var(--text-subtle);
689
+ text-align: center;
690
+ }
691
+
692
+ /* ── Print ───────────────────────────────────────────────────────────────── */
693
+ @media print {
694
+ .sidebar, .theme-toggle, .table-controls, .table-pagination, .page-header-right { display: none !important; }
695
+ .main { margin-left: 0 !important; padding: 16px; }
696
+ .accordion-panel { display: block !important; }
697
+ .accordion-panel[hidden] { display: block !important; }
698
+ .stat-card, .chart-card, .accordion, .table-wrapper { break-inside: avoid; }
699
+ body { background: white; color: black; }
700
+ }
701
+
702
+ /* ── Responsive ──────────────────────────────────────────────────────────── */
703
+ @media (max-width: 768px) {
704
+ body { flex-direction: column; }
705
+ .sidebar { position: relative; width: 100%; height: auto; flex-direction: row; flex-wrap: wrap; overflow: visible; border-right: none; border-bottom: 1px solid var(--border); }
706
+ .main { margin-left: 0; padding: 16px; }
707
+ .stats-grid { grid-template-columns: repeat(2, 1fr); }
708
+ .insights-grid { grid-template-columns: 1fr; }
709
+ .page-header { flex-direction: column; }
710
+ .theme-toggle { top: 8px; right: 8px; }
711
+ }
712
+
713
+ /* ── Scrollbar ───────────────────────────────────────────────────────────── */
714
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
715
+ ::-webkit-scrollbar-track { background: var(--bg); }
716
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
717
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-subtle); }
718
+
719
+ /* ── Focus ───────────────────────────────────────────────────────────────── */
720
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
721
+ </style>
722
+ </head>
723
+ <body>
724
+ <button class="theme-toggle" id="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme" title="Toggle dark/light"><span id="theme-icon">&#9790;</span></button>
725
+ <nav class="sidebar" id="sidebar"><div class="sidebar-header"><span class="sidebar-logo">&#127760;</span><span class="sidebar-brand">ai-localize</span><button class="sidebar-toggle" onclick="toggleSidebar()">&#9776;</button></div><div class="sidebar-meta"><div><span class="badge badge-blue">react-vite</span></div><div class="sidebar-date">5/27/2026, 6:13:25 PM</div></div><div class="nav-group"><div class="nav-group-label">Overview</div><a href="#overview" class="nav-item" data-section="overview"><span class="nav-icon">&#128202;</span><span class="nav-label">Summary</span><span class="nav-count"></span></a><a href="#charts" class="nav-item" data-section="charts"><span class="nav-icon">&#128200;</span><span class="nav-label">Charts</span><span class="nav-count"></span></a></div><div class="nav-group"><div class="nav-group-label">Analysis</div><a href="#hardcoded" class="nav-item nav-alert" data-section="hardcoded"><span class="nav-icon">&#128269;</span><span class="nav-label">Hardcoded Texts</span><span class="nav-count">37</span></a><a href="#missing" class="nav-item nav-alert" data-section="missing"><span class="nav-icon">&#10060;</span><span class="nav-label">Missing Trans.</span><span class="nav-count">12</span></a><a href="#unused" class="nav-item nav-alert" data-section="unused"><span class="nav-icon">&#128465;</span><span class="nav-label">Unused Keys</span><span class="nav-count">4</span></a><a href="#assets" class="nav-item" data-section="assets"><span class="nav-icon">&#128230;</span><span class="nav-label">Assets</span><span class="nav-count">23</span></a></div><div class="nav-group"><div class="nav-group-label">AI Insights</div><a href="#insights" class="nav-item nav-alert" data-section="insights"><span class="nav-icon">&#129504;</span><span class="nav-label">AI Insights</span><span class="nav-count">7</span></a></div><div class="nav-group"><div class="nav-group-label">Export</div><button class="nav-item nav-btn" onclick="exportFullJson()">&#8659; Export JSON</button><button class="nav-item nav-btn" onclick="exportFullCsv()">&#8659; Export CSV</button><button class="nav-item nav-btn" onclick="window.print()">&#128424; Print / PDF</button></div></nav>
726
+ <main class="main" id="main">
727
+ <header class="page-header">
728
+ <div class="page-header-left"><h1 class="page-title">&#127760; Localization Analytics Dashboard</h1><div class="page-meta"><span class="meta-chip"><span class="badge badge-blue">react-vite</span></span><span class="meta-item">&#128197; 5/27/2026, 6:13:25 PM</span><span class="meta-item">&#9201; 2340ms</span><span class="meta-item">&#128196; 148 files</span></div></div><div class="page-header-right"><button class="btn btn-primary" onclick="expandAll()">Expand All</button><button class="btn" onclick="collapseAll()">Collapse All</button></div></header>
729
+
730
+ <section id="overview" class="section"><div class="section-title-row"><h2 class="section-title">&#128202; Summary</h2></div><div class="stats-grid"><div class="stat-card stat-neutral"><div class="stat-icon">&#128196;</div><div class="stat-value">148</div><div class="stat-label">Files Scanned</div><div class="stat-hint">Source files processed</div></div><div class="stat-card stat-warn"><div class="stat-icon">&#128269;</div><div class="stat-value">37</div><div class="stat-label">Hardcoded Texts</div><div class="stat-hint">Raw strings not yet in t()</div></div><div class="stat-card stat-info"><div class="stat-icon">&#128273;</div><div class="stat-value">38</div><div class="stat-label">Unique Keys</div><div class="stat-hint">Deduplicated locale keys</div></div><div class="stat-card stat-err"><div class="stat-icon">&#10060;</div><div class="stat-value">12</div><div class="stat-label">Missing Translations</div><div class="stat-hint">Absent in target languages</div></div><div class="stat-card stat-warn"><div class="stat-icon">&#128465;</div><div class="stat-value">4</div><div class="stat-label">Unused Keys</div><div class="stat-hint">In locale but not in source</div></div><div class="stat-card stat-ok"><div class="stat-icon">&#128258;</div><div class="stat-value">0</div><div class="stat-label">Duplicate Keys</div><div class="stat-hint">Same key, different texts</div></div><div class="stat-card stat-err"><div class="stat-icon">&#127919;</div><div class="stat-value">68%</div><div class="stat-label">Translation Coverage</div><div class="stat-hint">% of keys in all languages</div></div><div class="stat-card stat-neutral"><div class="stat-icon">&#128230;</div><div class="stat-value">23</div><div class="stat-label">Assets Found</div><div class="stat-hint">Static asset references</div></div><div class="stat-card stat-warn"><div class="stat-icon">&#128279;</div><div class="stat-value">5</div><div class="stat-label">Legacy CDN URLs</div><div class="stat-hint">Old CDN refs pending</div></div></div><div class="info-banner info-banner-blue"><strong>&#8505; Why do Hardcoded Texts (37) and Keys Generated (29) differ?</strong><ul><li><strong>Hardcoded Texts</strong> = total raw string occurrences (same string in 5 files = 5).</li><li><strong>Keys Generated</strong> = unique keys after deduplication.</li><li>Difference (8) = duplicate strings consolidated into shared keys.</li></ul></div></section>
731
+
732
+ <section id="charts" class="section"><div class="section-title-row"><h2 class="section-title">&#128200; Analytics</h2></div><div class="charts-grid"><div class="chart-card"><h3 class="chart-title">&#127919; Translation Coverage</h3><div class="chart-body chart-donut-wrap"><svg class="donut-chart" viewBox="0 0 120 120"><circle cx="60" cy="60" r="52" fill="none" stroke="var(--border)" stroke-width="12"/><circle cx="60" cy="60" r="52" fill="none" stroke="#f59e0b" stroke-width="12" stroke-dasharray="222.17 326.73" stroke-dashoffset="81.68" stroke-linecap="round" transform="rotate(-90 60 60)"/><text x="60" y="56" text-anchor="middle" font-size="20" font-weight="700" fill="#f59e0b">68%</text><text x="60" y="72" text-anchor="middle" font-size="10" fill="var(--text-muted)">coverage</text></svg></div><p class="chart-caption">68% of keys covered across all languages</p></div><div class="chart-card"><h3 class="chart-title">&#128230; Keys by Namespace (Top 10)</h3><div class="chart-body"><div class="bar-chart"><div class="bar-row"><span class="bar-label">auth</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:var(--accent)"></div></div><span class="bar-value">6</span></div><div class="bar-row"><span class="bar-label">nav</span><div class="bar-track"><div class="bar-fill" style="width:83%;background:var(--accent)"></div></div><span class="bar-value">5</span></div><div class="bar-row"><span class="bar-label">users</span><div class="bar-track"><div class="bar-fill" style="width:83%;background:var(--accent)"></div></div><span class="bar-value">5</span></div><div class="bar-row"><span class="bar-label">common</span><div class="bar-track"><div class="bar-fill" style="width:67%;background:var(--accent)"></div></div><span class="bar-value">4</span></div><div class="bar-row"><span class="bar-label">dashboard</span><div class="bar-track"><div class="bar-fill" style="width:50%;background:var(--accent)"></div></div><span class="bar-value">3</span></div><div class="bar-row"><span class="bar-label">settings</span><div class="bar-track"><div class="bar-fill" style="width:50%;background:var(--accent)"></div></div><span class="bar-value">3</span></div><div class="bar-row"><span class="bar-label">toast</span><div class="bar-track"><div class="bar-fill" style="width:50%;background:var(--accent)"></div></div><span class="bar-value">3</span></div><div class="bar-row"><span class="bar-label">modal</span><div class="bar-track"><div class="bar-fill" style="width:33%;background:var(--accent)"></div></div><span class="bar-value">2</span></div><div class="bar-row"><span class="bar-label">alert</span><div class="bar-track"><div class="bar-fill" style="width:33%;background:var(--accent)"></div></div><span class="bar-value">2</span></div><div class="bar-row"><span class="bar-label">grid</span><div class="bar-track"><div class="bar-fill" style="width:33%;background:var(--accent)"></div></div><span class="bar-value">2</span></div></div></div></div><div class="chart-card"><h3 class="chart-title">&#127991; Texts by Context</h3><div class="chart-body"><div class="bar-chart"><div class="bar-row"><span class="bar-label">jsx-text</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:var(--accent)"></div></div><span class="bar-value">11</span></div><div class="bar-row"><span class="bar-label">button</span><div class="bar-track"><div class="bar-fill" style="width:64%;background:var(--accent)"></div></div><span class="bar-value">7</span></div><div class="bar-row"><span class="bar-label">table-header</span><div class="bar-track"><div class="bar-fill" style="width:45%;background:var(--accent)"></div></div><span class="bar-value">5</span></div><div class="bar-row"><span class="bar-label">label</span><div class="bar-track"><div class="bar-fill" style="width:36%;background:var(--accent)"></div></div><span class="bar-value">4</span></div><div class="bar-row"><span class="bar-label">toast</span><div class="bar-track"><div class="bar-fill" style="width:27%;background:var(--accent)"></div></div><span class="bar-value">3</span></div><div class="bar-row"><span class="bar-label">heading</span><div class="bar-track"><div class="bar-fill" style="width:18%;background:var(--accent)"></div></div><span class="bar-value">2</span></div><div class="bar-row"><span class="bar-label">modal</span><div class="bar-track"><div class="bar-fill" style="width:18%;background:var(--accent)"></div></div><span class="bar-value">2</span></div><div class="bar-row"><span class="bar-label">placeholder</span><div class="bar-track"><div class="bar-fill" style="width:18%;background:var(--accent)"></div></div><span class="bar-value">2</span></div><div class="bar-row"><span class="bar-label">alert</span><div class="bar-track"><div class="bar-fill" style="width:9%;background:var(--accent)"></div></div><span class="bar-value">1</span></div><div class="bar-row"><span class="bar-label">tooltip</span><div class="bar-track"><div class="bar-fill" style="width:9%;background:var(--accent)"></div></div><span class="bar-value">1</span></div></div></div></div></div></section>
733
+
734
+ <section id="hardcoded" class="section"><div class="section-title-row"><h2 class="section-title">&#128269; Hardcoded Texts <span class="section-count count-warn">37</span></h2></div><div class="accordion accordion-open" id="acc-hardcoded-main"><button class="accordion-trigger" aria-expanded="true" onclick="toggleAccordion('hardcoded-main')"><span class="accordion-icon severity-warn">&#9679;</span><span class="accordion-title">&#9888; 37 hardcoded string(s) detected</span><span class="accordion-subtitle">Strings not yet wrapped in translation calls</span><span class="accordion-chevron">&#8964;</span></button><div class="accordion-panel" id="panel-hardcoded-main"><div class="accordion-body"><div class="insight-legend">Raw text strings not yet wrapped in a translation call. Run <code>ai-localize full-migrate</code> to wrap them automatically.</div><div class="table-wrapper"><div class="table-controls"><div class="search-box"><span class="search-icon">&#128269;</span><input type="text" class="table-search" placeholder="Search&hellip;" oninput="filterTable('tbl-hardcoded',this.value)" aria-label="Search"/></div><div class="table-meta" id="tbl-hardcoded-meta"></div><div class="table-actions"><button class="btn btn-sm" onclick="exportTableCsv('tbl-hardcoded')">&#8659; CSV</button><button class="btn btn-sm" onclick="exportTableJson('tbl-hardcoded')">&#8659; JSON</button></div></div><div class="table-scroll"><table id="tbl-hardcoded" data-page-size="50" data-page="1"><thead><tr><th onclick="sortTable('tbl-hardcoded','file')" class="sortable">File <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-hardcoded','line')" class="sortable" style="width:60px">Line <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-hardcoded','text')" class="sortable">Text <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-hardcoded','key')" class="sortable">Suggested Key <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-hardcoded','ctx')" class="sortable" style="width:120px">Context <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-hardcoded','node')" class="sortable" style="width:120px">Node <span class="sort-icon">&#8597;</span></th></tr></thead><tbody><tr><td data-col="file"><code class="path-code">components/Button/PrimaryButton.tsx</code></td><td data-col="line"><span class="line-num">14</span></td><td data-col="text"><span class="text-preview">Submit</span></td><td data-col="key"><code class="key-code">common.submit</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
735
+ <tr><td data-col="file"><code class="path-code">components/Button/PrimaryButton.tsx</code></td><td data-col="line"><span class="line-num">22</span></td><td data-col="text"><span class="text-preview">Cancel</span></td><td data-col="key"><code class="key-code">common.cancel</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
736
+ <tr><td data-col="file"><code class="path-code">pages/Dashboard/Overview.tsx</code></td><td data-col="line"><span class="line-num">31</span></td><td data-col="text"><span class="text-preview">Welcome back!</span></td><td data-col="key"><code class="key-code">dashboard.welcomeBack</code></td><td data-col="ctx"><span class="badge badge-blue">heading</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
737
+ <tr><td data-col="file"><code class="path-code">pages/Dashboard/Overview.tsx</code></td><td data-col="line"><span class="line-num">45</span></td><td data-col="text"><span class="text-preview">Total Revenue</span></td><td data-col="key"><code class="key-code">dashboard.totalRevenue</code></td><td data-col="ctx"><span class="badge badge-blue">table-header</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
738
+ <tr><td data-col="file"><code class="path-code">pages/Dashboard/Overview.tsx</code></td><td data-col="line"><span class="line-num">67</span></td><td data-col="text"><span class="text-preview">No data available</span></td><td data-col="key"><code class="key-code">dashboard.noData</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
739
+ <tr><td data-col="file"><code class="path-code">components/Modal/ConfirmModal.tsx</code></td><td data-col="line"><span class="line-num">8</span></td><td data-col="text"><span class="text-preview">Are you sure?</span></td><td data-col="key"><code class="key-code">modal.confirmTitle</code></td><td data-col="ctx"><span class="badge badge-blue">modal</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
740
+ <tr><td data-col="file"><code class="path-code">components/Modal/ConfirmModal.tsx</code></td><td data-col="line"><span class="line-num">19</span></td><td data-col="text"><span class="text-preview">This action cannot be undone.</span></td><td data-col="key"><code class="key-code">modal.confirmBody</code></td><td data-col="ctx"><span class="badge badge-blue">modal</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
741
+ <tr><td data-col="file"><code class="path-code">components/Modal/ConfirmModal.tsx</code></td><td data-col="line"><span class="line-num">32</span></td><td data-col="text"><span class="text-preview">Delete</span></td><td data-col="key"><code class="key-code">common.delete</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
742
+ <tr><td data-col="file"><code class="path-code">src/forms/LoginForm.tsx</code></td><td data-col="line"><span class="line-num">12</span></td><td data-col="text"><span class="text-preview">Email address</span></td><td data-col="key"><code class="key-code">auth.emailLabel</code></td><td data-col="ctx"><span class="badge badge-blue">label</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
743
+ <tr><td data-col="file"><code class="path-code">src/forms/LoginForm.tsx</code></td><td data-col="line"><span class="line-num">18</span></td><td data-col="text"><span class="text-preview">Password</span></td><td data-col="key"><code class="key-code">auth.passwordLabel</code></td><td data-col="ctx"><span class="badge badge-blue">label</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
744
+ <tr><td data-col="file"><code class="path-code">src/forms/LoginForm.tsx</code></td><td data-col="line"><span class="line-num">24</span></td><td data-col="text"><span class="text-preview">Enter your email</span></td><td data-col="key"><code class="key-code">auth.emailPlaceholder</code></td><td data-col="ctx"><span class="badge badge-blue">placeholder</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
745
+ <tr><td data-col="file"><code class="path-code">src/forms/LoginForm.tsx</code></td><td data-col="line"><span class="line-num">30</span></td><td data-col="text"><span class="text-preview">Enter your password</span></td><td data-col="key"><code class="key-code">auth.passwordPlaceholder</code></td><td data-col="ctx"><span class="badge badge-blue">placeholder</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
746
+ <tr><td data-col="file"><code class="path-code">src/forms/LoginForm.tsx</code></td><td data-col="line"><span class="line-num">56</span></td><td data-col="text"><span class="text-preview">Sign in</span></td><td data-col="key"><code class="key-code">auth.signIn</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
747
+ <tr><td data-col="file"><code class="path-code">src/forms/LoginForm.tsx</code></td><td data-col="line"><span class="line-num">60</span></td><td data-col="text"><span class="text-preview">Forgot password?</span></td><td data-col="key"><code class="key-code">auth.forgotPassword</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
748
+ <tr><td data-col="file"><code class="path-code">components/Nav/Sidebar.tsx</code></td><td data-col="line"><span class="line-num">22</span></td><td data-col="text"><span class="text-preview">Dashboard</span></td><td data-col="key"><code class="key-code">nav.dashboard</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
749
+ <tr><td data-col="file"><code class="path-code">components/Nav/Sidebar.tsx</code></td><td data-col="line"><span class="line-num">30</span></td><td data-col="text"><span class="text-preview">Settings</span></td><td data-col="key"><code class="key-code">nav.settings</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
750
+ <tr><td data-col="file"><code class="path-code">components/Nav/Sidebar.tsx</code></td><td data-col="line"><span class="line-num">38</span></td><td data-col="text"><span class="text-preview">Users</span></td><td data-col="key"><code class="key-code">nav.users</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
751
+ <tr><td data-col="file"><code class="path-code">components/Nav/Sidebar.tsx</code></td><td data-col="line"><span class="line-num">46</span></td><td data-col="text"><span class="text-preview">Reports</span></td><td data-col="key"><code class="key-code">nav.reports</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
752
+ <tr><td data-col="file"><code class="path-code">components/Nav/Sidebar.tsx</code></td><td data-col="line"><span class="line-num">54</span></td><td data-col="text"><span class="text-preview">Log out</span></td><td data-col="key"><code class="key-code">nav.logout</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
753
+ <tr><td data-col="file"><code class="path-code">pages/Settings/ProfileSettings.tsx</code></td><td data-col="line"><span class="line-num">11</span></td><td data-col="text"><span class="text-preview">Profile Settings</span></td><td data-col="key"><code class="key-code">settings.profileTitle</code></td><td data-col="ctx"><span class="badge badge-blue">heading</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
754
+ <tr><td data-col="file"><code class="path-code">pages/Settings/ProfileSettings.tsx</code></td><td data-col="line"><span class="line-num">28</span></td><td data-col="text"><span class="text-preview">First name</span></td><td data-col="key"><code class="key-code">settings.firstName</code></td><td data-col="ctx"><span class="badge badge-blue">label</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
755
+ <tr><td data-col="file"><code class="path-code">pages/Settings/ProfileSettings.tsx</code></td><td data-col="line"><span class="line-num">36</span></td><td data-col="text"><span class="text-preview">Last name</span></td><td data-col="key"><code class="key-code">settings.lastName</code></td><td data-col="ctx"><span class="badge badge-blue">label</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
756
+ <tr><td data-col="file"><code class="path-code">pages/Settings/ProfileSettings.tsx</code></td><td data-col="line"><span class="line-num">44</span></td><td data-col="text"><span class="text-preview">Save changes</span></td><td data-col="key"><code class="key-code">common.saveChanges</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
757
+ <tr><td data-col="file"><code class="path-code">components/Toast/ToastManager.tsx</code></td><td data-col="line"><span class="line-num">9</span></td><td data-col="text"><span class="text-preview">Changes saved successfully</span></td><td data-col="key"><code class="key-code">toast.saveSuccess</code></td><td data-col="ctx"><span class="badge badge-blue">toast</span></td><td data-col="node"><span class="badge badge-grey">string-literal</span></td></tr>
758
+ <tr><td data-col="file"><code class="path-code">components/Toast/ToastManager.tsx</code></td><td data-col="line"><span class="line-num">15</span></td><td data-col="text"><span class="text-preview">An error occurred. Please try again.</span></td><td data-col="key"><code class="key-code">toast.genericError</code></td><td data-col="ctx"><span class="badge badge-blue">toast</span></td><td data-col="node"><span class="badge badge-grey">string-literal</span></td></tr>
759
+ <tr><td data-col="file"><code class="path-code">components/Toast/ToastManager.tsx</code></td><td data-col="line"><span class="line-num">21</span></td><td data-col="text"><span class="text-preview">Network connection lost</span></td><td data-col="key"><code class="key-code">toast.networkError</code></td><td data-col="ctx"><span class="badge badge-blue">toast</span></td><td data-col="node"><span class="badge badge-grey">string-literal</span></td></tr>
760
+ <tr><td data-col="file"><code class="path-code">pages/Users/UserTable.tsx</code></td><td data-col="line"><span class="line-num">17</span></td><td data-col="text"><span class="text-preview">Name</span></td><td data-col="key"><code class="key-code">users.columnName</code></td><td data-col="ctx"><span class="badge badge-blue">table-header</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
761
+ <tr><td data-col="file"><code class="path-code">pages/Users/UserTable.tsx</code></td><td data-col="line"><span class="line-num">24</span></td><td data-col="text"><span class="text-preview">Email</span></td><td data-col="key"><code class="key-code">users.columnEmail</code></td><td data-col="ctx"><span class="badge badge-blue">table-header</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
762
+ <tr><td data-col="file"><code class="path-code">pages/Users/UserTable.tsx</code></td><td data-col="line"><span class="line-num">31</span></td><td data-col="text"><span class="text-preview">Role</span></td><td data-col="key"><code class="key-code">users.columnRole</code></td><td data-col="ctx"><span class="badge badge-blue">table-header</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
763
+ <tr><td data-col="file"><code class="path-code">pages/Users/UserTable.tsx</code></td><td data-col="line"><span class="line-num">38</span></td><td data-col="text"><span class="text-preview">Status</span></td><td data-col="key"><code class="key-code">users.columnStatus</code></td><td data-col="ctx"><span class="badge badge-blue">table-header</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
764
+ <tr><td data-col="file"><code class="path-code">pages/Users/UserTable.tsx</code></td><td data-col="line"><span class="line-num">68</span></td><td data-col="text"><span class="text-preview">No users found</span></td><td data-col="key"><code class="key-code">users.emptyState</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
765
+ <tr><td data-col="file"><code class="path-code">components/Alert/ErrorBanner.tsx</code></td><td data-col="line"><span class="line-num">7</span></td><td data-col="text"><span class="text-preview">Something went wrong</span></td><td data-col="key"><code class="key-code">alert.errorTitle</code></td><td data-col="ctx"><span class="badge badge-blue">alert</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
766
+ <tr><td data-col="file"><code class="path-code">components/Alert/ErrorBanner.tsx</code></td><td data-col="line"><span class="line-num">13</span></td><td data-col="text"><span class="text-preview">Please refresh the page</span></td><td data-col="key"><code class="key-code">alert.refreshHint</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
767
+ <tr><td data-col="file"><code class="path-code">components/Tooltip/HelpTooltip.tsx</code></td><td data-col="line"><span class="line-num">5</span></td><td data-col="text"><span class="text-preview">Click for more information</span></td><td data-col="key"><code class="key-code">tooltip.helpInfo</code></td><td data-col="ctx"><span class="badge badge-blue">tooltip</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
768
+ <tr><td data-col="file"><code class="path-code">components/DataGrid/DataGrid.tsx</code></td><td data-col="line"><span class="line-num">42</span></td><td data-col="text"><span class="text-preview">Loading data...</span></td><td data-col="key"><code class="key-code">grid.loading</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
769
+ <tr><td data-col="file"><code class="path-code">components/DataGrid/DataGrid.tsx</code></td><td data-col="line"><span class="line-num">58</span></td><td data-col="text"><span class="text-preview">No results match your search</span></td><td data-col="key"><code class="key-code">grid.noResults</code></td><td data-col="ctx"><span class="badge badge-blue">jsx-text</span></td><td data-col="node"><span class="badge badge-grey">JSXText</span></td></tr>
770
+ <tr><td data-col="file"><code class="path-code">pages/Reports/ReportHeader.tsx</code></td><td data-col="line"><span class="line-num">10</span></td><td data-col="text"><span class="text-preview">Submit</span></td><td data-col="key"><code class="key-code">reports.submit</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr>
771
+ <tr><td data-col="file"><code class="path-code">pages/Reports/ReportHeader.tsx</code></td><td data-col="line"><span class="line-num">22</span></td><td data-col="text"><span class="text-preview">Cancel</span></td><td data-col="key"><code class="key-code">reports.cancel</code></td><td data-col="ctx"><span class="badge badge-blue">button</span></td><td data-col="node"><span class="badge badge-grey">JSXAttribute</span></td></tr></tbody></table></div><div class="table-pagination" id="tbl-hardcoded-pagination"></div></div></div></div></div></section>
772
+
773
+ <section id="missing" class="section"><div class="section-title-row"><h2 class="section-title">&#10060; Missing Translations <span class="section-count count-err">12</span></h2></div><div class="accordion accordion-open" id="acc-missing-main"><button class="accordion-trigger" aria-expanded="true" onclick="toggleAccordion('missing-main')"><span class="accordion-icon severity-err">&#9679;</span><span class="accordion-title">&#10060; 12 missing translation(s) found</span><span class="accordion-subtitle">Keys absent in target language files</span><span class="accordion-chevron">&#8964;</span></button><div class="accordion-panel" id="panel-missing-main"><div class="accordion-body"><div class="insight-legend">Keys in the default language but absent in target language files. Run <code>ai-localize extract</code> to seed them.</div><div class="chip-row">Filter by language: <span class="chip chip-red" onclick="filterTable('tbl-missing','fr')">fr <strong>3</strong></span> <span class="chip chip-red" onclick="filterTable('tbl-missing','de')">de <strong>4</strong></span> <span class="chip chip-red" onclick="filterTable('tbl-missing','es')">es <strong>2</strong></span> <span class="chip chip-red" onclick="filterTable('tbl-missing','ja')">ja <strong>3</strong></span></div><div class="table-wrapper"><div class="table-controls"><div class="search-box"><span class="search-icon">&#128269;</span><input type="text" class="table-search" placeholder="Search&hellip;" oninput="filterTable('tbl-missing',this.value)" aria-label="Search"/></div><div class="table-meta" id="tbl-missing-meta"></div><div class="table-actions"><button class="btn btn-sm" onclick="exportTableCsv('tbl-missing')">&#8659; CSV</button><button class="btn btn-sm" onclick="exportTableJson('tbl-missing')">&#8659; JSON</button></div></div><div class="table-scroll"><table id="tbl-missing" data-page-size="50" data-page="1"><thead><tr><th onclick="sortTable('tbl-missing','key')" class="sortable">Key <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-missing','lang')" class="sortable" style="width:100px">Language <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-missing','file')" class="sortable">Locale File <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-missing','msg')" class="sortable">Details <span class="sort-icon">&#8597;</span></th></tr></thead><tbody><tr><td data-col="key"><code class="key-code">common.submit</code></td><td data-col="lang"><span class="badge badge-red">fr</span></td><td data-col="file"><code class="path-code">fr/common.json</code></td><td data-col="msg"><span class="detail-text">Key missing in fr locale</span></td></tr>
774
+ <tr><td data-col="key"><code class="key-code">common.cancel</code></td><td data-col="lang"><span class="badge badge-red">fr</span></td><td data-col="file"><code class="path-code">fr/common.json</code></td><td data-col="msg"><span class="detail-text">Key missing in fr locale</span></td></tr>
775
+ <tr><td data-col="key"><code class="key-code">auth.signIn</code></td><td data-col="lang"><span class="badge badge-red">fr</span></td><td data-col="file"><code class="path-code">fr/auth.json</code></td><td data-col="msg"><span class="detail-text">Key missing in fr locale</span></td></tr>
776
+ <tr><td data-col="key"><code class="key-code">dashboard.welcomeBack</code></td><td data-col="lang"><span class="badge badge-red">de</span></td><td data-col="file"><code class="path-code">de/dashboard.json</code></td><td data-col="msg"><span class="detail-text">Key missing in de locale</span></td></tr>
777
+ <tr><td data-col="key"><code class="key-code">dashboard.totalRevenue</code></td><td data-col="lang"><span class="badge badge-red">de</span></td><td data-col="file"><code class="path-code">de/dashboard.json</code></td><td data-col="msg"><span class="detail-text">Key missing in de locale</span></td></tr>
778
+ <tr><td data-col="key"><code class="key-code">modal.confirmTitle</code></td><td data-col="lang"><span class="badge badge-red">de</span></td><td data-col="file"><code class="path-code">de/modal.json</code></td><td data-col="msg"><span class="detail-text">Key missing in de locale</span></td></tr>
779
+ <tr><td data-col="key"><code class="key-code">modal.confirmBody</code></td><td data-col="lang"><span class="badge badge-red">de</span></td><td data-col="file"><code class="path-code">de/modal.json</code></td><td data-col="msg"><span class="detail-text">Key missing in de locale</span></td></tr>
780
+ <tr><td data-col="key"><code class="key-code">nav.logout</code></td><td data-col="lang"><span class="badge badge-red">es</span></td><td data-col="file"><code class="path-code">es/nav.json</code></td><td data-col="msg"><span class="detail-text">Key missing in es locale</span></td></tr>
781
+ <tr><td data-col="key"><code class="key-code">settings.profileTitle</code></td><td data-col="lang"><span class="badge badge-red">es</span></td><td data-col="file"><code class="path-code">es/settings.json</code></td><td data-col="msg"><span class="detail-text">Key missing in es locale</span></td></tr>
782
+ <tr><td data-col="key"><code class="key-code">toast.genericError</code></td><td data-col="lang"><span class="badge badge-red">ja</span></td><td data-col="file"><code class="path-code">ja/toast.json</code></td><td data-col="msg"><span class="detail-text">Key missing in ja locale</span></td></tr>
783
+ <tr><td data-col="key"><code class="key-code">toast.saveSuccess</code></td><td data-col="lang"><span class="badge badge-red">ja</span></td><td data-col="file"><code class="path-code">ja/toast.json</code></td><td data-col="msg"><span class="detail-text">Key missing in ja locale</span></td></tr>
784
+ <tr><td data-col="key"><code class="key-code">users.emptyState</code></td><td data-col="lang"><span class="badge badge-red">ja</span></td><td data-col="file"><code class="path-code">ja/users.json</code></td><td data-col="msg"><span class="detail-text">Key missing in ja locale</span></td></tr></tbody></table></div><div class="table-pagination" id="tbl-missing-pagination"></div></div></div></div></div></section>
785
+
786
+ <section id="unused" class="section"><div class="section-title-row"><h2 class="section-title">&#128465; Unused Keys <span class="section-count count-warn">4</span></h2></div><div class="accordion" id="acc-unused-main"><button class="accordion-trigger" aria-expanded="false" onclick="toggleAccordion('unused-main')"><span class="accordion-icon severity-warn">&#9679;</span><span class="accordion-title">&#9888; 4 unused key(s) found</span><span class="accordion-subtitle">Keys in locale files but not in source</span><span class="accordion-chevron">&#8964;</span></button><div class="accordion-panel" id="panel-unused-main" hidden><div class="accordion-body"><div class="insight-legend">Keys in locale files not referenced in source code. Run <code>ai-localize cleanup</code>.</div><div class="table-wrapper"><div class="table-controls"><div class="search-box"><span class="search-icon">&#128269;</span><input type="text" class="table-search" placeholder="Search&hellip;" oninput="filterTable('tbl-unused',this.value)" aria-label="Search"/></div><div class="table-meta" id="tbl-unused-meta"></div><div class="table-actions"><button class="btn btn-sm" onclick="exportTableCsv('tbl-unused')">&#8659; CSV</button><button class="btn btn-sm" onclick="exportTableJson('tbl-unused')">&#8659; JSON</button></div></div><div class="table-scroll"><table id="tbl-unused" data-page-size="50" data-page="1"><thead><tr><th onclick="sortTable('tbl-unused','key')" class="sortable">Unused Key <span class="sort-icon">&#8597;</span></th></tr></thead><tbody><tr><td data-col="key"><code class="key-code">common.oldButton</code></td></tr>
787
+ <tr><td data-col="key"><code class="key-code">dashboard.legacyWidget</code></td></tr>
788
+ <tr><td data-col="key"><code class="key-code">auth.ssoLogin</code></td></tr>
789
+ <tr><td data-col="key"><code class="key-code">settings.betaFeature</code></td></tr></tbody></table></div><div class="table-pagination" id="tbl-unused-pagination"></div></div></div></div></div></section>
790
+
791
+ <section id="assets" class="section"><div class="section-title-row"><h2 class="section-title">&#128230; CDN Assets <span class="section-count count-neutral">23</span></h2></div><div class="accordion" id="acc-assets-main"><button class="accordion-trigger" aria-expanded="false" onclick="toggleAccordion('assets-main')"><span class="accordion-icon severity-warn">&#9679;</span><span class="accordion-title">&#128230; 23 assets found &middot; 18 uploaded &middot; 5 legacy URLs</span><span class="accordion-subtitle">S3/CloudFront asset migration status</span><span class="accordion-chevron">&#8964;</span></button><div class="accordion-panel" id="panel-assets-main" hidden><div class="accordion-body"><div class="asset-summary-row"><div class="stat-card stat-neutral"><div class="stat-icon">&#128190;</div><div class="stat-value">23</div><div class="stat-label">Total Assets</div><div class="stat-hint"></div></div><div class="stat-card stat-ok"><div class="stat-icon">&#9989;</div><div class="stat-value">18</div><div class="stat-label">Uploaded</div><div class="stat-hint">Pushed to S3/CDN</div></div><div class="stat-card stat-ok"><div class="stat-icon">&#128257;</div><div class="stat-value">15</div><div class="stat-label">URLs Replaced</div><div class="stat-hint"></div></div><div class="stat-card stat-warn"><div class="stat-icon">&#9888;</div><div class="stat-value">5</div><div class="stat-label">Legacy URLs</div><div class="stat-hint">run replace-cdn</div></div></div><div class="table-wrapper"><div class="table-controls"><div class="search-box"><span class="search-icon">&#128269;</span><input type="text" class="table-search" placeholder="Search&hellip;" oninput="filterTable('tbl-assets',this.value)" aria-label="Search"/></div><div class="table-meta" id="tbl-assets-meta"></div><div class="table-actions"><button class="btn btn-sm" onclick="exportTableCsv('tbl-assets')">&#8659; CSV</button><button class="btn btn-sm" onclick="exportTableJson('tbl-assets')">&#8659; JSON</button></div></div><div class="table-scroll"><table id="tbl-assets" data-page-size="50" data-page="1"><thead><tr><th onclick="sortTable('tbl-assets','path')" class="sortable">Local Path <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-assets','s3')" class="sortable">S3 Key <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-assets','url')" class="sortable">CloudFront URL <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-assets','size')" class="sortable" style="width:80px">Size <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-assets','type')" class="sortable" style="width:140px">Type <span class="sort-icon">&#8597;</span></th></tr></thead><tbody><tr><td data-col="path"><code class="path-code">public/images/logo.png</code></td><td data-col="s3"><code class="key-code">assets/images/logo.png</code></td><td data-col="url"><a href="https://d1abc123.cloudfront.net/assets/images/logo.png" target="_blank" class="cdn-link">https://d1abc123.cloudfront.net/assets/images/logo.png</a></td><td data-col="size"><span class="file-size">24.0 KB</span></td><td data-col="type"><span class="badge badge-grey">image/png</span></td></tr>
792
+ <tr><td data-col="path"><code class="path-code">public/images/hero-banner.webp</code></td><td data-col="s3"><code class="key-code">assets/images/hero-banner.webp</code></td><td data-col="url"><a href="https://d1abc123.cloudfront.net/assets/images/hero-banner.webp" target="_blank" class="cdn-link">https://d1abc123.cloudfront.net/assets/images/hero-bann&hellip;</a></td><td data-col="size"><span class="file-size">100.0 KB</span></td><td data-col="type"><span class="badge badge-grey">image/webp</span></td></tr>
793
+ <tr><td data-col="path"><code class="path-code">public/fonts/Inter-Regular.woff2</code></td><td data-col="s3"><code class="key-code">assets/fonts/Inter-Regular.woff2</code></td><td data-col="url"><a href="https://d1abc123.cloudfront.net/assets/fonts/Inter-Regular.woff2" target="_blank" class="cdn-link">https://d1abc123.cloudfront.net/assets/fonts/Inter-Regu&hellip;</a></td><td data-col="size"><span class="file-size">64.0 KB</span></td><td data-col="type"><span class="badge badge-grey">font/woff2</span></td></tr>
794
+ <tr><td data-col="path"><code class="path-code">public/fonts/Inter-Bold.woff2</code></td><td data-col="s3"><code class="key-code">assets/fonts/Inter-Bold.woff2</code></td><td data-col="url"><a href="https://d1abc123.cloudfront.net/assets/fonts/Inter-Bold.woff2" target="_blank" class="cdn-link">https://d1abc123.cloudfront.net/assets/fonts/Inter-Bold&hellip;</a></td><td data-col="size"><span class="file-size">67.5 KB</span></td><td data-col="type"><span class="badge badge-grey">font/woff2</span></td></tr>
795
+ <tr><td data-col="path"><code class="path-code">public/icons/favicon.svg</code></td><td data-col="s3"><code class="key-code">assets/icons/favicon.svg</code></td><td data-col="url"><a href="https://d1abc123.cloudfront.net/assets/icons/favicon.svg" target="_blank" class="cdn-link">https://d1abc123.cloudfront.net/assets/icons/favicon.sv&hellip;</a></td><td data-col="size"><span class="file-size">4.0 KB</span></td><td data-col="type"><span class="badge badge-grey">image/svg+xml</span></td></tr>
796
+ <tr><td data-col="path"><code class="path-code">public/videos/onboarding.mp4</code></td><td data-col="s3"><code class="key-code">assets/videos/onboarding.mp4</code></td><td data-col="url"><a href="https://d1abc123.cloudfront.net/assets/videos/onboarding.mp4" target="_blank" class="cdn-link">https://d1abc123.cloudfront.net/assets/videos/onboardin&hellip;</a></td><td data-col="size"><span class="file-size">5120.0 KB</span></td><td data-col="type"><span class="badge badge-grey">video/mp4</span></td></tr></tbody></table></div><div class="table-pagination" id="tbl-assets-pagination"></div></div></div></div></div></section>
797
+
798
+ <section id="insights" class="section"><div class="section-title-row"><h2 class="section-title">&#129504; AI Insights <span class="section-count count-warn">7</span></h2></div><div class="insight-banner">&#129504; <strong>Deterministic analysis</strong> &mdash; patterns identified from your actual locale data. No LLM required.</div><div class="insights-grid"><div class="insight-card"><h3 class="insight-heading">&#128258; Duplicate Text Detection</h3><p class="insight-count">2 duplicate group(s)</p><div class="insight-legend">Same literal text mapped to multiple keys. Consider consolidating.</div><div class="table-wrapper"><div class="table-controls"><div class="search-box"><span class="search-icon">&#128269;</span><input type="text" class="table-search" placeholder="Search&hellip;" oninput="filterTable('tbl-dups',this.value)" aria-label="Search"/></div><div class="table-meta" id="tbl-dups-meta"></div><div class="table-actions"><button class="btn btn-sm" onclick="exportTableCsv('tbl-dups')">&#8659; CSV</button><button class="btn btn-sm" onclick="exportTableJson('tbl-dups')">&#8659; JSON</button></div></div><div class="table-scroll"><table id="tbl-dups" data-page-size="50" data-page="1"><thead><tr><th onclick="sortTable('tbl-dups','text')" class="sortable">Text <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-dups','count')" class="sortable" style="width:90px">Key Count <span class="sort-icon">&#8597;</span></th><th onclick="sortTable('tbl-dups','keys')" class="sortable">Keys <span class="sort-icon">&#8597;</span></th></tr></thead><tbody><tr><td data-col="text"><span class="text-preview">Submit</span></td><td data-col="count"><span class="badge badge-orange">2</span></td><td data-col="keys"><code class="key-code">common.submit</code> <code class="key-code">reports.submit</code></td></tr>
799
+ <tr><td data-col="text"><span class="text-preview">Cancel</span></td><td data-col="count"><span class="badge badge-orange">2</span></td><td data-col="keys"><code class="key-code">common.cancel</code> <code class="key-code">reports.cancel</code></td></tr></tbody></table></div><div class="table-pagination" id="tbl-dups-pagination"></div></div></div><div class="insight-card"><h3 class="insight-heading">&#127757; Translation Inconsistencies</h3><p class="insight-count">12 missing translations across all languages</p><div class="insight-legend">Languages with the most missing translations — priority for your translators.</div><div class="bar-chart"><div class="bar-row"><span class="bar-label">de</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:#ef4444"></div></div><span class="bar-value">4</span></div><div class="bar-row"><span class="bar-label">fr</span><div class="bar-track"><div class="bar-fill" style="width:75%;background:#ef4444"></div></div><span class="bar-value">3</span></div><div class="bar-row"><span class="bar-label">ja</span><div class="bar-track"><div class="bar-fill" style="width:75%;background:#ef4444"></div></div><span class="bar-value">3</span></div><div class="bar-row"><span class="bar-label">es</span><div class="bar-track"><div class="bar-fill" style="width:50%;background:#ef4444"></div></div><span class="bar-value">2</span></div></div></div><div class="insight-card"><h3 class="insight-heading">&#128465; Unused Key Analysis</h3><p class="insight-count">4 unused key(s)</p><div class="insight-legend">4 keys in locale files with no source references. Run <code>ai-localize cleanup</code>.</div><div class="bar-chart"><div class="bar-row"><span class="bar-label">common.oldButton</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:#f59e0b"></div></div><span class="bar-value">1</span></div><div class="bar-row"><span class="bar-label">dashboard.legacyWidget</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:#f59e0b"></div></div><span class="bar-value">1</span></div><div class="bar-row"><span class="bar-label">auth.ssoLogin</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:#f59e0b"></div></div><span class="bar-value">1</span></div><div class="bar-row"><span class="bar-label">settings.betaFeature</span><div class="bar-track"><div class="bar-fill" style="width:100%;background:#f59e0b"></div></div><span class="bar-value">1</span></div></div></div><div class="insight-card"><h3 class="insight-heading">&#127800; Namespace Cleanup</h3><p class="insight-count">5 namespace(s) to consolidate</p><ul class="insight-list"><li><code class="key-code">modal</code> <span class="badge badge-orange">2 keys</span> &mdash; only 2 keys &mdash; consider merging.</li><li><code class="key-code">alert</code> <span class="badge badge-orange">2 keys</span> &mdash; only 2 keys &mdash; consider merging.</li><li><code class="key-code">tooltip</code> <span class="badge badge-orange">1 keys</span> &mdash; only 1 key &mdash; consider merging.</li><li><code class="key-code">grid</code> <span class="badge badge-orange">2 keys</span> &mdash; only 2 keys &mdash; consider merging.</li><li><code class="key-code">reports</code> <span class="badge badge-orange">2 keys</span> &mdash; only 2 keys &mdash; consider merging.</li></ul></div></div></section>
800
+
801
+ <footer class="page-footer">Generated by <strong>ai-localize-core</strong> &mdash; deterministic, offline-capable i18n tooling &mdash; 5/27/2026, 6:13:25 PM</footer>
802
+ </main>
803
+
804
+ <script>(function() {
805
+ 'use strict';
806
+ var THEME_KEY='ai-localize-theme';
807
+ function applyTheme(t){document.documentElement.setAttribute('data-theme',t);var icon=document.getElementById('theme-icon');if(icon)icon.textContent=t==='dark'?'\u2600':'\u263E';localStorage.setItem(THEME_KEY,t);}
808
+ window.toggleTheme=function(){var cur=document.documentElement.getAttribute('data-theme');applyTheme(cur==='dark'?'light':'dark');};
809
+ var saved=localStorage.getItem(THEME_KEY);if(saved)applyTheme(saved);else if(window.matchMedia&&window.matchMedia('(prefers-color-scheme:dark)').matches)applyTheme('dark');
810
+ window.toggleSidebar=function(){document.getElementById('sidebar').classList.toggle('sidebar-collapsed');document.getElementById('main').classList.toggle('main-expanded');};
811
+ var sections=document.querySelectorAll('section[id]');var navItems=document.querySelectorAll('.nav-item[data-section]');
812
+ function onScroll(){var y=window.scrollY+80;var cur='';sections.forEach(function(s){if(y>=s.offsetTop)cur=s.id;});navItems.forEach(function(a){a.classList.toggle('nav-active',a.getAttribute('data-section')===cur);});}
813
+ window.addEventListener('scroll',onScroll,{passive:true});onScroll();
814
+ window.toggleAccordion=function(id){var acc=document.getElementById('acc-'+id);var panel=document.getElementById('panel-'+id);var btn=acc.querySelector('.accordion-trigger');var open=acc.classList.toggle('accordion-open');btn.setAttribute('aria-expanded',open);if(open)panel.removeAttribute('hidden');else panel.setAttribute('hidden','');};
815
+ window.expandAll=function(){document.querySelectorAll('.accordion').forEach(function(acc){var id=acc.id.replace('acc-','');var panel=document.getElementById('panel-'+id);var btn=acc.querySelector('.accordion-trigger');acc.classList.add('accordion-open');btn.setAttribute('aria-expanded','true');if(panel)panel.removeAttribute('hidden');});};
816
+ window.collapseAll=function(){document.querySelectorAll('.accordion').forEach(function(acc){var id=acc.id.replace('acc-','');var panel=document.getElementById('panel-'+id);var btn=acc.querySelector('.accordion-trigger');acc.classList.remove('accordion-open');btn.setAttribute('aria-expanded','false');if(panel)panel.setAttribute('hidden','');});};
817
+ window.filterTable=function(tableId,query){var tbl=document.getElementById(tableId);if(!tbl)return;var q=query.toLowerCase();var rows=tbl.querySelectorAll('tbody tr');var shown=0;rows.forEach(function(row){var match=row.textContent.toLowerCase().indexOf(q)!==-1;row.style.display=match?'':'none';if(match)shown++;});var meta=document.getElementById(tableId+'-meta');if(meta)meta.textContent=shown+' / '+rows.length+' rows';renderPagination(tableId);};
818
+ var sortState={};
819
+ window.sortTable=function(tableId,col){var tbl=document.getElementById(tableId);if(!tbl)return;var dir=(sortState[tableId]===col+'_asc')?'desc':'asc';sortState[tableId]=col+'_'+dir;var colIndex=-1;tbl.querySelectorAll('thead th').forEach(function(th,i){if(th.getAttribute('data-col')===col)colIndex=i;var si=th.querySelector('.sort-icon');if(si)si.textContent='\u21C5';});if(colIndex===-1)return;var th=tbl.querySelector('thead th[data-col="'+col+'"]');if(th){var si=th.querySelector('.sort-icon');if(si)si.textContent=dir==='asc'?'\u2191':'\u2193';}var tbody=tbl.querySelector('tbody');var rows=Array.from(tbody.querySelectorAll('tr'));rows.sort(function(a,b){var av=(a.cells[colIndex]||{}).textContent||'';var bv=(b.cells[colIndex]||{}).textContent||'';var an=parseFloat(av),bn=parseFloat(bv);if(!isNaN(an)&&!isNaN(bn))return dir==='asc'?an-bn:bn-an;return dir==='asc'?av.localeCompare(bv):bv.localeCompare(av);});rows.forEach(function(r){tbody.appendChild(r);});renderPagination(tableId);};
820
+ function renderPagination(tableId){var tbl=document.getElementById(tableId);var pag=document.getElementById(tableId+'-pagination');var meta=document.getElementById(tableId+'-meta');if(!tbl||!pag)return;var pageSize=parseInt(tbl.getAttribute('data-page-size')||'50',10);var rows=Array.from(tbl.querySelectorAll('tbody tr')).filter(function(r){return r.style.display!=='none';});var total=rows.length;var pages=Math.ceil(total/pageSize);var page=parseInt(tbl.getAttribute('data-page')||'1',10);if(page>pages)page=1;tbl.setAttribute('data-page',page);rows.forEach(function(r,i){r.style.display=(i>=(page-1)*pageSize&&i<page*pageSize)?'':'none';});if(meta)meta.textContent=total+' row'+(total!==1?'s':'');pag.innerHTML='';if(pages<=1)return;function btn(label,p,active,disabled){var b=document.createElement('button');b.textContent=label;b.className='pag-btn'+(active?' pag-active':'')+(disabled?' pag-disabled':'');b.disabled=disabled;b.onclick=function(){tbl.setAttribute('data-page',p);renderPagination(tableId);};return b;}pag.appendChild(btn('\u00AB',1,false,page===1));pag.appendChild(btn('\u2039',page-1,false,page===1));var start=Math.max(1,page-2),end=Math.min(pages,page+2);for(var i=start;i<=end;i++)pag.appendChild(btn(i,i,i===page,false));pag.appendChild(btn('\u203A',page+1,false,page===pages));pag.appendChild(btn('\u00BB',pages,false,page===pages));var info=document.createElement('span');info.className='pag-info';info.textContent='Page '+page+' of '+pages;pag.appendChild(info);}
821
+ document.querySelectorAll('table[id]').forEach(function(tbl){renderPagination(tbl.id);var meta=document.getElementById(tbl.id+'-meta');if(meta){var total=tbl.querySelectorAll('tbody tr').length;meta.textContent=total+' row'+(total!==1?'s':'');}});
822
+ window.exportTableCsv=function(tableId){var tbl=document.getElementById(tableId);if(!tbl)return;var rows=[Array.from(tbl.querySelectorAll('thead th')).map(function(th){return'"'+th.textContent.replace(/"/g,'""').trim()+'"';}).join(',')];tbl.querySelectorAll('tbody tr').forEach(function(tr){rows.push(Array.from(tr.cells).map(function(td){return'"'+td.textContent.replace(/"/g,'""').trim()+'"';}).join(','));});downloadFile(tableId+'.csv',rows.join('\n'),'text/csv');};
823
+ window.exportTableJson=function(tableId){var tbl=document.getElementById(tableId);if(!tbl)return;var headers=Array.from(tbl.querySelectorAll('thead th')).map(function(th){return th.textContent.trim().replace(/[\u2191\u2193\u21C5]/g,'').trim();});var data=Array.from(tbl.querySelectorAll('tbody tr')).map(function(tr){var obj={};Array.from(tr.cells).forEach(function(td,i){obj[headers[i]||i]=td.textContent.trim();});return obj;});downloadFile(tableId+'.json',JSON.stringify(data,null,2),'application/json');};
824
+ window.exportFullJson=function(){downloadFile('ai-localize-report.json',JSON.stringify({framework:'react-vite',filesScanned:148,hardcodedTexts:37,missingTranslations:12,unusedKeys:4,coveragePct:68},null,2),'application/json');};
825
+ window.exportFullCsv=function(){var lines=['"Metric","Value"','"Framework","react-vite"','"Files Scanned",148','"Hardcoded Texts",37','"Missing Translations",12','"Unused Keys",4','"Coverage %",68'];downloadFile('ai-localize-summary.csv',lines.join('\n'),'text/csv');};
826
+ function downloadFile(filename,content,mimeType){var a=document.createElement('a');a.href=URL.createObjectURL(new Blob([content],{type:mimeType}));a.download=filename;a.click();setTimeout(function(){URL.revokeObjectURL(a.href);},1000);}
827
+ document.addEventListener('keydown',function(e){if(e.key==='Escape'){document.querySelectorAll('.table-search').forEach(function(inp){inp.value='';var tbl=inp.closest('.table-wrapper').querySelector('table');if(tbl)filterTable(tbl.id,'');});}if((e.metaKey||e.ctrlKey)&&e.key==='d'){e.preventDefault();window.toggleTheme();}});
828
+ document.querySelectorAll('a.nav-item[href^="#"]').forEach(function(a){a.addEventListener('click',function(e){e.preventDefault();var target=document.querySelector(a.getAttribute('href'));if(target)target.scrollIntoView({behavior:'smooth',block:'start'});});});
829
+ })();</script>
830
+ </body>
831
+ </html>