feat: dynamic language loading and dropdown list

This commit is contained in:
Alessio Tudisco
2026-04-05 00:56:59 +02:00
parent cfa9c34962
commit c3c8f56181
3 changed files with 50 additions and 35 deletions
-3
View File
@@ -34,9 +34,6 @@
</div>
<div class="header-actions">
<div class="lang-seg">
<button class="lang-btn active" data-lang-code="en">EN</button>
<button class="lang-btn" data-lang-code="de">DE</button>
<button class="lang-btn" data-lang-code="it">IT</button>
</div>
<button id="theme-toggle" class="theme-toggle"
data-i18n-title="theme.toggleTitle"
+39 -21
View File
@@ -11,7 +11,7 @@ import { decimate } from './decimation.js';
import { exportSTL } from './exporter.js';
import { buildAdjacency, bucketFill,
buildExclusionOverlayGeo, buildFaceWeights } from './exclusion.js';
import { t, initLang, setLang, getLang, applyTranslations } from './i18n.js';
import { t, initLang, setLang, getLang, applyTranslations, TRANSLATIONS } from './i18n.js';
// ── State ─────────────────────────────────────────────────────────────────────
@@ -239,6 +239,9 @@ const imprintLink = document.getElementById('imprint-link');
const imprintOverlay = document.getElementById('imprint-overlay');
const imprintClose = document.getElementById('imprint-close');
// ── Language selector DOM refs ────────────────────────────────────────────────────
const languageSelector = document.querySelector('.lang-seg');
// ── Scale slider log helpers ──────────────────────────────────────────────────
// Slider stores 01000; actual scale spans 0.0510 on a log axis.
// Middle position 500 → scale ~0.71 (log midpoint between 0.05 and 10).
@@ -265,15 +268,46 @@ initViewer(canvas);
// Apply saved theme to 3D viewport on startup
setViewerTheme(document.documentElement.getAttribute('data-theme') === 'light');
// Populate the language selector
function populateLanguageSelector() {
if (!languageSelector) return;
languageSelector.innerHTML = '';
const select = document.createElement('select');
select.className = 'lang-dropdown';
for (const langKey in TRANSLATIONS) {
const opt = document.createElement('option');
opt.value = langKey;
opt.className = 'lang-option';
opt.textContent = TRANSLATIONS[langKey]['lang.name'] || langKey.toUpperCase();
select.appendChild(opt);
}
select.addEventListener('change', (e) => {
setLang(e.target.value);
// Re-translate <option> elements (innerHTML won't reach these)
document.querySelectorAll('select[id="mapping-mode"] option[data-i18n-opt]').forEach(opt => {
opt.textContent = t(opt.dataset.i18nOpt);
});
// Refresh dynamic count text to current language
if (currentGeometry) refreshExclusionOverlay();
});
languageSelector.appendChild(select);
}
populateLanguageSelector();
// Initialise language (reads localStorage / browser preference, applies translations)
initLang();
// Sync lang buttons to current language
// Sync lang dropdown to current language
(function() {
const lang = getLang();
document.querySelectorAll('.lang-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.langCode === lang);
});
const select = languageSelector.querySelector('select');
if (select) {
select.value = lang;
}
})();
// Theme toggle
@@ -340,22 +374,6 @@ function selectPreset(idx, swatchEl) {
// ── Event wiring ──────────────────────────────────────────────────────────────
function wireEvents() {
// ── Language toggle ──
document.querySelectorAll('.lang-btn').forEach(btn => {
btn.addEventListener('click', () => {
const lang = btn.dataset.langCode;
setLang(lang);
document.querySelectorAll('.lang-btn').forEach(b =>
b.classList.toggle('active', b.dataset.langCode === lang));
// Re-translate <option> elements (innerHTML won't reach these)
document.querySelectorAll('select[id="mapping-mode"] option[data-i18n-opt]').forEach(opt => {
opt.textContent = t(opt.dataset.i18nOpt);
});
// Refresh dynamic count text to current language
if (currentGeometry) refreshExclusionOverlay();
});
});
// ── Model loading ──
stlFileInput.addEventListener('change', (e) => {
if (e.target.files[0]) handleModelFile(e.target.files[0]);
+11 -11
View File
@@ -47,31 +47,31 @@
flex-shrink: 0;
}
.lang-btn {
.lang-dropdown {
height: 28px;
padding: 0 10px;
padding: 0 24px 0 10px; /* Add right padding for default select arrow if present */
background: var(--surface2);
border: none;
border-radius: 0;
color: var(--text-muted);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
cursor: pointer;
appearance: none; /* Attempt to remove default styling */
-moz-appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath fill='%2366667a' d='M0 0l4 4 4-4H0z'/%3E%3C/svg%3E"); /* Custom arrow */
background-repeat: no-repeat;
background-position: right 8px center;
transition: background 0.15s, color 0.15s;
}
.lang-btn:not(:last-child) {
border-right: 1px solid var(--border);
}
.lang-btn:hover {
.lang-dropdown:hover {
color: var(--accent);
}
.lang-btn.active {
background: var(--accent);
color: #fff;
.lang-dropdown:focus {
outline: none;
}
/* ── Theme toggle button ─────────────────────────────────────────────── */