mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
feat: dynamic language loading and dropdown list
This commit is contained in:
@@ -34,9 +34,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<div class="lang-seg">
|
<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>
|
</div>
|
||||||
<button id="theme-toggle" class="theme-toggle"
|
<button id="theme-toggle" class="theme-toggle"
|
||||||
data-i18n-title="theme.toggleTitle"
|
data-i18n-title="theme.toggleTitle"
|
||||||
|
|||||||
+39
-21
@@ -11,7 +11,7 @@ import { decimate } from './decimation.js';
|
|||||||
import { exportSTL } from './exporter.js';
|
import { exportSTL } from './exporter.js';
|
||||||
import { buildAdjacency, bucketFill,
|
import { buildAdjacency, bucketFill,
|
||||||
buildExclusionOverlayGeo, buildFaceWeights } from './exclusion.js';
|
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 ─────────────────────────────────────────────────────────────────────
|
// ── State ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -239,6 +239,9 @@ const imprintLink = document.getElementById('imprint-link');
|
|||||||
const imprintOverlay = document.getElementById('imprint-overlay');
|
const imprintOverlay = document.getElementById('imprint-overlay');
|
||||||
const imprintClose = document.getElementById('imprint-close');
|
const imprintClose = document.getElementById('imprint-close');
|
||||||
|
|
||||||
|
// ── Language selector DOM refs ────────────────────────────────────────────────────
|
||||||
|
const languageSelector = document.querySelector('.lang-seg');
|
||||||
|
|
||||||
// ── Scale slider log helpers ──────────────────────────────────────────────────
|
// ── Scale slider log helpers ──────────────────────────────────────────────────
|
||||||
// Slider stores 0–1000; actual scale spans 0.05–10 on a log axis.
|
// Slider stores 0–1000; actual scale spans 0.05–10 on a log axis.
|
||||||
// Middle position 500 → scale ~0.71 (log midpoint between 0.05 and 10).
|
// 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
|
// Apply saved theme to 3D viewport on startup
|
||||||
setViewerTheme(document.documentElement.getAttribute('data-theme') === 'light');
|
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)
|
// Initialise language (reads localStorage / browser preference, applies translations)
|
||||||
initLang();
|
initLang();
|
||||||
|
|
||||||
// Sync lang buttons to current language
|
// Sync lang dropdown to current language
|
||||||
(function() {
|
(function() {
|
||||||
const lang = getLang();
|
const lang = getLang();
|
||||||
document.querySelectorAll('.lang-btn').forEach(btn => {
|
const select = languageSelector.querySelector('select');
|
||||||
btn.classList.toggle('active', btn.dataset.langCode === lang);
|
if (select) {
|
||||||
});
|
select.value = lang;
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Theme toggle
|
// Theme toggle
|
||||||
@@ -340,22 +374,6 @@ function selectPreset(idx, swatchEl) {
|
|||||||
// ── Event wiring ──────────────────────────────────────────────────────────────
|
// ── Event wiring ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function wireEvents() {
|
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 ──
|
// ── Model loading ──
|
||||||
stlFileInput.addEventListener('change', (e) => {
|
stlFileInput.addEventListener('change', (e) => {
|
||||||
if (e.target.files[0]) handleModelFile(e.target.files[0]);
|
if (e.target.files[0]) handleModelFile(e.target.files[0]);
|
||||||
|
|||||||
@@ -47,31 +47,31 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn {
|
.lang-dropdown {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 0 10px;
|
padding: 0 24px 0 10px; /* Add right padding for default select arrow if present */
|
||||||
background: var(--surface2);
|
background: var(--surface2);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
cursor: pointer;
|
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;
|
transition: background 0.15s, color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn:not(:last-child) {
|
.lang-dropdown:hover {
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lang-btn:hover {
|
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn.active {
|
.lang-dropdown:focus {
|
||||||
background: var(--accent);
|
outline: none;
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Theme toggle button ─────────────────────────────────────────────── */
|
/* ── Theme toggle button ─────────────────────────────────────────────── */
|
||||||
|
|||||||
Reference in New Issue
Block a user