chore: auto-merge develop → main

Triggered by: 34dcb05 Improve scan flow, AI UX, expiry history, and shopping sync.
This commit is contained in:
github-actions[bot]
2026-06-06 09:39:52 +00:00
19 changed files with 3370 additions and 558 deletions
+24 -3
View File
@@ -44,9 +44,10 @@ try {
$itemCount = count($decoded['items'] ?? []); $itemCount = count($decoded['items'] ?? []);
echo '[' . date('Y-m-d H:i:s') . '] OK — ' . $itemCount . " items cached\n"; echo '[' . date('Y-m-d H:i:s') . '] OK — ' . $itemCount . " items cached\n";
// ── Bring! server-side cleanup ──────────────────────────────────────── // ── Bring! server-side sync ───────────────────────────────────────────
// After computing smart shopping, automatically remove stale Bring! items // After computing smart shopping, remove stale Bring! items and push every
// and add/update critical ones. This runs fully server-side every cron cycle. // product that needs restocking (esauriti, quasi finiti, previsione).
// Runs fully server-side every cron cycle (~5 min).
try { try {
$cleanupResult = bringCleanupObsolete($db); $cleanupResult = bringCleanupObsolete($db);
if (isset($cleanupResult['skipped'])) { if (isset($cleanupResult['skipped'])) {
@@ -57,6 +58,21 @@ try {
. ($cleanupResult['errors'] ? ', errors: ' . $cleanupResult['errors'] : '') . "\n"; . ($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); $addResult = bringAutoAddCritical($db);
if (isset($addResult['skipped'])) { if (isset($addResult['skipped'])) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! auto-add skipped: ' . $addResult['skipped'] . "\n"; 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) echo '[' . date('Y-m-d H:i:s') . '] Bring! auto-add — added: ' . ($addResult['added'] ?? 0)
. ', updated specs: ' . ($addResult['updated'] ?? 0) . "\n"; . ', 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) { } catch (Throwable $be) {
echo '[' . date('Y-m-d H:i:s') . '] Bring! sync warning: ' . $be->getMessage() . "\n"; echo '[' . date('Y-m-d H:i:s') . '] Bring! sync warning: ' . $be->getMessage() . "\n";
} }
+1236 -117
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -156,7 +156,7 @@ function evershelfDemoReadOnlyActions(): array {
return [ return [
'ping', 'check_update', 'health_check', 'get_settings', 'gemini_usage', 'ping', 'check_update', 'health_check', 'get_settings', 'gemini_usage',
'search_barcode', 'lookup_barcode', 'stock_for_name', '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', 'inventory_list', 'inventory_summary', 'inventory_finished_items',
'transactions_list', 'stats', 'monthly_stats', 'macro_stats', 'transactions_list', 'stats', 'monthly_stats', 'macro_stats',
'consumption_predictions', 'inventory_anomalies', 'inventory_duplicate_loss_checks', 'consumption_predictions', 'inventory_anomalies', 'inventory_duplicate_loss_checks',
+215 -21
View File
@@ -666,6 +666,118 @@ body.server-offline .bottom-nav {
flex-shrink: 0; 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 { @keyframes pulse-scan {
0%, 100% { box-shadow: 0 2px 8px rgba(0,0,0,0.2); } 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); } 50% { box-shadow: 0 2px 16px rgba(255,255,255,0.4); }
@@ -1845,6 +1957,19 @@ body.server-offline .bottom-nav {
overflow: hidden; 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 { .scanner-viewport video {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -2046,21 +2171,25 @@ body.server-offline .bottom-nav {
max-width: 220px; max-width: 220px;
} }
/* — AI retry button (shown below scanner after visual ID fails) — */ /* — Manual AI button (user-triggered only) — */
.scan-ai-retry-btn { .scan-ai-manual-btn {
width: 100%; width: 100%;
margin-top: 10px; margin-top: 10px;
font-size: 0.95rem; font-size: 1.05rem;
padding: 12px; padding: 14px 16px;
border-radius: var(--radius); border-radius: 14px;
border: 2px solid var(--accent); border: 2px solid var(--accent);
background: rgba(124,58,237,0.1); background: rgba(124,58,237,0.12);
color: var(--accent); color: var(--accent);
font-weight: 600; font-weight: 700;
cursor: pointer; 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) — */ /* — Viewport overlay controls (torch / zoom / flip) — */
.scan-viewport-controls { .scan-viewport-controls {
@@ -2115,17 +2244,59 @@ body.server-offline .bottom-nav {
.scan-ai-match-box { .scan-ai-match-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 14px;
} }
.scan-ai-match-head { .scan-ai-hero {
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 4px; 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 { .scan-ai-match-title {
font-size: 1rem; font-size: 0.82rem;
font-weight: 700; 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); 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 { .scan-ai-match-subtitle {
font-size: 0.82rem; font-size: 0.82rem;
@@ -2137,9 +2308,9 @@ body.server-offline .bottom-nav {
gap: 8px; gap: 8px;
} }
.scan-ai-match-list-title { .scan-ai-match-list-title {
font-size: 0.78rem; font-size: 0.88rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.06em; letter-spacing: 0.04em;
color: var(--text-muted); color: var(--text-muted);
font-weight: 700; font-weight: 700;
} }
@@ -2160,6 +2331,22 @@ body.server-offline .bottom-nav {
text-align: left; text-align: left;
} }
.scan-ai-candidate-item:active { transform: scale(0.99); } .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 { .scan-ai-candidate-icon {
font-size: 1.3rem; font-size: 1.3rem;
flex-shrink: 0; flex-shrink: 0;
@@ -2172,26 +2359,27 @@ body.server-offline .bottom-nav {
flex: 1; flex: 1;
} }
.scan-ai-candidate-name { .scan-ai-candidate-name {
font-size: 0.9rem; font-size: 1rem;
font-weight: 600; font-weight: 700;
color: var(--text); color: var(--text);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.scan-ai-candidate-meta { .scan-ai-candidate-meta {
font-size: 0.76rem; font-size: 0.84rem;
color: var(--text-muted); color: var(--text-muted);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.scan-ai-candidate-cta { .scan-ai-candidate-cta {
font-size: 0.74rem; font-size: 0.82rem;
font-weight: 700;
color: var(--accent); color: var(--accent);
border: 1px solid var(--accent); border: 1px solid var(--accent);
border-radius: 999px; border-radius: 999px;
padding: 3px 8px; padding: 6px 10px;
flex-shrink: 0; flex-shrink: 0;
} }
.scan-ai-match-empty { .scan-ai-match-empty {
@@ -2204,6 +2392,12 @@ body.server-offline .bottom-nav {
} }
.scan-ai-add-btn { .scan-ai-add-btn {
width: 100%; 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 { .scan-ai-detected-label {
font-size: 0.72rem; font-size: 0.72rem;
+1034 -322
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.
+30 -15
View File
@@ -11,11 +11,21 @@
<title>EverShelf</title> <title>EverShelf</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<link rel="icon" type="image/png" href="assets/img/logo/logo_icon.png"> <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) --> <!-- Core modules (auth, DOM helpers) -->
<script src="assets/js/core/dom.js?v=20260603a"></script> <script src="assets/js/core/dom.js?v=20260603a"></script>
<script src="assets/js/core/auth.js?v=20260603b"></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 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> <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 --> <!-- @xenova/transformers: ES-module bootstrap that exposes a lazy category-classifier as window._categoryPipelinePromise -->
@@ -31,13 +41,25 @@
try { try {
const localBase = 'assets/vendor/transformers/'; const localBase = 'assets/vendor/transformers/';
const cdnBase = 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/'; 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; let pipeline, env;
try { try {
({ pipeline, env } = await import(localBase + 'transformers.min.js')); ({ pipeline, env } = await import(localBase + 'transformers.min.js'));
} catch (_) { } catch (_) {
({ pipeline, env } = await import(cdnBase + 'transformers.min.js')); ({ pipeline, env } = await import(cdnBase + 'transformers.min.js'));
} }
// 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.localModelPath = localBase;
env.allowLocalModels = true;
} else {
env.allowLocalModels = false;
}
env.allowRemoteModels = true; env.allowRemoteModels = true;
env.useBrowserCache = true; env.useBrowserCache = true;
const pipe = await pipeline( 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" id="scan-ai-overlay" style="display:none">
<div class="scan-ai-overlay-inner"> <div class="scan-ai-overlay-inner">
<div class="loading-spinner"></div> <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> <span class="scan-ai-overlay-msg" id="scan-ai-overlay-msg"></span>
</div> </div>
</div> </div>
@@ -290,8 +312,8 @@
<!-- Scan errors --> <!-- Scan errors -->
<div class="scan-result" id="scan-result" style="display:none"></div> <div class="scan-result" id="scan-result" style="display:none"></div>
<!-- AI retry button (shown after visual identification fails) --> <!-- Manual AI identification (only when user taps — never automatic) -->
<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> <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 --> <!-- Recent scans -->
<div class="scan-recents" id="scan-recents" style="display:none"> <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> <button class="btn btn-small btn-secondary mt-2" onclick="loadCameraDevices()" data-i18n="settings.camera.detect_btn">🔄 Rileva fotocamere</button>
</div> </div>
<div class="form-group" style="margin-top:14px"> <div class="form-group" style="margin-top:14px">
<label class="toggle-row"> <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>
<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>
</div> </div>
</div> </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> <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-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-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> </div>
<button class="btn btn-large btn-primary full-width mt-2" onclick="closeRecipeDialog()" data-i18n="recipes.close_btn"> <button class="btn btn-large btn-primary full-width mt-2" onclick="closeRecipeDialog()" data-i18n="recipes.close_btn">
✅ Chiudi ✅ Chiudi
@@ -1970,6 +1985,6 @@
</div> </div>
</div> </div>
<script src="assets/js/app.js?v=20260604f"></script> <script src="assets/js/app.js?v=20260606g"></script>
</body> </body>
</html> </html>
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Standard wiederherstellen", "reset_default": "↺ Standard wiederherstellen",
"save_info": "💾 Info speichern", "save_info": "💾 Info speichern",
"retry": "🔄 Erneut versuchen", "retry": "🔄 Erneut versuchen",
"next": "Weiter →",
"yes_short": "Ja", "yes_short": "Ja",
"no_short": "Nein" "no_short": "Nein"
}, },
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Einkaufsmodus", "mode_shopping": "🛒 Einkaufsmodus",
"mode_shopping_end": "✅ Einkauf beenden", "mode_shopping_end": "✅ Einkauf beenden",
"spesa_btn": "🛒 Einkauf", "spesa_btn": "🛒 Einkauf",
"spesa_camera_hint": "Barcode mit der Kamera erfassen. Kein Barcode? Tippe unten auf «Mit KI erkennen».",
"zoom": "Zoom", "zoom": "Zoom",
"tab_barcode": "Barcode", "tab_barcode": "Barcode",
"tab_name": "Name", "tab_name": "Name",
@@ -234,21 +236,36 @@
"status_confirmed": "Bestätigt!", "status_confirmed": "Bestätigt!",
"status_parallel": "Kombinierter Scan aktiv...", "status_parallel": "Kombinierter Scan aktiv...",
"status_ocr_searching": "Ich lese die Barcode-Ziffern...", "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...", "status_ai_visual_searching": "Jetzt versuche ich, das Produkt zu erkennen...",
"method_ai_ocr": "Gemini OCR", "method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision", "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_searching": "KI identifiziert Produkt...",
"ai_fallback_found": "Produkt von KI erkannt", "ai_fallback_found": "Produkt von KI erkannt",
"ai_fallback_not_found": "KI: Produkt nicht erkannt", "ai_fallback_not_found": "KI: Produkt nicht erkannt",
"ai_fallback_exhausted": "KI: Produkt nicht erkannt — Barcode erneut scannen", "ai_fallback_exhausted": "KI: Produkt nicht erkannt — Barcode erneut scannen",
"ai_overlay_label": "Gemini Vision",
"ai_overlay_msg": "Gemini Vision analysiert das Produkt...", "ai_overlay_msg": "Gemini Vision analysiert das Produkt...",
"ai_retry_btn": "Mit KI erneut versuchen", "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_title": "Produkt von KI erkannt",
"ai_match_subtitle": "Waehle ein vorhandenes Produkt oder fuege das erkannte hinzu.", "ai_match_subtitle": "Waehle ein vorhandenes Produkt oder fuege das erkannte hinzu.",
"ai_match_existing": "Mogliche Treffer in der Vorratskammer", "ai_match_existing": "Aktuell in der Vorratskammer",
"ai_match_none": "Keine ahnlichen Produkte in der Vorratskammer gefunden.", "ai_match_finished": "Aufgebraucht / leer",
"ai_match_use_btn": "Dieses nutzen", "ai_match_catalog": "Im Katalog (ohne Bestand)",
"ai_match_add_btn": "\"{name}\" hinzufugen", "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", "ai_detected_label": "KI erkannt",
"mode_shopping_activated": "🛒 Einkaufsmodus aktiviert!" "mode_shopping_activated": "🛒 Einkaufsmodus aktiviert!"
}, },
@@ -289,8 +306,9 @@
"hint_modify": "📝 Du kannst das Datum ändern oder mit der Kamera scannen", "hint_modify": "📝 Du kannst das Datum ändern oder mit der Kamera scannen",
"scan_expiry_title": "📷 Ablaufdatum scannen", "scan_expiry_title": "📷 Ablaufdatum scannen",
"product_added": "✅ {name} hinzugefügt!{qty}", "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)", "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_question": "Vakuumiert?",
"vacuum_saved": "🔒 Als vakuumiert gespeichert" "vacuum_saved": "🔒 Als vakuumiert gespeichert"
}, },
@@ -343,6 +361,8 @@
"name_label": "🏷️ Produktname *", "name_label": "🏷️ Produktname *",
"name_placeholder": "z.B.: Vollmilch, Penne Nudeln...", "name_placeholder": "z.B.: Vollmilch, Penne Nudeln...",
"brand_label": "🏢 Marke", "brand_label": "🏢 Marke",
"allergens_label": "Allergene:",
"ingredients_summary": "📋 Zutaten",
"brand_placeholder": "z.B.: Barilla, Müller, Knorr...", "brand_placeholder": "z.B.: Barilla, Müller, Knorr...",
"category_label": "📂 Kategorie", "category_label": "📂 Kategorie",
"unit_label": "📏 Maßeinheit", "unit_label": "📏 Maßeinheit",
@@ -375,7 +395,7 @@
"labels_label": "Etiketten", "labels_label": "Etiketten",
"select_variant": "Genaue Variante auswählen oder KI-Daten nutzen:", "select_variant": "Genaue Variante auswählen oder KI-Daten nutzen:",
"history_badge": "📊 Verlauf", "history_badge": "📊 Verlauf",
"from_history": " (aus Verlauf)" "from_history": " (Ø letzte 3)"
}, },
"products": { "products": {
"title": "📦 Alle Produkte", "title": "📦 Alle Produkte",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} von der Liste entfernt!", "item_removed": "✅ {name} von der Liste entfernt!",
"urgency_spec_critical": "⚡ Dringend", "urgency_spec_critical": "⚡ Dringend",
"urgency_spec_high": "🟠 Bald", "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_n": "{n} zu Bring! hinzufügen",
"bring_add_selected": "Ausgewählte zu Bring! hinzufügen", "bring_add_selected": "Ausgewählte zu Bring! hinzufügen",
"bring_adding": "Wird hinzugefügt...", "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.", "devices_hint": "Bei mehreren Kameras kannst du nach Freigabe der Berechtigungen eine bestimmte aus der Liste oben wählen.",
"detect_btn": "🔄 Kameras erkennen", "detect_btn": "🔄 Kameras erkennen",
"ai_fallback_label": "KI-Bilderkennung (5s Fallback)", "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": { "security": {
"title": "🔒 HTTPS-Zertifikat", "title": "🔒 HTTPS-Zertifikat",
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Reset to default", "reset_default": "↺ Reset to default",
"save_info": "💾 Save information", "save_info": "💾 Save information",
"retry": "🔄 Retry", "retry": "🔄 Retry",
"next": "Next →",
"yes_short": "Yes", "yes_short": "Yes",
"no_short": "No" "no_short": "No"
}, },
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Shopping Mode", "mode_shopping": "🛒 Shopping Mode",
"mode_shopping_end": "✅ End shopping", "mode_shopping_end": "✅ End shopping",
"spesa_btn": "🛒 Shopping", "spesa_btn": "🛒 Shopping",
"spesa_camera_hint": "Point the camera at the barcode. No barcode? Tap «Identify with AI» below.",
"zoom": "Zoom", "zoom": "Zoom",
"tab_barcode": "Barcode", "tab_barcode": "Barcode",
"tab_name": "Name", "tab_name": "Name",
@@ -234,21 +236,36 @@
"status_confirmed": "Confirmed!", "status_confirmed": "Confirmed!",
"status_parallel": "Using combined scan methods...", "status_parallel": "Using combined scan methods...",
"status_ocr_searching": "Reading the barcode digits...", "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...", "status_ai_visual_searching": "Now trying to recognize the product...",
"method_ai_ocr": "Gemini OCR", "method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision", "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_searching": "AI identifying product...",
"ai_fallback_found": "Product identified by AI", "ai_fallback_found": "Product identified by AI",
"ai_fallback_not_found": "AI: product not recognized", "ai_fallback_not_found": "AI: product not recognized",
"ai_fallback_exhausted": "AI: product not recognized — try scanning the barcode", "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_overlay_msg": "Gemini Vision is analyzing the product...",
"ai_retry_btn": "Retry with AI", "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_title": "Product recognized by AI",
"ai_match_subtitle": "Choose an existing pantry item or add the detected one.", "ai_match_subtitle": "Choose an existing pantry item or add the detected one.",
"ai_match_existing": "Possible pantry matches", "ai_match_existing": "Currently in pantry",
"ai_match_none": "No similar pantry products found.", "ai_match_finished": "Finished / depleted",
"ai_match_use_btn": "Use this", "ai_match_catalog": "In catalog (no stock)",
"ai_match_add_btn": "Add \"{name}\"", "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", "ai_detected_label": "AI detected",
"mode_shopping_activated": "🛒 Shopping mode activated!" "mode_shopping_activated": "🛒 Shopping mode activated!"
}, },
@@ -289,8 +306,9 @@
"hint_modify": "📝 You can change the date or scan it with the camera", "hint_modify": "📝 You can change the date or scan it with the camera",
"scan_expiry_title": "📷 Scan Expiry Date", "scan_expiry_title": "📷 Scan Expiry Date",
"product_added": "✅ {name} added!{qty}", "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)", "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_question": "Vacuum sealed?",
"vacuum_saved": "🔒 Vacuum sealed!" "vacuum_saved": "🔒 Vacuum sealed!"
}, },
@@ -343,6 +361,8 @@
"name_label": "🏷️ Product Name *", "name_label": "🏷️ Product Name *",
"name_placeholder": "E.g.: Whole milk, Penne pasta...", "name_placeholder": "E.g.: Whole milk, Penne pasta...",
"brand_label": "🏢 Brand", "brand_label": "🏢 Brand",
"allergens_label": "Allergens:",
"ingredients_summary": "📋 Ingredients",
"brand_placeholder": "E.g.: Barilla, Granarolo, Mutti...", "brand_placeholder": "E.g.: Barilla, Granarolo, Mutti...",
"category_label": "📂 Category", "category_label": "📂 Category",
"unit_label": "📏 Unit of measure", "unit_label": "📏 Unit of measure",
@@ -375,7 +395,7 @@
"labels_label": "Labels", "labels_label": "Labels",
"select_variant": "Select the exact variant or use AI data:", "select_variant": "Select the exact variant or use AI data:",
"history_badge": "📊 history", "history_badge": "📊 history",
"from_history": " (from history)" "from_history": " (last 3 avg)"
}, },
"products": { "products": {
"title": "📦 All Products", "title": "📦 All Products",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} removed from list!", "item_removed": "✅ {name} removed from list!",
"urgency_spec_critical": "⚡ Urgent", "urgency_spec_critical": "⚡ Urgent",
"urgency_spec_high": "🟠 Soon", "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_n": "Add {n} to Bring!",
"bring_add_selected": "Add selected to Bring!", "bring_add_selected": "Add selected to Bring!",
"bring_adding": "Adding...", "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.", "devices_hint": "If you have multiple cameras, you can select a specific one from the list above after granting permissions.",
"detect_btn": "🔄 Detect cameras", "detect_btn": "🔄 Detect cameras",
"ai_fallback_label": "AI visual identification (5s fallback)", "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": { "security": {
"title": "🔒 HTTPS Certificate", "title": "🔒 HTTPS Certificate",
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Restablecer valores por defecto", "reset_default": "↺ Restablecer valores por defecto",
"save_info": "💾 Guardar información", "save_info": "💾 Guardar información",
"retry": "🔄 Reintentar", "retry": "🔄 Reintentar",
"next": "Siguiente →",
"yes_short": "Sí", "yes_short": "Sí",
"no_short": "No" "no_short": "No"
}, },
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Modo compras", "mode_shopping": "🛒 Modo compras",
"mode_shopping_end": "✅ Finalizar compras", "mode_shopping_end": "✅ Finalizar compras",
"spesa_btn": "🛒 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", "zoom": "Zoom",
"tab_barcode": "Código de barras", "tab_barcode": "Código de barras",
"tab_name": "Nombre", "tab_name": "Nombre",
@@ -233,21 +235,36 @@
"status_confirmed": "Confirmado!", "status_confirmed": "Confirmado!",
"status_parallel": "Escaneo combinado activo...", "status_parallel": "Escaneo combinado activo...",
"status_ocr_searching": "Estoy leyendo los números del código de barras...", "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...", "status_ai_visual_searching": "Ahora intento reconocer el producto...",
"method_ai_ocr": "Gemini OCR", "method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision", "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_searching": "Identificación de IA en curso...",
"ai_fallback_found": "Producto identificado por IA", "ai_fallback_found": "Producto identificado por IA",
"ai_fallback_not_found": "IA: producto no reconocido", "ai_fallback_not_found": "IA: producto no reconocido",
"ai_fallback_exhausted": "IA: producto no reconocido — prueba a escanear el código", "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_overlay_msg": "Gemini Vision está analizando el producto...",
"ai_retry_btn": "Reintentar con IA", "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_title": "Producto reconocido por IA",
"ai_match_subtitle": "Elige un producto ya en despensa o agrega el detectado.", "ai_match_subtitle": "Elige un producto ya en despensa o agrega el detectado.",
"ai_match_existing": "Posibles coincidencias en despensa", "ai_match_existing": "Actualmente en despensa",
"ai_match_none": "No se encontraron productos similares en despensa.", "ai_match_finished": "Agotados / terminados",
"ai_match_use_btn": "Usar este", "ai_match_catalog": "En catálogo (sin stock)",
"ai_match_add_btn": "Agregar \"{name}\"", "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", "ai_detected_label": "IA detecto",
"stock_in_pantry": "Ya en despensa:", "stock_in_pantry": "Ya en despensa:",
"mode_shopping_activated": "🛒 ¡Modo compras activado!" "mode_shopping_activated": "🛒 ¡Modo compras activado!"
@@ -289,8 +306,9 @@
"hint_modify": "📝 Puedes cambiar la fecha o escanearla con la cámara", "hint_modify": "📝 Puedes cambiar la fecha o escanearla con la cámara",
"scan_expiry_title": "📷 Escanear fecha de caducidad", "scan_expiry_title": "📷 Escanear fecha de caducidad",
"product_added": "✅ ¡{name} añadido!{qty}", "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)", "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_question": "¿Al vacío?",
"vacuum_saved": "🔒 ¡Al vacío!" "vacuum_saved": "🔒 ¡Al vacío!"
}, },
@@ -343,6 +361,8 @@
"name_label": "🏷️ Nombre del producto *", "name_label": "🏷️ Nombre del producto *",
"name_placeholder": "Ej.: Leche entera, Pasta penne...", "name_placeholder": "Ej.: Leche entera, Pasta penne...",
"brand_label": "🏢 Marca", "brand_label": "🏢 Marca",
"allergens_label": "Alérgenos:",
"ingredients_summary": "📋 Ingredientes",
"brand_placeholder": "Ej.: Barilla, Danone, Heinz...", "brand_placeholder": "Ej.: Barilla, Danone, Heinz...",
"category_label": "📂 Categoría", "category_label": "📂 Categoría",
"unit_label": "📏 Unidad de medida", "unit_label": "📏 Unidad de medida",
@@ -375,7 +395,7 @@
"labels_label": "Etiquetas", "labels_label": "Etiquetas",
"select_variant": "Selecciona la variante exacta o usa los datos de IA:", "select_variant": "Selecciona la variante exacta o usa los datos de IA:",
"history_badge": "📊 historial", "history_badge": "📊 historial",
"from_history": " (del historial)" "from_history": " (media últimos 3)"
}, },
"products": { "products": {
"title": "📦 Todos los productos", "title": "📦 Todos los productos",
@@ -499,6 +519,16 @@
"item_removed": "✅ ¡{name} eliminado de la lista!", "item_removed": "✅ ¡{name} eliminado de la lista!",
"urgency_spec_critical": "⚡ Urgente", "urgency_spec_critical": "⚡ Urgente",
"urgency_spec_high": "🟠 Pronto", "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_n": "Añadir {n} a Bring!",
"bring_add_selected": "Añadir selección a Bring!", "bring_add_selected": "Añadir selección a Bring!",
"bring_adding": "Añadiendo...", "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.", "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", "detect_btn": "🔄 Detectar cámaras",
"ai_fallback_label": "Identificación visual IA (repuesto 5s)", "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": { "security": {
"title": "🔒 Certificado HTTPS", "title": "🔒 Certificado HTTPS",
+38 -7
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Rétablir les valeurs par défaut", "reset_default": "↺ Rétablir les valeurs par défaut",
"save_info": "💾 Enregistrer les informations", "save_info": "💾 Enregistrer les informations",
"retry": "🔄 Réessayer", "retry": "🔄 Réessayer",
"next": "Suivant →",
"yes_short": "Oui", "yes_short": "Oui",
"no_short": "Non" "no_short": "Non"
}, },
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Mode courses", "mode_shopping": "🛒 Mode courses",
"mode_shopping_end": "✅ Terminer les courses", "mode_shopping_end": "✅ Terminer les courses",
"spesa_btn": "🛒 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", "zoom": "Zoom",
"tab_barcode": "Code-barres", "tab_barcode": "Code-barres",
"tab_name": "Nom", "tab_name": "Nom",
@@ -233,21 +235,36 @@
"status_confirmed": "Confirmé !", "status_confirmed": "Confirmé !",
"status_parallel": "Scan combiné actif...", "status_parallel": "Scan combiné actif...",
"status_ocr_searching": "Je lis les chiffres du code-barres...", "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...", "status_ai_visual_searching": "J'essaie maintenant de reconnaître le produit...",
"method_ai_ocr": "Gemini OCR", "method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision", "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_searching": "Identification IA en cours...",
"ai_fallback_found": "Produit identifié par l'IA", "ai_fallback_found": "Produit identifié par l'IA",
"ai_fallback_not_found": "IA : produit non reconnu", "ai_fallback_not_found": "IA : produit non reconnu",
"ai_fallback_exhausted": "IA : produit non reconnu — réessayez avec le code-barres", "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_overlay_msg": "Gemini Vision analyse le produit...",
"ai_retry_btn": "Reessayer avec l'IA", "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_title": "Produit reconnu par l'IA",
"ai_match_subtitle": "Choisissez un produit deja en stock ou ajoutez celui detecte.", "ai_match_subtitle": "Choisissez un produit deja en stock ou ajoutez celui detecte.",
"ai_match_existing": "Correspondances possibles dans le stock", "ai_match_existing": "Actuellement en stock",
"ai_match_none": "Aucun produit similaire trouve dans le stock.", "ai_match_finished": "Terminés / épuisés",
"ai_match_use_btn": "Utiliser celui-ci", "ai_match_catalog": "Au catalogue (sans stock)",
"ai_match_add_btn": "Ajouter \"{name}\"", "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", "ai_detected_label": "IA a detecte",
"stock_in_pantry": "Déjà à la maison :", "stock_in_pantry": "Déjà à la maison :",
"mode_shopping_activated": "🛒 Mode courses activé !" "mode_shopping_activated": "🛒 Mode courses activé !"
@@ -289,8 +306,9 @@
"hint_modify": "📝 Vous pouvez modifier la date ou la scanner avec la caméra", "hint_modify": "📝 Vous pouvez modifier la date ou la scanner avec la caméra",
"scan_expiry_title": "📷 Scanner la date de péremption", "scan_expiry_title": "📷 Scanner la date de péremption",
"product_added": "✅ {name} ajouté !{qty}", "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)", "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_question": "Sous vide ?",
"vacuum_saved": "🔒 Sous vide !" "vacuum_saved": "🔒 Sous vide !"
}, },
@@ -343,6 +361,8 @@
"name_label": "🏷️ Nom du produit *", "name_label": "🏷️ Nom du produit *",
"name_placeholder": "Ex. : Lait entier, Pâtes penne...", "name_placeholder": "Ex. : Lait entier, Pâtes penne...",
"brand_label": "🏢 Marque", "brand_label": "🏢 Marque",
"allergens_label": "Allergènes :",
"ingredients_summary": "📋 Ingrédients",
"brand_placeholder": "Ex. : Barilla, Danone, Heinz...", "brand_placeholder": "Ex. : Barilla, Danone, Heinz...",
"category_label": "📂 Catégorie", "category_label": "📂 Catégorie",
"unit_label": "📏 Unité de mesure", "unit_label": "📏 Unité de mesure",
@@ -375,7 +395,7 @@
"labels_label": "Labels", "labels_label": "Labels",
"select_variant": "Sélectionnez la variante exacte ou utilisez les données IA :", "select_variant": "Sélectionnez la variante exacte ou utilisez les données IA :",
"history_badge": "📊 historique", "history_badge": "📊 historique",
"from_history": " (historique)" "from_history": " (moy. 3 derniers)"
}, },
"products": { "products": {
"title": "📦 Tous les produits", "title": "📦 Tous les produits",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} retiré de la liste !", "item_removed": "✅ {name} retiré de la liste !",
"urgency_spec_critical": "⚡ Urgent", "urgency_spec_critical": "⚡ Urgent",
"urgency_spec_high": "🟠 Bientôt", "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_n": "Ajouter {n} à Bring !",
"bring_add_selected": "Ajouter la sélection à Bring !", "bring_add_selected": "Ajouter la sélection à Bring !",
"bring_adding": "Ajout en cours...", "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.", "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", "detect_btn": "🔄 Détecter les caméras",
"ai_fallback_label": "Identification visuelle IA (repli 5s)", "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": { "security": {
"title": "🔒 Certificat HTTPS", "title": "🔒 Certificat HTTPS",
+39 -8
View File
@@ -32,6 +32,7 @@
"reset_default": "↺ Ripristina default", "reset_default": "↺ Ripristina default",
"save_info": "💾 Salva informazioni", "save_info": "💾 Salva informazioni",
"retry": "🔄 Riprova", "retry": "🔄 Riprova",
"next": "Avanti →",
"yes_short": "Sì", "yes_short": "Sì",
"no_short": "No" "no_short": "No"
}, },
@@ -199,6 +200,7 @@
"mode_shopping": "🛒 Modalità Spesa", "mode_shopping": "🛒 Modalità Spesa",
"mode_shopping_end": "✅ Fine spesa", "mode_shopping_end": "✅ Fine spesa",
"spesa_btn": "🛒 Spesa", "spesa_btn": "🛒 Spesa",
"spesa_camera_hint": "Inquadra il codice con la telecamera. Senza barcode? Premi «Identifica con AI» sotto.",
"zoom": "Zoom", "zoom": "Zoom",
"tab_barcode": "Barcode", "tab_barcode": "Barcode",
"tab_name": "Nome", "tab_name": "Nome",
@@ -234,21 +236,36 @@
"status_confirmed": "Confermato!", "status_confirmed": "Confermato!",
"status_parallel": "Doppia scansione attiva...", "status_parallel": "Doppia scansione attiva...",
"status_ocr_searching": "Sto leggendo i numeri del codice a barre...", "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...", "status_ai_visual_searching": "Ora provo a riconoscere il prodotto...",
"method_ai_ocr": "Gemini OCR", "method_ai_ocr": "Gemini OCR",
"method_ai_vision": "Gemini Vision", "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_searching": "Identificazione AI in corso...",
"ai_fallback_found": "Prodotto identificato dall'AI", "ai_fallback_found": "Prodotto identificato dall'AI",
"ai_fallback_not_found": "AI: prodotto non riconosciuto", "ai_fallback_not_found": "AI: prodotto non riconosciuto",
"ai_fallback_exhausted": "AI: prodotto non riconosciuto — riprova con il barcode", "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_overlay_msg": "Gemini Vision sta analizzando il prodotto...",
"ai_retry_btn": "Riprova con AI", "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_title": "Prodotto riconosciuto con AI",
"ai_match_subtitle": "Scegli se usare un prodotto gia presente oppure aggiungere quello rilevato.", "ai_match_subtitle": "Scegli un prodotto esistente o creane uno nuovo con il nome rilevato.",
"ai_match_existing": "Possibili corrispondenze in dispensa", "ai_match_existing": "In dispensa adesso",
"ai_match_none": "Nessun prodotto simile trovato in dispensa.", "ai_match_finished": "Finiti / esauriti",
"ai_match_use_btn": "Usa questo", "ai_match_catalog": "Nel catalogo (senza scorte)",
"ai_match_add_btn": "Aggiungi \"{name}\"", "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", "ai_detected_label": "AI ha trovato",
"mode_shopping_activated": "🛒 Modalità Spesa attivata!" "mode_shopping_activated": "🛒 Modalità Spesa attivata!"
}, },
@@ -289,8 +306,9 @@
"hint_modify": "📝 Puoi modificare la data o scansionarla con la fotocamera", "hint_modify": "📝 Puoi modificare la data o scansionarla con la fotocamera",
"scan_expiry_title": "📷 Scansiona Data Scadenza", "scan_expiry_title": "📷 Scansiona Data Scadenza",
"product_added": "✅ {name} aggiunto!{qty}", "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)", "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_question": "Messo sotto vuoto?",
"vacuum_saved": "🔒 Sotto vuoto registrato" "vacuum_saved": "🔒 Sotto vuoto registrato"
}, },
@@ -343,6 +361,8 @@
"name_label": "🏷️ Nome Prodotto *", "name_label": "🏷️ Nome Prodotto *",
"name_placeholder": "Es: Latte intero, Pasta penne rigate...", "name_placeholder": "Es: Latte intero, Pasta penne rigate...",
"brand_label": "🏢 Marca", "brand_label": "🏢 Marca",
"allergens_label": "Allergeni:",
"ingredients_summary": "📋 Ingredienti",
"brand_placeholder": "Es: Barilla, Granarolo, Mutti...", "brand_placeholder": "Es: Barilla, Granarolo, Mutti...",
"category_label": "📂 Categoria", "category_label": "📂 Categoria",
"unit_label": "📏 Unità di misura", "unit_label": "📏 Unità di misura",
@@ -375,7 +395,7 @@
"labels_label": "Etichette", "labels_label": "Etichette",
"select_variant": "Seleziona la variante esatta o usa i dati AI:", "select_variant": "Seleziona la variante esatta o usa i dati AI:",
"history_badge": "📊 storico", "history_badge": "📊 storico",
"from_history": " (da storico)" "from_history": " (media ultimi 3)"
}, },
"products": { "products": {
"title": "📦 Tutti i Prodotti", "title": "📦 Tutti i Prodotti",
@@ -499,6 +519,16 @@
"item_removed": "✅ {name} rimosso dalla lista!", "item_removed": "✅ {name} rimosso dalla lista!",
"urgency_spec_critical": "⚡ Urgente", "urgency_spec_critical": "⚡ Urgente",
"urgency_spec_high": "🟠 Presto", "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_n": "Aggiungi {n} a Bring!",
"bring_add_selected": "Aggiungi selezionati a Bring!", "bring_add_selected": "Aggiungi selezionati a Bring!",
"bring_adding": "Aggiunta in corso...", "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.", "devices_hint": "Se hai più fotocamere, puoi selezionarne una specifica dall'elenco sopra dopo aver concesso i permessi.",
"detect_btn": "🔄 Rileva fotocamere", "detect_btn": "🔄 Rileva fotocamere",
"ai_fallback_label": "Identificazione visiva AI (fallback 5s)", "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": { "security": {
"title": "🔒 Certificato HTTPS", "title": "🔒 Certificato HTTPS",