From da4aa5a1ae6b00814f27261c1cbe00fc355d416a Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sun, 17 May 2026 18:19:13 +0000 Subject: [PATCH] fix: shelf life formaggio, banner vacuum/modifica, preloader redesign, ricetta da ingrediente, porzioni, modal ricetta testo tradotto, use_btn semplificato --- api/database.php | 4 +- api/index.php | 58 +++++++++++++----- assets/css/style.css | 143 +++++++++++++++++++------------------------ assets/js/app.js | 102 ++++++++++++++++++++++++------ translations/de.json | 11 ++-- translations/en.json | 11 ++-- translations/it.json | 11 ++-- 7 files changed, 210 insertions(+), 130 deletions(-) diff --git a/api/database.php b/api/database.php index 8d0c593..fa8660f 100644 --- a/api/database.php +++ b/api/database.php @@ -379,8 +379,10 @@ function estimateOpenedExpiryDaysPHP(string $name, string $category, string $loc if (preg_match('/\b(yogurt|yaourt|yoghurt)\b/', $n)) return 5; if (preg_match('/mozzarella|burrata|stracciatella/', $n)) return 3; if (preg_match('/philadelphia|spalmabile/', $n)) return 7; + // Specific hard cheeses that contain 'fresco' in their commercial name (e.g. Asiago fresco) + // must be matched BEFORE the generic 'formaggio fresco' catch-all + if (preg_match('/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza|groviera/', $n)) return 28; if (preg_match('/formaggio.*(fresco|ricotta|mascarpone|stracchino|crescenza)/', $n)) return 5; - if (preg_match('/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza/', $n)) return 28; if (preg_match('/formaggio/', $n)) return 10; if (preg_match('/\bburro\b/', $n)) return 30; if (preg_match('/\bpanna\b/', $n)) return 4; diff --git a/api/index.php b/api/index.php index b99f6aa..d607717 100644 --- a/api/index.php +++ b/api/index.php @@ -3828,7 +3828,7 @@ You are an expert home chef. Generate ONE recipe for $mealLabel for $persons per REGOLE: {$mealPlanRule}1. PRIORITΓ€: usa prima gli ingredienti scaduti/in scadenza (βš οΈπŸ”΄πŸŸ ), poi quelli [APERTO], poi il resto. 2. Usa SOLO ingredienti dalla lista + acqua/sale/pepe/olio (sempre disponibili). -3. QuantitΓ  MASSIME per $persons persona/e (NON superare mai): pasta/riso asciutto 90g/pers, carne 180g/pers, pesce 200g/pers, legumi secchi 80g/pers (lessi 200g/pers), verdure contorno 200g/pers, formaggio 80g/pers, latte 200ml/pers, farina per dolci 200g/pers. Se un ingrediente rimasto Γ¨ inferiore a questi limiti, usalo tutto. +3. QuantitΓ  MASSIME per $persons persona/e (NON superare mai): pasta/riso asciutto 90g/pers, carne 150g/pers, affettati/salumi/speck/prosciutto 70g/pers, pesce 180g/pers, legumi secchi 80g/pers (lessi 200g/pers), verdure contorno 150g/pers, verdure intere grosse (peperoni/melanzane/zucchine) 1 pz/pers, formaggio 70g/pers, latte 200ml/pers, farina per dolci 200g/pers, piadina/tortilla/wrap 1-2 pz/pers. Se un ingrediente rimasto Γ¨ inferiore a questi limiti, usalo tutto. 4. "qty_number": valore NUMERICO nella STESSA unitΓ  della dispensa (g/ml/pz/conf, MAI kg o litri). Per non-dispensa: 0. IMPORTANTE: per ingredienti con unitΓ  "pz" scrivi qty_number come numero di PEZZI (es. 2, non 200g). 5. "name": usa ESATTAMENTE il nome dalla lista (il sistema lo usa per scalare l'inventario). 6. Includi nella lista ingredienti TUTTI quelli citati nei passi (tranne acqua/sale/pepe/olio). @@ -4196,6 +4196,7 @@ function recipeFromIngredient(PDO $db): void { } $lang = recipeNormalizeLang($input['lang'] ?? 'it'); $langName = recipeLangName($lang); + $persons = max(1, intval($input['persons'] ?? 1)); // Fetch inventory (same as generateRecipe) $stmt = $db->query(" @@ -4208,22 +4209,45 @@ function recipeFromIngredient(PDO $db): void { "); $items = $stmt->fetchAll(PDO::FETCH_ASSOC); + // Build compact pantry text (same logic as generateRecipe) + $ingredientLines = []; + foreach ($items as $item) { + $line = "- {$item['name']}: {$item['quantity']} {$item['unit']}"; + if ($item['unit'] === 'conf' && !empty($item['package_unit']) && $item['default_quantity'] > 0) { + $line .= " ({$item['default_quantity']}{$item['package_unit']}/conf)"; + } + if ($item['unit'] === 'pz') $line .= ' [usa PEZZI interi]'; + $dl = intval($item['days_left']); + if (!empty($item['expiry_date'])) { + if ($dl < 0) $line .= ' ⚠️SCADUTO'; + elseif ($dl <= 3) $line .= " πŸ”΄{$dl}gg"; + elseif ($dl <= 7) $line .= " 🟠{$dl}gg"; + } + if (!empty($item['opened_at'])) $line .= ' [APERTO]'; + $ingredientLines[] = $line; + } + $ingredientsText = implode("\n", $ingredientLines); + $safeName = htmlspecialchars($ingredientName, ENT_QUOTES, 'UTF-8'); $prompt = << 'Missing meal or recipe']); + if (!$recipe) { + echo json_encode(['error' => 'Missing recipe']); return; } diff --git a/assets/css/style.css b/assets/css/style.css index 9757498..528c815 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -72,12 +72,12 @@ body { #app-preloader { position: fixed; inset: 0; - background: var(--bg-dark, #0f172a); + background: #0c1222; z-index: 200000; display: flex; align-items: center; justify-content: center; - transition: opacity 0.35s ease; + transition: opacity 0.45s ease; } #app-preloader.fade-out { opacity: 0; @@ -87,123 +87,89 @@ body { display: flex; flex-direction: column; align-items: center; - gap: 20px; + gap: 28px; } .app-preloader-spinner { - width: 48px; - height: 48px; - border: 4px solid rgba(255,255,255,0.15); + width: 40px; + height: 40px; + border: 3px solid rgba(255,255,255,0.1); border-top-color: #4ade80; border-radius: 50%; - animation: spin 0.8s linear infinite; + animation: spin 0.9s linear infinite; } .app-preloader-label { color: rgba(255,255,255,0.75); - font-size: 1.2rem; - font-weight: 600; + font-size: 1.1rem; + font-weight: 500; letter-spacing: 0.5px; } .app-preloader-logo { - height: 160px; + height: 150px; width: auto; object-fit: contain; - filter: drop-shadow(0 4px 16px rgba(74,222,128,0.2)); + animation: logoPulse 3.2s ease-in-out infinite; +} +@keyframes logoPulse { + 0%, 100% { filter: drop-shadow(0 4px 20px rgba(74,222,128,0.18)); } + 50% { filter: drop-shadow(0 4px 36px rgba(74,222,128,0.48)); } } .app-preloader-version { - color: rgba(255,255,255,0.35); - font-size: 0.72rem; + color: rgba(255,255,255,0.22); + font-size: 0.68rem; font-family: monospace; - letter-spacing: 0.5px; - margin-top: -8px; + letter-spacing: 0.6px; + margin-top: -16px; } -/* ── Startup progress bar ───────────────────────────────────────────── */ +/* ── Startup progress section ────────────────────────────────────────── */ .preloader-progress-wrap { display: flex; flex-direction: column; align-items: center; - gap: 10px; - width: min(92vw, 600px); - animation: zwFadeIn 0.2s ease; + gap: 14px; + width: min(92vw, 520px); + animation: zwFadeIn 0.25s ease; } .preloader-bar-track { width: 100%; - height: 6px; - background: rgba(255,255,255,0.12); + height: 3px; + background: rgba(255,255,255,0.08); border-radius: 99px; - overflow: hidden; + overflow: visible; } .preloader-bar { height: 100%; width: 0%; background: linear-gradient(90deg, #4ade80, #22c55e); border-radius: 99px; - transition: width 0.18s ease, background 0.3s ease; + transition: width 0.22s cubic-bezier(0.4,0,0.2,1), background 0.3s ease; + box-shadow: 0 0 8px rgba(74,222,128,0.55); } -.preloader-bar.bar-error { background: linear-gradient(90deg, #f87171, #ef4444); } -.preloader-bar.bar-warn { background: linear-gradient(90deg, #fbbf24, #f59e0b); } -.preloader-check-label { display: none; } /* replaced by check-wheel */ +.preloader-bar.bar-error { background: linear-gradient(90deg,#f87171,#ef4444); box-shadow: 0 0 8px rgba(239,68,68,0.5); } +.preloader-bar.bar-warn { background: linear-gradient(90deg,#fbbf24,#f59e0b); box-shadow: 0 0 8px rgba(251,191,36,0.5); } +.preloader-check-label { display: none; } -/* ── Startup check ticker (smooth fade queue) ───────────────────────── */ -.preloader-progress-wrap { - width: min(96vw, 860px) !important; -} +/* ── Status line: single element, opacity crossfade via JS ─────────── */ .check-ticker { - position: relative; width: 100%; - height: 170px; - overflow: hidden; - margin-top: 6px; + height: 1.5rem; + display: flex; + align-items: center; + justify-content: center; } -.ticker-item { - position: absolute; - left: 0; right: 0; - text-align: center; - padding: 0 12px; - line-height: 1.45; - pointer-events: none; +.preloader-status-text { + font-size: clamp(0.78rem, 2vw, 0.9rem); + font-weight: 500; + letter-spacing: 0.03em; + color: rgba(255,255,255,0.45); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + max-width: 100%; + padding: 0 12px; } -/* Current β€” fades in from below, bright and large */ -.ticker-current { - bottom: 8px; - font-size: clamp(1.1rem, 3.2vw, 1.35rem); - font-weight: 700; - opacity: 1; - color: #4ade80; - letter-spacing: 0.01em; - animation: tickerFadeIn 0.5s cubic-bezier(0.22, 1, 0.36, 1); -} -.ticker-current.state-ok { color: #4ade80; } -.ticker-current.state-warn { color: #fbbf24; } -.ticker-current.state-error { color: #f87171; } -/* Previous items β€” progressively dimmer and smaller, scrolling up */ -.ticker-prev-1 { - bottom: 54px; - font-size: clamp(0.92rem, 2.5vw, 1.1rem); - font-weight: 600; - opacity: 0.48; - color: #94a3b8; -} -.ticker-prev-2 { - bottom: 94px; - font-size: clamp(0.78rem, 2vw, 0.92rem); - font-weight: 500; - opacity: 0.22; - color: #64748b; -} -.ticker-prev-3 { - bottom: 128px; - font-size: clamp(0.66rem, 1.7vw, 0.78rem); - font-weight: 400; - opacity: 0.09; - color: #475569; -} -@keyframes tickerFadeIn { - from { opacity: 0; transform: translateY(22px); } - to { opacity: 1; transform: translateY(0); } -} +.preloader-status-text.state-ok { color: #86efac; } +.preloader-status-text.state-warn { color: #fde68a; } +.preloader-status-text.state-error { color: #fca5a5; } .preloader-warnings { max-width: min(92vw, 600px); width: 100%; @@ -4225,6 +4191,15 @@ body.server-offline .bottom-nav { min-width: 0; line-height: 1.4; } +.recipe-ing-name { + cursor: pointer; + border-bottom: 1px dashed rgba(74,222,128,0.5); + transition: color 0.15s, border-color 0.15s; +} +.recipe-ing-name:hover { + color: #4ade80; + border-bottom-color: #4ade80; +} .btn-use-ingredient { flex-shrink: 0; @@ -7069,6 +7044,14 @@ body.cooking-mode-active .app-header { color: #9ca3af; font-size: 0.8em; } +.btn-banner-vacuum { + background: #ede9fe; + color: #6d28d9; +} +.btn-banner-edit2 { + background: #e0f2fe; + color: #0369a1; +} /* ===== PAGE HEADER ACTION BUTTON (export etc.) ===== */ .page-header-action-btn { diff --git a/assets/js/app.js b/assets/js/app.js index f27a5ad..03b3c00 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1740,8 +1740,10 @@ function estimateOpenedExpiryDays(product, location) { if (/\b(yogurt|yaourt|yoghurt)\b/.test(name)) return 5; if (/mozzarella|burrata|stracciatella/.test(name)) return 3; if (/philadelphia|spalmabile/.test(name)) return 7; + // Specific hard cheeses that contain 'fresco' in their commercial name (e.g. Asiago fresco) + // must be matched BEFORE the generic 'formaggio fresco' catch-all + if (/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza|groviera/.test(name)) return 28; if (/formaggio.*(fresco|ricotta|mascarpone|stracchino|crescenza)/.test(name)) return 5; - if (/parmigiano|grana|pecorino|provolone|asiago|fontina|emmental|gruyere|scamorza/.test(name)) return 28; if (/formaggio/.test(name)) return 10; if (/\bburro\b/.test(name)) return 30; if (/\bpanna\b/.test(name)) return 4; @@ -4356,7 +4358,12 @@ function renderBannerItem() { btns += ``; } btns += ``; - btns += ``; + // "Modifica" β€” opens full edit modal (includes date correction) + btns += ``; + if (isOpenedExpiry && !item.vacuum_sealed) { + // Offer to re-seal with vacuum β€” extends shelf life + btns += ``; + } if (!isOpenedExpiry && safety.level === 'danger') { btns += ``; } @@ -4652,6 +4659,43 @@ function bannerThrowAway() { dismissBannerItem(); } +async function bannerMarkVacuum() { + const entry = _bannerQueue[_bannerIndex]; + if (!entry || entry.type !== 'expired') return; + const item = entry.data; + if (item.vacuum_sealed) return; // already sealed + + // Calculate new expiry: opened_at + opened_shelf_life_days_with_vacuum + let newExpiry = null; + if (item.opened_at) { + // estimateOpenedExpiryDays returns days without vacuum; add 50% for vacuum sealed + const baseDays = estimateOpenedExpiryDays( + { name: item.name, category: item.category || '' }, + item.location + ); + const vacuumDays = Math.round(baseDays * 1.5); + const d = new Date(item.opened_at); + d.setDate(d.getDate() + vacuumDays); + newExpiry = d.toISOString().slice(0, 10); + } + + const body = { id: item.id, vacuum_sealed: 1 }; + if (newExpiry) body.expiry_date = newExpiry; + + try { + const res = await api('inventory_update', {}, 'POST', body); + if (res.success || res.ok) { + showToast(t('toast.vacuum_sealed', { name: item.name }), 'success'); + dismissBannerItem(); + loadDashboard(); + } else { + showToast(res.error || t('error.generic'), 'error'); + } + } catch(e) { + showToast(t('error.connection'), 'error'); + } +} + function bannerFinishAll() { const entry = _bannerQueue[_bannerIndex]; if (!entry) return; @@ -8549,7 +8593,7 @@ function showMoveAfterUseModal(product, fromLoc, remaining, openedId, openedVacu const vacuumRow = ` `; document.getElementById('modal-content').innerHTML = ` `; document.getElementById('modal-overlay').style.display = 'flex'; @@ -12316,7 +12360,7 @@ function renderRecipe(r) { const loc = (ing.location || 'dispensa').replace(/'/g, "\\'"); const alreadyUsed = ing.used === true; html += `
  • `; - html += `${ing.name}${ing.brand ? ' (' + ing.brand + ')' : ''}: ${ing.qty} βœ…`; + html += `${ing.name}${ing.brand ? ' (' + ing.brand + ')' : ''}: ${ing.qty} βœ…`; // Detail line: location + expiry let details = []; const ingredientLocLabels = Object.fromEntries(Object.entries(LOCATIONS).map(([k,v]) => [k, `${v.icon} ${v.label}`])); @@ -13694,6 +13738,21 @@ async function chatTransferToRecipes(btn, replyText) { } } +async function openIngredientDetail(productId, location) { + try { + const res = await api('inventory_list'); + const items = res.inventory || res; + // Find by product_id + location; fallback to any row with that product_id + let item = items.find(i => i.product_id === productId && i.location === location); + if (!item) item = items.find(i => i.product_id === productId); + if (!item) { showToast(t('error.not_found'), 'error'); return; } + currentInventory = items; + editInventoryItem(item.id); + } catch(e) { + showToast(t('error.connection'), 'error'); + } +} + async function generateRecipeForIngredient(ingredientName) { if (!_requireGemini()) return; document.getElementById('recipe-overlay').style.display = 'flex'; @@ -14899,9 +14958,8 @@ async function _runStartupCheck() { if (spinnerEl) spinnerEl.style.display = 'none'; wrapEl.style.display = ''; - // Helper: set progress bar + fade ticker queue + // Helper: set progress bar + crossfade status text let _curPct = 0; - const _tickerHistory = []; const setProgress = (pct, label, state) => { _curPct = pct; if (barEl) { @@ -14911,13 +14969,18 @@ async function _runStartupCheck() { if (!label) return; const ticker = document.getElementById('check-ticker'); if (!ticker) return; - _tickerHistory.unshift({ label, state: state || 'ok' }); - if (_tickerHistory.length > 4) _tickerHistory.pop(); - const posClass = ['ticker-current','ticker-prev-1','ticker-prev-2','ticker-prev-3']; - ticker.innerHTML = _tickerHistory.map((item, i) => { - const sc = item.state === 'error' ? 'state-error' : item.state === 'warn' ? 'state-warn' : 'state-ok'; - return `
    ${item.label}
    `; - }).join(''); + const sc = state === 'error' ? 'state-error' : state === 'warn' ? 'state-warn' : 'state-ok'; + // Strip emoji from label β€” colors convey the state + const cleanLabel = label.replace(/[\u{1F000}-\u{1FFFF}\u{2600}-\u{27BF}\u{2700}-\u{27BF}βœ…βŒβš οΈπŸ”„]/gu, '').trim().replace(/^[-–—\s]+/, ''); + let el = ticker.querySelector('.preloader-status-text'); + if (!el) { + el = document.createElement('div'); + el.className = 'preloader-status-text'; + ticker.appendChild(el); + } + // Direct update β€” checks fire every 40ms, any fade would hide most labels + el.className = `preloader-status-text ${sc}`; + el.textContent = cleanLabel; }; // Phase 1: animate 0β†’15% while fetching (so it never looks stuck) @@ -14945,7 +15008,7 @@ async function _runStartupCheck() { tl('error_network_detail', 'Il browser non riesce a raggiungere il server PHP.\n\nPossibili cause:\nβ€’ Il server Apache/PHP non Γ¨ in esecuzione\nβ€’ Problema di rete o firewall\nβ€’ URL dell\'app non corretta\n\nControlla che il server sia avviato e riprova.'), errorEl, retryBtn ); - setProgress(100, `❌ ${tl('error_network', 'Server non raggiungibile')}`, 'error'); + setProgress(100, tl('error_network', 'Server non raggiungibile'), 'error'); return false; } clearInterval(slowAnim); @@ -15016,8 +15079,7 @@ async function _runStartupCheck() { if (!isOk && c.error) lbl += ` β€” ${c.error}`; if (!isOk && c.missing?.length) lbl += ` β€” mancanti: ${c.missing.join(', ')}`; - const icon = isOk ? 'βœ…' : isOpt ? '⚠️' : '❌'; - setProgress(pct, `${icon} ${lbl}`); + setProgress(pct, lbl, isOk ? 'ok' : isOpt ? 'warn' : 'error'); if (!isOk && !isFresh) { (isOpt ? warnings : errors).push({ def, c }); @@ -15028,7 +15090,7 @@ async function _runStartupCheck() { // ── Errors β†’ red bar + blocking popup ──────────────────────────────────── if (errors.length > 0) { - setProgress(100, `❌ ${tl('critical_error_short', 'Errore critico')}`, 'error'); + setProgress(100, tl('critical_error_short', 'Errore critico'), 'error'); await new Promise(r => setTimeout(r, 300)); const errLines = errors.map(e => { const hint = e.c.hint || (e.c.error ? e.c.error : null); @@ -15044,7 +15106,7 @@ async function _runStartupCheck() { // ── Warnings β†’ amber bar + warning popup auto-close 5s ─────────────────── if (warnings.length > 0) { - setProgress(100, `⚠️ ${warnings.length} ${tl('warnings_found', 'avvisi')}`, 'warn'); + setProgress(100, `${warnings.length} ${tl('warnings_found', 'avvisi')}`, 'warn'); await new Promise(r => setTimeout(r, 200)); // Build warning popup (auto-close 5s) @@ -15056,7 +15118,7 @@ async function _runStartupCheck() { // Hide warning popup warningsEl.style.display = 'none'; } else { - setProgress(100, `βœ… ${tl('all_ok', 'Sistema OK')}`); + setProgress(100, tl('all_ok', 'Sistema OK'), 'ok'); await new Promise(r => setTimeout(r, 600)); } diff --git a/translations/de.json b/translations/de.json index 6ba2db4..91dacc4 100644 --- a/translations/de.json +++ b/translations/de.json @@ -113,6 +113,8 @@ "banner_expired_action_finished": "Habe ich verbraucht!", "banner_expired_action_throw": "Habe ich weggeworfen", "banner_expired_action_edit": "Datum korrigieren", + "banner_expired_action_modify": "Bearbeiten", + "banner_expired_action_vacuum": "Vakuumieren", "banner_anomaly_action_edit": "Bestand korrigieren", "banner_anomaly_action_dismiss": "Menge ist korrekt", "banner_no_expiry_title": "Ablaufdatum fehlt: {name}", @@ -217,7 +219,7 @@ "title": "Was mΓΆchtest du tun?", "add_btn": "πŸ“₯ HINZUFÜGEN", "add_sub": "in Vorrat/KΓΌhlschrank", - "use_btn": "πŸ“€ VERWENDEN / VERBRAUCHEN", + "use_btn": "VERWENDEN", "use_sub": "aus Vorrat/KΓΌhlschrank", "have_title": "πŸ“¦ Schon auf Lager!", "add_more_sub": "weitere Menge", @@ -533,7 +535,7 @@ "prev": "β—€ ZurΓΌck", "next": "Weiter β–Ά", "ingredient_used": "βœ”οΈ Abgezogen", - "ingredient_use_btn": "πŸ“¦ Verwenden", + "ingredient_use_btn": "Usa", "ingredient_deduct_title": "Von Vorrat abziehen", "timer_expired_tts": "Timer {label} abgelaufen!", "timer_warning_tts": "Achtung! {label}: noch 10 Sekunden!", @@ -831,6 +833,7 @@ "thrown_away": "πŸ—‘οΈ {name} weggeworfen!", "thrown_away_partial": "πŸ—‘οΈ {qty} {unit} von {name} weggeworfen", "finished_all": "πŸ“€ {name} aufgebraucht!", + "vacuum_sealed": "{name} als vakuumversiegelt gespeichert", "product_finished_confirmed": "βœ… Entfernt β€” wieder hinzufΓΌgen, wenn du nachkaufst", "appliance_added": "GerΓ€t hinzugefΓΌgt", "item_added": "{name} hinzugefΓΌgt" @@ -1007,8 +1010,8 @@ "thing_rest": "den Rest", "stay_btn": "Nein, bleibt in {location}", "moved_toast": "πŸ“¦ Offene Packung bewegt nach {location}", - "vacuum_restore": "πŸ«™ Vakuum wiederherstellen", - "vacuum_seal_rest": "πŸ”’ Rest vakuumieren" + "vacuum_restore": "Vakuum wiederherstellen", + "vacuum_seal_rest": "Rest vakuumieren" }, "nova": { "1": "Unverarbeitet", diff --git a/translations/en.json b/translations/en.json index f79ce0e..2f97dfd 100644 --- a/translations/en.json +++ b/translations/en.json @@ -113,6 +113,8 @@ "banner_expired_action_finished": "I finished it!", "banner_expired_action_throw": "I threw it away", "banner_expired_action_edit": "Fix date", + "banner_expired_action_modify": "Edit", + "banner_expired_action_vacuum": "Put in vacuum seal", "banner_anomaly_action_edit": "Fix inventory", "banner_anomaly_action_dismiss": "Quantity is correct", "banner_no_expiry_title": "Missing expiry: {name}", @@ -217,7 +219,7 @@ "title": "What do you want to do?", "add_btn": "πŸ“₯ ADD", "add_sub": "to pantry/fridge", - "use_btn": "πŸ“€ USE / CONSUME", + "use_btn": "USE", "use_sub": "from pantry/fridge", "have_title": "πŸ“¦ Already in stock!", "add_more_sub": "add more", @@ -533,7 +535,7 @@ "prev": "β—€ Previous", "next": "Next β–Ά", "ingredient_used": "βœ”οΈ Deducted", - "ingredient_use_btn": "πŸ“¦ Use", + "ingredient_use_btn": "Use", "ingredient_deduct_title": "Deduct from pantry", "timer_expired_tts": "Timer {label} expired!", "timer_warning_tts": "Heads up! {label}: 10 seconds left!", @@ -831,6 +833,7 @@ "thrown_away": "πŸ—‘οΈ {name} thrown away!", "thrown_away_partial": "πŸ—‘οΈ Thrown away {qty} {unit} of {name}", "finished_all": "πŸ“€ {name} finished!", + "vacuum_sealed": "{name} saved as vacuum sealed", "product_finished_confirmed": "βœ… Removed β€” add it again when you restock", "appliance_added": "Appliance added", "item_added": "{name} added" @@ -1007,8 +1010,8 @@ "thing_rest": "rest", "stay_btn": "No, stay in {location}", "moved_toast": "πŸ“¦ Opened package moved to {location}", - "vacuum_restore": "πŸ«™ Restore vacuum sealed", - "vacuum_seal_rest": "πŸ”’ Vacuum seal the rest" + "vacuum_restore": "Restore vacuum sealed", + "vacuum_seal_rest": "Vacuum seal the rest" }, "nova": { "1": "Unprocessed", diff --git a/translations/it.json b/translations/it.json index dc71620..7496049 100644 --- a/translations/it.json +++ b/translations/it.json @@ -113,6 +113,8 @@ "banner_expired_action_finished": "L'ho finito!", "banner_expired_action_throw": "L'ho buttato", "banner_expired_action_edit": "Correggi data", + "banner_expired_action_modify": "Modifica", + "banner_expired_action_vacuum": "Metti sottovuoto", "banner_anomaly_action_edit": "Correggi inventario", "banner_anomaly_action_dismiss": "La quantitΓ  Γ¨ giusta", "banner_no_expiry_title": "Scadenza mancante: {name}", @@ -217,7 +219,7 @@ "title": "Cosa vuoi fare?", "add_btn": "πŸ“₯ AGGIUNGI", "add_sub": "in dispensa/frigo", - "use_btn": "πŸ“€ USA / CONSUMA", + "use_btn": "USA", "use_sub": "dalla dispensa/frigo", "have_title": "πŸ“¦ Ce l'hai giΓ !", "add_more_sub": "altra quantitΓ ", @@ -533,7 +535,7 @@ "prev": "β—€ Precedente", "next": "Successivo β–Ά", "ingredient_used": "βœ”οΈ Scalato", - "ingredient_use_btn": "πŸ“¦ Usa", + "ingredient_use_btn": "Usa", "ingredient_deduct_title": "Scala dalla dispensa", "timer_expired_tts": "Timer {label} scaduto!", "timer_warning_tts": "Attenzione! {label}: mancano 10 secondi!", @@ -831,6 +833,7 @@ "thrown_away": "πŸ—‘οΈ {name} buttato!", "thrown_away_partial": "πŸ—‘οΈ Buttato {qty} {unit} di {name}", "finished_all": "πŸ“€ {name} terminato!", + "vacuum_sealed": "{name} salvato come sottovuoto", "product_finished_confirmed": "βœ… Rimosso β€” riaggiungi quando ne ricompri", "appliance_added": "Elettrodomestico aggiunto", "item_added": "{name} aggiunto" @@ -1007,8 +1010,8 @@ "thing_rest": "il resto", "stay_btn": "No, resta in {location}", "moved_toast": "πŸ“¦ Confezione aperta spostata in {location}", - "vacuum_restore": "πŸ«™ Torna sotto vuoto", - "vacuum_seal_rest": "πŸ”’ Metti sotto vuoto il resto" + "vacuum_restore": "Torna sotto vuoto", + "vacuum_seal_rest": "Metti sotto vuoto il resto" }, "nova": { "1": "Non trasformato",