Improve scan flow, AI UX, expiry history, and shopping sync.

Manual AI identification replaces auto-fallback; add duplicate-add guard,
AI product match UI, ZBar/Tesseract offline scanning, expiry averages from
last 3 insertions, family sibling hints, and missing i18n keys.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dadaloop82
2026-06-06 09:38:05 +00:00
parent 7eda4a5eb9
commit 34dcb05c05
19 changed files with 3370 additions and 558 deletions
+24 -3
View File
@@ -44,9 +44,10 @@ try {
$itemCount = count($decoded['items'] ?? []);
echo '[' . date('Y-m-d H:i:s') . '] OK — ' . $itemCount . " items cached\n";
// ── Bring! server-side cleanup ────────────────────────────────────────
// After computing smart shopping, automatically remove stale Bring! items
// and add/update critical ones. This runs fully server-side every cron cycle.
// ── Bring! server-side sync ───────────────────────────────────────────
// After computing smart shopping, remove stale Bring! items and push every
// product that needs restocking (esauriti, quasi finiti, previsione).
// Runs fully server-side every cron cycle (~5 min).
try {
$cleanupResult = bringCleanupObsolete($db);
if (isset($cleanupResult['skipped'])) {
@@ -57,6 +58,21 @@ try {
. ($cleanupResult['errors'] ? ', errors: ' . $cleanupResult['errors'] : '') . "\n";
}
$dedupeResult = bringDedupeGenerics($db);
if (isset($dedupeResult['skipped'])) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! dedupe skipped: ' . $dedupeResult['skipped'] . "\n";
} else {
echo '[' . date('Y-m-d H:i:s') . '] Bring! dedupe — removed: ' . ($dedupeResult['removed'] ?? 0)
. ', merged specs: ' . ($dedupeResult['merged'] ?? 0) . "\n";
}
$specsResult = bringSyncSpecs($db);
if (isset($specsResult['skipped'])) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! specs skipped: ' . $specsResult['skipped'] . "\n";
} else {
echo '[' . date('Y-m-d H:i:s') . '] Bring! specs — updated: ' . ($specsResult['updated'] ?? 0) . "\n";
}
$addResult = bringAutoAddCritical($db);
if (isset($addResult['skipped'])) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! auto-add skipped: ' . $addResult['skipped'] . "\n";
@@ -64,6 +80,11 @@ try {
echo '[' . date('Y-m-d H:i:s') . '] Bring! auto-add — added: ' . ($addResult['added'] ?? 0)
. ', updated specs: ' . ($addResult['updated'] ?? 0) . "\n";
}
$dedupeFinal = bringDedupeGenerics($db);
if (!isset($dedupeFinal['skipped']) && (($dedupeFinal['removed'] ?? 0) > 0)) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! dedupe (final) — removed: ' . ($dedupeFinal['removed'] ?? 0) . "\n";
}
} catch (Throwable $be) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! sync warning: ' . $be->getMessage() . "\n";
}
+1251 -132
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -156,7 +156,7 @@ function evershelfDemoReadOnlyActions(): array {
return [
'ping', 'check_update', 'health_check', 'get_settings', 'gemini_usage',
'search_barcode', 'lookup_barcode', 'stock_for_name',
'product_get', 'products_list', 'products_search', 'inventory_search',
'product_get', 'products_list', 'products_search', 'inventory_search', 'ai_product_suggest',
'inventory_list', 'inventory_summary', 'inventory_finished_items',
'transactions_list', 'stats', 'monthly_stats', 'macro_stats',
'consumption_predictions', 'inventory_anomalies', 'inventory_duplicate_loss_checks',
+215 -21
View File
@@ -666,6 +666,118 @@ body.server-offline .bottom-nav {
flex-shrink: 0;
}
/* Family sibling hint (spesa mode) — compact card with thumbnail */
.family-sibling-prompt {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
z-index: 9998;
background: linear-gradient(145deg, #1e3a5f 0%, #0f2744 100%);
color: #fff;
border-radius: 14px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 8px;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
max-width: min(440px, calc(100vw - 20px));
width: calc(100% - 20px);
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.12);
animation: family-sibling-in 0.22s ease-out;
}
@keyframes family-sibling-in {
from { opacity: 0; transform: translateX(-50%) translateY(10px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.family-sibling-prompt-body {
display: flex;
gap: 10px;
align-items: flex-start;
}
.family-sibling-prompt-thumb {
flex-shrink: 0;
width: 52px;
height: 52px;
border-radius: 10px;
overflow: hidden;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.family-sibling-prompt-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.family-sibling-prompt-icon {
font-size: 1.6rem;
line-height: 1;
}
.family-sibling-prompt-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.family-sibling-prompt-title {
font-size: 0.72rem;
font-weight: 700;
line-height: 1.2;
margin: 0;
text-transform: uppercase;
letter-spacing: 0.03em;
opacity: 0.8;
}
.family-sibling-prompt-name {
font-size: 0.95rem;
font-weight: 700;
line-height: 1.25;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.family-sibling-prompt-meta {
font-size: 0.8rem;
line-height: 1.3;
margin: 0;
opacity: 0.88;
}
.family-sibling-prompt-question {
font-size: 0.82rem;
line-height: 1.25;
margin: 2px 0 0;
opacity: 0.9;
}
.family-sibling-prompt-actions {
display: flex;
gap: 8px;
}
.family-sibling-prompt-actions button {
flex: 1;
border: none;
border-radius: 10px;
padding: 9px 10px;
font-size: 0.88rem;
font-weight: 700;
cursor: pointer;
min-height: 38px;
}
.family-sibling-prompt-yes {
background: #22c55e;
color: #fff;
}
.family-sibling-prompt-no {
background: #475569;
color: #fff;
}
@keyframes pulse-scan {
0%, 100% { box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
50% { box-shadow: 0 2px 16px rgba(255,255,255,0.4); }
@@ -1845,6 +1957,19 @@ body.server-offline .bottom-nav {
overflow: hidden;
}
/* Spesa mode: hide manual barcode field only — tabs and normal viewport stay */
#page-scan.spesa-scan-layout .barcode-input-row {
display: none !important;
}
.spesa-scan-barcode-hint {
margin: 0;
padding: 6px 4px 2px;
font-size: 0.85rem;
color: var(--text-muted);
text-align: center;
line-height: 1.35;
}
.scanner-viewport video {
width: 100%;
height: 100%;
@@ -2046,21 +2171,25 @@ body.server-offline .bottom-nav {
max-width: 220px;
}
/* — AI retry button (shown below scanner after visual ID fails) — */
.scan-ai-retry-btn {
/* — Manual AI button (user-triggered only) — */
.scan-ai-manual-btn {
width: 100%;
margin-top: 10px;
font-size: 0.95rem;
padding: 12px;
border-radius: var(--radius);
font-size: 1.05rem;
padding: 14px 16px;
border-radius: 14px;
border: 2px solid var(--accent);
background: rgba(124,58,237,0.1);
background: rgba(124,58,237,0.12);
color: var(--accent);
font-weight: 600;
font-weight: 700;
cursor: pointer;
transition: background 0.15s;
transition: background 0.15s, opacity 0.15s;
}
.scan-ai-retry-btn:active { background: rgba(124,58,237,0.22); }
.scan-ai-manual-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.scan-ai-manual-btn:active:not(:disabled) { background: rgba(124,58,237,0.22); }
/* — Viewport overlay controls (torch / zoom / flip) — */
.scan-viewport-controls {
@@ -2115,17 +2244,59 @@ body.server-offline .bottom-nav {
.scan-ai-match-box {
display: flex;
flex-direction: column;
gap: 12px;
gap: 14px;
}
.scan-ai-match-head {
.scan-ai-hero {
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
gap: 14px;
padding: 14px;
background: linear-gradient(135deg, rgba(124,58,237,0.12), rgba(34,197,94,0.1));
border-radius: 14px;
border: 1px solid var(--border);
}
.scan-ai-hero-icon {
font-size: 2.6rem;
line-height: 1;
flex-shrink: 0;
}
.scan-ai-hero-text {
min-width: 0;
flex: 1;
}
.scan-ai-match-title {
font-size: 1rem;
font-size: 0.82rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
margin-bottom: 4px;
}
.scan-ai-hero-name {
font-size: 1.35rem;
font-weight: 800;
line-height: 1.2;
color: var(--text);
word-break: break-word;
}
.scan-ai-hero-brand {
font-size: 1rem;
color: var(--text-muted);
margin-top: 2px;
}
.scan-ai-action-hint {
margin: -6px 0 0;
font-size: 0.92rem;
line-height: 1.4;
color: var(--text-muted);
text-align: center;
}
.scan-ai-or-divider {
margin: 0;
font-size: 0.88rem;
font-weight: 700;
color: var(--text-muted);
text-align: center;
}
.scan-ai-match-subtitle {
font-size: 0.82rem;
@@ -2137,9 +2308,9 @@ body.server-offline .bottom-nav {
gap: 8px;
}
.scan-ai-match-list-title {
font-size: 0.78rem;
font-size: 0.88rem;
text-transform: uppercase;
letter-spacing: 0.06em;
letter-spacing: 0.04em;
color: var(--text-muted);
font-weight: 700;
}
@@ -2160,6 +2331,22 @@ body.server-offline .bottom-nav {
text-align: left;
}
.scan-ai-candidate-item:active { transform: scale(0.99); }
.scan-ai-candidate-thumb {
width: 44px;
height: 44px;
flex-shrink: 0;
border-radius: 10px;
overflow: hidden;
background: var(--bg-card);
display: flex;
align-items: center;
justify-content: center;
}
.scan-ai-candidate-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.scan-ai-candidate-icon {
font-size: 1.3rem;
flex-shrink: 0;
@@ -2172,26 +2359,27 @@ body.server-offline .bottom-nav {
flex: 1;
}
.scan-ai-candidate-name {
font-size: 0.9rem;
font-weight: 600;
font-size: 1rem;
font-weight: 700;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scan-ai-candidate-meta {
font-size: 0.76rem;
font-size: 0.84rem;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scan-ai-candidate-cta {
font-size: 0.74rem;
font-size: 0.82rem;
font-weight: 700;
color: var(--accent);
border: 1px solid var(--accent);
border-radius: 999px;
padding: 3px 8px;
padding: 6px 10px;
flex-shrink: 0;
}
.scan-ai-match-empty {
@@ -2204,6 +2392,12 @@ body.server-offline .bottom-nav {
}
.scan-ai-add-btn {
width: 100%;
font-size: 1.15rem !important;
font-weight: 800 !important;
padding: 16px 20px !important;
min-height: 54px;
border-radius: 14px !important;
box-shadow: 0 4px 14px rgba(34, 197, 94, 0.35);
}
.scan-ai-detected-label {
font-size: 0.72rem;
+1045 -333
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
+26
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+31 -16
View File
@@ -11,11 +11,21 @@
<title>EverShelf</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" type="image/png" href="assets/img/logo/logo_icon.png">
<link rel="stylesheet" href="assets/css/style.css?v=20260603a">
<link rel="stylesheet" href="assets/css/style.css?v=20260606g">
<!-- Core modules (auth, DOM helpers) -->
<script src="assets/js/core/dom.js?v=20260603a"></script>
<script src="assets/js/core/auth.js?v=20260603b"></script>
<!-- QuaggaJS — local vendor with CDN fallback -->
<!-- ZBar WASM — fast barcode engine (primary fallback when native API missing/slow) -->
<script src="assets/vendor/zbar/index.js?v=20260606a"></script>
<script>
if (window.zbarWasm && zbarWasm.setModuleArgs) {
zbarWasm.setModuleArgs({
locateFile: (file) => 'assets/vendor/zbar/' + file
});
}
</script>
<script src="assets/vendor/zbar/polyfill.js?v=20260606a"></script>
<!-- QuaggaJS — legacy last-resort only -->
<script src="assets/vendor/quagga/quagga.min.js?v=20260603a"></script>
<script>if(typeof Quagga==='undefined'){document.write('<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"><\\/script>');}</script>
<!-- @xenova/transformers: ES-module bootstrap that exposes a lazy category-classifier as window._categoryPipelinePromise -->
@@ -31,13 +41,25 @@
try {
const localBase = 'assets/vendor/transformers/';
const cdnBase = 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/';
const modelProbe = localBase + 'Xenova/all-MiniLM-L6-v2/tokenizer.json';
let pipeline, env;
try {
({ pipeline, env } = await import(localBase + 'transformers.min.js'));
} catch (_) {
({ pipeline, env } = await import(cdnBase + 'transformers.min.js'));
}
env.localModelPath = localBase;
// Use bundled model files when present; otherwise HuggingFace CDN (no 404 spam)
let localModels = false;
try {
const r = await fetch(modelProbe, { method: 'HEAD' });
localModels = r.ok;
} catch (_) {}
if (localModels) {
env.localModelPath = localBase;
env.allowLocalModels = true;
} else {
env.allowLocalModels = false;
}
env.allowRemoteModels = true;
env.useBrowserCache = true;
const pipe = await pipeline(
@@ -268,7 +290,7 @@
<div class="scan-ai-overlay" id="scan-ai-overlay" style="display:none">
<div class="scan-ai-overlay-inner">
<div class="loading-spinner"></div>
<span class="scan-ai-overlay-label">Gemini Vision</span>
<span class="scan-ai-overlay-label" data-i18n="scan.ai_overlay_label">Gemini Vision</span>
<span class="scan-ai-overlay-msg" id="scan-ai-overlay-msg"></span>
</div>
</div>
@@ -290,8 +312,8 @@
<!-- Scan errors -->
<div class="scan-result" id="scan-result" style="display:none"></div>
<!-- AI retry button (shown after visual identification fails) -->
<button class="btn btn-accent scan-ai-retry-btn" id="scan-ai-retry-btn" style="display:none" onclick="_retryAiScan()" data-i18n="scan.ai_retry_btn">🤖 Riprova con AI</button>
<!-- Manual AI identification (only when user taps — never automatic) -->
<button class="btn btn-accent scan-ai-manual-btn" id="scan-ai-manual-btn" type="button" style="display:none" onclick="_triggerManualAiScan()" data-i18n="scan.ai_manual_btn">🤖 Identifica con AI</button>
<!-- Recent scans -->
<div class="scan-recents" id="scan-recents" style="display:none">
@@ -1203,14 +1225,7 @@
<button class="btn btn-small btn-secondary mt-2" onclick="loadCameraDevices()" data-i18n="settings.camera.detect_btn">🔄 Rileva fotocamere</button>
</div>
<div class="form-group" style="margin-top:14px">
<label class="toggle-row">
<span data-i18n="settings.camera.ai_fallback_label">Identificazione visiva AI (fallback 5s)</span>
<span class="toggle-switch">
<input type="checkbox" id="setting-barcode-ai-fallback">
<span class="toggle-slider"></span>
</span>
</label>
<p class="settings-hint mt-2" data-i18n="settings.camera.ai_fallback_hint">Se il codice a barre non viene letto entro 5 secondi, un fotogramma viene inviato automaticamente all'AI per identificare visivamente il prodotto. Richiede Gemini configurato.</p>
<p class="settings-hint" data-i18n="settings.camera.ai_manual_hint">Se il barcode non si legge, usa il pulsante «Identifica con AI» sotto la fotocamera. Richiede Gemini configurato.</p>
</div>
</div>
</div>
@@ -1860,7 +1875,7 @@
<p class="recipe-regen-choice-title" data-i18n="recipes.regen_choice_title">Cosa vuoi fare con questa ricetta?</p>
<button class="btn btn-large btn-warning full-width" onclick="doRegenerateReplace()" data-i18n="recipes.regen_replace">🔄 Genera un'altra (scarta questa)</button>
<button class="btn btn-large btn-success full-width mt-2" onclick="doRegenerateSave()" data-i18n="recipes.regen_save_new">💾 Salva nell'archivio e genera nuova</button>
<button class="btn btn-large btn-ghost full-width mt-2" onclick="cancelRegenChoice()" data-i18n="action.cancel">Annulla</button>
<button class="btn btn-large btn-ghost full-width mt-2" onclick="cancelRegenChoice()" data-i18n="confirm.cancel">Annulla</button>
</div>
<button class="btn btn-large btn-primary full-width mt-2" onclick="closeRecipeDialog()" data-i18n="recipes.close_btn">
✅ Chiudi
@@ -1970,6 +1985,6 @@
</div>
</div>
<script src="assets/js/app.js?v=20260604f"></script>
<script src="assets/js/app.js?v=20260606g"></script>
</body>
</html>
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Standard wiederherstellen",
"save_info": "💾 Info speichern",
"retry": "🔄 Erneut versuchen",
"next": "Weiter →",
"yes_short": "Ja",
"no_short": "Nein"
},
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Einkaufsmodus",
"mode_shopping_end": "✅ Einkauf beenden",
"spesa_btn": "🛒 Einkauf",
"spesa_camera_hint": "Barcode mit der Kamera erfassen. Kein Barcode? Tippe unten auf «Mit KI erkennen».",
"zoom": "Zoom",
"tab_barcode": "Barcode",
"tab_name": "Name",
@@ -234,21 +236,36 @@
"status_confirmed": "Bestätigt!",
"status_parallel": "Kombinierter Scan aktiv...",
"status_ocr_searching": "Ich lese die Barcode-Ziffern...",
"status_digit_ocr": "Lese Ziffern unter dem Barcode...",
"status_ai_visual_searching": "Jetzt versuche ich, das Produkt zu erkennen...",
"method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision",
"method_local_ocr": "Ziffern-OCR",
"method_zbar": "ZBar",
"local_ocr_found": "Code aus Ziffern: {code}",
"ai_fallback_searching": "KI identifiziert Produkt...",
"ai_fallback_found": "Produkt von KI erkannt",
"ai_fallback_not_found": "KI: Produkt nicht erkannt",
"ai_fallback_exhausted": "KI: Produkt nicht erkannt — Barcode erneut scannen",
"ai_overlay_label": "Gemini Vision",
"ai_overlay_msg": "Gemini Vision analysiert das Produkt...",
"ai_retry_btn": "Mit KI erneut versuchen",
"ai_manual_btn": "🤖 Mit KI erkennen",
"ai_not_recognized": "KI: Produkt nicht erkannt. Erneut versuchen oder manuell eingeben.",
"ai_match_title": "Produkt von KI erkannt",
"ai_match_subtitle": "Waehle ein vorhandenes Produkt oder fuege das erkannte hinzu.",
"ai_match_existing": "Mogliche Treffer in der Vorratskammer",
"ai_match_none": "Keine ahnlichen Produkte in der Vorratskammer gefunden.",
"ai_match_use_btn": "Dieses nutzen",
"ai_match_add_btn": "\"{name}\" hinzufugen",
"ai_match_existing": "Aktuell in der Vorratskammer",
"ai_match_finished": "Aufgebraucht / leer",
"ai_match_catalog": "Im Katalog (ohne Bestand)",
"ai_match_finished_badge": "aufgebraucht",
"ai_match_finished_hint": "Produkt aufgebraucht — Menge auffüllen",
"ai_match_merged_existing": "Mit vorhandenem Katalogprodukt verknüpft",
"ai_match_none": "Keine ähnlichen Produkte — du kannst ein neues anlegen.",
"ai_match_use_btn": "Nutzen",
"ai_match_create_btn": " Neu anlegen: {name}",
"ai_match_add_btn": "{name} hinzufugen",
"ai_match_action_hint": "Tippe auf den gruenen Button, um dieses Produkt hinzuzufuegen",
"ai_match_or_similar": "Oder waehle ein aehnliches Produkt:",
"ai_detected_label": "KI erkannt",
"mode_shopping_activated": "🛒 Einkaufsmodus aktiviert!"
},
@@ -289,8 +306,9 @@
"hint_modify": "📝 Du kannst das Datum ändern oder mit der Kamera scannen",
"scan_expiry_title": "📷 Ablaufdatum scannen",
"product_added": "✅ {name} hinzugefügt!{qty}",
"duplicate_recent_confirm": "Du hast «{name}» gerade hinzugefuegt ({when}).\n\nDie Menge ist bereits {total}.\n\nUm {qty} erhoehen?",
"suffix_freezer_vacuum": "(Tiefkühler + vakuumversiegelt)",
"history_badge_tip": "Durchschnitt aus {n} früheren Einträgen",
"history_badge_tip": "Durchschnitt der letzten {n} Einträge — wird bei jedem Kauf aktualisiert",
"vacuum_question": "Vakuumiert?",
"vacuum_saved": "🔒 Als vakuumiert gespeichert"
},
@@ -343,6 +361,8 @@
"name_label": "🏷️ Produktname *",
"name_placeholder": "z.B.: Vollmilch, Penne Nudeln...",
"brand_label": "🏢 Marke",
"allergens_label": "Allergene:",
"ingredients_summary": "📋 Zutaten",
"brand_placeholder": "z.B.: Barilla, Müller, Knorr...",
"category_label": "📂 Kategorie",
"unit_label": "📏 Maßeinheit",
@@ -375,7 +395,7 @@
"labels_label": "Etiketten",
"select_variant": "Genaue Variante auswählen oder KI-Daten nutzen:",
"history_badge": "📊 Verlauf",
"from_history": " (aus Verlauf)"
"from_history": " (Ø letzte 3)"
},
"products": {
"title": "📦 Alle Produkte",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} von der Liste entfernt!",
"urgency_spec_critical": "⚡ Dringend",
"urgency_spec_high": "🟠 Bald",
"urgency_spec_medium": "🟡 Bald",
"urgency_spec_low": "🔵 Prognose",
"family_sibling_title": "Ähnlich in {location}",
"family_sibling_location": "Standort: {location}",
"family_sibling_qty": "Menge: {qty}",
"family_sibling_purchased": "Gekauft am {date}",
"family_sibling_question": "Ist die Menge noch korrekt?",
"family_sibling_prompt": "Du hast auch {name}: {qty} auf Lager. Menge bestätigen?",
"family_sibling_yes": "Ja, passt",
"family_sibling_no": "Nein, aktualisieren",
"bring_add_n": "{n} zu Bring! hinzufügen",
"bring_add_selected": "Ausgewählte zu Bring! hinzufügen",
"bring_adding": "Wird hinzugefügt...",
@@ -715,7 +745,8 @@
"devices_hint": "Bei mehreren Kameras kannst du nach Freigabe der Berechtigungen eine bestimmte aus der Liste oben wählen.",
"detect_btn": "🔄 Kameras erkennen",
"ai_fallback_label": "KI-Bilderkennung (5s Fallback)",
"ai_fallback_hint": "Wird kein Barcode innerhalb von 5 Sekunden gelesen, wird automatisch ein Bild an die KI zur visuellen Produktidentifizierung gesendet. Erfordert konfiguriertes Gemini."
"ai_fallback_hint": "Wird kein Barcode innerhalb von 5 Sekunden gelesen, wird automatisch ein Bild an die KI zur visuellen Produktidentifizierung gesendet. Erfordert konfiguriertes Gemini.",
"ai_manual_hint": "Wenn der Barcode nicht lesbar ist, nutze den Button «Mit KI erkennen» unter der Kamera. Erfordert konfiguriertes Gemini."
},
"security": {
"title": "🔒 HTTPS-Zertifikat",
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Reset to default",
"save_info": "💾 Save information",
"retry": "🔄 Retry",
"next": "Next →",
"yes_short": "Yes",
"no_short": "No"
},
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Shopping Mode",
"mode_shopping_end": "✅ End shopping",
"spesa_btn": "🛒 Shopping",
"spesa_camera_hint": "Point the camera at the barcode. No barcode? Tap «Identify with AI» below.",
"zoom": "Zoom",
"tab_barcode": "Barcode",
"tab_name": "Name",
@@ -234,21 +236,36 @@
"status_confirmed": "Confirmed!",
"status_parallel": "Using combined scan methods...",
"status_ocr_searching": "Reading the barcode digits...",
"status_digit_ocr": "Reading numbers below the barcode...",
"status_ai_visual_searching": "Now trying to recognize the product...",
"method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision",
"method_local_ocr": "Digit OCR",
"method_zbar": "ZBar",
"local_ocr_found": "Code from digits: {code}",
"ai_fallback_searching": "AI identifying product...",
"ai_fallback_found": "Product identified by AI",
"ai_fallback_not_found": "AI: product not recognized",
"ai_fallback_exhausted": "AI: product not recognized — try scanning the barcode",
"ai_overlay_label": "Gemini Vision",
"ai_overlay_msg": "Gemini Vision is analyzing the product...",
"ai_retry_btn": "Retry with AI",
"ai_manual_btn": "🤖 Identify with AI",
"ai_not_recognized": "AI could not recognize the product. Try again or add manually.",
"ai_match_title": "Product recognized by AI",
"ai_match_subtitle": "Choose an existing pantry item or add the detected one.",
"ai_match_existing": "Possible pantry matches",
"ai_match_none": "No similar pantry products found.",
"ai_match_use_btn": "Use this",
"ai_match_add_btn": "Add \"{name}\"",
"ai_match_existing": "Currently in pantry",
"ai_match_finished": "Finished / depleted",
"ai_match_catalog": "In catalog (no stock)",
"ai_match_finished_badge": "depleted",
"ai_match_finished_hint": "Finished product — restock the quantity",
"ai_match_merged_existing": "Linked to an existing catalog product",
"ai_match_none": "No similar products found — you can create a new one.",
"ai_match_use_btn": "Use",
"ai_match_create_btn": " Create new: {name}",
"ai_match_add_btn": "Add {name}",
"ai_match_action_hint": "Tap the green button to add this product",
"ai_match_or_similar": "Or pick a similar product:",
"ai_detected_label": "AI detected",
"mode_shopping_activated": "🛒 Shopping mode activated!"
},
@@ -289,8 +306,9 @@
"hint_modify": "📝 You can change the date or scan it with the camera",
"scan_expiry_title": "📷 Scan Expiry Date",
"product_added": "✅ {name} added!{qty}",
"duplicate_recent_confirm": "You just added \"{name}\" ({when}).\n\nQuantity is already {total}.\n\nIncrease it by {qty}?",
"suffix_freezer_vacuum": "(freezer + vacuum sealed)",
"history_badge_tip": "Average from {n} previous entries",
"history_badge_tip": "Average of the last {n} entries — updates with each new purchase",
"vacuum_question": "Vacuum sealed?",
"vacuum_saved": "🔒 Vacuum sealed!"
},
@@ -343,6 +361,8 @@
"name_label": "🏷️ Product Name *",
"name_placeholder": "E.g.: Whole milk, Penne pasta...",
"brand_label": "🏢 Brand",
"allergens_label": "Allergens:",
"ingredients_summary": "📋 Ingredients",
"brand_placeholder": "E.g.: Barilla, Granarolo, Mutti...",
"category_label": "📂 Category",
"unit_label": "📏 Unit of measure",
@@ -375,7 +395,7 @@
"labels_label": "Labels",
"select_variant": "Select the exact variant or use AI data:",
"history_badge": "📊 history",
"from_history": " (from history)"
"from_history": " (last 3 avg)"
},
"products": {
"title": "📦 All Products",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} removed from list!",
"urgency_spec_critical": "⚡ Urgent",
"urgency_spec_high": "🟠 Soon",
"urgency_spec_medium": "🟡 Soon",
"urgency_spec_low": "🔵 Forecast",
"family_sibling_title": "Similar in {location}",
"family_sibling_location": "Location: {location}",
"family_sibling_qty": "Quantity: {qty}",
"family_sibling_purchased": "Purchased on {date}",
"family_sibling_question": "Is the quantity still correct?",
"family_sibling_prompt": "You also have {name}: {qty} in stock. Confirm the quantity?",
"family_sibling_yes": "Yes, all good",
"family_sibling_no": "No, update",
"bring_add_n": "Add {n} to Bring!",
"bring_add_selected": "Add selected to Bring!",
"bring_adding": "Adding...",
@@ -715,7 +745,8 @@
"devices_hint": "If you have multiple cameras, you can select a specific one from the list above after granting permissions.",
"detect_btn": "🔄 Detect cameras",
"ai_fallback_label": "AI visual identification (5s fallback)",
"ai_fallback_hint": "If no barcode is read within 5 seconds, a frame is automatically sent to AI to visually identify the product. Requires Gemini configured."
"ai_fallback_hint": "If no barcode is read within 5 seconds, a frame is automatically sent to AI to visually identify the product. Requires Gemini configured.",
"ai_manual_hint": "If the barcode cannot be read, use the «Identify with AI» button below the camera. Requires Gemini configured."
},
"security": {
"title": "🔒 HTTPS Certificate",
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Restablecer valores por defecto",
"save_info": "💾 Guardar información",
"retry": "🔄 Reintentar",
"next": "Siguiente →",
"yes_short": "Sí",
"no_short": "No"
},
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Modo compras",
"mode_shopping_end": "✅ Finalizar compras",
"spesa_btn": "🛒 Compras",
"spesa_camera_hint": "Enfoca el código con la cámara. ¿Sin código? Pulsa «Identificar con IA» abajo.",
"zoom": "Zoom",
"tab_barcode": "Código de barras",
"tab_name": "Nombre",
@@ -233,21 +235,36 @@
"status_confirmed": "Confirmado!",
"status_parallel": "Escaneo combinado activo...",
"status_ocr_searching": "Estoy leyendo los números del código de barras...",
"status_digit_ocr": "Leyendo los números bajo el código...",
"status_ai_visual_searching": "Ahora intento reconocer el producto...",
"method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision",
"method_local_ocr": "OCR números",
"method_zbar": "ZBar",
"local_ocr_found": "Código desde los números: {code}",
"ai_fallback_searching": "Identificación de IA en curso...",
"ai_fallback_found": "Producto identificado por IA",
"ai_fallback_not_found": "IA: producto no reconocido",
"ai_fallback_exhausted": "IA: producto no reconocido — prueba a escanear el código",
"ai_overlay_label": "Gemini Vision",
"ai_overlay_msg": "Gemini Vision está analizando el producto...",
"ai_retry_btn": "Reintentar con IA",
"ai_manual_btn": "🤖 Identificar con IA",
"ai_not_recognized": "IA: producto no reconocido. Reintenta o añádelo manualmente.",
"ai_match_title": "Producto reconocido por IA",
"ai_match_subtitle": "Elige un producto ya en despensa o agrega el detectado.",
"ai_match_existing": "Posibles coincidencias en despensa",
"ai_match_none": "No se encontraron productos similares en despensa.",
"ai_match_use_btn": "Usar este",
"ai_match_add_btn": "Agregar \"{name}\"",
"ai_match_existing": "Actualmente en despensa",
"ai_match_finished": "Agotados / terminados",
"ai_match_catalog": "En catálogo (sin stock)",
"ai_match_finished_badge": "agotado",
"ai_match_finished_hint": "Producto agotado — repón la cantidad",
"ai_match_merged_existing": "Vinculado a un producto existente del catálogo",
"ai_match_none": "Sin productos similares — puedes crear uno nuevo.",
"ai_match_use_btn": "Usar",
"ai_match_create_btn": " Crear nuevo: {name}",
"ai_match_add_btn": "Agregar {name}",
"ai_match_action_hint": "Toca el boton verde para agregar este producto",
"ai_match_or_similar": "O elige un producto similar:",
"ai_detected_label": "IA detecto",
"stock_in_pantry": "Ya en despensa:",
"mode_shopping_activated": "🛒 ¡Modo compras activado!"
@@ -289,8 +306,9 @@
"hint_modify": "📝 Puedes cambiar la fecha o escanearla con la cámara",
"scan_expiry_title": "📷 Escanear fecha de caducidad",
"product_added": "✅ ¡{name} añadido!{qty}",
"duplicate_recent_confirm": "Acabas de añadir «{name}» ({when}).\n\nLa cantidad ya es {total}.\n\n¿Aumentarla en {qty}?",
"suffix_freezer_vacuum": "(congelador + al vacío)",
"history_badge_tip": "Media de {n} entradas anteriores",
"history_badge_tip": "Media de los últimos {n} registros — se actualiza con cada compra",
"vacuum_question": "¿Al vacío?",
"vacuum_saved": "🔒 ¡Al vacío!"
},
@@ -343,6 +361,8 @@
"name_label": "🏷️ Nombre del producto *",
"name_placeholder": "Ej.: Leche entera, Pasta penne...",
"brand_label": "🏢 Marca",
"allergens_label": "Alérgenos:",
"ingredients_summary": "📋 Ingredientes",
"brand_placeholder": "Ej.: Barilla, Danone, Heinz...",
"category_label": "📂 Categoría",
"unit_label": "📏 Unidad de medida",
@@ -375,7 +395,7 @@
"labels_label": "Etiquetas",
"select_variant": "Selecciona la variante exacta o usa los datos de IA:",
"history_badge": "📊 historial",
"from_history": " (del historial)"
"from_history": " (media últimos 3)"
},
"products": {
"title": "📦 Todos los productos",
@@ -499,6 +519,16 @@
"item_removed": "✅ ¡{name} eliminado de la lista!",
"urgency_spec_critical": "⚡ Urgente",
"urgency_spec_high": "🟠 Pronto",
"urgency_spec_medium": "🟡 Pronto",
"urgency_spec_low": "🔵 Previsión",
"family_sibling_title": "Similar en {location}",
"family_sibling_location": "Ubicación: {location}",
"family_sibling_qty": "Cantidad: {qty}",
"family_sibling_purchased": "Comprado el {date}",
"family_sibling_question": "¿La cantidad sigue siendo correcta?",
"family_sibling_prompt": "También tienes {name}: {qty} en stock. ¿Confirmas la cantidad?",
"family_sibling_yes": "Sí, correcto",
"family_sibling_no": "No, actualizar",
"bring_add_n": "Añadir {n} a Bring!",
"bring_add_selected": "Añadir selección a Bring!",
"bring_adding": "Añadiendo...",
@@ -715,7 +745,8 @@
"devices_hint": "Si tienes varias cámaras, puedes seleccionar una específica de la lista de arriba tras conceder los permisos.",
"detect_btn": "🔄 Detectar cámaras",
"ai_fallback_label": "Identificación visual IA (repuesto 5s)",
"ai_fallback_hint": "Si no se lee ningún código de barras en 5 segundos, se envía automáticamente un fotograma a la IA para identificar el producto visualmente. Requiere Gemini configurado."
"ai_fallback_hint": "Si no se lee ningún código de barras en 5 segundos, se envía automáticamente un fotograma a la IA para identificar el producto visualmente. Requiere Gemini configurado.",
"ai_manual_hint": "Si el código no se lee, usa el botón «Identificar con IA» debajo de la cámara. Requiere Gemini configurado."
},
"security": {
"title": "🔒 Certificado HTTPS",
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Rétablir les valeurs par défaut",
"save_info": "💾 Enregistrer les informations",
"retry": "🔄 Réessayer",
"next": "Suivant →",
"yes_short": "Oui",
"no_short": "Non"
},
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Mode courses",
"mode_shopping_end": "✅ Terminer les courses",
"spesa_btn": "🛒 Courses",
"spesa_camera_hint": "Visez le code-barres avec la caméra. Pas de code ? Appuyez sur « Identifier avec l'IA » ci-dessous.",
"zoom": "Zoom",
"tab_barcode": "Code-barres",
"tab_name": "Nom",
@@ -233,21 +235,36 @@
"status_confirmed": "Confirmé !",
"status_parallel": "Scan combiné actif...",
"status_ocr_searching": "Je lis les chiffres du code-barres...",
"status_digit_ocr": "Lecture des chiffres sous le code-barres...",
"status_ai_visual_searching": "J'essaie maintenant de reconnaître le produit...",
"method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision",
"method_local_ocr": "OCR chiffres",
"method_zbar": "ZBar",
"local_ocr_found": "Code depuis les chiffres : {code}",
"ai_fallback_searching": "Identification IA en cours...",
"ai_fallback_found": "Produit identifié par l'IA",
"ai_fallback_not_found": "IA : produit non reconnu",
"ai_fallback_exhausted": "IA : produit non reconnu — réessayez avec le code-barres",
"ai_overlay_label": "Gemini Vision",
"ai_overlay_msg": "Gemini Vision analyse le produit...",
"ai_retry_btn": "Reessayer avec l'IA",
"ai_manual_btn": "🤖 Identifier avec l'IA",
"ai_not_recognized": "IA : produit non reconnu. Réessayez ou saisissez-le manuellement.",
"ai_match_title": "Produit reconnu par l'IA",
"ai_match_subtitle": "Choisissez un produit deja en stock ou ajoutez celui detecte.",
"ai_match_existing": "Correspondances possibles dans le stock",
"ai_match_none": "Aucun produit similaire trouve dans le stock.",
"ai_match_use_btn": "Utiliser celui-ci",
"ai_match_add_btn": "Ajouter \"{name}\"",
"ai_match_existing": "Actuellement en stock",
"ai_match_finished": "Terminés / épuisés",
"ai_match_catalog": "Au catalogue (sans stock)",
"ai_match_finished_badge": "épuisé",
"ai_match_finished_hint": "Produit terminé — réapprovisionner la quantité",
"ai_match_merged_existing": "Lié à un produit déjà au catalogue",
"ai_match_none": "Aucun produit similaire — vous pouvez en créer un nouveau.",
"ai_match_use_btn": "Utiliser",
"ai_match_create_btn": " Créer nouveau : {name}",
"ai_match_add_btn": "Ajouter {name}",
"ai_match_action_hint": "Appuyez sur le bouton vert pour ajouter ce produit",
"ai_match_or_similar": "Ou choisissez un produit similaire :",
"ai_detected_label": "IA a detecte",
"stock_in_pantry": "Déjà à la maison :",
"mode_shopping_activated": "🛒 Mode courses activé !"
@@ -289,8 +306,9 @@
"hint_modify": "📝 Vous pouvez modifier la date ou la scanner avec la caméra",
"scan_expiry_title": "📷 Scanner la date de péremption",
"product_added": "✅ {name} ajouté !{qty}",
"duplicate_recent_confirm": "Vous venez d'ajouter « {name} » ({when}).\n\nLa quantité est déjà {total}.\n\nL'augmenter de {qty} ?",
"suffix_freezer_vacuum": "(congélateur + sous vide)",
"history_badge_tip": "Moyenne de {n} entrées précédentes",
"history_badge_tip": "Moyenne des {n} derniers ajouts — mise à jour à chaque achat",
"vacuum_question": "Sous vide ?",
"vacuum_saved": "🔒 Sous vide !"
},
@@ -343,6 +361,8 @@
"name_label": "🏷️ Nom du produit *",
"name_placeholder": "Ex. : Lait entier, Pâtes penne...",
"brand_label": "🏢 Marque",
"allergens_label": "Allergènes :",
"ingredients_summary": "📋 Ingrédients",
"brand_placeholder": "Ex. : Barilla, Danone, Heinz...",
"category_label": "📂 Catégorie",
"unit_label": "📏 Unité de mesure",
@@ -375,7 +395,7 @@
"labels_label": "Labels",
"select_variant": "Sélectionnez la variante exacte ou utilisez les données IA :",
"history_badge": "📊 historique",
"from_history": " (historique)"
"from_history": " (moy. 3 derniers)"
},
"products": {
"title": "📦 Tous les produits",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} retiré de la liste !",
"urgency_spec_critical": "⚡ Urgent",
"urgency_spec_high": "🟠 Bientôt",
"urgency_spec_medium": "🟡 Bientôt",
"urgency_spec_low": "🔵 Prévision",
"family_sibling_title": "Similaire dans {location}",
"family_sibling_location": "Emplacement : {location}",
"family_sibling_qty": "Quantité : {qty}",
"family_sibling_purchased": "Acheté le {date}",
"family_sibling_question": "La quantité est-elle encore correcte ?",
"family_sibling_prompt": "Tu as aussi {name} : {qty} en stock. Confirmer la quantité ?",
"family_sibling_yes": "Oui, c'est bon",
"family_sibling_no": "Non, mettre à jour",
"bring_add_n": "Ajouter {n} à Bring !",
"bring_add_selected": "Ajouter la sélection à Bring !",
"bring_adding": "Ajout en cours...",
@@ -715,7 +745,8 @@
"devices_hint": "Si vous avez plusieurs caméras, vous pouvez en sélectionner une dans la liste ci-dessus après avoir accordé les permissions.",
"detect_btn": "🔄 Détecter les caméras",
"ai_fallback_label": "Identification visuelle IA (repli 5s)",
"ai_fallback_hint": "Si aucun code-barres n'est lu en 5 secondes, une image est automatiquement envoyée à l'IA pour identifier visuellement le produit. Nécessite Gemini configuré."
"ai_fallback_hint": "Si aucun code-barres n'est lu en 5 secondes, une image est automatiquement envoyée à l'IA pour identifier visuellement le produit. Nécessite Gemini configuré.",
"ai_manual_hint": "Si le code-barres ne se lit pas, utilisez le bouton « Identifier avec l'IA » sous la caméra. Nécessite Gemini configuré."
},
"security": {
"title": "🔒 Certificat HTTPS",
+39 -8
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Ripristina default",
"save_info": "💾 Salva informazioni",
"retry": "🔄 Riprova",
"next": "Avanti →",
"yes_short": "Sì",
"no_short": "No"
},
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Modalità Spesa",
"mode_shopping_end": "✅ Fine spesa",
"spesa_btn": "🛒 Spesa",
"spesa_camera_hint": "Inquadra il codice con la telecamera. Senza barcode? Premi «Identifica con AI» sotto.",
"zoom": "Zoom",
"tab_barcode": "Barcode",
"tab_name": "Nome",
@@ -234,21 +236,36 @@
"status_confirmed": "Confermato!",
"status_parallel": "Doppia scansione attiva...",
"status_ocr_searching": "Sto leggendo i numeri del codice a barre...",
"status_digit_ocr": "Leggo i numeri sotto il codice...",
"status_ai_visual_searching": "Ora provo a riconoscere il prodotto...",
"method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision",
"method_local_ocr": "OCR numeri",
"method_zbar": "ZBar",
"local_ocr_found": "Codice dai numeri: {code}",
"ai_fallback_searching": "Identificazione AI in corso...",
"ai_fallback_found": "Prodotto identificato dall'AI",
"ai_fallback_not_found": "AI: prodotto non riconosciuto",
"ai_fallback_exhausted": "AI: prodotto non riconosciuto — riprova con il barcode",
"ai_overlay_label": "Gemini Vision",
"ai_overlay_msg": "Gemini Vision sta analizzando il prodotto...",
"ai_retry_btn": "Riprova con AI",
"ai_manual_btn": "🤖 Identifica con AI",
"ai_not_recognized": "AI: prodotto non riconosciuto. Riprova o inserisci manualmente.",
"ai_match_title": "Prodotto riconosciuto con AI",
"ai_match_subtitle": "Scegli se usare un prodotto gia presente oppure aggiungere quello rilevato.",
"ai_match_existing": "Possibili corrispondenze in dispensa",
"ai_match_none": "Nessun prodotto simile trovato in dispensa.",
"ai_match_use_btn": "Usa questo",
"ai_match_add_btn": "Aggiungi \"{name}\"",
"ai_match_subtitle": "Scegli un prodotto esistente o creane uno nuovo con il nome rilevato.",
"ai_match_existing": "In dispensa adesso",
"ai_match_finished": "Finiti / esauriti",
"ai_match_catalog": "Nel catalogo (senza scorte)",
"ai_match_finished_badge": "esaurito",
"ai_match_finished_hint": "Prodotto finito — reintegra la quantità",
"ai_match_merged_existing": "Collegato a un prodotto già presente nel catalogo",
"ai_match_none": "Nessun prodotto simile trovato — puoi crearne uno nuovo.",
"ai_match_use_btn": "Usa",
"ai_match_create_btn": " Crea nuovo: {name}",
"ai_match_add_btn": "Aggiungi {name}",
"ai_match_action_hint": "Tocca il pulsante verde per aggiungere questo prodotto",
"ai_match_or_similar": "Oppure scegli un prodotto simile:",
"ai_detected_label": "AI ha trovato",
"mode_shopping_activated": "🛒 Modalità Spesa attivata!"
},
@@ -289,8 +306,9 @@
"hint_modify": "📝 Puoi modificare la data o scansionarla con la fotocamera",
"scan_expiry_title": "📷 Scansiona Data Scadenza",
"product_added": "✅ {name} aggiunto!{qty}",
"duplicate_recent_confirm": "Hai appena aggiunto «{name}» ({when}).\n\nLa quantità è già {total}.\n\nVuoi aumentarla di {qty}?",
"suffix_freezer_vacuum": "(freezer + sotto vuoto)",
"history_badge_tip": "Media da {n} inserimenti precedenti",
"history_badge_tip": "Media degli ultimi {n} inserimenti — si aggiorna ad ogni nuovo acquisto",
"vacuum_question": "Messo sotto vuoto?",
"vacuum_saved": "🔒 Sotto vuoto registrato"
},
@@ -343,6 +361,8 @@
"name_label": "🏷️ Nome Prodotto *",
"name_placeholder": "Es: Latte intero, Pasta penne rigate...",
"brand_label": "🏢 Marca",
"allergens_label": "Allergeni:",
"ingredients_summary": "📋 Ingredienti",
"brand_placeholder": "Es: Barilla, Granarolo, Mutti...",
"category_label": "📂 Categoria",
"unit_label": "📏 Unità di misura",
@@ -375,7 +395,7 @@
"labels_label": "Etichette",
"select_variant": "Seleziona la variante esatta o usa i dati AI:",
"history_badge": "📊 storico",
"from_history": " (da storico)"
"from_history": " (media ultimi 3)"
},
"products": {
"title": "📦 Tutti i Prodotti",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} rimosso dalla lista!",
"urgency_spec_critical": "⚡ Urgente",
"urgency_spec_high": "🟠 Presto",
"urgency_spec_medium": "🟡 A breve",
"urgency_spec_low": "🔵 Previsione",
"family_sibling_title": "Simile in {location}",
"family_sibling_location": "Si trova in: {location}",
"family_sibling_qty": "Quantità: {qty}",
"family_sibling_purchased": "Acquistato il {date}",
"family_sibling_question": "La quantità è ancora corretta?",
"family_sibling_prompt": "Hai anche {name}: ne hai {qty} in dispensa. Confermi la quantità?",
"family_sibling_yes": "Sì, tutto ok",
"family_sibling_no": "No, aggiorna",
"bring_add_n": "Aggiungi {n} a Bring!",
"bring_add_selected": "Aggiungi selezionati a Bring!",
"bring_adding": "Aggiunta in corso...",
@@ -715,7 +745,8 @@
"devices_hint": "Se hai più fotocamere, puoi selezionarne una specifica dall'elenco sopra dopo aver concesso i permessi.",
"detect_btn": "🔄 Rileva fotocamere",
"ai_fallback_label": "Identificazione visiva AI (fallback 5s)",
"ai_fallback_hint": "Se il codice a barre non viene letto entro 5 secondi, un fotogramma viene inviato automaticamente all'AI per identificare il prodotto visivamente. Richiede Gemini configurato."
"ai_fallback_hint": "Se il codice a barre non viene letto entro 5 secondi, un fotogramma viene inviato automaticamente all'AI per identificare il prodotto visivamente. Richiede Gemini configurato.",
"ai_manual_hint": "Se il barcode non si legge, usa il pulsante «Identifica con AI» sotto la fotocamera. Richiede Gemini configurato."
},
"security": {
"title": "🔒 Certificato HTTPS",