diff --git a/CHANGELOG.md b/CHANGELOG.md index fbab979..fc1faba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap. +## [1.7.18] - 2026-05-19 + +### Added +- **Dark mode** — New theme selector in Settings (Appearance card): **Off (Light)**, **On (Dark)**, **Auto (follows system)**. Applied immediately on page load to prevent white flash. Resolves [#78](https://github.com/dadaloop82/EverShelf/issues/78). +- **Export inventory** — New 📤 button in inventory page header opens a modal to download the inventory as **CSV** (UTF-8 with BOM, Excel-compatible) or open a **print-ready HTML page** (auto-triggers print dialog for PDF). Export card also available in Settings tab. Resolves [#64](https://github.com/dadaloop82/EverShelf/issues/64). +- `translations/de.json`: fixed missing `log.recipe_prefix` key. + ## [1.7.17] - 2026-05-19 ### Added diff --git a/README.md b/README.md index 7fea76e..b191c39 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ [![SQLite](https://img.shields.io/badge/SQLite-3-blue.svg)](https://www.sqlite.org/) [![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](Dockerfile) [![i18n](https://img.shields.io/badge/i18n-IT%20%7C%20EN%20%7C%20DE%20%7C%20FR%20%7C%20ES-orange.svg)](translations/) -[![Version](https://img.shields.io/badge/version-1.7.17-brightgreen.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-1.7.18-brightgreen.svg)](CHANGELOG.md) [![GitHub stars](https://img.shields.io/github/stars/dadaloop82/EverShelf?style=social)](https://github.com/dadaloop82/EverShelf/stargazers) [![Last commit](https://img.shields.io/github/last-commit/dadaloop82/EverShelf/main)](https://github.com/dadaloop82/EverShelf/commits/main) [![Contributors](https://img.shields.io/github/contributors/dadaloop82/EverShelf)](https://github.com/dadaloop82/EverShelf/graphs/contributors) diff --git a/api/index.php b/api/index.php index 7977f7b..479b9c0 100644 --- a/api/index.php +++ b/api/index.php @@ -474,6 +474,10 @@ try { guessCategoryFromAI(); break; + case 'export_inventory': + exportInventory($db); + break; + default: http_response_code(404); echo json_encode(['error' => 'Unknown action: ' . $action]); @@ -485,6 +489,107 @@ try { } endif; // end !CRON_MODE +// ===== EXPORT INVENTORY ===== +function exportInventory(PDO $db): void { + $format = strtolower($_GET['format'] ?? 'csv'); + + $stmt = $db->query(" + SELECT p.name, p.brand, p.category, i.location, i.quantity, p.unit, + i.expiry_date, i.added_at, i.opened_at, + COALESCE(i.vacuum_sealed, 0) as vacuum_sealed, + p.barcode, p.notes + FROM inventory i + JOIN products p ON i.product_id = p.id + WHERE i.quantity > 0 + ORDER BY p.name ASC + "); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + $date = date('Y-m-d'); + + if ($format === 'html') { + // Print-ready HTML for browser PDF + header('Content-Type: text/html; charset=utf-8'); + $rows_html = ''; + foreach ($rows as $r) { + $loc_icon = ['dispensa'=>'🗄️','frigo'=>'🧊','freezer'=>'❄️','altro'=>'📦'][$r['location']] ?? '📦'; + $expiry = $r['expiry_date'] ? htmlspecialchars($r['expiry_date']) : '—'; + $brand = $r['brand'] ? htmlspecialchars($r['brand']) : ''; + $rows_html .= '' + . '' . htmlspecialchars($r['name']) . ($brand ? '
' . $brand . '' : '') . '' + . '' . htmlspecialchars(ucfirst($r['category'] ?? '')) . '' + . '' . $loc_icon . ' ' . htmlspecialchars(ucfirst($r['location'])) . '' + . '' . htmlspecialchars($r['quantity']) . ' ' . htmlspecialchars($r['unit'] ?? 'pz') . '' + . '' . $expiry . '' + . '' . ($r['opened_at'] ? '📭 ' . htmlspecialchars($r['opened_at']) : '') . '' + . ''; + } + $count = count($rows); + echo << + + + +EverShelf — Inventory Export {$date} + + + + +

