Skip to content

Commit a703b9a

Browse files
feat: Implement translation management system with GitHub TSV fetching, caching, and glossary support.
1 parent ff13740 commit a703b9a

File tree

3 files changed

+312
-12
lines changed

3 files changed

+312
-12
lines changed

docs/assets/css/translate.css

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,49 @@ main {
3131
color: var(--text-secondary);
3232
}
3333

34+
#loading p {
35+
color: var(--text-secondary);
36+
font-size: 0.95rem;
37+
margin-top: 1rem;
38+
}
39+
40+
/* Loading progress steps */
41+
.loading-progress {
42+
display: flex;
43+
gap: 0.5rem;
44+
margin-top: 1.5rem;
45+
flex-wrap: wrap;
46+
justify-content: center;
47+
}
48+
49+
.loading-step {
50+
display: flex;
51+
align-items: center;
52+
gap: 0.4rem;
53+
padding: 0.5rem 0.75rem;
54+
background: rgba(255, 255, 255, 0.05);
55+
border: 1px solid rgba(255, 255, 255, 0.1);
56+
border-radius: 8px;
57+
font-size: 0.75rem;
58+
color: var(--text-muted);
59+
transition: all 0.3s ease;
60+
}
61+
62+
.loading-step.active {
63+
background: rgba(201, 162, 39, 0.15);
64+
border-color: var(--gold-primary);
65+
color: var(--gold-primary);
66+
}
67+
68+
.loading-step.active i {
69+
animation: pulse 1s infinite;
70+
}
71+
72+
@keyframes pulse {
73+
0%, 100% { opacity: 1; }
74+
50% { opacity: 0.5; }
75+
}
76+
3477
/* ========== STATS BAR ========== */
3578
.stats-bar {
3679
display: flex;
@@ -519,6 +562,47 @@ tr:hover {
519562
font-style: italic;
520563
}
521564

565+
/* ========== ID WRAPPER WITH COPY ========== */
566+
.id-wrapper {
567+
display: inline-flex;
568+
align-items: center;
569+
gap: 0.4rem;
570+
}
571+
572+
.btn-copy-id {
573+
display: inline-flex;
574+
align-items: center;
575+
justify-content: center;
576+
width: 24px;
577+
height: 24px;
578+
padding: 0;
579+
background: transparent;
580+
border: 1px solid rgba(255, 255, 255, 0.1);
581+
border-radius: 4px;
582+
color: var(--text-muted);
583+
font-size: 0.7rem;
584+
cursor: pointer;
585+
opacity: 0;
586+
transition: all 0.2s ease;
587+
}
588+
589+
tr:hover .btn-copy-id {
590+
opacity: 1;
591+
}
592+
593+
.btn-copy-id:hover {
594+
background: rgba(201, 162, 39, 0.15);
595+
border-color: var(--gold-primary);
596+
color: var(--gold-primary);
597+
}
598+
599+
.btn-copy-id.copied {
600+
background: rgba(34, 197, 94, 0.2);
601+
border-color: #4ade80;
602+
color: #4ade80;
603+
opacity: 1;
604+
}
605+
522606
/* ========== ACTION BUTTONS ========== */
523607
.btn-edit {
524608
display: inline-flex;
@@ -2033,6 +2117,62 @@ footer a {
20332117
transform: translateY(0);
20342118
}
20352119

2120+
/* ========== CHARACTER COUNTER ========== */
2121+
.char-counter {
2122+
display: flex;
2123+
align-items: center;
2124+
gap: 0.5rem;
2125+
margin-top: 0.5rem;
2126+
padding: 0.5rem 0.75rem;
2127+
background: rgba(255, 255, 255, 0.03);
2128+
border-radius: 6px;
2129+
font-size: 0.8rem;
2130+
font-family: 'JetBrains Mono', monospace;
2131+
}
2132+
2133+
.char-counter .counter-current {
2134+
font-weight: 600;
2135+
color: var(--text-primary);
2136+
}
2137+
2138+
.char-counter .counter-separator {
2139+
color: var(--text-muted);
2140+
}
2141+
2142+
.char-counter .counter-original {
2143+
color: var(--text-secondary);
2144+
}
2145+
2146+
.char-counter .counter-diff {
2147+
margin-left: auto;
2148+
display: flex;
2149+
align-items: center;
2150+
gap: 0.3rem;
2151+
padding: 0.2rem 0.5rem;
2152+
border-radius: 4px;
2153+
font-size: 0.75rem;
2154+
}
2155+
2156+
.char-counter.neutral .counter-diff {
2157+
background: rgba(255, 255, 255, 0.05);
2158+
color: var(--text-muted);
2159+
}
2160+
2161+
.char-counter.longer .counter-diff {
2162+
background: rgba(59, 130, 246, 0.15);
2163+
color: #60a5fa;
2164+
}
2165+
2166+
.char-counter.shorter .counter-diff {
2167+
background: rgba(34, 197, 94, 0.15);
2168+
color: #4ade80;
2169+
}
2170+
2171+
.char-counter.warning .counter-diff {
2172+
background: rgba(245, 158, 11, 0.2);
2173+
color: #fbbf24;
2174+
}
2175+
20362176
/* ===== MOBILE LAYOUT - Stacked & Compact ===== */
20372177
@media (max-width: 900px) {
20382178
.modal-overlay {

docs/assets/js/glossary.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ async function loadGlossary() {
3737
// Initial render
3838
applyFilters();
3939

40+
// ========== DEEP-LINK SUPPORT ==========
41+
// Check if URL has #term_id to open modal directly
42+
if (window.location.hash) {
43+
const termId = decodeURIComponent(window.location.hash.slice(1));
44+
const term = glossaryData.terms.find(t => t.id === termId);
45+
if (term) {
46+
setTimeout(() => openTermModal(termId), 300);
47+
}
48+
}
49+
4050
} catch (error) {
4151
console.error('Error loading glossary:', error);
4252

@@ -228,10 +238,11 @@ function renderGrid() {
228238
const category = glossaryData.categories[term.category];
229239
const categoryColor = category?.color || '#c9a227';
230240

241+
// Usar data-id para evitar problemas com aspas no onclick
231242
return `
232243
<div class="term-card ${term.doNotTranslate ? 'no-translate' : ''}"
233244
style="--category-color: ${categoryColor}"
234-
onclick="openTermModal('${term.id}')">
245+
data-term-id="${encodeURIComponent(term.id)}">
235246
<div class="term-header">
236247
<span class="term-original">${escapeHtml(term.original)}</span>
237248
${term.chinese ? `<span class="term-chinese">${term.chinese}</span>` : ''}
@@ -250,6 +261,14 @@ function renderGrid() {
250261
</div>
251262
`;
252263
}).join('');
264+
265+
// Event delegation para cards
266+
grid.querySelectorAll('.term-card').forEach(card => {
267+
card.addEventListener('click', function() {
268+
const termId = decodeURIComponent(this.dataset.termId);
269+
openTermModal(termId);
270+
});
271+
});
253272
}
254273

255274
// ========== RENDER PAGINATION ==========
@@ -326,6 +345,9 @@ function openTermModal(termId) {
326345
currentTermId = termId;
327346
const category = glossaryData.categories[term.category];
328347

348+
// Update URL hash for sharing
349+
history.replaceState(null, '', `#${encodeURIComponent(termId)}`);
350+
329351
// Populate modal
330352
document.getElementById('modal-title').textContent = term.original;
331353
document.getElementById('modal-original').textContent = term.original;
@@ -383,6 +405,9 @@ function closeTermModal() {
383405
document.getElementById('term-modal').classList.remove('active');
384406
document.body.style.overflow = '';
385407
currentTermId = null;
408+
409+
// Clear URL hash
410+
history.replaceState(null, '', window.location.pathname);
386411
}
387412

388413
function copyTerm() {

0 commit comments

Comments
 (0)