🏠 EverShelf — Inventory

+
Exported: {$date}  ·  {$count} items
+ + + + +{$rows_html} +
Name / BrandCategoryLocationQtyExpiryOpened
+ + + +HTML; + exit; + } + + // Default: CSV download + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename="evershelf-inventory-' . $date . '.csv"'); + // UTF-8 BOM for Excel compatibility + echo "\xEF\xBB\xBF"; + $out = fopen('php://output', 'w'); + fputcsv($out, ['Name','Brand','Category','Location','Quantity','Unit','Expiry Date','Added','Opened At','Vacuum Sealed','Barcode','Notes']); + foreach ($rows as $r) { + fputcsv($out, [ + $r['name'], + $r['brand'] ?? '', + $r['category'] ?? '', + $r['location'], + $r['quantity'], + $r['unit'] ?? 'pz', + $r['expiry_date'] ?? '', + $r['added_at'] ?? '', + $r['opened_at'] ?? '', + $r['vacuum_sealed'] ? 'Yes' : 'No', + $r['barcode'] ?? '', + $r['notes'] ?? '', + ]); + } + fclose($out); + exit; +} + // ===== TTS PROXY ===== function ttsProxy() { $body = json_decode(file_get_contents('php://input'), true); diff --git a/assets/css/style.css b/assets/css/style.css index 9372da4..6459c7b 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -6857,3 +6857,277 @@ body.cooking-mode-active .app-header { color: #9ca3af; font-size: 0.8em; } + +/* ===== PAGE HEADER ACTION BUTTON (export etc.) ===== */ +.page-header-action-btn { + margin-left: auto; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 8px 12px; + font-size: 1.1rem; + cursor: pointer; + transition: all 0.2s; + color: var(--primary); +} +.page-header-action-btn:active { + transform: scale(0.95); + opacity: 0.8; +} + +/* ===== DARK MODE ===== */ +[data-theme="dark"] { + --bg: #0f172a; + --bg-card: #1e293b; + --bg-dark: #020617; + --text: #e2e8f0; + --text-light: #94a3b8; + --text-muted: #64748b; + --text-secondary: #94a3b8; + --border: #334155; + --shadow: 0 2px 8px rgba(0,0,0,0.45); + --shadow-lg: 0 4px 16px rgba(0,0,0,0.6); + color-scheme: dark; +} +[data-theme="dark"] body { + background: var(--bg); + color: var(--text); +} +/* Bottom nav */ +[data-theme="dark"] .bottom-nav { + box-shadow: 0 -2px 10px rgba(0,0,0,0.4); +} +/* Location tabs */ +[data-theme="dark"] .tab { + background: var(--bg-card); + color: var(--text-light); +} +[data-theme="dark"] .tab.active { + background: var(--primary); + color: #fff; +} +/* Location selector (add/use modal) */ +[data-theme="dark"] .location-option { + background: var(--bg-card); + border-color: var(--border); + color: var(--text); +} +[data-theme="dark"] .location-option.selected { + border-color: var(--primary); + background: rgba(45,80,22,0.3); +} +/* Inputs & selects */ +[data-theme="dark"] .form-input, +[data-theme="dark"] .form-control, +[data-theme="dark"] input[type="text"], +[data-theme="dark"] input[type="email"], +[data-theme="dark"] input[type="password"], +[data-theme="dark"] input[type="number"], +[data-theme="dark"] textarea, +[data-theme="dark"] select { + background: var(--bg-card); + color: var(--text); + border-color: var(--border); +} +[data-theme="dark"] input::placeholder, +[data-theme="dark"] textarea::placeholder { + color: var(--text-muted); +} +/* Buttons */ +[data-theme="dark"] .btn-secondary, +[data-theme="dark"] .btn-outline, +[data-theme="dark"] .back-btn, +[data-theme="dark"] .page-header-action-btn { + background: var(--bg-card); + color: var(--text); + border-color: var(--border); +} +[data-theme="dark"] .btn-outline { + color: var(--primary-light); + border-color: var(--primary-light); +} +/* Inventory items */ +[data-theme="dark"] .inventory-item { + background: var(--bg-card); +} +[data-theme="dark"] .inv-location-badge { + background: rgba(45,80,22,0.35); + color: #86efac; +} +/* Shopping items */ +[data-theme="dark"] .shopping-item { + background: var(--bg-card) !important; +} +[data-theme="dark"] .shopping-item-tag-menu-container { + background: var(--bg-card); + border-color: var(--border); +} +[data-theme="dark"] .shopping-item-tag-btn { + background: #1e293b; + color: var(--text-light); + border-color: var(--border); +} +[data-theme="dark"] .badge-local-tag { + background: #0c2a4e; + color: #7dd3fc; +} +[data-theme="dark"] .badge-freq-med { + background: #2e1a4a; + color: #c4b5fd; +} +[data-theme="dark"] .badge-freq-low { + background: #1e293b; + color: #94a3b8; +} +/* Settings rows */ +[data-theme="dark"] .settings-row { + border-color: var(--border); +} +[data-theme="dark"] .settings-label { + color: var(--text); +} +[data-theme="dark"] .settings-hint { + color: var(--text-muted); +} +/* Toggle switch */ +[data-theme="dark"] .toggle-slider { + background: #334155; +} +/* Search bar */ +[data-theme="dark"] .search-bar input { + background: var(--bg-card); + color: var(--text); + border-color: var(--border); +} +/* Action modal location selector */ +[data-theme="dark"] .action-location-btn { + background: var(--bg-card); + border-color: var(--border); + color: var(--text); +} +/* Scan page */ +[data-theme="dark"] .scan-input-row { + background: var(--bg-card); +} +[data-theme="dark"] .scan-result-item { + background: var(--bg-card); + border-color: var(--border); +} +/* Quick access chips */ +[data-theme="dark"] .quick-access-chip { + background: var(--bg-card); + border-color: var(--border); + color: var(--text); +} +/* Scan recents */ +[data-theme="dark"] .scan-recent-chip { + background: var(--bg-card); + border-color: var(--border); + color: var(--text-light); +} +/* Alert banners */ +[data-theme="dark"] .alert-banner { + background: #1e293b; + border-color: #334155; +} +[data-theme="dark"] .alert-banner.banner-expiring { + background: #1c1300; + border-color: #78350f; +} +[data-theme="dark"] .alert-banner.banner-expired { + background: #1f0808; + border-color: #7f1d1d; +} +[data-theme="dark"] .alert-banner.banner-finished { + background: #0f1f0f; + border-color: #166534; +} +[data-theme="dark"] .alert-banner.banner-anomaly { + background: #1a1a2e; + border-color: #4c1d95; +} +/* Recipe dialog */ +[data-theme="dark"] .recipe-dialog-content { + background: var(--bg-card); +} +[data-theme="dark"] .recipe-option-btn { + background: var(--bg-card); + border-color: var(--border); + color: var(--text); +} +[data-theme="dark"] .recipe-option-btn.active { + background: rgba(45,80,22,0.4); + border-color: var(--primary-light); + color: var(--primary-light); +} +/* Log rows */ +[data-theme="dark"] .log-item { + background: var(--bg-card); + border-color: var(--border); +} +/* Dashboard stat cards */ +[data-theme="dark"] .stat-card { + background: var(--bg-card); +} +/* Screensaver */ +[data-theme="dark"] .screensaver-overlay { + background: #020617; +} +/* Charts / nutrition */ +[data-theme="dark"] .nutrition-chart-bg { + background: var(--bg-card); +} +/* AW badges */ +[data-theme="dark"] .aw-badge-rate { background: #2e1a4a; color: #c4b5fd; border-color: #6d28d9; } +[data-theme="dark"] .aw-badge-money { background: #1c1300; color: #fde047; border-color: #78350f; } +[data-theme="dark"] .aw-badge-meals { background: #0f1f0f; color: #4ade80; border-color: #166534; } +[data-theme="dark"] .aw-badge-co2 { background: #0c1f3a; color: #7dd3fc; border-color: #1e3a5f; } +[data-theme="dark"] .aw-badge-wasted{ background: #1f0808; color: #fca5a5; border-color: #7f1d1d; } +[data-theme="dark"] .aw-badge-better{ background: #0f1f0f; color: #4ade80; border-color: #166534; } +/* Chat */ +[data-theme="dark"] .chat-input { + background: var(--bg-card); + color: var(--text); + border-color: var(--border); +} +[data-theme="dark"] .chat-message.user { + background: var(--primary-dark); +} +[data-theme="dark"] .chat-message.bot { + background: var(--bg-card); +} +/* Smart shopping forecast */ +[data-theme="dark"] .smart-item { + background: var(--bg-card); + border-color: var(--border); +} +[data-theme="dark"] .smart-filter-btn { + background: var(--bg-card); + color: var(--text-light); + border-color: var(--border); +} +[data-theme="dark"] .smart-filter-btn.active { + background: var(--primary); + color: #fff; + border-color: var(--primary); +} +/* Offline banner */ +[data-theme="dark"] #offline-banner { + background: #450a0a; + border-color: #7f1d1d; +} +/* Setup wizard */ +[data-theme="dark"] .setup-content { + background: var(--bg-card); +} +[data-theme="dark"] .setup-lang-btn { + background: var(--bg-card); + color: var(--text); + border-color: var(--border); +} +[data-theme="dark"] .setup-lang-btn.selected { + background: rgba(45,80,22,0.4); + border-color: var(--primary-light); + color: var(--primary-light); +} +/* @media prefers-color-scheme: auto handled in JS */ diff --git a/assets/js/app.js b/assets/js/app.js index 564ab90..663ff3b 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1047,6 +1047,16 @@ let _currentLang = localStorage.getItem('evershelf_lang') || navigator.language? const _SUPPORTED_LANGS = { it: 'Italiano', en: 'English', de: 'Deutsch', fr: 'Français', es: 'Español' }; if (!_SUPPORTED_LANGS[_currentLang]) _currentLang = 'en'; +// Apply theme IMMEDIATELY to prevent flash of unstyled content +(function _earlyTheme() { + try { + const s = JSON.parse(localStorage.getItem('evershelf_settings') || '{}'); + const mode = s.dark_mode || 'auto'; + const dark = mode === 'on' || (mode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches); + document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); + } catch(e) {} +})(); + // Flatten nested JSON: { a: { b: "x" } } → { "a.b": "x" } function _flattenI18n(obj, prefix = '') { const result = {}; @@ -1156,6 +1166,69 @@ function changeLanguage(lang) { location.reload(); } +// ===== DARK MODE ===== +function _applyTheme() { + const s = getSettings(); + const mode = s.dark_mode || 'auto'; + let isDark; + if (mode === 'on') { + isDark = true; + } else if (mode === 'off') { + isDark = false; + } else { + isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + } + document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); +} + +function _setThemeMode(mode) { + const s = getSettings(); + s.dark_mode = mode; + saveSettingsToStorage(s); + _applyTheme(); +} + +// Listen to system theme changes (for 'auto' mode) +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const s = getSettings(); + if ((s.dark_mode || 'auto') === 'auto') _applyTheme(); +}); + +// ===== EXPORT INVENTORY ===== +function exportInventory(format) { + const url = `api/index.php?action=export_inventory&format=${encodeURIComponent(format)}&_t=${Date.now()}`; + if (format === 'csv') { + // Direct download via trick + const a = document.createElement('a'); + a.href = url; + a.download = `evershelf-inventory-${new Date().toISOString().slice(0,10)}.csv`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } else { + // Open print-ready HTML in new tab + window.open(url, '_blank', 'noopener'); + } +} + +function _showExportModal() { + const html = ` + +
+

${t('export.hint')}

+ + +
`; + openModal(html); +} + const LOCATIONS = { 'dispensa': { icon: '🗄️', label: t('locations.dispensa') }, 'frigo': { icon: '🧊', label: t('locations.frigo') }, @@ -2462,6 +2535,10 @@ async function loadSettingsUI() { if (nativePanel) nativePanel.style.display = ''; } + // Dark mode setting + const dmEl = document.getElementById('setting-dark-mode'); + if (dmEl) dmEl.value = s.dark_mode || 'auto'; + // Populate About section version _loadAboutSection(); } @@ -2784,6 +2861,9 @@ async function saveSettings() { if (ssEl) s.screensaver_enabled = ssEl.checked; const ssTimeoutEl = document.getElementById('setting-screensaver-timeout'); if (ssTimeoutEl) s.screensaver_timeout = parseInt(ssTimeoutEl.value, 10) || 5; + // Dark mode + const dmSaveEl = document.getElementById('setting-dark-mode'); + if (dmSaveEl) { s.dark_mode = dmSaveEl.value; _applyTheme(); } // Meal plan enabled toggle const mpEnabledEl = document.getElementById('setting-meal-plan-enabled'); if (mpEnabledEl) s.meal_plan_enabled = mpEnabledEl.checked; diff --git a/index.html b/index.html index c53152c..6b4792f 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ EverShelf - + @@ -185,6 +185,7 @@
@@ -1287,6 +1288,30 @@
+
+

🌙 Tema / Aspetto

+

Scegli il tema dell'interfaccia.

+
+ + +
+
+
+

📤 Esporta inventario

+

Scarica l'inventario corrente in CSV o apri una versione stampabile (PDF).

+
+ + +
+
@@ -1560,6 +1585,6 @@ - + diff --git a/manifest.json b/manifest.json index d801b48..900e6bd 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name": "EverShelf", "short_name": "EverShelf", "description": "Gestione completa della dispensa di casa con scansione barcode", - "version": "1.7.17", + "version": "1.7.18", "start_url": "/evershelf/", "display": "standalone", "background_color": "#f0f4e8", diff --git a/translations/de.json b/translations/de.json index cf8d215..6406ea6 100644 --- a/translations/de.json +++ b/translations/de.json @@ -500,7 +500,8 @@ "undo_success": "↩ Vorgang rückgängig gemacht für {name}", "already_undone": "Vorgang bereits rückgängig gemacht", "too_old": "Vorgänge älter als 24 Stunden können nicht rückgängig gemacht werden", - "undo_error": "Fehler beim Rückgängigmachen" + "undo_error": "Fehler beim Rückgängigmachen", + "recipe_prefix": "Rezept" }, "chat": { "title": "Gemini Chef", @@ -743,7 +744,15 @@ }, "saved": "✅ Konfiguration gespeichert!", "saved_local": "✅ Konfiguration lokal gespeichert", - "saved_local_error": "⚠️ Lokal gespeichert, Serverfehler: {error}" + "saved_local_error": "⚠️ Lokal gespeichert, Serverfehler: {error}", + "theme": { + "title": "🌙 Erscheinungsbild", + "hint": "Wähle das Interface-Design.", + "label": "🌙 Design", + "off": "☀️ Hell", + "on": "🌙 Dunkel", + "auto": "🔄 Automatisch (System)" + } }, "expiry": { "today": "HEUTE", @@ -1178,5 +1187,12 @@ "report_bug_error": "Bericht konnte nicht gesendet werden. Verbindung prüfen.", "changelog": "Changelog", "github": "GitHub-Repository" + }, + "export": { + "title": "Inventar exportieren", + "hint": "Lade das aktuelle Inventar als CSV herunter oder öffne die druckfertige Version (PDF).", + "btn_csv": "CSV herunterladen", + "btn_pdf": "PDF / Drucken", + "btn_title": "Exportieren" } } \ No newline at end of file diff --git a/translations/en.json b/translations/en.json index f51c17f..4c841e3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -744,7 +744,15 @@ }, "saved": "✅ Configuration saved!", "saved_local": "✅ Configuration saved locally", - "saved_local_error": "⚠️ Saved locally, server error: {error}" + "saved_local_error": "⚠️ Saved locally, server error: {error}", + "theme": { + "title": "🌙 Appearance", + "hint": "Choose the interface theme.", + "label": "🌙 Theme", + "off": "☀️ Light", + "on": "🌙 Dark", + "auto": "🔄 Auto (system)" + } }, "expiry": { "today": "TODAY", @@ -1179,5 +1187,12 @@ "report_bug_error": "Could not send the report. Check your connection.", "changelog": "Changelog", "github": "GitHub Repository" + }, + "export": { + "title": "Export inventory", + "hint": "Download the current inventory as CSV or open a print-ready version (PDF).", + "btn_csv": "Download CSV", + "btn_pdf": "PDF / Print", + "btn_title": "Export" } } \ No newline at end of file diff --git a/translations/es.json b/translations/es.json index be1b924..346ce54 100644 --- a/translations/es.json +++ b/translations/es.json @@ -744,7 +744,15 @@ }, "saved": "✅ ¡Configuración guardada!", "saved_local": "✅ Configuración guardada localmente", - "saved_local_error": "⚠️ Guardado localmente, error del servidor: {error}" + "saved_local_error": "⚠️ Guardado localmente, error del servidor: {error}", + "theme": { + "title": "🌙 Apariencia", + "hint": "Elige el tema de la interfaz.", + "label": "🌙 Tema", + "off": "☀️ Claro", + "on": "🌙 Oscuro", + "auto": "🔄 Automático (sistema)" + } }, "expiry": { "today": "HOY", @@ -1179,5 +1187,12 @@ "report_bug_error": "No se pudo enviar el informe. Comprueba tu conexión.", "changelog": "Registro de cambios", "github": "Repositorio GitHub" + }, + "export": { + "title": "Exportar inventario", + "hint": "Descarga el inventario actual en CSV o abre la versión imprimible (PDF).", + "btn_csv": "Descargar CSV", + "btn_pdf": "PDF / Imprimir", + "btn_title": "Exportar" } -} +} \ No newline at end of file diff --git a/translations/fr.json b/translations/fr.json index ef7de32..c8fcf07 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -744,7 +744,15 @@ }, "saved": "✅ Configuration enregistrée !", "saved_local": "✅ Configuration enregistrée localement", - "saved_local_error": "⚠️ Enregistré localement, erreur serveur : {error}" + "saved_local_error": "⚠️ Enregistré localement, erreur serveur : {error}", + "theme": { + "title": "🌙 Apparence", + "hint": "Choisissez le thème de l'interface.", + "label": "🌙 Thème", + "off": "☀️ Clair", + "on": "🌙 Sombre", + "auto": "🔄 Automatique (système)" + } }, "expiry": { "today": "AUJOURD'HUI", @@ -1179,5 +1187,12 @@ "report_bug_error": "Impossible d'envoyer le rapport. Vérifiez votre connexion.", "changelog": "Journal des modifications", "github": "Dépôt GitHub" + }, + "export": { + "title": "Exporter l'inventaire", + "hint": "Téléchargez l'inventaire actuel en CSV ou ouvrez la version imprimable (PDF).", + "btn_csv": "Télécharger CSV", + "btn_pdf": "PDF / Imprimer", + "btn_title": "Exporter" } -} +} \ No newline at end of file diff --git a/translations/it.json b/translations/it.json index 25d003b..b69ab60 100644 --- a/translations/it.json +++ b/translations/it.json @@ -744,7 +744,15 @@ }, "saved": "✅ Configurazione salvata!", "saved_local": "✅ Configurazione salvata localmente", - "saved_local_error": "⚠️ Salvato localmente, errore server: {error}" + "saved_local_error": "⚠️ Salvato localmente, errore server: {error}", + "theme": { + "title": "🌙 Tema / Aspetto", + "hint": "Scegli il tema dell interfaccia.", + "label": "🌙 Tema", + "off": "☀️ Chiaro", + "on": "🌙 Scuro", + "auto": "🔄 Automatico (sistema)" + } }, "expiry": { "today": "OGGI", @@ -1179,5 +1187,12 @@ "report_bug_error": "Impossibile inviare la segnalazione. Controlla la connessione.", "changelog": "Changelog", "github": "Repository GitHub" + }, + "export": { + "title": "Esporta inventario", + "hint": "Scarica l inventario corrente in CSV o apri la versione stampabile (PDF).", + "btn_csv": "Scarica CSV", + "btn_pdf": "PDF / Stampa", + "btn_title": "Esporta" } } \ No newline at end of file