diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7b6fda..2983463 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,15 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap.
-## [1.7.25] - 2026-05-23
-
-### Fixed
-- **Bring! items re-appearing after manual purchase removal** — `removeBringItem` and `confirmShoppingItemFound` now call `_markBringPurchased` immediately after removing an item, so the purchased blocklist is populated before the next background refresh. `autoAddCriticalItems` now also respects the blocklist for depleted items (quantity = 0), preventing re-addition of just-bought products.
-- **Barcode lookup false "not found"** — Added `_offFetchProduct()` PHP helper that tries up to three barcode candidates (given code, UPC-A → EAN-13 by prepending `0`, EAN-13 → UPC-A by stripping leading `0`) against two Open Food Facts locales (`it` and default), with one automatic retry on network error. UPCItemDB fallback also iterates the same candidates. Reduces false negatives on 12-digit US barcodes and region-specific EAN codes.
-- **Partial throw from expired-items banner** — The "Butta" button in the dashboard banner previously discarded the entire inventory row with no confirmation. It now opens the existing throw modal (location picker + quantity input + "Butta tutto" option), consistent with the throw flow available from the scan → action page.
+## [1.7.25] - 2026-05-25
### Added
-- **Related stock display when scanning branded products** — When scanning a product, the action page now shows a green card listing any inventory items from the same generic family (matching first name token or shopping name, different product ID) already at home, so you avoid double-buying.
+- **Home Assistant integration** — Full bidirectional HA support: inventory sensor (`sensor.evershelf_*`) exposes item counts, expiring items, shopping total, opened items and next-expiry info. Webhooks fire on inventory changes (add/use/shopping). Daily cron alert notifies via HA for items expiring within the configured threshold. TTS announces cooking steps through HA Media Player. New Settings tab 🏠 with connection test, TTS preset (Piper, Google, Nabu Casa), webhook config, and YAML snippet for `configuration.yaml`. Resolves [#111](https://github.com/dadaloop82/EverShelf/issues/111).
+- **Offline mode** — Full offline-first support. Full-screen overlay on network loss; "Continue offline" button after 3 s, auto-enter after 8 s. Inventory and settings are synced to `localStorage` at startup and cached on every successful API call. Writes (add/use/update/delete) are queued and synced on reconnect with optimistic UI updates. Pending operations survive page refresh and are re-synced automatically at next startup. AI/network-dependent sections (anti-waste chart, nutrition analysis, recipe generator, price fetching, Gemini chat) are hidden in offline mode. `remoteLog` and `reportError` are buffered offline and flushed on restore. Broken external images replaced with a grey placeholder.
+- **Offline-computed dashboard** — While offline, `inventory_summary` and `stats` (expiring/expired/opened) are derived client-side from the local cache so all dashboard stat cards and expiry alerts show accurate data.
+
+### Fixed
+- **Offline banner flood** — Opened items in the offline `stats` response lacked `is_edible`; `!undefined` evaluated to `true`, causing every opened item to be shown as "not edible" in the dashboard banner. Field is now set to `true` (client-side shelf-life check already handles genuinely expired items).
+- **Version update badge showing older versions** — `_checkWebappUpdate` used `latestTag !== _loadedVersion` (inequality only), so running a newer dev build triggered an "update available" badge for an older GitHub release. Now uses `_semverGt(latest, current)` so only genuinely newer releases trigger the badge.
+- **Bring! items re-appearing after manual purchase removal** — `removeBringItem` and `confirmShoppingItemFound` now call `_markBringPurchased` immediately, and `autoAddCriticalItems` respects the blocklist for depleted items.
+- **Barcode lookup false "not found"** — New `_offFetchProduct()` tries three barcode candidates (given, UPC-A↔EAN-13 conversion) across two Open Food Facts locales with auto-retry.
+- **Partial throw from expired-items banner** — "Butta" now opens the throw modal (qty + location) instead of silently deleting the entire inventory row.
+- **Related stock display when scanning branded products** — When scanning a product, the action page now shows a green card listing any inventory items from the same generic family already at home.
## [1.7.24] - 2026-05-21
diff --git a/README.md b/README.md
index 3a87f20..b096d67 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,16 @@ Connect your pantry to your smart home in minutes — no YAML, no manual sensor
- **Mobile-first design** — Optimized for phones, works on tablets and desktop
- **Installable** — Add to home screen for a native app experience
- **Multi-device** — All user data (shopping tags, pinned items, location preferences, scan history) is stored server-side in SQLite and shared across every device on the same instance; no data is siloed in a single browser's localStorage
-
+### 📶 Offline Mode
+- **Automatic detection** — Full-screen overlay appears immediately on network loss; shows a "Continue offline" button after 3 s, and auto-enters offline mode after 8 s
+- **Local inventory cache** — Inventory is synced to `localStorage` at every startup and on each successful API call; the offline view always reflects the last known state
+- **Write queue** — Add, use, update and delete operations performed while offline are queued locally and synced to the server automatically on reconnect (including after a page refresh)
+- **Optimistic UI** — Queued writes are applied immediately to the local cache so the interface stays responsive
+- **Offline-computed stats** — Expiring and expired items are derived client-side from the cache; dashboard stat cards show real counts instead of zeros
+- **AI/network sections hidden** — Anti-waste chart, nutrition analysis, recipe generator, price fetching, and Gemini chat are hidden in offline mode; the inventory, history, and manually-managed shopping list remain fully functional
+- **Broken image fallback** — External product images (Open Food Facts, etc.) that fail to load are replaced with a neutral grey placeholder, keeping the layout intact
+- **Startup recovery** — If the page is refreshed while operations are queued, they are detected and synced automatically on the next successful startup
+- **Buffered error reporting** — `remoteLog` and `reportError` calls made while offline are stored locally and flushed to the server (and to GitHub issues) when the connection is restored
### ⚖️ Smart Scale Integration (Add-on)
- **Bluetooth gateway** — Connects a BLE smart scale to EverShelf via local WebSocket
- **SSE relay** — Server-side relay avoids mixed-content (HTTPS→WS) issues
diff --git a/assets/css/style.css b/assets/css/style.css
index 81c92cb..3d2880d 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -596,13 +596,37 @@ body {
}
.offline-banner-retry:hover { background: rgba(255,255,255,0.38); }
+/* Pulsing dot shown in the banner while the offline cache is being read */
+.offline-banner-dot {
+ display: inline-block;
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: #f87171;
+ margin-right: 6px;
+ vertical-align: middle;
+ animation: offline-dot-pulse 1.1s ease-in-out infinite;
+}
+@keyframes offline-dot-pulse {
+ 0%, 100% { opacity: 0.3; transform: scale(0.8); }
+ 50% { opacity: 1; transform: scale(1.15); }
+}
+
/* When server is offline, block interactions with the main content */
-body.server-offline .app-content {
+body.server-offline:not(.offline-mode) .app-content {
opacity: 0.4;
pointer-events: none;
user-select: none;
transition: opacity 0.3s;
}
+/* In offline-mode the app is usable; just a subtle left-border indicator */
+body.offline-mode .app-content {
+ border-left: 3px solid rgba(239, 68, 68, 0.45);
+}
+/* Hide the "Retry" button in the banner when in offline mode — use the Continue button instead */
+body.offline-mode .offline-banner-retry {
+ display: none;
+}
body.server-offline .bottom-nav {
opacity: 0.4;
pointer-events: none;
@@ -7655,3 +7679,117 @@ body.cooking-mode-active .app-header {
/* ── Appliance remove active ── */
[data-theme="dark"] .appliance-item .appliance-remove:active { background: #2a0808; }
+
+/* ===== NETWORK ERROR OVERLAY ===== */
+#network-error-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(6, 8, 20, 0.97);
+ z-index: 300000; /* highest: above screensaver(10000), cooking(99999), preloader(200000) */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.4s ease;
+ pointer-events: none;
+}
+#network-error-overlay.visible {
+ opacity: 1;
+ pointer-events: auto;
+}
+.net-error-body {
+ text-align: center;
+ padding: 2.5rem 2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+.net-error-icon {
+ font-size: 5.5rem;
+ line-height: 1;
+ margin-bottom: 1.75rem;
+ animation: net-pulse 2.2s ease-in-out infinite;
+ display: block;
+ filter: drop-shadow(0 0 32px rgba(248, 113, 113, 0.35));
+}
+#network-error-overlay.restored .net-error-icon {
+ animation: none;
+ filter: drop-shadow(0 0 32px rgba(74, 222, 128, 0.45));
+}
+#network-error-overlay.checking .net-error-icon {
+ animation: net-spin 1.2s linear infinite;
+}
+@keyframes net-pulse {
+ 0%, 100% { opacity: 0.45; transform: scale(0.92); }
+ 50% { opacity: 1; transform: scale(1.06); }
+}
+@keyframes net-spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+.net-error-title {
+ font-size: 2.5rem;
+ font-weight: 700;
+ color: #f87171;
+ margin-bottom: 0.75rem;
+ letter-spacing: -0.02em;
+ transition: color 0.4s;
+}
+#network-error-overlay.restored .net-error-title {
+ color: #4ade80;
+}
+.net-error-subtitle {
+ font-size: 1.1rem;
+ color: #94a3b8;
+ max-width: 420px;
+ line-height: 1.6;
+ margin: 0 auto;
+}
+.net-error-status {
+ margin-top: 1.5rem;
+ font-size: 0.88rem;
+ color: #475569;
+ min-height: 1.3em;
+ letter-spacing: 0.01em;
+}
+/* "Continue in offline mode" button — appears after 3 s */
+.net-error-continue-btn {
+ margin-top: 2.2rem;
+ background: rgba(255,255,255,0.07);
+ border: 1px solid rgba(255,255,255,0.22);
+ color: #94a3b8;
+ border-radius: 10px;
+ padding: 0.7rem 1.6rem;
+ font-size: 0.92rem;
+ cursor: pointer;
+ transition: background 0.2s, color 0.2s, transform 0.3s, opacity 0.3s;
+ opacity: 0;
+ transform: translateY(8px);
+}
+.net-error-continue-btn.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+.net-error-continue-btn:hover {
+ background: rgba(255,255,255,0.16);
+ color: #e2e8f0;
+}
+
+/* ─── Offline mode: hide AI and network-dependent UI ────────────────────────
+ Sections that require a live server response or AI are hidden so the user
+ isn't confronted with empty/broken widgets while offline. */
+body.offline-mode #waste-chart-section,
+body.offline-mode #nutrition-section,
+body.offline-mode #quick-recipe-bar,
+body.offline-mode .header-gemini-btn,
+body.offline-mode #btn-suggest,
+body.offline-mode #btn-fetch-prices,
+body.offline-mode .recipe-generate-btn {
+ display: none !important;
+}
+/* Smart-shopping AI section: show as disabled rather than disappearing entirely */
+body.offline-mode #smart-shopping {
+ opacity: 0.45;
+ pointer-events: none;
+ filter: grayscale(0.6);
+}
diff --git a/assets/js/app.js b/assets/js/app.js
index 729d332..2f3fd57 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -13,6 +13,8 @@
// 2. reportError() — immediate single POST → report_error endpoint → GitHub Issue
const _remoteLogBuffer = [];
+const _OFFLINE_LOGS_KEY = '_evershelf_offline_logs'; // buffered log msgs while offline
+const _OFFLINE_ERRORS_KEY = '_evershelf_offline_errors'; // buffered error reports while offline
let _remoteLogTimer = null;
const _origConsoleError = console.error.bind(console);
const _origConsoleWarn = console.warn.bind(console);
@@ -33,11 +35,25 @@ function flushRemoteLog() {
_remoteLogTimer = null;
if (_remoteLogBuffer.length === 0) return;
const msgs = _remoteLogBuffer.splice(0);
+ // If offline, buffer for flush on reconnect instead of losing them
+ const isOfflineNow = (typeof _serverOffline !== 'undefined' && _serverOffline) ||
+ (typeof _networkDown !== 'undefined' && _networkDown) ||
+ (typeof _offlineMode !== 'undefined' && _offlineMode);
+ if (isOfflineNow) { _bufferOfflineLogs(msgs); return; }
fetch(`api/index.php?action=client_log`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: msgs })
- }).catch(() => {});
+ }).catch(() => { _bufferOfflineLogs(msgs); }); // store if request itself fails
+}
+
+function _bufferOfflineLogs(msgs) {
+ try {
+ const pending = JSON.parse(localStorage.getItem(_OFFLINE_LOGS_KEY) || '[]');
+ pending.push(...msgs);
+ if (pending.length > 500) pending.splice(0, pending.length - 500);
+ localStorage.setItem(_OFFLINE_LOGS_KEY, JSON.stringify(pending));
+ } catch(e) {}
}
// Override console.error and console.warn to also send remotely
@@ -65,21 +81,48 @@ function reportError(payload) {
version: document.querySelector('.header-version')?.textContent?.trim() || '',
url: location.href,
user_agent: navigator.userAgent,
+ ts: new Date().toISOString(),
}, payload);
+ // When offline, buffer for replay when reconnected (→ GitHub issue on restore)
+ const isOfflineNow = (typeof _serverOffline !== 'undefined' && _serverOffline) ||
+ (typeof _networkDown !== 'undefined' && _networkDown) ||
+ (typeof _offlineMode !== 'undefined' && _offlineMode);
+ if (isOfflineNow) { _bufferOfflineError(body); return; }
+
fetch('api/index.php?action=report_error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
- }).catch(() => {}); // fire-and-forget; never throw from error handler
+ }).catch(() => { _bufferOfflineError(body); }); // store if request itself fails
// Note: the server will also skip issue creation if this version is not the latest.
}
+function _bufferOfflineError(body) {
+ try {
+ const pending = JSON.parse(localStorage.getItem(_OFFLINE_ERRORS_KEY) || '[]');
+ pending.push(body);
+ if (pending.length > 50) pending.splice(0, pending.length - 50);
+ localStorage.setItem(_OFFLINE_ERRORS_KEY, JSON.stringify(pending));
+ } catch(e) {}
+}
+
// ── Webapp update notification ───────────────────────────────────────────────
// Checks both the deployed webapp version and the latest GitHub release.
// Fires on tab focus and every 5 minutes.
const _loadedVersion = (document.querySelector('.header-version')?.textContent?.trim() || '').replace(/^v/, '');
+// ── Broken image fallback ─────────────────────────────────────────────────────
+// External product images (Open Food Facts, etc.) are unavailable when offline.
+// Replace any broken
with a neutral grey placeholder so the layout stays intact.
+document.addEventListener('error', (e) => {
+ if (e.target.tagName !== 'IMG' || e.target.dataset.offlineErr) return;
+ e.target.dataset.offlineErr = '1';
+ // 60x60 grey placeholder SVG with a '?' glyph
+ e.target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Crect width='60' height='60' rx='8' fill='%231e293b'/%3E%3Ctext x='30' y='38' text-anchor='middle' fill='%2364748b' font-size='24' font-family='sans-serif'%3E%3F%3C/text%3E%3C/svg%3E";
+ e.target.style.opacity = '0.45';
+}, true);
+
// ── Gemini AI availability ────────────────────────────────────────────────────
// Set to true in _initApp / syncSettingsFromDB once server confirms key is set.
// All AI entry points call _requireGemini() before opening camera / API calls.
@@ -133,6 +176,17 @@ function _applyDemoModeUI() {
}
}
+function _semverGt(a, b) {
+ // Returns true if version string a is strictly greater than b (e.g. "1.7.25" > "1.7.23")
+ const pa = a.split('.').map(Number);
+ const pb = b.split('.').map(Number);
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
+ const na = pa[i] || 0, nb = pb[i] || 0;
+ if (na !== nb) return na > nb;
+ }
+ return false;
+}
+
function _checkWebappUpdate() {
const STORAGE_KEY = '_evershelf_update_checked_at';
const SEEN_KEY = '_evershelf_update_seen_ts';
@@ -151,7 +205,7 @@ function _checkWebappUpdate() {
// ── Check 1: server has a newer version deployed since this page loaded ──
const serverVer = (data.webapp_version || '').replace(/^v/, '');
- const deployChanged = serverVer && _loadedVersion && serverVer !== _loadedVersion;
+ const deployChanged = serverVer && _loadedVersion && _semverGt(serverVer, _loadedVersion);
// ── Check 2: a newer GitHub release not yet acknowledged ──
const publishedAt = data.published_at || '';
@@ -159,7 +213,7 @@ function _checkWebappUpdate() {
const latestTag = (data.latest_tag || '').replace(/^v/, '');
const releaseNewer = publishedAt && publishedAt !== seenTs &&
/^\d+\.\d+/.test(latestTag) &&
- _loadedVersion && latestTag !== _loadedVersion;
+ _loadedVersion && _semverGt(latestTag, _loadedVersion);
if (!deployChanged && !releaseNewer) return;
@@ -3611,6 +3665,10 @@ async function api(action, params = {}, method = 'GET', body = null, extraHeader
return { success: true, purchase: shoppingItems, listUUID: 'demo-list', _demo: true };
}
}
+ // In offline mode, serve from cache / queue writes
+ if (_offlineMode) {
+ return _handleOfflineApi(action, params, body);
+ }
let url = `${API_BASE}?action=${action}`;
if (method === 'GET') {
Object.entries(params).forEach(([k, v]) => {
@@ -3624,7 +3682,20 @@ async function api(action, params = {}, method = 'GET', body = null, extraHeader
} else if (Object.keys(extraHeaders).length > 0) {
opts.headers = { ...extraHeaders };
}
- const res = await fetch(url, opts);
+ let res;
+ try {
+ res = await fetch(url, opts);
+ // Server responded → reset failure counter and hide overlay if it was showing
+ if (_networkDown) _hideNetworkOverlay(true);
+ _networkFailCount = 0;
+ } catch (fetchErr) {
+ // Network-level failure (no route to host, Wi-Fi down, etc.)
+ _networkFailCount++;
+ if (_networkFailCount >= _NETWORK_FAIL_THRESHOLD) {
+ _showNetworkOverlay();
+ }
+ throw fetchErr;
+ }
if (!res.ok) {
remoteLog('API_ERROR', `${action} HTTP ${res.status}`);
// Report HTTP 5xx as server errors (not 4xx which are usually user errors)
@@ -3637,6 +3708,13 @@ async function api(action, params = {}, method = 'GET', body = null, extraHeader
}
}
const data = await res.json();
+ // Keep local caches fresh for offline use (only ever written when server responds successfully)
+ if (action === 'inventory_list' && data && Array.isArray(data.inventory)) {
+ _offlineCacheSet(data.inventory);
+ }
+ if (action === 'get_settings' && data && data.success !== false) {
+ _offlineCacheSetSettings(data);
+ }
if (data && data.error) {
remoteLog('API_FAIL', `${action}: ${data.error}`);
}
@@ -13514,12 +13592,13 @@ function renderCookingStep() {
}).join('');
}
- // Show ALL unused from_pantry ingredients (not filtered by step text).
- // The AI often uses pronouns ("tagliarla", "aggiungile") instead of the ingredient
- // name, so text-matching would miss them. Better to always show what's available.
- const ings = (_cookingRecipe.ingredients || [])
- .map((ing, idx) => ({ ...ing, _idx: idx }))
- .filter(ing => ing.from_pantry && ing.product_id && ing.used !== true);
+ // Show ALL unused from_pantry ingredients only on the first step.
+ // On subsequent steps the ingredient panel stays hidden to avoid distraction.
+ const ings = _cookingStep === 0
+ ? (_cookingRecipe.ingredients || [])
+ .map((ing, idx) => ({ ...ing, _idx: idx }))
+ .filter(ing => ing.from_pantry && ing.product_id && ing.used !== true)
+ : [];
const ingsEl = document.getElementById('cooking-step-ings');
if (ings.length > 0) {
@@ -15032,6 +15111,328 @@ function saveChatHistory() {
}).catch(() => {});
}
+// ===== NETWORK ERROR OVERLAY + OFFLINE MODE =====
+// ─────────────────────────────────────────────────────────────────────────────
+// State
+let _networkDown = false; // true while overlay is visible
+let _networkFailCount = 0; // consecutive TypeError failures in api()
+let _offlineMode = false; // true = overlay hidden, banner showing, cache reads/write queue
+let _offlineBannerTimer = null; // auto-enter offline mode after delay
+let _continueBtnTimer = null; // show "Continue offline" button after 3 s
+const _NETWORK_FAIL_THRESHOLD = 3;
+const _OFFLINE_MODE_DELAY_MS = 8000; // auto-enter offline mode after 8 s of overlay
+const _OFFLINE_CACHE_KEY = '_evershelf_inv_cache';
+const _OFFLINE_SETTINGS_KEY = '_evershelf_settings_cache';
+const _OFFLINE_QUEUE_KEY = '_evershelf_op_queue';
+
+// ─── Local cache helpers ────────────────────────────────────────────────────
+function _offlineCacheGet() {
+ try { return JSON.parse(localStorage.getItem(_OFFLINE_CACHE_KEY)) || null; } catch { return null; }
+}
+function _offlineCacheSet(inventory) {
+ try { localStorage.setItem(_OFFLINE_CACHE_KEY, JSON.stringify(inventory)); } catch(e) {}
+}
+function _offlineCacheGetSettings() {
+ try { return JSON.parse(localStorage.getItem(_OFFLINE_SETTINGS_KEY)) || null; } catch { return null; }
+}
+function _offlineCacheSetSettings(settings) {
+ try { localStorage.setItem(_OFFLINE_SETTINGS_KEY, JSON.stringify(settings)); } catch(e) {}
+}
+function _offlineQueueGet() {
+ try { return JSON.parse(localStorage.getItem(_OFFLINE_QUEUE_KEY)) || []; } catch { return []; }
+}
+function _offlineQueueSet(q) {
+ try { localStorage.setItem(_OFFLINE_QUEUE_KEY, JSON.stringify(q)); } catch(e) {}
+}
+function _offlineQueuePush(action, body) {
+ const q = _offlineQueueGet();
+ if (q.length >= 100) q.shift(); // cap at 100
+ q.push({ action, body: body ? { ...body } : null, ts: Date.now() });
+ _offlineQueueSet(q);
+ _renderOfflineBanner();
+}
+
+// ─── Offline API handler: called by api() when _offlineMode is true ─────────
+function _handleOfflineApi(action, params, body) {
+ // ─── Reads: computed from or served directly from local cache ────────────
+ if (action === 'inventory_list') {
+ let inv = _offlineCacheGet() || [];
+ // Client-side location filter so per-location views work correctly
+ if (params && params.location) inv = inv.filter(i => i.location === params.location);
+ return { success: true, inventory: inv, _offline: true };
+ }
+ if (action === 'inventory_summary') {
+ const inv = _offlineCacheGet() || [];
+ const byLoc = {};
+ inv.forEach(item => {
+ const loc = item.location || 'other';
+ if (!byLoc[loc]) byLoc[loc] = { location: loc, product_count: 0 };
+ byLoc[loc].product_count++;
+ });
+ return { success: true, summary: Object.values(byLoc), _offline: true };
+ }
+ if (action === 'stats') {
+ const inv = _offlineCacheGet() || [];
+ const now = Date.now();
+ const MS = 86400000;
+ const expiring_soon = inv.filter(i => {
+ if (!i.expiry_date || !(parseFloat(i.quantity) > 0)) return false;
+ const d = Math.floor((new Date(i.expiry_date).getTime() - now) / MS);
+ return d >= 0 && d <= 7;
+ });
+ const expired = inv.filter(i => {
+ if (!i.expiry_date || !(parseFloat(i.quantity) > 0)) return false;
+ return new Date(i.expiry_date).getTime() < now;
+ });
+ return {
+ success: true, _offline: true,
+ expiring_soon, expired,
+ // Mark is_edible:true so the banner's server-trust check produces no false positives offline.
+ // The client-side opened-shelf-life check in loadBannerAlerts() already handles genuinely
+ // expired opened items using estimateOpenedExpiryDays().
+ opened: inv.filter(i => i.opened_at && parseFloat(i.quantity) > 0).map(i => ({ ...i, is_edible: true })),
+ used_30d: 0, wasted_30d: 0, used_prev_30d: 0, wasted_prev_30d: 0,
+ used_prev_60d: 0, wasted_prev_60d: 0,
+ };
+ }
+ if (action === 'get_settings') {
+ const cached = _offlineCacheGetSettings();
+ if (cached) return { ...cached, _offline: true };
+ return { success: false, _offline: true };
+ }
+ if (action === 'get_settings') {
+ const cached = _offlineCacheGetSettings();
+ if (cached) return { ...cached, _offline: true };
+ return { success: false, _offline: true };
+ }
+ // Safe empty responses for read-only endpoints that can't be served from cache
+ const EMPTY_READS = {
+ 'recent_popular_products': { success: true, recent: [], popular: [], recent_ids: [], _offline: true },
+ 'consumption_predictions': { success: true, predictions: [], _offline: true },
+ 'inventory_anomalies': { success: true, anomalies: [], _offline: true },
+ 'inventory_finished_items':{ success: true, finished: [], _offline: true },
+ 'shopping_list': { success: true, purchase: [], listUUID: '', _offline: true },
+ 'bring_list': { success: true, purchase: [], listUUID: '', _offline: true },
+ 'smart_shopping': { success: true, items: [], _offline: true },
+ 'recipe_archive': { success: true, recipes: [], _offline: true },
+ 'food_facts': { success: false, _offline: true },
+ 'notifications': { success: true, notifications: [], _offline: true },
+ };
+ if (EMPTY_READS[action]) return EMPTY_READS[action];
+
+ // ─── Writes: queue and apply optimistic update to cache ──────────────────
+ const QUEUEABLE = ['inventory_update', 'inventory_use', 'inventory_delete',
+ 'inventory_add', 'inventory_confirm_finished'];
+ if (QUEUEABLE.includes(action)) {
+ _offlineQueuePush(action, body);
+ _applyOptimisticUpdate(action, body);
+ return { success: true, _offline: true, _queued: true };
+ }
+ // Everything else (AI, Bring!, etc.): return offline error
+ return { success: false, error: 'offline', _offline: true };
+}
+
+// Optimistically update the cached inventory so the UI reflects the change immediately
+function _applyOptimisticUpdate(action, body) {
+ if (!body) return;
+ const cache = _offlineCacheGet();
+ if (!cache) return;
+ let changed = false;
+ if (action === 'inventory_update' && body.id) {
+ const idx = cache.findIndex(i => i.id === body.id);
+ if (idx >= 0) { Object.assign(cache[idx], body); changed = true; }
+ } else if (action === 'inventory_use' && body.id) {
+ const idx = cache.findIndex(i => i.id === body.id);
+ if (idx >= 0) {
+ const used = parseFloat(body.qty ?? body.amount ?? 0);
+ cache[idx].quantity = Math.max(0, parseFloat(cache[idx].quantity ?? 0) - used);
+ changed = true;
+ }
+ } else if (action === 'inventory_delete' && body.id) {
+ const idx = cache.findIndex(i => i.id === body.id);
+ if (idx >= 0) { cache.splice(idx, 1); changed = true; }
+ } else if (action === 'inventory_add') {
+ cache.push({ ...body, id: -(Date.now()), _offline: true });
+ changed = true;
+ }
+ if (changed) _offlineCacheSet(cache);
+}
+
+// ─── Offline mode: banner + cache reads/write queue ─────────────────────────
+function _enterOfflineMode() {
+ if (_offlineMode) return;
+ _offlineMode = true;
+ clearTimeout(_offlineBannerTimer);
+ clearTimeout(_continueBtnTimer);
+ // Hide the full overlay (no "restored" animation)
+ _hideNetworkOverlay(false);
+ // Unblock the UI and show a subtle offline indicator
+ document.body.classList.add('offline-mode');
+ _renderOfflineBanner(true); // loading state
+ // Load page content from the local cache, then update banner to "ready"
+ const p = refreshCurrentPage();
+ const afterLoad = () => _renderOfflineBanner(false);
+ if (p && typeof p.then === 'function') p.then(afterLoad).catch(afterLoad);
+ else setTimeout(afterLoad, 600);
+}
+
+async function _exitOfflineMode() {
+ _offlineMode = false; // first — so api() calls inside sync go to server
+ document.body.classList.remove('offline-mode');
+ const q = _offlineQueueGet();
+ if (q.length > 0) {
+ const synced = await _syncOfflineQueue();
+ if (synced > 0) {
+ showToast(t('error.offline_synced').replace('{n}', synced), 'success');
+ refreshCurrentPage();
+ }
+ }
+ const banner = document.getElementById('offline-banner');
+ if (banner) banner.style.display = 'none';
+}
+
+function _renderOfflineBanner(loading = false) {
+ const banner = document.getElementById('offline-banner');
+ const textEl = banner ? banner.querySelector('.offline-banner-text') : null;
+ if (!banner || !textEl) return;
+ const q = _offlineQueueGet();
+ if (q.length > 0) {
+ textEl.innerHTML = t('error.offline_ops_pending').replace('{n}', q.length);
+ } else if (loading) {
+ textEl.innerHTML = `${t('error.offline_reading_cache')}`;
+ } else {
+ const n = (_offlineCacheGet() || []).filter(i => parseFloat(i.quantity) > 0).length;
+ const msg = t('error.offline_cache_ready').replace('{n}', n);
+ textEl.innerHTML = msg;
+ }
+ banner.style.display = '';
+}
+
+async function _syncOfflineQueue() {
+ const q = _offlineQueueGet();
+ if (q.length === 0) return 0;
+ let synced = 0;
+ const failed = [];
+ const tempIdMap = {}; // negative temp id → real server id
+ for (const op of q) {
+ if (!op.action) continue;
+ let body = op.body ? { ...op.body } : {};
+ // Resolve any temp IDs from earlier add operations in this batch
+ if (typeof body.id === 'number' && body.id < 0 && tempIdMap[body.id]) {
+ body = { ...body, id: tempIdMap[body.id] };
+ }
+ try {
+ const result = await api(op.action, {}, 'POST', body);
+ if (result && result._offline) { failed.push(op); continue; }
+ if (op.action === 'inventory_add' && result?.id && op.body?.id < 0) {
+ tempIdMap[op.body.id] = result.id;
+ }
+ synced++;
+ } catch(_) { failed.push(op); }
+ }
+ _offlineQueueSet(failed);
+ return synced;
+}
+
+// ─── Overlay show / hide ────────────────────────────────────────────────────
+function _showNetworkOverlay() {
+ if (_networkDown) return;
+ if (_offlineMode) return; // Already in offline mode — don't interrupt the user
+ _networkDown = true;
+ _networkFailCount = 0;
+ const el = document.getElementById('network-error-overlay');
+ if (!el) return;
+ el.classList.remove('restored', 'checking');
+ const titleEl = document.getElementById('net-error-title');
+ const subtitleEl = document.getElementById('net-error-subtitle');
+ const iconEl = document.getElementById('net-error-icon');
+ const statusEl = document.getElementById('net-error-status');
+ const contBtn = document.getElementById('net-error-continue-btn');
+ if (titleEl) titleEl.textContent = t('error.offline_title');
+ if (subtitleEl) subtitleEl.textContent = t('error.offline_subtitle');
+ if (iconEl) iconEl.textContent = '📡';
+ if (statusEl) statusEl.textContent = '';
+ if (contBtn) { contBtn.style.display = 'none'; contBtn.classList.remove('visible'); }
+ el.style.display = 'flex';
+ requestAnimationFrame(() => el.classList.add('visible'));
+ // Show "Continue offline" button after 3 s
+ clearTimeout(_continueBtnTimer);
+ _continueBtnTimer = setTimeout(() => {
+ if (!_networkDown) return;
+ if (contBtn) {
+ contBtn.textContent = t('error.offline_continue');
+ contBtn.style.display = '';
+ requestAnimationFrame(() => contBtn.classList.add('visible'));
+ }
+ }, 3000);
+ // Auto-enter offline mode after 8 s if user hasn't acted
+ clearTimeout(_offlineBannerTimer);
+ _offlineBannerTimer = setTimeout(() => {
+ if (_networkDown && !_offlineMode) _enterOfflineMode();
+ }, _OFFLINE_MODE_DELAY_MS);
+}
+
+function _hideNetworkOverlay(showRestoredMsg) {
+ clearTimeout(_continueBtnTimer);
+ clearTimeout(_offlineBannerTimer);
+ _networkDown = false;
+ _networkFailCount = 0;
+ const el = document.getElementById('network-error-overlay');
+ const contBtn = document.getElementById('net-error-continue-btn');
+ if (contBtn) { contBtn.style.display = 'none'; contBtn.classList.remove('visible'); }
+ if (!el) return;
+ if (showRestoredMsg) {
+ el.classList.add('restored');
+ el.classList.remove('checking');
+ const titleEl = document.getElementById('net-error-title');
+ const iconEl = document.getElementById('net-error-icon');
+ const statusEl = document.getElementById('net-error-status');
+ if (titleEl) titleEl.textContent = t('error.offline_restored');
+ if (iconEl) iconEl.textContent = '✅';
+ if (statusEl) statusEl.textContent = '';
+ setTimeout(() => {
+ el.classList.remove('visible');
+ setTimeout(() => { el.style.display = 'none'; el.classList.remove('restored', 'checking'); }, 450);
+ }, 1800);
+ } else {
+ el.classList.remove('visible');
+ setTimeout(() => { el.style.display = 'none'; el.classList.remove('restored', 'checking'); }, 450);
+ }
+}
+
+// Ping the server; if reachable call _handleServerRestored() via heartbeat
+async function _networkPingOnce() {
+ const el = document.getElementById('network-error-overlay');
+ const iconEl = document.getElementById('net-error-icon');
+ const statusEl = document.getElementById('net-error-status');
+ if (el && el.classList.contains('visible')) {
+ el.classList.add('checking');
+ if (iconEl) iconEl.textContent = '🔄';
+ if (statusEl) statusEl.textContent = t('error.offline_checking');
+ }
+ try {
+ const res = await fetch(`${API_BASE}?action=ping&_t=${Date.now()}`, {
+ method: 'GET', cache: 'no-store',
+ signal: AbortSignal.timeout(4000),
+ });
+ if (res.ok) {
+ // Let heartbeat confirm the state authoritatively
+ _heartbeatRetry();
+ } else { throw new Error('not-ok'); }
+ } catch (_) {
+ if (el) el.classList.remove('checking');
+ if (iconEl) iconEl.textContent = '📡';
+ if (statusEl) statusEl.textContent = '';
+ }
+}
+
+// Browser-native online/offline events
+window.addEventListener('offline', () => _showNetworkOverlay());
+window.addEventListener('online', () => {
+ clearTimeout(_offlineBannerTimer); // don't auto-enter offline mode if we just came back
+ _networkPingOnce();
+});
+
// ===== SCREENSAVER & INACTIVITY AUTO-REFRESH =====
let _inactivityTimer = null;
let _screensaverActive = false;
@@ -16074,19 +16475,61 @@ function _setServerOffline(offline) {
}
_serverOffline = offline;
document.body.classList.toggle('server-offline', offline);
- const banner = document.getElementById('offline-banner');
- if (banner) banner.style.display = offline ? '' : 'none';
if (offline) {
- showToast(t('error.server_offline'), 'error');
+ if (!_offlineMode) {
+ // Show the full-screen network overlay (also auto-enters offline mode after 8 s)
+ _showNetworkOverlay();
+ }
+ // In offline mode the banner is already managed by _renderOfflineBanner()
} else {
- showToast(t('error.server_restored'), 'success');
- // Refresh the current page since updates may have been missed
- refreshCurrentPage();
+ // Server came back: exit offline mode (sync queue, refresh) then hide overlay
+ _handleServerRestored();
}
_heartbeatTimer = setTimeout(_runHeartbeat,
offline ? _HB_INTERVAL_OFFLINE : _HB_INTERVAL_ONLINE);
}
+/** Flush log messages and error reports that were buffered while offline. */
+async function _flushOfflineReports() {
+ try {
+ const logs = JSON.parse(localStorage.getItem(_OFFLINE_LOGS_KEY) || '[]');
+ if (logs.length > 0) {
+ localStorage.removeItem(_OFFLINE_LOGS_KEY);
+ await fetch('api/index.php?action=client_log', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages: logs })
+ });
+ }
+ } catch(e) {}
+ try {
+ const errors = JSON.parse(localStorage.getItem(_OFFLINE_ERRORS_KEY) || '[]');
+ if (errors.length > 0) {
+ localStorage.removeItem(_OFFLINE_ERRORS_KEY);
+ for (const errBody of errors) {
+ await fetch('api/index.php?action=report_error', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(errBody)
+ }).catch(() => {});
+ }
+ }
+ } catch(e) {}
+}
+
+/** Async handler called when the server comes back online. */
+async function _handleServerRestored() {
+ if (_offlineMode) {
+ await _exitOfflineMode();
+ }
+ // Now hide the overlay with the "restored" message (if still visible)
+ if (_networkDown) {
+ _hideNetworkOverlay(true);
+ }
+ // Flush all logs and error reports buffered while offline → GitHub issues when applicable
+ _flushOfflineReports().catch(() => {});
+ showToast(t('error.server_restored'), 'success');
+ refreshCurrentPage();
+}
+
/** Called by the banner "Retry" button to trigger an immediate check. */
function _heartbeatRetry() {
clearTimeout(_heartbeatTimer);
@@ -16280,6 +16723,23 @@ async function _runStartupCheck() {
await new Promise(r => setTimeout(r, 600));
}
+ // ── Final step: sync local offline cache (inventory + settings) ──────────
+ // This ensures the offline copy is always fresh at startup while connected.
+ // The bar already shows 100%; we just update the label for a moment.
+ try {
+ setProgress(100, tl('syncing_local', 'Sincronizzazione dati locali...'), 'ok');
+ const [invData, settingsData] = await Promise.all([
+ fetch('api/index.php?action=inventory_list').then(r => r.json()).catch(() => null),
+ fetch('api/index.php?action=get_settings').then(r => r.json()).catch(() => null),
+ ]);
+ if (invData && Array.isArray(invData.inventory)) _offlineCacheSet(invData.inventory);
+ if (settingsData && settingsData.success !== false) _offlineCacheSetSettings(settingsData);
+ setProgress(100, tl('sync_done', 'Dati locali aggiornati'), 'ok');
+ await new Promise(r => setTimeout(r, 400));
+ } catch(e) {
+ // Non-critical — app continues normally; cache may be stale or empty
+ }
+
wrapEl.style.display = 'none';
return true;
}
@@ -16401,6 +16861,24 @@ async function _initApp() {
initScreensaverShortcuts();
startBgShoppingRefresh();
startHeartbeat();
+ // ── Recover any pending offline operations left over from a previous session ──
+ // This handles the case where the user refreshed the page while offline ops
+ // were queued — the queue survives in localStorage but _offlineMode is false.
+ (() => {
+ const startupQueue = _offlineQueueGet();
+ if (startupQueue.length === 0) return;
+ setTimeout(async () => {
+ const synced = await _syncOfflineQueue();
+ await _flushOfflineReports().catch(() => {});
+ if (synced > 0) {
+ showToast(t('error.offline_synced').replace('{n}', synced), 'success');
+ refreshCurrentPage();
+ } else {
+ // All ops failed to sync — keep them for next attempt
+ showToast(t('error.offline_ops_pending').replace('{n}', _offlineQueueGet().length), 'warning');
+ }
+ }, 1200);
+ })();
_injectKioskOverlay(); // kiosk X / refresh buttons (only when running inside Android WebView)
// Sync version label in preloader (in case HTML is stale)
diff --git a/index.html b/index.html
index a0dab14..3433085 100644
--- a/index.html
+++ b/index.html
@@ -64,7 +64,7 @@
diff --git a/manifest.json b/manifest.json
index 9767f69..8b6b0f5 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,7 +2,7 @@
"name": "EverShelf",
"short_name": "EverShelf",
"description": "Gestione completa della dispensa di casa con scansione barcode",
- "version": "1.7.24",
+ "version": "1.7.25",
"start_url": "/evershelf/",
"display": "standalone",
"background_color": "#f0f4e8",
diff --git a/translations/de.json b/translations/de.json
index 6799e7e..bafb3e6 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -1,1407 +1,1419 @@
{
- "app": {
- "name": "EverShelf",
- "loading": "Laden..."
- },
- "nav": {
- "title": "EverShelf",
- "home": "Home",
- "inventory": "Vorrat",
- "recipes": "Rezepte",
- "shopping": "Einkauf",
- "log": "Verlauf",
- "settings": "Einstellungen"
- },
- "btn": {
- "back": "← Zurück",
- "save": "💾 Speichern",
- "cancel": "✕ Abbrechen",
- "close": "Schließen",
- "add": "✅ Hinzufügen",
- "delete": "Löschen",
- "edit": "✏️ Bearbeiten",
- "use": "Verwenden",
- "edit_item": "Bearbeiten",
- "search": "🔍 Suchen",
- "go": "✅ Los",
- "toggle_password": "👁️ Anzeigen/Ausblenden",
- "load_more": "Mehr laden...",
- "save_config": "💾 Konfiguration speichern",
- "save_product": "💾 Produkt speichern",
- "restart": "↺ Neustart",
- "reset_default": "↺ Standard wiederherstellen",
- "save_info": "💾 Info speichern",
- "retry": "🔄 Erneut versuchen",
- "yes_short": "Ja",
- "no_short": "Nein"
- },
- "form": {
- "select_placeholder": "-- Auswählen --"
- },
- "locations": {
- "dispensa": "Vorratskammer",
- "frigo": "Kühlschrank",
- "freezer": "Gefrierschrank",
- "altro": "Sonstiges"
- },
- "categories": {
- "latticini": "Milchprodukte",
- "carne": "Fleisch",
- "pesce": "Fisch",
- "frutta": "Obst",
- "verdura": "Gemüse",
- "pasta": "Pasta & Reis",
- "pane": "Brot & Backwaren",
- "surgelati": "Tiefkühl",
- "bevande": "Getränke",
- "condimenti": "Gewürze",
- "snack": "Snacks & Süßes",
- "conserve": "Konserven",
- "cereali": "Getreide & Hülsenfrüchte",
- "igiene": "Hygiene",
- "pulizia": "Reinigung",
- "altro": "Sonstiges",
- "select": "-- Auswählen --"
- },
- "units": {
- "pz": "Stk",
- "conf": "Pkg",
- "g": "g",
- "ml": "ml",
- "pieces": "Stück",
- "grams": "Gramm",
- "box": "Packung",
- "boxes": "Packungen",
- "millilitres": "Milliliter",
- "from": "von"
- },
- "shopping_sections": {
- "frutta_verdura": "Obst & Gemüse",
- "carne_pesce": "Fleisch & Fisch",
- "latticini": "Milchprodukte & Frisches",
- "pane_dolci": "Brot & Süßes",
- "pasta": "Pasta & Getreide",
- "conserve": "Konserven & Soßen",
- "surgelati": "Tiefkühl",
- "bevande": "Getränke",
- "pulizia_igiene": "Reinigung & Hygiene",
- "altro": "Sonstiges"
- },
- "dashboard": {
- "expired_title": "🚫 Abgelaufen",
- "expiring_title": "⏰ Bald ablaufend",
- "stats_period": "📊 Letzte 30 Tage",
- "opened_title": "📦 Geöffnete Produkte",
- "review_title": "🔍 Zu prüfen",
- "review_hint": "Mengen, die ungewöhnlich erscheinen. Bestätigen oder ändern.",
- "quick_recipe": "Schnelles Rezept mit ablaufenden Produkten",
- "banner_review_title": "Ungewöhnliche Menge",
- "banner_review_action_ok": "Ist korrekt",
- "banner_review_action_finish": "🗑️ Alles aufgebraucht",
- "banner_review_action_edit": "Korrigieren",
- "banner_review_action_weigh": "Wiegen",
- "banner_review_dismiss": "Ignorieren",
- "banner_prediction_title": "Verbrauch zur Prüfung",
- "banner_prediction_hint": "Die Verbrauchsschätzung passt sich aktuellen Daten an: bitte nur die aktuelle Menge bestätigen.",
- "banner_prediction_action_confirm": "{qty} {unit} bestätigen",
- "banner_prediction_action_weigh": "Jetzt wiegen",
- "banner_prediction_action_edit": "Menge aktualisieren",
- "banner_expired_title": "Abgelaufenes Produkt",
- "banner_expired_today": "Heute abgelaufen",
- "banner_expired_days": "Seit {days} Tagen abgelaufen",
- "banner_expired_action_use": "Trotzdem verwenden",
- "banner_expired_action_finished": "Habe ich verbraucht!",
- "banner_expired_action_throw": "Habe ich weggeworfen",
- "banner_expired_action_edit": "Datum korrigieren",
- "banner_expired_action_modify": "Bearbeiten",
- "banner_expired_action_vacuum": "Vakuumieren",
- "banner_anomaly_action_edit": "Bestand korrigieren",
- "banner_anomaly_action_dismiss": "Menge ist korrekt",
- "banner_no_expiry_title": "Ablaufdatum fehlt: {name}",
- "banner_no_expiry_detail": "Dieses Produkt hat kein Ablaufdatum. Möchten Sie eines hinzufügen oder bestätigen, dass es nicht verfällt?",
- "banner_no_expiry_action_set": "Ablaufdatum setzen",
- "banner_no_expiry_action_dismiss": "Läuft nicht ab ✓",
- "banner_no_expiry_toast_dismissed": "Als 'läuft nicht ab' markiert",
- "banner_expiring_title": "Bald ablaufend",
- "banner_expiring_today": "Läuft heute ab!",
- "banner_expiring_tomorrow": "Läuft morgen ab",
- "banner_expiring_days": "Läuft in {days} Tagen ab",
- "banner_expiring_action_use": "Jetzt verwenden",
- "banner_finished_title": "aufgebraucht?",
- "banner_finished_detail": "Ich habe vermerkt, dass {name} auf null gesunken ist. Ist es wirklich leer, oder hast du noch welches?",
- "banner_finished_action_yes": "Ja, aufgebraucht",
- "banner_finished_action_no": "Nein, ich habe noch welches",
- "banner_review_unusual_pkg_title": "Ungewöhnliche Packungsgröße",
- "banner_review_unusual_pkg_detail": "Du hast eine Packung von {qty} {unit} eingestellt — die Größe scheint sehr groß. Überprüfe ob es korrekt ist.",
- "banner_review_low_qty_title": "Sehr geringe Menge",
- "banner_review_low_qty_detail": "Du hast nur {qty} im Bestand — das scheint sehr wenig, möglicherweise ein Eingabefehler. Bestätige wenn korrekt.",
- "banner_review_high_qty_title": "Ungewöhnlich hohe Menge",
- "banner_review_high_qty_detail": "Du hast {qty} im Bestand — die Zahl scheint sehr hoch. Bestätige wenn korrekt oder korrigiere.",
- "banner_prediction_rate_day": "Durchschnitt ~{n} {unit}/Tag",
- "banner_prediction_rate_week": "Durchschnitt ~{n} {unit}/Woche",
- "banner_prediction_days_ago": "Vor {n} Tagen aufgefüllt",
- "banner_prediction_more": "frühere Schätzung: {expected} {unit}{time}; aktuelle Menge: {actual} {unit}.",
- "banner_prediction_less": "Schätzung: {expected} {unit}{time}; aktuelle Menge: {actual} {unit}. Wenn sich dein Verbrauch geändert hat, aktualisiert sich die Prognose automatisch.",
- "banner_finished_zero": "Bestand zeigt null, aber gespeicherte Buchungen deuten an, dass es nicht leer sein sollte.",
- "banner_finished_expected": "Laut Aufzeichnungen solltest du noch {qty} {unit} haben.",
- "banner_finished_check": "Kannst du nachschauen?",
- "banner_anomaly_phantom_title": "mehr Bestand als erwartet",
- "banner_anomaly_phantom_detail": "Bestand zeigt {inv_qty} {unit}, aber laut Buchungen solltest du nur {expected_qty} {unit} haben. Hast du Bestand ohne Buchung hinzugefügt?",
- "banner_anomaly_untracked_title": "Anfangsbestand nicht als Eingang gebucht",
- "banner_anomaly_untracked_detail": "Du hast
{inv_qty} {unit} im Bestand, aber die gebuchten Abgänge übersteigen die Eingänge — der Anfangsbestand wurde wahrscheinlich nie als \"Eingang\" erfasst. Bitte korrigiere die Menge oder trage die fehlenden Eingänge nach.",
- "banner_anomaly_ghost_title": "weniger Bestand als erwartet",
- "banner_anomaly_ghost_detail": "Laut Buchungen solltest du {expected_qty} {unit} von {name} haben, aber der Bestand zeigt nur {inv_qty} {unit}. Hast du etwas ohne Buchung entnommen?",
- "consumed": "Verbraucht: {n} ({pct}%)",
- "wasted": "Weggeworfen: {n} ({pct}%)",
- "more_opened": "und {n} weitere geöffnet...",
- "banner_expired_detail": "{when} · du hast noch
{qty}.",
- "banner_opened_detail": "{when} in {location} · du hast noch
{qty}.",
- "banner_explain_title": "Gemini um eine Erklärung bitten",
- "banner_explain_btn": "Erklären",
- "banner_analyzing": "🤖 Analysiere…"
- },
- "inventory": {
- "title": "Vorrat",
- "filter_all": "Alle",
- "search_placeholder": "🔍 Produkt suchen...",
- "recent_title": "🕐 Zuletzt verwendet",
- "popular_title": "⭐ Meistverwendet",
- "empty": "Keine Produkte hier.\nScanne ein Produkt, um es hinzuzufügen!",
- "no_items_found": "Keine Bestandseinträge gefunden",
- "qty_remainder_suffix": "übrig",
- "vacuum_badge": "🫙 Vakuumiert",
- "opened_badge": "📭 Geöffnet",
- "label_expiry": "📅 Ablaufdatum",
- "label_storage": "🫙 Aufbewahrung",
- "label_status": "📭 Status",
- "opened_since": "Geöffnet seit {date}",
- "label_position": "📍 Standort",
- "label_quantity": "📦 Menge",
- "label_added": "📅 Hinzugefügt",
- "empty_text": "Keine Produkte hier.
Scanne ein Produkt, um es hinzuzufügen!",
- "empty_db": "Keine Produkte in der Datenbank.
Scanne ein Produkt, um loszulegen!",
- "qty_trace": "< 1"
- },
- "scan": {
- "title": "Scannen",
- "mode_shopping": "🛒 Einkaufsmodus",
- "mode_shopping_end": "✅ Einkauf beenden",
- "spesa_btn": "🛒 Einkauf",
- "zoom": "Zoom",
- "tab_barcode": "Barcode",
- "tab_name": "Name",
- "tab_ai": "KI",
- "recents_label": "Zuletzt",
- "torch_hint": "Taschenlampe",
- "torch_on": "Taschenlampe an",
- "torch_off": "Taschenlampe aus",
- "torch_unavailable": "Taschenlampe auf diesem Gerät nicht verfügbar",
- "flip_hint": "Kamera wechseln",
- "flip_front": "Frontkamera",
- "flip_back": "Rückkamera",
- "num_ocr_btn": "🔢 Zahlen mit KI lesen",
- "num_ocr_searching": "Suche Barcode mit KI...",
- "num_ocr_found": "Code gefunden: {code}",
- "num_ocr_not_found": "Kein Barcode im Bild gefunden",
- "barcode_placeholder": "Barcode eingeben...",
- "quick_name_divider": "oder Name eingeben",
- "quick_name_placeholder": "z.B.: Äpfel, Zucchini, Brot...",
- "manual_entry": "✏️ Manuelle Eingabe",
- "ai_identify": "🤖 Mit KI identifizieren",
- "hint": "Barcode scannen, Produktname eingeben oder KI zur Identifikation nutzen",
- "debug_toggle": "🐛 Debug Log",
- "barcode_acquired": "🔖 Barcode gescannt: {code}",
- "scan_barcode": "🔖 Barcode scannen",
- "create_named": "{name} erstellen",
- "new_without_barcode": "Neues Produkt ohne Barcode",
- "stock_in_pantry": "Bereits im Vorrat:"
- },
- "action": {
- "title": "Was möchtest du tun?",
- "add_btn": "📥 HINZUFÜGEN",
- "add_sub": "in Vorrat/Kühlschrank",
- "use_btn": "VERWENDEN",
- "use_sub": "aus Vorrat/Kühlschrank",
- "have_title": "📦 Schon auf Lager!",
- "add_more_sub": "weitere Menge",
- "use_qty_sub": "wie viel verwendet",
- "throw_btn": "🗑️ ENTSORGEN",
- "throw_sub": "wegwerfen",
- "edit_sub": "Ablauf, Ort…",
- "create_recipe_btn": "Rezept",
- "related_stock_title": "Auch zuhause"
- },
- "add": {
- "title": "Zum Vorrat hinzufügen",
- "location_label": "📍 Wohin?",
- "quantity_label": "📦 Menge",
- "conf_size_label": "📦 Jede Packung enthält:",
- "conf_size_placeholder": "z.B. 300",
- "vacuum_label": "🫙 Vakuumiert",
- "vacuum_hint": "Ablaufdatum wird automatisch verlängert",
- "submit": "✅ Hinzufügen",
- "purchase_type_label": "🛒 Dieses Produkt ist...",
- "new_btn": "🆕 Gerade gekauft",
- "existing_btn": "📦 Hatte ich schon",
- "remaining_label": "📦 Verbleibende Menge",
- "remaining_hint": "Ungefähr wie viel ist noch übrig?",
- "remaining_full": "🟢 Voll",
- "remaining_half": "🟠 Halb",
- "estimated_expiry": "Geschätzte Haltbarkeit:",
- "suffix_freezer": "(Tiefkühler)",
- "suffix_vacuum": "(vakuumversiegelt)",
- "hint_modify": "📝 Du kannst das Datum ändern oder mit der Kamera scannen",
- "scan_expiry_title": "📷 Ablaufdatum scannen",
- "product_added": "✅ {name} hinzugefügt!{qty}",
- "suffix_freezer_vacuum": "(Tiefkühler + vakuumversiegelt)",
- "history_badge_tip": "Durchschnitt aus {n} früheren Einträgen",
- "vacuum_question": "Vakuumiert?",
- "vacuum_saved": "🔒 Als vakuumiert gespeichert"
- },
- "use": {
- "title": "Verwenden / Verbrauchen",
- "location_label": "📍 Woher?",
- "quantity_label": "Wie viel hast du benutzt?",
- "change": "ändern",
- "partial_hint": "Oder genaue Menge angeben:",
- "partial_piece_hint": "Hast du nur einen Teil verwendet?",
- "piece": "Stück",
- "one_whole": "1 ganzes",
- "use_all": "🗑️ ALLES verwendet / Aufgebraucht",
- "submit": "📤 Diese Menge verwenden",
- "available": "📦 Verfügbar:",
- "opened_badge": "GEOEFFNET",
- "not_in_inventory": "⚠️ Produkt nicht im Bestand.",
- "expiry_warning": "⚠️ Verwende zuerst die{loc}, die am {date} abläuft — {when}!",
- "expiry_warning_opened": "⚠️ Die{loc} ist seit {when} geöffnet — zuerst verwenden!",
- "throw_title": "🗑️ Produkt entsorgen",
- "throw_all": "🗑️ ALLES entsorgen ({qty})",
- "throw_qty_label": "Wie viel wegwerfen?",
- "throw_qty_hint": "oder Menge angeben:",
- "throw_partial_btn": "🗑️ Diese Menge entsorgen",
- "when_expired": "seit {n} Tagen abgelaufen",
- "when_today": "läuft
heute ab",
- "when_tomorrow": "läuft
morgen ab",
- "when_days": "läuft in
{n} Tagen ab",
- "toast_used": "📤 {qty} von {name} verwendet",
- "toast_bring": "🛒 Produkt aufgebraucht → zu Bring! hinzugefügt",
- "toast_opened_finished": "🔓 Geöffnete Packung von {name} aufgebraucht!",
- "disambiguation_hint": "Was meinst du mit \"alles aufgebraucht\"?",
- "disambiguation_all": "🗑️ ALLES verbraucht ({qty})",
- "error_exceeds_stock": "⚠️ Du kannst nicht mehr verwenden als du verfügbar hast!",
- "use_all_confirm_title": "✅ Alles aufbrauchen",
- "use_all_confirm_msg": "Bestätige, dass du das Produkt vollständig aufgebraucht hast:",
- "use_all_confirm_btn": "✅ Ja, aufgebraucht",
- "throw_all_confirm_title": "🗑️ Alles entsorgen",
- "throw_all_confirm_msg": "Möchtest du wirklich das gesamte Produkt entsorgen?",
- "throw_all_confirm_btn": "🗑️ Ja, entsorgen"
- },
- "product": {
- "title_new": "Neues Produkt",
- "title_edit": "Produkt bearbeiten",
- "ai_fill": "📷 Foto machen und mit KI identifizieren",
- "ai_fill_hint": "KI füllt die Produktfelder automatisch aus",
- "name_label": "🏷️ Produktname *",
- "name_placeholder": "z.B.: Vollmilch, Penne Nudeln...",
- "brand_label": "🏢 Marke",
- "brand_placeholder": "z.B.: Barilla, Müller, Knorr...",
- "category_label": "📂 Kategorie",
- "unit_label": "📏 Maßeinheit",
- "default_qty_label": "🔢 Standardmenge",
- "conf_size_label": "📦 Jede Packung enthält:",
- "conf_size_placeholder": "z.B. 300",
- "notes_label": "📝 Notizen",
- "notes_placeholder": "z.B.: laktosefrei, bio, nach dem Öffnen im Kühlschrank aufbewahren...",
- "barcode_label": "🔖 Barcode",
- "barcode_placeholder": "Barcode (falls vorhanden)",
- "barcode_hint": "⚠️ Barcode hinzufügen, damit du beim nächsten Einkauf nur scannen musst!",
- "submit": "💾 Produkt speichern",
- "name_required": "Produktname eingeben",
- "conf_size_required": "Packungsinhalt angeben",
- "expiry_estimated": "Geschätztes Ablaufdatum:",
- "scan_expiry": "Ablaufdatum scannen",
- "expiry_hint": "📝 Du kannst das Datum ändern oder mit der Kamera scannen",
- "add_batch": "📦 + Charge mit anderem Ablaufdatum",
- "package_info": "📦 Packung: {info}",
- "edit_catalog": "⚙️ Produktinfo bearbeiten (Name, Marke, Kategorie…)",
- "not_recognized": "⚠️ Produkt nicht erkannt",
- "edit_info": "✏️ Informationen bearbeiten",
- "modify_details": "BEARBEITEN\nAblauf, Ort…",
- "already_in_pantry": "📋 Bereits im Vorratsschrank",
- "no_barcode": "Kein Barcode",
- "unknown_product": "Unbekanntes Produkt",
- "edit_name_brand": "Name/Marke bearbeiten",
- "weight_label": "Gewicht",
- "origin_label": "Herkunft",
- "labels_label": "Etiketten",
- "select_variant": "Genaue Variante auswählen oder KI-Daten nutzen:"
- },
- "products": {
- "title": "📦 Alle Produkte",
- "search_placeholder": "🔍 Produkt suchen...",
- "empty": "Keine Produkte in der Datenbank.\nScanne ein Produkt zum Starten!",
- "no_category": "Keine Produkte in dieser Kategorie"
- },
- "recipes": {
- "title": "🍳 Rezepte",
- "generate": "✨ Neues Rezept generieren",
- "archive_empty": "Keine Rezepte gespeichert. Erstelle dein erstes Rezept!",
- "dialog_title": "🍳 Rezept",
- "dialog_desc": "Ich erstelle ein gesundes Rezept mit Zutaten aus dem Vorrat und priorisiere Produkte mit nahendem Ablaufdatum.",
- "meal_label": "🕐 Für welche Mahlzeit?",
- "persons_label": "👥 Für wie viele Personen?",
- "meal_type_label": "🎯 Art der Mahlzeit",
- "opt_fast": "⚡ Schnelle Mahlzeit",
- "opt_light": "🥗 Kleiner Hunger",
- "opt_expiry": "⏰ Ablaufdaten priorisieren",
- "opt_healthy": "💚 Extra gesund",
- "opt_opened": "📦 Geöffnete Produkte priorisieren",
- "opt_zero_waste": "♻️ Zero Waste",
- "generate_btn": "✨ Rezept generieren",
- "loading_msg": "Rezept wird vorbereitet...",
- "start_cooking": "👨🍳 Kochmodus",
- "regenerate": "🔄 Noch eins generieren",
- "close_btn": "✅ Schließen",
- "ingredients_title": "🧾 Zutaten",
- "tools_title": "Benötigte Geräte",
- "steps_title": "👨🍳 Zubereitung",
- "no_steps": "Keine Zubereitungsschritte verfügbar",
- "generate_error": "Fehler bei der Generierung",
- "stream_interrupted": "Generierung unterbrochen (unvollstaendige Antwort vom Server). Protokolle pruefen oder erneut versuchen.",
- "persons_short": "Pers.",
- "use_ingredient_title": "Zutat verwenden",
- "recipe_qty_label": "Rezept",
- "from_where_label": "Von wo?",
- "amount_label": "Wie viel",
- "use_amount_btn": "Diese Menge verwenden",
- "use_all_btn": "ALLES verwenden / Aufgebraucht",
- "packs_label": "Packungen",
- "quantity_in_total": "Menge in {unit} (gesamt: {total})",
- "packs_of_have": "Packungen à {size} (du hast {count} Pack.)",
- "scale_wait_stable": "10s stabiles Gewicht für Auto-Ausfüllen abwarten…",
- "ingredient_scaled_toast": "📦 Zutat vom Vorrat abgezogen!",
- "finished_added_bring_toast": "🛒 Produkt aufgebraucht → zu Bring! hinzugefügt!",
- "load_error": "Fehler beim Laden"
- },
- "shopping": {
- "title": "🛒 Einkaufsliste",
- "bring_loading": "Verbindung zu Bring!...",
- "bring_not_configured": "Bring! ist nicht konfiguriert. Füge E-Mail und Passwort in den
Einstellungen hinzu.",
- "tab_to_buy": "🛍️ Zu kaufen",
- "tab_forecast": "🧠 Vorhersage",
- "total_label": "💰 Geschätzter Gesamtbetrag",
- "section_to_buy": "🛍️ Zu kaufen",
- "suggestions_title": "💡 KI-Vorschläge",
- "suggestions_add": "✅ Ausgewählte zu Bring! hinzufügen",
- "search_prices": "🔍 Alle Preise suchen",
- "suggest_btn": "Einkaufsvorschläge",
- "smart_title": "🧠 Intelligente Vorhersagen",
- "smart_empty": "Keine Vorhersagen verfügbar.
Füge Produkte zur Vorratskammer hinzu, um intelligente Vorhersagen zu erhalten.",
- "smart_filter_all": "Alle",
- "smart_filter_critical": "🔴 Dringend",
- "smart_filter_high": "🟠 Bald",
- "smart_filter_medium": "🟡 Planen",
- "smart_filter_low": "🟢 Vorhersage",
- "smart_add": "🛒 Ausgewählte zu Bring! hinzufügen",
- "empty": "Einkaufsliste leer!\nNutze den Button unten, um Vorschläge zu generieren.",
- "already_in_list": "🛒 \"{name}\" ist bereits in der Einkaufsliste",
- "already_in_list_short": "ℹ️ Bereits in der Einkaufsliste",
- "add_prompt": "Möchtest du es zur Einkaufsliste hinzufügen?",
- "smart_already": "📊 Intelligenter Einkauf sagt bereits {name} voraus",
- "all_searched": "Alle Produkte wurden bereits gesucht. Nutze 🔄 für einzelne Suchen.",
- "search_complete": "Suche abgeschlossen: {count} Produkte",
- "suggest_buy": "🛒 Kaufen: {qty} {unit}",
- "suggest_buy_approx": "🛒 Mindestens: {qty} {unit}",
- "suggest_buy_tip": "Empfohlene Menge basierend auf dem Verbrauch der letzten 14 Tage",
- "suggest_buy_approx_tip": "Mindestschätzung basierend auf Verbrauch (nächste Packungsgröße kaufen)",
- "removed_sufficient": "🧹 {removed} Produkt(e) mit ausreichendem Bestand von der Liste entfernt",
- "bring_badge": "🛒 Schon auf Bring!",
- "add_urgent_toast": "🔴 {n} dringende(s) Produkt(e) automatisch zu Bring! hinzugefügt",
- "migration_done": "✅ {migrated} aktualisiert, {skipped} bereits ok",
- "added_to_bring": "🛒 {n} Produkte zu Bring! hinzugefügt",
- "added_to_bring_skip": "{n} bereits vorhanden",
- "all_on_bring": "Alle Produkte waren bereits auf Bring!",
- "freq_high": "📈 Häufig",
- "freq_regular": "📊 Regelmäßig",
- "freq_occasional": "📉 Gelegentlich",
- "out_of_stock": "Ausverkauft",
- "scan_toast": "📷 Scannen: {name}",
- "empty_category": "Keine Produkte in dieser Kategorie",
- "session_empty": "🛒 Noch keine Produkte",
- "urgency_critical": "Dringend",
- "urgency_high": "Bald",
- "urgency_medium": "Planen",
- "urgency_low": "Vorschau",
- "urgency_medium_short": "Mittel",
- "urgency_low_short": "Ok",
- "tag_urgent": "🔴 Dringend",
- "tag_priority": "⭐ Priorität",
- "tag_check": "✅ Prüfen",
- "smart_already_predicted": "📊 Einkauf wird bereits vorhergesagt:
{name}{urgency}.",
- "item_removed": "✅ {name} von der Liste entfernt!",
- "urgency_spec_critical": "⚡ Dringend",
- "urgency_spec_high": "🟠 Bald",
- "bring_add_n": "{n} zu Bring! hinzufügen",
- "bring_add_selected": "Ausgewählte zu Bring! hinzufügen",
- "bring_adding": "Wird hinzugefügt...",
- "bring_added_one": "1 Produkt zu Bring! hinzugefügt",
- "bring_added_many": "{n} Produkte zu Bring! hinzugefügt",
- "bring_skipped": "({n} bereits in Liste)",
- "force_sync": "Bring!-Synchronisierung erzwingen",
- "scan_target_label": "Du suchst",
- "scan_target_found": "Gefunden! Aus Liste entfernen",
- "bring_add_one": "1 Produkt zu Bring! hinzufügen",
- "bring_add_many": "{n} Produkte zu Bring! hinzufügen",
- "syncing": "Synchronisiere…",
- "sync_done": "Synchronisierung abgeschlossen",
- "price_searching": "Suche...",
- "search_action": "Suchen",
- "open_action": "Öffnen",
- "not_found": "Nicht gefunden",
- "search_price": "Preis suchen",
- "tap_to_scan": "Zum Scannen tippen",
- "tag_title": "Tag",
- "remove_title": "Entfernen",
- "found_count": "{found}/{total} Produkte gefunden",
- "savings_offers": "· 🏷️ Du sparst €{amount} mit Angeboten",
- "searching_progress": "Suche {current}/{total}...",
- "remove_error": "Fehler beim Entfernen",
- "btn_fetch_prices": "Preise suchen",
- "price_total_label": "💰 Geschätzter Gesamtpreis:",
- "price_loading": "Preise werden gesucht…",
- "price_not_found": "Preis n/v",
- "suggest_loading": "Analyse läuft...",
- "suggest_error": "Fehler bei der Vorschlagserstellung",
- "priority_high": "Hoch",
- "priority_medium": "Mittel",
- "priority_low": "Niedrig",
- "smart_last_update": "Aktualisiert {time}",
- "names_already_updated": "Alle Namen sind bereits aktuell",
- "pantry_hint": "Bereits zuhause: {qty}"
- },
- "ai": {
- "title": "🤖 KI-Identifikation",
- "capture": "📸 Foto aufnehmen",
- "retake": "🔄 Neu aufnehmen",
- "hint": "Mache ein Foto des Produkts und die KI versucht es zu identifizieren",
- "identifying": "🤖 Identifiziere Produkt...",
- "no_api_key": "⚠️ Gemini API-Schlüssel nicht konfiguriert.\n
Füge GEMINI_API_KEY in der .env Datei auf dem Server hinzu.",
- "fields_filled": "✅ Felder von KI ausgefüllt",
- "use_data": "✅ KI-Daten verwenden",
- "use_data_no_barcode": "✅ KI-Daten verwenden (ohne Barcode)"
- },
- "log": {
- "title": "📒 Verlauf",
- "type_added": "Hinzugefügt",
- "type_waste": "Entsorgt",
- "type_used": "Verwendet",
- "type_bring": "Zu Bring! hinzugefügt",
- "undone_badge": "Rückgängig",
- "undo_title": "Diese Operation rückgängig machen",
- "load_error": "Fehler beim Laden des Verlaufs",
- "empty": "Keine Operationen aufgezeichnet.",
- "undo_action_remove": "Entfernen von",
- "undo_action_restore": "Wiederherstellen von",
- "undo_confirm": "Vorgang rückgängig machen?\n→ {action} {name}",
- "undo_success": "↩ Vorgang rückgängig gemacht für {name}",
- "already_undone": "Vorgang bereits rückgängig gemacht",
- "too_old": "Vorgänge älter als 24 Stunden können nicht rückgängig gemacht werden",
- "undo_error": "Fehler beim Rückgängigmachen",
- "recipe_prefix": "Rezept"
- },
- "chat": {
- "title": "Gemini Chef",
- "welcome": "Hallo! Ich bin dein Küchenassistent",
- "welcome_desc": "Frag mich, dir einen Saft, einen Snack, ein schnelles Gericht zu machen... Ich kenne deinen Vorrat, deine Geräte und deine Vorlieben!",
- "suggestion_snack": "🍿 Schneller Snack",
- "suggestion_juice": "🥤 Saft/Smoothie",
- "suggestion_light": "🥗 Etwas Leichtes",
- "suggestion_expiry": "⏰ Ablaufende nutzen",
- "clear": "Neues Gespräch",
- "placeholder": "Frag etwas...",
- "cleared": "Chat geleert",
- "suggestion_snack_text": "Was kann ich als schnellen Snack machen?",
- "suggestion_juice_text": "Mach mir einen Saft oder Smoothie mit dem was ich habe",
- "suggestion_light_text": "Ich habe Hunger, möchte aber etwas Leichtes",
- "suggestion_expiry_text": "Was läuft bald ab und wie kann ich es verwenden?",
- "transfer_to_recipes": "Zu Rezepten hinzufügen",
- "transferring": "Übertrage...",
- "transferred": "Zu Rezepten hinzugefügt!",
- "open_recipe": "Rezept öffnen",
- "quick_recipe_prompt": "Schlage mir ein schnelles Rezept FÜR EINE PERSON vor, das die Produkte mit dem nächsten Ablaufdatum verwendet! Ignoriere Tiefkühlprodukte, konzentriere dich auf Kühlschrank und Vorratsschrank."
- },
- "cooking": {
- "close": "Schließen",
- "tts_btn": "Vorlesen",
- "restart": "↺ Neustart",
- "replay": "🔊 Nochmal",
- "timer": "⏱️ {time} · Timer",
- "prev": "◀ Zurück",
- "next": "Weiter ▶",
- "ingredient_used": "✔️ Abgezogen",
- "ingredient_use_btn": "Usa",
- "ingredient_deduct_title": "Von Vorrat abziehen",
- "timer_expired_tts": "Timer {label} abgelaufen!",
- "timer_warning_tts": "Achtung! {label}: noch 10 Sekunden!",
- "recipe_done_tts": "Rezept abgeschlossen! Guten Appetit!",
- "expires_chip": "läuft ab {date}",
- "finish": "✅ Fertig",
- "step_fallback": "Schritt {n}",
- "zerowaste_label": "♻️ Abfall",
- "zerowaste_tip_title": "Zero-Waste-Tipp"
- },
- "settings": {
- "title": "⚙️ Einstellungen",
- "tab_api": "API Keys",
- "tab_bring": "Bring!",
- "tab_recipe": "Rezepte",
- "tab_mealplan": "Wochenplan",
- "tab_appliances": "Geräte",
- "tab_spesa": "Online-Einkauf",
- "tab_camera": "Kamera",
- "tab_security": "Sicherheit",
- "tab_tts": "Sprache (TTS)",
- "tab_language": "Sprache",
- "tab_scale": "Smart-Waage",
- "gemini": {
- "title": "🤖 Google Gemini AI",
- "hint": "API-Schlüssel für Produkterkennung, Ablaufdaten und Rezepte.",
- "key_label": "Gemini API Key"
+ "app": {
+ "name": "EverShelf",
+ "loading": "Laden..."
},
- "bring": {
- "title": "🛒 Bring! Einkaufsliste",
- "hint": "Zugangsdaten für die Bring! Einkaufslisten-Integration.",
- "email_label": "📧 Bring! E-Mail",
- "password_label": "🔒 Bring! Passwort"
+ "nav": {
+ "title": "EverShelf",
+ "home": "Home",
+ "inventory": "Vorrat",
+ "recipes": "Rezepte",
+ "shopping": "Einkauf",
+ "log": "Verlauf",
+ "settings": "Einstellungen"
},
- "price": {
- "title": "💰 Preisschätzung (KI)",
- "hint": "Zeigt geschätzte Kosten pro Produkt in der Einkaufsliste mithilfe von KI an.",
- "enabled_label": "Preisschätzung aktivieren",
- "country_label": "🌍 Referenzland",
- "currency_label": "💱 Währung",
- "update_label": "🔄 Preise aktualisieren alle",
- "update_suffix": "Monate"
+ "btn": {
+ "back": "← Zurück",
+ "save": "💾 Speichern",
+ "cancel": "✕ Abbrechen",
+ "close": "Schließen",
+ "add": "✅ Hinzufügen",
+ "delete": "Löschen",
+ "edit": "✏️ Bearbeiten",
+ "use": "Verwenden",
+ "edit_item": "Bearbeiten",
+ "search": "🔍 Suchen",
+ "go": "✅ Los",
+ "toggle_password": "👁️ Anzeigen/Ausblenden",
+ "load_more": "Mehr laden...",
+ "save_config": "💾 Konfiguration speichern",
+ "save_product": "💾 Produkt speichern",
+ "restart": "↺ Neustart",
+ "reset_default": "↺ Standard wiederherstellen",
+ "save_info": "💾 Info speichern",
+ "retry": "🔄 Erneut versuchen",
+ "yes_short": "Ja",
+ "no_short": "Nein"
},
- "recipe": {
- "title": "🍳 Rezept-Einstellungen",
- "hint": "Konfiguriere die Standardoptionen für die Rezeptgenerierung.",
- "persons_label": "👥 Standard-Portionen",
- "options_label": "🎯 Standard-Rezeptoptionen",
- "fast": "⚡ Schnelles Gericht",
- "light": "🥗 Leichte Mahlzeit",
- "expiry": "⏰ Ablauf-Priorität",
- "healthy": "💚 Extra Gesund",
- "opened": "📦 Offene Produkte zuerst",
- "zerowaste": "♻️ Keine Verschwendung",
- "dietary_label": "🚫 Unverträglichkeiten / Einschränkungen",
- "dietary_placeholder": "z.B.: glutenfrei, laktosefrei, vegetarisch..."
+ "form": {
+ "select_placeholder": "-- Auswählen --"
},
- "mealplan": {
- "title": "📅 Wöchentlicher Essensplan",
- "hint": "Lege die Mahlzeitenart für jeden Tag fest. Wird als Leitfaden bei der Rezeptgenerierung verwendet.",
- "enabled": "✅ Wöchentlichen Essensplan aktivieren",
- "legend": "🌤️ = Mittagessen · 🌙 = Abendessen · Tippe auf ein Badge, um es zu ändern.",
- "types_title": "📋 Verfügbare Typen",
- "reset_btn": "↺ Standard wiederherstellen"
+ "locations": {
+ "dispensa": "Vorratskammer",
+ "frigo": "Kühlschrank",
+ "freezer": "Gefrierschrank",
+ "altro": "Sonstiges"
},
- "appliances": {
- "title": "🔌 Verfügbare Geräte",
- "hint": "Gib an, welche Geräte du hast. Sie werden bei der Rezeptgenerierung berücksichtigt.",
- "new_placeholder": "z.B.: Brotbackmaschine, Thermomix, Heißluftfritteuse...",
- "quick_title": "Schnell hinzufügen:",
- "oven": "🔥 Backofen",
- "microwave": "📡 Mikrowelle",
- "air_fryer": "🍟 Heißluftfritteuse",
- "bread_maker": "🍞 Brotbackmaschine",
- "bimby": "🤖 Thermomix/Cookeo",
- "mixer": "🌀 Küchenmaschine",
- "steamer": "♨️ Dampfgarer",
- "pressure_cooker": "🫕 Schnellkochtopf",
- "toaster": "🍞 Toaster",
- "blender": "🍹 Mixer",
- "empty": "Keine Geräte hinzugefügt"
+ "categories": {
+ "latticini": "Milchprodukte",
+ "carne": "Fleisch",
+ "pesce": "Fisch",
+ "frutta": "Obst",
+ "verdura": "Gemüse",
+ "pasta": "Pasta & Reis",
+ "pane": "Brot & Backwaren",
+ "surgelati": "Tiefkühl",
+ "bevande": "Getränke",
+ "condimenti": "Gewürze",
+ "snack": "Snacks & Süßes",
+ "conserve": "Konserven",
+ "cereali": "Getreide & Hülsenfrüchte",
+ "igiene": "Hygiene",
+ "pulizia": "Reinigung",
+ "altro": "Sonstiges",
+ "select": "-- Auswählen --"
},
- "spesa": {
- "title": "🛍️ Online-Einkauf",
- "hint": "Online-Einkaufsanbieter konfigurieren.",
- "provider_label": "🏪 Anbieter",
- "email_label": "📧 E-Mail",
- "password_label": "🔒 Passwort",
- "login_btn": "🔐 Anmelden",
- "ai_prompt_label": "🤖 KI-Produktauswahl Prompt",
- "ai_prompt_placeholder": "Anweisungen für die KI bei der Auswahl zwischen mehreren Produkten...",
- "ai_prompt_hint": "Die KI verwendet diesen Prompt zur Auswahl des passendsten Produkts. Leer lassen für Standardverhalten.",
- "configure_first": "Konfiguriere zuerst den Online-Einkauf in den Einstellungen",
- "missing_credentials": "E-Mail und Passwort eingeben",
- "login_in_progress": "Anmeldung läuft...",
- "login_error_prefix": "Fehler:",
- "login_network_error_prefix": "Netzwerkfehler:",
- "login_success_default": "Anmeldung erfolgreich!",
- "result_name_label": "Name",
- "result_card_label": "Karte",
- "result_pickup_label": "Abholpunkt",
- "result_points_label": "Treuepunkte",
- "connected_relogin": "✅ Verbunden — Erneut anmelden",
- "connected_as": "Verbunden als {name}"
+ "units": {
+ "pz": "Stk",
+ "conf": "Pkg",
+ "g": "g",
+ "ml": "ml",
+ "pieces": "Stück",
+ "grams": "Gramm",
+ "box": "Packung",
+ "boxes": "Packungen",
+ "millilitres": "Milliliter",
+ "from": "von"
},
- "camera": {
- "title": "📷 Kamera",
- "hint": "Wähle die Kamera für Barcode-Scanning und KI-Identifikation.",
- "device_label": "📸 Standardkamera",
- "back": "📱 Rückkamera (Standard)",
- "front": "🤳 Frontkamera",
- "devices_hint": "Bei mehreren Kameras kannst du nach Freigabe der Berechtigungen eine bestimmte aus der Liste oben wählen.",
- "detect_btn": "🔄 Kameras erkennen"
+ "shopping_sections": {
+ "frutta_verdura": "Obst & Gemüse",
+ "carne_pesce": "Fleisch & Fisch",
+ "latticini": "Milchprodukte & Frisches",
+ "pane_dolci": "Brot & Süßes",
+ "pasta": "Pasta & Getreide",
+ "conserve": "Konserven & Soßen",
+ "surgelati": "Tiefkühl",
+ "bevande": "Getränke",
+ "pulizia_igiene": "Reinigung & Hygiene",
+ "altro": "Sonstiges"
},
- "security": {
- "title": "🔒 HTTPS-Zertifikat",
- "hint": "Wenn der Browser den Fehler \"Verbindung nicht sicher\" (ERR_CERT_AUTHORITY_INVALID) zeigt, installiere das CA-Zertifikat auf dem Gerät.",
- "download_btn": "📥 CA-Zertifikat herunterladen",
- "token_title": "🔑 Einstellungs-Token",
- "token_label": "Zugriffstoken",
- "token_hint": "Falls `SETTINGS_TOKEN` in der Server-`.env` konfiguriert ist, gib hier den Token ein, bevor du die Einstellungen speicherst. Leer lassen, wenn nicht konfiguriert.",
- "token_placeholder": "(leer = kein Schutz)",
- "token_required_hint": "🔒 Dieser Server benötigt einen Token zum Speichern der Einstellungen.",
- "cert_instructions": "
Anleitung für Chrome (Android):1. Zertifikat oben herunterladen
2. Gehe zu
Einstellungen → Sicherheit & Datenschutz → Weitere Sicherheitseinstellungen → Vom Gerätespeicher installieren3. Wähle die heruntergeladene
EverShelf_CA.crt Datei
4. Wähle \"CA\" und bestätige
5. Chrome neu starten
Anleitung für Chrome (PC):1. Zertifikat oben herunterladen
2. Gehe zu
chrome://settings/certificates (oder Einstellungen → Datenschutz und Sicherheit → Sicherheit → Zertifikate verwalten)
3. Tab \"Zertifizierungsstellen\" → Importieren → Datei auswählen
4. Häkchen bei \"Dieser Zertifizierungsstelle für die Identifikation von Webseiten vertrauen\"
5. Chrome neu starten"
+ "dashboard": {
+ "expired_title": "🚫 Abgelaufen",
+ "expiring_title": "⏰ Bald ablaufend",
+ "stats_period": "📊 Letzte 30 Tage",
+ "opened_title": "📦 Geöffnete Produkte",
+ "review_title": "🔍 Zu prüfen",
+ "review_hint": "Mengen, die ungewöhnlich erscheinen. Bestätigen oder ändern.",
+ "quick_recipe": "Schnelles Rezept mit ablaufenden Produkten",
+ "banner_review_title": "Ungewöhnliche Menge",
+ "banner_review_action_ok": "Ist korrekt",
+ "banner_review_action_finish": "🗑️ Alles aufgebraucht",
+ "banner_review_action_edit": "Korrigieren",
+ "banner_review_action_weigh": "Wiegen",
+ "banner_review_dismiss": "Ignorieren",
+ "banner_prediction_title": "Verbrauch zur Prüfung",
+ "banner_prediction_hint": "Die Verbrauchsschätzung passt sich aktuellen Daten an: bitte nur die aktuelle Menge bestätigen.",
+ "banner_prediction_action_confirm": "{qty} {unit} bestätigen",
+ "banner_prediction_action_weigh": "Jetzt wiegen",
+ "banner_prediction_action_edit": "Menge aktualisieren",
+ "banner_expired_title": "Abgelaufenes Produkt",
+ "banner_expired_today": "Heute abgelaufen",
+ "banner_expired_days": "Seit {days} Tagen abgelaufen",
+ "banner_expired_action_use": "Trotzdem verwenden",
+ "banner_expired_action_finished": "Habe ich verbraucht!",
+ "banner_expired_action_throw": "Habe ich weggeworfen",
+ "banner_expired_action_edit": "Datum korrigieren",
+ "banner_expired_action_modify": "Bearbeiten",
+ "banner_expired_action_vacuum": "Vakuumieren",
+ "banner_anomaly_action_edit": "Bestand korrigieren",
+ "banner_anomaly_action_dismiss": "Menge ist korrekt",
+ "banner_no_expiry_title": "Ablaufdatum fehlt: {name}",
+ "banner_no_expiry_detail": "Dieses Produkt hat kein Ablaufdatum. Möchten Sie eines hinzufügen oder bestätigen, dass es nicht verfällt?",
+ "banner_no_expiry_action_set": "Ablaufdatum setzen",
+ "banner_no_expiry_action_dismiss": "Läuft nicht ab ✓",
+ "banner_no_expiry_toast_dismissed": "Als 'läuft nicht ab' markiert",
+ "banner_expiring_title": "Bald ablaufend",
+ "banner_expiring_today": "Läuft heute ab!",
+ "banner_expiring_tomorrow": "Läuft morgen ab",
+ "banner_expiring_days": "Läuft in {days} Tagen ab",
+ "banner_expiring_action_use": "Jetzt verwenden",
+ "banner_finished_title": "aufgebraucht?",
+ "banner_finished_detail": "Ich habe vermerkt, dass {name} auf null gesunken ist. Ist es wirklich leer, oder hast du noch welches?",
+ "banner_finished_action_yes": "Ja, aufgebraucht",
+ "banner_finished_action_no": "Nein, ich habe noch welches",
+ "banner_review_unusual_pkg_title": "Ungewöhnliche Packungsgröße",
+ "banner_review_unusual_pkg_detail": "Du hast eine Packung von {qty} {unit} eingestellt — die Größe scheint sehr groß. Überprüfe ob es korrekt ist.",
+ "banner_review_low_qty_title": "Sehr geringe Menge",
+ "banner_review_low_qty_detail": "Du hast nur {qty} im Bestand — das scheint sehr wenig, möglicherweise ein Eingabefehler. Bestätige wenn korrekt.",
+ "banner_review_high_qty_title": "Ungewöhnlich hohe Menge",
+ "banner_review_high_qty_detail": "Du hast {qty} im Bestand — die Zahl scheint sehr hoch. Bestätige wenn korrekt oder korrigiere.",
+ "banner_prediction_rate_day": "Durchschnitt ~{n} {unit}/Tag",
+ "banner_prediction_rate_week": "Durchschnitt ~{n} {unit}/Woche",
+ "banner_prediction_days_ago": "Vor {n} Tagen aufgefüllt",
+ "banner_prediction_more": "frühere Schätzung: {expected} {unit}{time}; aktuelle Menge: {actual} {unit}.",
+ "banner_prediction_less": "Schätzung: {expected} {unit}{time}; aktuelle Menge: {actual} {unit}. Wenn sich dein Verbrauch geändert hat, aktualisiert sich die Prognose automatisch.",
+ "banner_finished_zero": "Bestand zeigt null, aber gespeicherte Buchungen deuten an, dass es nicht leer sein sollte.",
+ "banner_finished_expected": "Laut Aufzeichnungen solltest du noch {qty} {unit} haben.",
+ "banner_finished_check": "Kannst du nachschauen?",
+ "banner_anomaly_phantom_title": "mehr Bestand als erwartet",
+ "banner_anomaly_phantom_detail": "Bestand zeigt {inv_qty} {unit}, aber laut Buchungen solltest du nur {expected_qty} {unit} haben. Hast du Bestand ohne Buchung hinzugefügt?",
+ "banner_anomaly_untracked_title": "Anfangsbestand nicht als Eingang gebucht",
+ "banner_anomaly_untracked_detail": "Du hast
{inv_qty} {unit} im Bestand, aber die gebuchten Abgänge übersteigen die Eingänge — der Anfangsbestand wurde wahrscheinlich nie als \"Eingang\" erfasst. Bitte korrigiere die Menge oder trage die fehlenden Eingänge nach.",
+ "banner_anomaly_ghost_title": "weniger Bestand als erwartet",
+ "banner_anomaly_ghost_detail": "Laut Buchungen solltest du {expected_qty} {unit} von {name} haben, aber der Bestand zeigt nur {inv_qty} {unit}. Hast du etwas ohne Buchung entnommen?",
+ "consumed": "Verbraucht: {n} ({pct}%)",
+ "wasted": "Weggeworfen: {n} ({pct}%)",
+ "more_opened": "und {n} weitere geöffnet...",
+ "banner_expired_detail": "{when} · du hast noch
{qty}.",
+ "banner_opened_detail": "{when} in {location} · du hast noch
{qty}.",
+ "banner_explain_title": "Gemini um eine Erklärung bitten",
+ "banner_explain_btn": "Erklären",
+ "banner_analyzing": "🤖 Analysiere…"
},
- "tts": {
- "title": "🔊 Sprache & TTS",
- "hint": "Sprachsynthese über externe REST-API konfigurieren. Rezeptschritte und abgelaufene Timer werden an den Endpunkt gesendet.",
- "enabled": "✅ TTS aktivieren",
- "engine_label": "⚙️ TTS-Engine",
- "engine_browser": "🔇 Browser (offline, keine Konfiguration erforderlich)",
- "engine_server": "🌐 Externer Server (Home Assistant, REST API...)",
- "voice_label": "🗣️ Stimme",
- "rate_label": "⚡ Geschwindigkeit",
- "pitch_label": "🎵 Tonhöhe",
- "url_label": "🌐 Endpunkt-URL",
- "method_label": "📡 HTTP-Methode",
- "auth_label": "🔐 Authentifizierung",
- "auth_bearer": "Bearer Token",
- "auth_custom": "Benutzerdefinierter Header",
- "auth_none": "Keine",
- "token_label": "🔑 Bearer Token",
- "custom_header_name": "📋 Header-Name",
- "custom_header_value": "📋 Header-Wert",
- "content_type_label": "📄 Content-Type",
- "payload_key_label": "🗝️ Textfeld im Payload",
- "payload_key_hint": "Name des JSON-Feldes für den zu lesenden Text (z.B.: message, text).",
- "extra_fields_label": "➕ Zusätzliche Felder (JSON)",
- "extra_fields_placeholder": "{\"entity_id\": \"media_player.living_room\"}",
- "extra_fields_hint": "Zusätzliche Felder im Payload, im JSON-Format. Leer lassen wenn nicht benötigt.",
- "test_btn": "🔊 Testansage senden",
- "voices_loading": "Stimmen werden geladen…",
- "voice_not_supported": "Stimme vom Browser nicht unterstützt",
- "voices_none": "Keine Stimmen auf diesem Gerät verfügbar",
- "voices_hint": "Verfügbare Stimmen hängen vom Betriebssystem und Browser ab. Auf macOS/iOS ist die Stimme Paola (Italienisch) verfügbar. Drücken Sie ↺ wenn die Liste nicht lädt.",
- "url_missing": "⚠️ Endpunkt-URL fehlt.",
- "test_sending": "⏳ Wird gesendet…",
- "test_ok": "✅ Antwort {code} — prüfe ob der Lautsprecher gesprochen hat."
+ "inventory": {
+ "title": "Vorrat",
+ "filter_all": "Alle",
+ "search_placeholder": "🔍 Produkt suchen...",
+ "recent_title": "🕐 Zuletzt verwendet",
+ "popular_title": "⭐ Meistverwendet",
+ "empty": "Keine Produkte hier.\nScanne ein Produkt, um es hinzuzufügen!",
+ "no_items_found": "Keine Bestandseinträge gefunden",
+ "qty_remainder_suffix": "übrig",
+ "vacuum_badge": "🫙 Vakuumiert",
+ "opened_badge": "📭 Geöffnet",
+ "label_expiry": "📅 Ablaufdatum",
+ "label_storage": "🫙 Aufbewahrung",
+ "label_status": "📭 Status",
+ "opened_since": "Geöffnet seit {date}",
+ "label_position": "📍 Standort",
+ "label_quantity": "📦 Menge",
+ "label_added": "📅 Hinzugefügt",
+ "empty_text": "Keine Produkte hier.
Scanne ein Produkt, um es hinzuzufügen!",
+ "empty_db": "Keine Produkte in der Datenbank.
Scanne ein Produkt, um loszulegen!",
+ "qty_trace": "< 1"
},
- "language": {
- "title": "🌐 Sprache",
- "hint": "Wähle die Sprache der Benutzeroberfläche.",
- "label": "🌐 Sprache",
- "restart_notice": "Die Seite wird neu geladen, um die neue Sprache anzuwenden."
+ "scan": {
+ "title": "Scannen",
+ "mode_shopping": "🛒 Einkaufsmodus",
+ "mode_shopping_end": "✅ Einkauf beenden",
+ "spesa_btn": "🛒 Einkauf",
+ "zoom": "Zoom",
+ "tab_barcode": "Barcode",
+ "tab_name": "Name",
+ "tab_ai": "KI",
+ "recents_label": "Zuletzt",
+ "torch_hint": "Taschenlampe",
+ "torch_on": "Taschenlampe an",
+ "torch_off": "Taschenlampe aus",
+ "torch_unavailable": "Taschenlampe auf diesem Gerät nicht verfügbar",
+ "flip_hint": "Kamera wechseln",
+ "flip_front": "Frontkamera",
+ "flip_back": "Rückkamera",
+ "num_ocr_btn": "🔢 Zahlen mit KI lesen",
+ "num_ocr_searching": "Suche Barcode mit KI...",
+ "num_ocr_found": "Code gefunden: {code}",
+ "num_ocr_not_found": "Kein Barcode im Bild gefunden",
+ "barcode_placeholder": "Barcode eingeben...",
+ "quick_name_divider": "oder Name eingeben",
+ "quick_name_placeholder": "z.B.: Äpfel, Zucchini, Brot...",
+ "manual_entry": "✏️ Manuelle Eingabe",
+ "ai_identify": "🤖 Mit KI identifizieren",
+ "hint": "Barcode scannen, Produktname eingeben oder KI zur Identifikation nutzen",
+ "debug_toggle": "🐛 Debug Log",
+ "barcode_acquired": "🔖 Barcode gescannt: {code}",
+ "scan_barcode": "🔖 Barcode scannen",
+ "create_named": "{name} erstellen",
+ "new_without_barcode": "Neues Produkt ohne Barcode",
+ "stock_in_pantry": "Bereits im Vorrat:"
+ },
+ "action": {
+ "title": "Was möchtest du tun?",
+ "add_btn": "📥 HINZUFÜGEN",
+ "add_sub": "in Vorrat/Kühlschrank",
+ "use_btn": "VERWENDEN",
+ "use_sub": "aus Vorrat/Kühlschrank",
+ "have_title": "📦 Schon auf Lager!",
+ "add_more_sub": "weitere Menge",
+ "use_qty_sub": "wie viel verwendet",
+ "throw_btn": "🗑️ ENTSORGEN",
+ "throw_sub": "wegwerfen",
+ "edit_sub": "Ablauf, Ort…",
+ "create_recipe_btn": "Rezept",
+ "related_stock_title": "Auch zuhause"
+ },
+ "add": {
+ "title": "Zum Vorrat hinzufügen",
+ "location_label": "📍 Wohin?",
+ "quantity_label": "📦 Menge",
+ "conf_size_label": "📦 Jede Packung enthält:",
+ "conf_size_placeholder": "z.B. 300",
+ "vacuum_label": "🫙 Vakuumiert",
+ "vacuum_hint": "Ablaufdatum wird automatisch verlängert",
+ "submit": "✅ Hinzufügen",
+ "purchase_type_label": "🛒 Dieses Produkt ist...",
+ "new_btn": "🆕 Gerade gekauft",
+ "existing_btn": "📦 Hatte ich schon",
+ "remaining_label": "📦 Verbleibende Menge",
+ "remaining_hint": "Ungefähr wie viel ist noch übrig?",
+ "remaining_full": "🟢 Voll",
+ "remaining_half": "🟠 Halb",
+ "estimated_expiry": "Geschätzte Haltbarkeit:",
+ "suffix_freezer": "(Tiefkühler)",
+ "suffix_vacuum": "(vakuumversiegelt)",
+ "hint_modify": "📝 Du kannst das Datum ändern oder mit der Kamera scannen",
+ "scan_expiry_title": "📷 Ablaufdatum scannen",
+ "product_added": "✅ {name} hinzugefügt!{qty}",
+ "suffix_freezer_vacuum": "(Tiefkühler + vakuumversiegelt)",
+ "history_badge_tip": "Durchschnitt aus {n} früheren Einträgen",
+ "vacuum_question": "Vakuumiert?",
+ "vacuum_saved": "🔒 Als vakuumiert gespeichert"
+ },
+ "use": {
+ "title": "Verwenden / Verbrauchen",
+ "location_label": "📍 Woher?",
+ "quantity_label": "Wie viel hast du benutzt?",
+ "change": "ändern",
+ "partial_hint": "Oder genaue Menge angeben:",
+ "partial_piece_hint": "Hast du nur einen Teil verwendet?",
+ "piece": "Stück",
+ "one_whole": "1 ganzes",
+ "use_all": "🗑️ ALLES verwendet / Aufgebraucht",
+ "submit": "📤 Diese Menge verwenden",
+ "available": "📦 Verfügbar:",
+ "opened_badge": "GEOEFFNET",
+ "not_in_inventory": "⚠️ Produkt nicht im Bestand.",
+ "expiry_warning": "⚠️ Verwende zuerst die{loc}, die am {date} abläuft — {when}!",
+ "expiry_warning_opened": "⚠️ Die{loc} ist seit {when} geöffnet — zuerst verwenden!",
+ "throw_title": "🗑️ Produkt entsorgen",
+ "throw_all": "🗑️ ALLES entsorgen ({qty})",
+ "throw_qty_label": "Wie viel wegwerfen?",
+ "throw_qty_hint": "oder Menge angeben:",
+ "throw_partial_btn": "🗑️ Diese Menge entsorgen",
+ "when_expired": "seit {n} Tagen abgelaufen",
+ "when_today": "läuft
heute ab",
+ "when_tomorrow": "läuft
morgen ab",
+ "when_days": "läuft in
{n} Tagen ab",
+ "toast_used": "📤 {qty} von {name} verwendet",
+ "toast_bring": "🛒 Produkt aufgebraucht → zu Bring! hinzugefügt",
+ "toast_opened_finished": "🔓 Geöffnete Packung von {name} aufgebraucht!",
+ "disambiguation_hint": "Was meinst du mit \"alles aufgebraucht\"?",
+ "disambiguation_all": "🗑️ ALLES verbraucht ({qty})",
+ "error_exceeds_stock": "⚠️ Du kannst nicht mehr verwenden als du verfügbar hast!",
+ "use_all_confirm_title": "✅ Alles aufbrauchen",
+ "use_all_confirm_msg": "Bestätige, dass du das Produkt vollständig aufgebraucht hast:",
+ "use_all_confirm_btn": "✅ Ja, aufgebraucht",
+ "throw_all_confirm_title": "🗑️ Alles entsorgen",
+ "throw_all_confirm_msg": "Möchtest du wirklich das gesamte Produkt entsorgen?",
+ "throw_all_confirm_btn": "🗑️ Ja, entsorgen"
+ },
+ "product": {
+ "title_new": "Neues Produkt",
+ "title_edit": "Produkt bearbeiten",
+ "ai_fill": "📷 Foto machen und mit KI identifizieren",
+ "ai_fill_hint": "KI füllt die Produktfelder automatisch aus",
+ "name_label": "🏷️ Produktname *",
+ "name_placeholder": "z.B.: Vollmilch, Penne Nudeln...",
+ "brand_label": "🏢 Marke",
+ "brand_placeholder": "z.B.: Barilla, Müller, Knorr...",
+ "category_label": "📂 Kategorie",
+ "unit_label": "📏 Maßeinheit",
+ "default_qty_label": "🔢 Standardmenge",
+ "conf_size_label": "📦 Jede Packung enthält:",
+ "conf_size_placeholder": "z.B. 300",
+ "notes_label": "📝 Notizen",
+ "notes_placeholder": "z.B.: laktosefrei, bio, nach dem Öffnen im Kühlschrank aufbewahren...",
+ "barcode_label": "🔖 Barcode",
+ "barcode_placeholder": "Barcode (falls vorhanden)",
+ "barcode_hint": "⚠️ Barcode hinzufügen, damit du beim nächsten Einkauf nur scannen musst!",
+ "submit": "💾 Produkt speichern",
+ "name_required": "Produktname eingeben",
+ "conf_size_required": "Packungsinhalt angeben",
+ "expiry_estimated": "Geschätztes Ablaufdatum:",
+ "scan_expiry": "Ablaufdatum scannen",
+ "expiry_hint": "📝 Du kannst das Datum ändern oder mit der Kamera scannen",
+ "add_batch": "📦 + Charge mit anderem Ablaufdatum",
+ "package_info": "📦 Packung: {info}",
+ "edit_catalog": "⚙️ Produktinfo bearbeiten (Name, Marke, Kategorie…)",
+ "not_recognized": "⚠️ Produkt nicht erkannt",
+ "edit_info": "✏️ Informationen bearbeiten",
+ "modify_details": "BEARBEITEN\nAblauf, Ort…",
+ "already_in_pantry": "📋 Bereits im Vorratsschrank",
+ "no_barcode": "Kein Barcode",
+ "unknown_product": "Unbekanntes Produkt",
+ "edit_name_brand": "Name/Marke bearbeiten",
+ "weight_label": "Gewicht",
+ "origin_label": "Herkunft",
+ "labels_label": "Etiketten",
+ "select_variant": "Genaue Variante auswählen oder KI-Daten nutzen:"
+ },
+ "products": {
+ "title": "📦 Alle Produkte",
+ "search_placeholder": "🔍 Produkt suchen...",
+ "empty": "Keine Produkte in der Datenbank.\nScanne ein Produkt zum Starten!",
+ "no_category": "Keine Produkte in dieser Kategorie"
+ },
+ "recipes": {
+ "title": "🍳 Rezepte",
+ "generate": "✨ Neues Rezept generieren",
+ "archive_empty": "Keine Rezepte gespeichert. Erstelle dein erstes Rezept!",
+ "dialog_title": "🍳 Rezept",
+ "dialog_desc": "Ich erstelle ein gesundes Rezept mit Zutaten aus dem Vorrat und priorisiere Produkte mit nahendem Ablaufdatum.",
+ "meal_label": "🕐 Für welche Mahlzeit?",
+ "persons_label": "👥 Für wie viele Personen?",
+ "meal_type_label": "🎯 Art der Mahlzeit",
+ "opt_fast": "⚡ Schnelle Mahlzeit",
+ "opt_light": "🥗 Kleiner Hunger",
+ "opt_expiry": "⏰ Ablaufdaten priorisieren",
+ "opt_healthy": "💚 Extra gesund",
+ "opt_opened": "📦 Geöffnete Produkte priorisieren",
+ "opt_zero_waste": "♻️ Zero Waste",
+ "generate_btn": "✨ Rezept generieren",
+ "loading_msg": "Rezept wird vorbereitet...",
+ "start_cooking": "👨🍳 Kochmodus",
+ "regenerate": "🔄 Noch eins generieren",
+ "close_btn": "✅ Schließen",
+ "ingredients_title": "🧾 Zutaten",
+ "tools_title": "Benötigte Geräte",
+ "steps_title": "👨🍳 Zubereitung",
+ "no_steps": "Keine Zubereitungsschritte verfügbar",
+ "generate_error": "Fehler bei der Generierung",
+ "stream_interrupted": "Generierung unterbrochen (unvollstaendige Antwort vom Server). Protokolle pruefen oder erneut versuchen.",
+ "persons_short": "Pers.",
+ "use_ingredient_title": "Zutat verwenden",
+ "recipe_qty_label": "Rezept",
+ "from_where_label": "Von wo?",
+ "amount_label": "Wie viel",
+ "use_amount_btn": "Diese Menge verwenden",
+ "use_all_btn": "ALLES verwenden / Aufgebraucht",
+ "packs_label": "Packungen",
+ "quantity_in_total": "Menge in {unit} (gesamt: {total})",
+ "packs_of_have": "Packungen à {size} (du hast {count} Pack.)",
+ "scale_wait_stable": "10s stabiles Gewicht für Auto-Ausfüllen abwarten…",
+ "ingredient_scaled_toast": "📦 Zutat vom Vorrat abgezogen!",
+ "finished_added_bring_toast": "🛒 Produkt aufgebraucht → zu Bring! hinzugefügt!",
+ "load_error": "Fehler beim Laden"
+ },
+ "shopping": {
+ "title": "🛒 Einkaufsliste",
+ "bring_loading": "Verbindung zu Bring!...",
+ "bring_not_configured": "Bring! ist nicht konfiguriert. Füge E-Mail und Passwort in den
Einstellungen hinzu.",
+ "tab_to_buy": "🛍️ Zu kaufen",
+ "tab_forecast": "🧠 Vorhersage",
+ "total_label": "💰 Geschätzter Gesamtbetrag",
+ "section_to_buy": "🛍️ Zu kaufen",
+ "suggestions_title": "💡 KI-Vorschläge",
+ "suggestions_add": "✅ Ausgewählte zu Bring! hinzufügen",
+ "search_prices": "🔍 Alle Preise suchen",
+ "suggest_btn": "Einkaufsvorschläge",
+ "smart_title": "🧠 Intelligente Vorhersagen",
+ "smart_empty": "Keine Vorhersagen verfügbar.
Füge Produkte zur Vorratskammer hinzu, um intelligente Vorhersagen zu erhalten.",
+ "smart_filter_all": "Alle",
+ "smart_filter_critical": "🔴 Dringend",
+ "smart_filter_high": "🟠 Bald",
+ "smart_filter_medium": "🟡 Planen",
+ "smart_filter_low": "🟢 Vorhersage",
+ "smart_add": "🛒 Ausgewählte zu Bring! hinzufügen",
+ "empty": "Einkaufsliste leer!\nNutze den Button unten, um Vorschläge zu generieren.",
+ "already_in_list": "🛒 \"{name}\" ist bereits in der Einkaufsliste",
+ "already_in_list_short": "ℹ️ Bereits in der Einkaufsliste",
+ "add_prompt": "Möchtest du es zur Einkaufsliste hinzufügen?",
+ "smart_already": "📊 Intelligenter Einkauf sagt bereits {name} voraus",
+ "all_searched": "Alle Produkte wurden bereits gesucht. Nutze 🔄 für einzelne Suchen.",
+ "search_complete": "Suche abgeschlossen: {count} Produkte",
+ "suggest_buy": "🛒 Kaufen: {qty} {unit}",
+ "suggest_buy_approx": "🛒 Mindestens: {qty} {unit}",
+ "suggest_buy_tip": "Empfohlene Menge basierend auf dem Verbrauch der letzten 14 Tage",
+ "suggest_buy_approx_tip": "Mindestschätzung basierend auf Verbrauch (nächste Packungsgröße kaufen)",
+ "removed_sufficient": "🧹 {removed} Produkt(e) mit ausreichendem Bestand von der Liste entfernt",
+ "bring_badge": "🛒 Schon auf Bring!",
+ "add_urgent_toast": "🔴 {n} dringende(s) Produkt(e) automatisch zu Bring! hinzugefügt",
+ "migration_done": "✅ {migrated} aktualisiert, {skipped} bereits ok",
+ "added_to_bring": "🛒 {n} Produkte zu Bring! hinzugefügt",
+ "added_to_bring_skip": "{n} bereits vorhanden",
+ "all_on_bring": "Alle Produkte waren bereits auf Bring!",
+ "freq_high": "📈 Häufig",
+ "freq_regular": "📊 Regelmäßig",
+ "freq_occasional": "📉 Gelegentlich",
+ "out_of_stock": "Ausverkauft",
+ "scan_toast": "📷 Scannen: {name}",
+ "empty_category": "Keine Produkte in dieser Kategorie",
+ "session_empty": "🛒 Noch keine Produkte",
+ "urgency_critical": "Dringend",
+ "urgency_high": "Bald",
+ "urgency_medium": "Planen",
+ "urgency_low": "Vorschau",
+ "urgency_medium_short": "Mittel",
+ "urgency_low_short": "Ok",
+ "tag_urgent": "🔴 Dringend",
+ "tag_priority": "⭐ Priorität",
+ "tag_check": "✅ Prüfen",
+ "smart_already_predicted": "📊 Einkauf wird bereits vorhergesagt:
{name}{urgency}.",
+ "item_removed": "✅ {name} von der Liste entfernt!",
+ "urgency_spec_critical": "⚡ Dringend",
+ "urgency_spec_high": "🟠 Bald",
+ "bring_add_n": "{n} zu Bring! hinzufügen",
+ "bring_add_selected": "Ausgewählte zu Bring! hinzufügen",
+ "bring_adding": "Wird hinzugefügt...",
+ "bring_added_one": "1 Produkt zu Bring! hinzugefügt",
+ "bring_added_many": "{n} Produkte zu Bring! hinzugefügt",
+ "bring_skipped": "({n} bereits in Liste)",
+ "force_sync": "Bring!-Synchronisierung erzwingen",
+ "scan_target_label": "Du suchst",
+ "scan_target_found": "Gefunden! Aus Liste entfernen",
+ "bring_add_one": "1 Produkt zu Bring! hinzufügen",
+ "bring_add_many": "{n} Produkte zu Bring! hinzufügen",
+ "syncing": "Synchronisiere…",
+ "sync_done": "Synchronisierung abgeschlossen",
+ "price_searching": "Suche...",
+ "search_action": "Suchen",
+ "open_action": "Öffnen",
+ "not_found": "Nicht gefunden",
+ "search_price": "Preis suchen",
+ "tap_to_scan": "Zum Scannen tippen",
+ "tag_title": "Tag",
+ "remove_title": "Entfernen",
+ "found_count": "{found}/{total} Produkte gefunden",
+ "savings_offers": "· 🏷️ Du sparst €{amount} mit Angeboten",
+ "searching_progress": "Suche {current}/{total}...",
+ "remove_error": "Fehler beim Entfernen",
+ "btn_fetch_prices": "Preise suchen",
+ "price_total_label": "💰 Geschätzter Gesamtpreis:",
+ "price_loading": "Preise werden gesucht…",
+ "price_not_found": "Preis n/v",
+ "suggest_loading": "Analyse läuft...",
+ "suggest_error": "Fehler bei der Vorschlagserstellung",
+ "priority_high": "Hoch",
+ "priority_medium": "Mittel",
+ "priority_low": "Niedrig",
+ "smart_last_update": "Aktualisiert {time}",
+ "names_already_updated": "Alle Namen sind bereits aktuell",
+ "pantry_hint": "Bereits zuhause: {qty}"
+ },
+ "ai": {
+ "title": "🤖 KI-Identifikation",
+ "capture": "📸 Foto aufnehmen",
+ "retake": "🔄 Neu aufnehmen",
+ "hint": "Mache ein Foto des Produkts und die KI versucht es zu identifizieren",
+ "identifying": "🤖 Identifiziere Produkt...",
+ "no_api_key": "⚠️ Gemini API-Schlüssel nicht konfiguriert.\n
Füge GEMINI_API_KEY in der .env Datei auf dem Server hinzu.",
+ "fields_filled": "✅ Felder von KI ausgefüllt",
+ "use_data": "✅ KI-Daten verwenden",
+ "use_data_no_barcode": "✅ KI-Daten verwenden (ohne Barcode)"
+ },
+ "log": {
+ "title": "📒 Verlauf",
+ "type_added": "Hinzugefügt",
+ "type_waste": "Entsorgt",
+ "type_used": "Verwendet",
+ "type_bring": "Zu Bring! hinzugefügt",
+ "undone_badge": "Rückgängig",
+ "undo_title": "Diese Operation rückgängig machen",
+ "load_error": "Fehler beim Laden des Verlaufs",
+ "empty": "Keine Operationen aufgezeichnet.",
+ "undo_action_remove": "Entfernen von",
+ "undo_action_restore": "Wiederherstellen von",
+ "undo_confirm": "Vorgang rückgängig machen?\n→ {action} {name}",
+ "undo_success": "↩ Vorgang rückgängig gemacht für {name}",
+ "already_undone": "Vorgang bereits rückgängig gemacht",
+ "too_old": "Vorgänge älter als 24 Stunden können nicht rückgängig gemacht werden",
+ "undo_error": "Fehler beim Rückgängigmachen",
+ "recipe_prefix": "Rezept"
+ },
+ "chat": {
+ "title": "Gemini Chef",
+ "welcome": "Hallo! Ich bin dein Küchenassistent",
+ "welcome_desc": "Frag mich, dir einen Saft, einen Snack, ein schnelles Gericht zu machen... Ich kenne deinen Vorrat, deine Geräte und deine Vorlieben!",
+ "suggestion_snack": "🍿 Schneller Snack",
+ "suggestion_juice": "🥤 Saft/Smoothie",
+ "suggestion_light": "🥗 Etwas Leichtes",
+ "suggestion_expiry": "⏰ Ablaufende nutzen",
+ "clear": "Neues Gespräch",
+ "placeholder": "Frag etwas...",
+ "cleared": "Chat geleert",
+ "suggestion_snack_text": "Was kann ich als schnellen Snack machen?",
+ "suggestion_juice_text": "Mach mir einen Saft oder Smoothie mit dem was ich habe",
+ "suggestion_light_text": "Ich habe Hunger, möchte aber etwas Leichtes",
+ "suggestion_expiry_text": "Was läuft bald ab und wie kann ich es verwenden?",
+ "transfer_to_recipes": "Zu Rezepten hinzufügen",
+ "transferring": "Übertrage...",
+ "transferred": "Zu Rezepten hinzugefügt!",
+ "open_recipe": "Rezept öffnen",
+ "quick_recipe_prompt": "Schlage mir ein schnelles Rezept FÜR EINE PERSON vor, das die Produkte mit dem nächsten Ablaufdatum verwendet! Ignoriere Tiefkühlprodukte, konzentriere dich auf Kühlschrank und Vorratsschrank."
+ },
+ "cooking": {
+ "close": "Schließen",
+ "tts_btn": "Vorlesen",
+ "restart": "↺ Neustart",
+ "replay": "🔊 Nochmal",
+ "timer": "⏱️ {time} · Timer",
+ "prev": "◀ Zurück",
+ "next": "Weiter ▶",
+ "ingredient_used": "✔️ Abgezogen",
+ "ingredient_use_btn": "Usa",
+ "ingredient_deduct_title": "Von Vorrat abziehen",
+ "timer_expired_tts": "Timer {label} abgelaufen!",
+ "timer_warning_tts": "Achtung! {label}: noch 10 Sekunden!",
+ "recipe_done_tts": "Rezept abgeschlossen! Guten Appetit!",
+ "expires_chip": "läuft ab {date}",
+ "finish": "✅ Fertig",
+ "step_fallback": "Schritt {n}",
+ "zerowaste_label": "♻️ Abfall",
+ "zerowaste_tip_title": "Zero-Waste-Tipp"
+ },
+ "settings": {
+ "title": "⚙️ Einstellungen",
+ "tab_api": "API Keys",
+ "tab_bring": "Bring!",
+ "tab_recipe": "Rezepte",
+ "tab_mealplan": "Wochenplan",
+ "tab_appliances": "Geräte",
+ "tab_spesa": "Online-Einkauf",
+ "tab_camera": "Kamera",
+ "tab_security": "Sicherheit",
+ "tab_tts": "Sprache (TTS)",
+ "tab_language": "Sprache",
+ "tab_scale": "Smart-Waage",
+ "gemini": {
+ "title": "🤖 Google Gemini AI",
+ "hint": "API-Schlüssel für Produkterkennung, Ablaufdaten und Rezepte.",
+ "key_label": "Gemini API Key"
+ },
+ "bring": {
+ "title": "🛒 Bring! Einkaufsliste",
+ "hint": "Zugangsdaten für die Bring! Einkaufslisten-Integration.",
+ "email_label": "📧 Bring! E-Mail",
+ "password_label": "🔒 Bring! Passwort"
+ },
+ "price": {
+ "title": "💰 Preisschätzung (KI)",
+ "hint": "Zeigt geschätzte Kosten pro Produkt in der Einkaufsliste mithilfe von KI an.",
+ "enabled_label": "Preisschätzung aktivieren",
+ "country_label": "🌍 Referenzland",
+ "currency_label": "💱 Währung",
+ "update_label": "🔄 Preise aktualisieren alle",
+ "update_suffix": "Monate"
+ },
+ "recipe": {
+ "title": "🍳 Rezept-Einstellungen",
+ "hint": "Konfiguriere die Standardoptionen für die Rezeptgenerierung.",
+ "persons_label": "👥 Standard-Portionen",
+ "options_label": "🎯 Standard-Rezeptoptionen",
+ "fast": "⚡ Schnelles Gericht",
+ "light": "🥗 Leichte Mahlzeit",
+ "expiry": "⏰ Ablauf-Priorität",
+ "healthy": "💚 Extra Gesund",
+ "opened": "📦 Offene Produkte zuerst",
+ "zerowaste": "♻️ Keine Verschwendung",
+ "dietary_label": "🚫 Unverträglichkeiten / Einschränkungen",
+ "dietary_placeholder": "z.B.: glutenfrei, laktosefrei, vegetarisch..."
+ },
+ "mealplan": {
+ "title": "📅 Wöchentlicher Essensplan",
+ "hint": "Lege die Mahlzeitenart für jeden Tag fest. Wird als Leitfaden bei der Rezeptgenerierung verwendet.",
+ "enabled": "✅ Wöchentlichen Essensplan aktivieren",
+ "legend": "🌤️ = Mittagessen · 🌙 = Abendessen · Tippe auf ein Badge, um es zu ändern.",
+ "types_title": "📋 Verfügbare Typen",
+ "reset_btn": "↺ Standard wiederherstellen"
+ },
+ "appliances": {
+ "title": "🔌 Verfügbare Geräte",
+ "hint": "Gib an, welche Geräte du hast. Sie werden bei der Rezeptgenerierung berücksichtigt.",
+ "new_placeholder": "z.B.: Brotbackmaschine, Thermomix, Heißluftfritteuse...",
+ "quick_title": "Schnell hinzufügen:",
+ "oven": "🔥 Backofen",
+ "microwave": "📡 Mikrowelle",
+ "air_fryer": "🍟 Heißluftfritteuse",
+ "bread_maker": "🍞 Brotbackmaschine",
+ "bimby": "🤖 Thermomix/Cookeo",
+ "mixer": "🌀 Küchenmaschine",
+ "steamer": "♨️ Dampfgarer",
+ "pressure_cooker": "🫕 Schnellkochtopf",
+ "toaster": "🍞 Toaster",
+ "blender": "🍹 Mixer",
+ "empty": "Keine Geräte hinzugefügt"
+ },
+ "spesa": {
+ "title": "🛍️ Online-Einkauf",
+ "hint": "Online-Einkaufsanbieter konfigurieren.",
+ "provider_label": "🏪 Anbieter",
+ "email_label": "📧 E-Mail",
+ "password_label": "🔒 Passwort",
+ "login_btn": "🔐 Anmelden",
+ "ai_prompt_label": "🤖 KI-Produktauswahl Prompt",
+ "ai_prompt_placeholder": "Anweisungen für die KI bei der Auswahl zwischen mehreren Produkten...",
+ "ai_prompt_hint": "Die KI verwendet diesen Prompt zur Auswahl des passendsten Produkts. Leer lassen für Standardverhalten.",
+ "configure_first": "Konfiguriere zuerst den Online-Einkauf in den Einstellungen",
+ "missing_credentials": "E-Mail und Passwort eingeben",
+ "login_in_progress": "Anmeldung läuft...",
+ "login_error_prefix": "Fehler:",
+ "login_network_error_prefix": "Netzwerkfehler:",
+ "login_success_default": "Anmeldung erfolgreich!",
+ "result_name_label": "Name",
+ "result_card_label": "Karte",
+ "result_pickup_label": "Abholpunkt",
+ "result_points_label": "Treuepunkte",
+ "connected_relogin": "✅ Verbunden — Erneut anmelden",
+ "connected_as": "Verbunden als {name}"
+ },
+ "camera": {
+ "title": "📷 Kamera",
+ "hint": "Wähle die Kamera für Barcode-Scanning und KI-Identifikation.",
+ "device_label": "📸 Standardkamera",
+ "back": "📱 Rückkamera (Standard)",
+ "front": "🤳 Frontkamera",
+ "devices_hint": "Bei mehreren Kameras kannst du nach Freigabe der Berechtigungen eine bestimmte aus der Liste oben wählen.",
+ "detect_btn": "🔄 Kameras erkennen"
+ },
+ "security": {
+ "title": "🔒 HTTPS-Zertifikat",
+ "hint": "Wenn der Browser den Fehler \"Verbindung nicht sicher\" (ERR_CERT_AUTHORITY_INVALID) zeigt, installiere das CA-Zertifikat auf dem Gerät.",
+ "download_btn": "📥 CA-Zertifikat herunterladen",
+ "token_title": "🔑 Einstellungs-Token",
+ "token_label": "Zugriffstoken",
+ "token_hint": "Falls `SETTINGS_TOKEN` in der Server-`.env` konfiguriert ist, gib hier den Token ein, bevor du die Einstellungen speicherst. Leer lassen, wenn nicht konfiguriert.",
+ "token_placeholder": "(leer = kein Schutz)",
+ "token_required_hint": "🔒 Dieser Server benötigt einen Token zum Speichern der Einstellungen.",
+ "cert_instructions": "
Anleitung für Chrome (Android):1. Zertifikat oben herunterladen
2. Gehe zu
Einstellungen → Sicherheit & Datenschutz → Weitere Sicherheitseinstellungen → Vom Gerätespeicher installieren3. Wähle die heruntergeladene
EverShelf_CA.crt Datei
4. Wähle \"CA\" und bestätige
5. Chrome neu starten
Anleitung für Chrome (PC):1. Zertifikat oben herunterladen
2. Gehe zu
chrome://settings/certificates (oder Einstellungen → Datenschutz und Sicherheit → Sicherheit → Zertifikate verwalten)
3. Tab \"Zertifizierungsstellen\" → Importieren → Datei auswählen
4. Häkchen bei \"Dieser Zertifizierungsstelle für die Identifikation von Webseiten vertrauen\"
5. Chrome neu starten"
+ },
+ "tts": {
+ "title": "🔊 Sprache & TTS",
+ "hint": "Sprachsynthese über externe REST-API konfigurieren. Rezeptschritte und abgelaufene Timer werden an den Endpunkt gesendet.",
+ "enabled": "✅ TTS aktivieren",
+ "engine_label": "⚙️ TTS-Engine",
+ "engine_browser": "🔇 Browser (offline, keine Konfiguration erforderlich)",
+ "engine_server": "🌐 Externer Server (Home Assistant, REST API...)",
+ "voice_label": "🗣️ Stimme",
+ "rate_label": "⚡ Geschwindigkeit",
+ "pitch_label": "🎵 Tonhöhe",
+ "url_label": "🌐 Endpunkt-URL",
+ "method_label": "📡 HTTP-Methode",
+ "auth_label": "🔐 Authentifizierung",
+ "auth_bearer": "Bearer Token",
+ "auth_custom": "Benutzerdefinierter Header",
+ "auth_none": "Keine",
+ "token_label": "🔑 Bearer Token",
+ "custom_header_name": "📋 Header-Name",
+ "custom_header_value": "📋 Header-Wert",
+ "content_type_label": "📄 Content-Type",
+ "payload_key_label": "🗝️ Textfeld im Payload",
+ "payload_key_hint": "Name des JSON-Feldes für den zu lesenden Text (z.B.: message, text).",
+ "extra_fields_label": "➕ Zusätzliche Felder (JSON)",
+ "extra_fields_placeholder": "{\"entity_id\": \"media_player.living_room\"}",
+ "extra_fields_hint": "Zusätzliche Felder im Payload, im JSON-Format. Leer lassen wenn nicht benötigt.",
+ "test_btn": "🔊 Testansage senden",
+ "voices_loading": "Stimmen werden geladen…",
+ "voice_not_supported": "Stimme vom Browser nicht unterstützt",
+ "voices_none": "Keine Stimmen auf diesem Gerät verfügbar",
+ "voices_hint": "Verfügbare Stimmen hängen vom Betriebssystem und Browser ab. Auf macOS/iOS ist die Stimme Paola (Italienisch) verfügbar. Drücken Sie ↺ wenn die Liste nicht lädt.",
+ "url_missing": "⚠️ Endpunkt-URL fehlt.",
+ "test_sending": "⏳ Wird gesendet…",
+ "test_ok": "✅ Antwort {code} — prüfe ob der Lautsprecher gesprochen hat."
+ },
+ "language": {
+ "title": "🌐 Sprache",
+ "hint": "Wähle die Sprache der Benutzeroberfläche.",
+ "label": "🌐 Sprache",
+ "restart_notice": "Die Seite wird neu geladen, um die neue Sprache anzuwenden."
+ },
+ "screensaver": {
+ "label": "Bildschirmschoner aktivieren",
+ "card_title": "🌙 Bildschirmschoner",
+ "card_hint": "Zeigt nach 5 Minuten Inaktivität eine Uhr mit nützlichen Fakten. Standardmäßig deaktiviert.",
+ "timeout_1": "1 Minute",
+ "timeout_2": "2 Minuten",
+ "timeout_5": "5 Minuten",
+ "timeout_10": "10 Minuten",
+ "timeout_15": "15 Minuten",
+ "timeout_30": "30 Minuten",
+ "timeout_60": "1 Stunde",
+ "start_after": "⏱️ Starten nach"
+ },
+ "scale": {
+ "title": "⚖️ Smart-Waage",
+ "hint": "Verbinde eine Bluetooth-Waage über das Android-Gateway, um das Gewicht automatisch auszulesen.",
+ "tab": "Smart-Waage",
+ "enabled": "✅ Smart-Waage aktivieren",
+ "url_label": "🌐 WebSocket-Gateway-URL",
+ "url_placeholder": "ws://192.168.1.x:8765",
+ "url_hint": "URL der Android-App (gleiches WLAN). z.B.:",
+ "test_btn": "🔗 Verbindung testen",
+ "download_btn": "📥 Android-Gateway herunterladen (APK)",
+ "download_hint": "Android-App als Brücke zwischen BLE-Waage und EverShelf.",
+ "download_sub": "Quellcode: evershelf-scale-gateway/ im Projektstamm",
+ "live_weight": "Echtzeit-Gewicht",
+ "auto_reconnect": "🔁 Verbindung: automatisch",
+ "kiosk_title": "📡 BLE-Waage im Kiosk integriert",
+ "kiosk_hint": "Die Waage wird direkt vom internen BLE-Gateway des Kiosks verwaltet. Um ein neues Gerät zu koppeln, verwende den Konfigurationsassistenten.",
+ "kiosk_reconfigure": "🔄 BLE-Waage neu konfigurieren",
+ "ble_protocols": "
🔌 Unterstützte BLE-Protokolle:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — Gewicht, Fett, BMI
- Xiaomi Mi Body Composition Scale 2
- Generisch — automatische Heuristik für 100+ Modelle
"
+ },
+ "kiosk": {
+ "hint": "Verwandeln Sie ein Android-Tablet in ein EverShelf-Panel mit integriertem BLE-Waagen-Gateway.",
+ "download_btn": "📥 EverShelf Kiosk herunterladen (APK)",
+ "download_sub": "Vollbild-Kioskmodus + integriertes Waagen-Gateway. Quellcode: evershelf-kiosk/",
+ "native_title": "Kiosk-Konfiguration",
+ "native_hint": "Server-URL, BLE-Waage, Bildschirmschoner und Einrichtungsassistent.",
+ "native_btn": "Kiosk-Konfiguration öffnen",
+ "native_tap_hint": "Zahnrad oben rechts antippen",
+ "native_update_hint": "Kiosk-App aktualisieren, um diese Funktion zu nutzen",
+ "update_title": "Kiosk-Aktualisierung",
+ "check_updates_btn": "🔍 Nach Updates suchen",
+ "needs_update": "⚠️ Das installierte Kiosk unterstützt diese Funktion nicht. Aktualisiere die Kiosk-App, um sie zu aktivieren."
+ },
+ "saved": "✅ Konfiguration gespeichert!",
+ "saved_local": "✅ Konfiguration lokal gespeichert",
+ "saved_local_error": "⚠️ Lokal gespeichert, Serverfehler: {error}",
+ "theme": {
+ "title": "🌙 Erscheinungsbild",
+ "hint": "Wähle das Interface-Design.",
+ "label": "🌙 Design",
+ "off": "☀️ Hell",
+ "on": "🌙 Dunkel",
+ "auto": "🔄 Automatisch (Tageszeit)"
+ },
+ "zerowaste": {
+ "card_title": "♻️ Zero-Waste-Tipps",
+ "card_hint": "Zeige während des Kochens Tipps zur Wiederverwendung von Abfällen (Schalen, Kochwasser usw.). Standardmäßig deaktiviert.",
+ "label": "Tipps beim Kochen anzeigen"
+ },
+ "backup": {
+ "tab": "Backup",
+ "local_title": "Lokales Backup",
+ "local_hint": "Täglicher Datenbank-Snapshot. Konfiguriere, wie viele Tage Backups aufbewahrt werden.",
+ "enabled": "Tägliches automatisches Backup aktivieren",
+ "retention_days": "Aufbewahrung (Tage)",
+ "retention_info": "Backups werden aufbewahrt für",
+ "backup_now": "Jetzt sichern",
+ "backing_up": "Sicherung läuft…",
+ "backed_up": "Sicherung abgeschlossen",
+ "backup_error": "Sicherungsfehler",
+ "last_backup": "Letztes Backup",
+ "no_backup_yet": "Noch kein Backup erstellt",
+ "list_empty": "Keine Backups verfügbar",
+ "restore_btn": "Wiederherstellen",
+ "restore_confirm": "Backup wiederherstellen",
+ "delete_btn": "Löschen",
+ "delete_confirm": "Backup löschen",
+ "gdrive_title": "Google Drive",
+ "gdrive_hint": "Backups automatisch via OAuth 2.0 auf Google Drive hochladen. Keine externen Bibliotheken erforderlich.",
+ "gdrive_enabled": "Google Drive Backup aktivieren",
+ "gdrive_folder_id": "Drive-Ordner-ID",
+ "gdrive_folder_id_hint": "Kopiere die ID aus der Drive-Ordner-URL: …/folders/
ID",
+ "gdrive_retention_days": "Drive-Aufbewahrung (Tage, 0=alles behalten)",
+ "gdrive_test": "Verbindung testen",
+ "gdrive_ok": "Verbindung erfolgreich!",
+ "gdrive_error": "Verbindung fehlgeschlagen",
+ "gdrive_push_now": "Jetzt auf Drive hochladen",
+ "gdrive_pushing": "Wird hochgeladen…",
+ "gdrive_pushed": "Auf Drive hochgeladen",
+ "gdrive_wizard_hint": "Optional: täglich automatisch via OAuth 2.0 auf Google Drive sichern.",
+ "gdrive_skip": "Überspringen — später in Einstellungen konfigurieren",
+ "gdrive_client_id": "Client-ID",
+ "gdrive_client_secret": "Client-Secret",
+ "gdrive_redirect_uri_hint": "Füge
http://localhost als autorisierten Weiterleitungs-URI in der Google Cloud Console hinzu. Funktioniert auf jedem Server, auch ohne öffentliche Domain.",
+ "gdrive_code_title": "Autorisierungs-URL oder Code einfügen",
+ "gdrive_code_hint": "Nach der Autorisierung öffnet der Browser http://localhost und zeigt möglicherweise einen Verbindungsfehler — das ist normal. Kopiere die URL aus der Adressleiste (z.B.
http://localhost/?code=4%2F0A...) und füge sie hier ein.",
+ "gdrive_code_submit": "Bestätigen",
+ "gdrive_code_empty": "Bitte zuerst die URL oder den Autorisierungscode einfügen",
+ "gdrive_redirect_uri_label": "Redirect-URI (in Google Cloud Console eintragen):",
+ "gdrive_oauth_authorize": "Mit Google autorisieren",
+ "gdrive_oauth_authorized": "Autorisiert",
+ "gdrive_oauth_not_authorized": "Noch nicht autorisiert",
+ "gdrive_oauth_window_opened": "Browserfenster geöffnet — autorisieren und zurückkehren",
+ "gdrive_oauth_how_to": "OAuth 2.0 einrichten (Schritt für Schritt)",
+ "gdrive_oauth_steps": "
Gehe zu console.cloud.google.com und wähle dein ProjektAktiviere die Google Drive API: APIs & Dienste → APIs aktivieren → Google Drive APIGehe zu APIs & Dienste → Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-IDAnwendungstyp: Webanwendung; füge die unten angezeigte URL als Autorisierter Weiterleitungs-URI hinzuKopiere Client-ID und Client-Secret in die Felder oben und speichereKlicke auf Mit Google autorisieren: melde dich an und erteile den ZugriffDas Fenster schließt sich automatisch und Backups sind bereit"
+ },
+ "info": {
+ "tab": "Info",
+ "ai_title": "Gemini AI — Token-Nutzung",
+ "ai_hint": "Monatlicher Verbrauch und geschätzte Kosten für den aktuellen API-Schlüssel.",
+ "loading": "Laden…",
+ "total_tokens": "Token gesamt",
+ "est_cost": "Gesch. Kosten",
+ "input_tok": "Eingabe-Token",
+ "output_tok": "Ausgabe-Token",
+ "ai_calls": "Aufrufe",
+ "by_action": "Aufschlüsselung nach Funktion",
+ "by_model": "Aufschlüsselung nach Modell",
+ "pricing_note": "Gemini Referenzpreise: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.",
+ "system_title": "System",
+ "db_size": "Datenbank",
+ "log_size": "Protokolle",
+ "log_level": "Log-Level",
+ "ai_overview": "KI-Nutzungsübersicht, Inventar und Systemstatus",
+ "calls_unit": "Aufrufe",
+ "inv_title": "Inventar",
+ "inv_active": "Aktiv",
+ "inv_products": "Produkte gesamt",
+ "inv_expiring": "Ablaufend (7T)",
+ "inv_expired": "Abgelaufen",
+ "inv_finished": "Leer",
+ "act_title": "Monatliche Aktivität",
+ "act_tx_month": "Bewegungen",
+ "act_restock": "Einkäufe",
+ "act_use": "Verbrauch",
+ "act_new_products": "Neue Produkte",
+ "act_tx_year": "Jährl. Bewegungen",
+ "price_cache": "Preiscache",
+ "cache_entries": "Produkte",
+ "last_backup": "Letztes Backup",
+ "bring_days": "Token läuft in {n} Tagen ab",
+ "bring_expired": "Token abgelaufen",
+ "year_label": "Jahr {year}",
+ "currency_title": "Währung",
+ "currency_hint": "Die Währung, die für alle Kosten und Preise in der App verwendet wird."
+ },
+ "tab_general": "Allgemein",
+ "shopping": {
+ "tab": "Einkaufsliste",
+ "title": "Einkaufsliste",
+ "hint": "Konfiguriere die integrierte Einkaufsliste oder verbinde Bring!.",
+ "enable_label": "Einkaufsliste aktivieren",
+ "mode_label": "Anbieter",
+ "mode_internal": "Intern (ohne Bring!)",
+ "mode_bring": "Bring! (externe App)",
+ "bring_section_title": "Bring!-Konfiguration",
+ "ai_section_title": "KI-Unterstützung",
+ "smart_suggestions_label": "KI-Vorschläge",
+ "forecast_label": "Prognose für bald leere Produkte",
+ "auto_add_label": "Automatisch hinzufügen wenn",
+ "auto_add_suffix": "im Lager verbleibend (0 = nur wenn leer)"
+ },
+ "ha": {
+ "tab": "Home Assistant",
+ "title": "Home Assistant",
+ "hint": "Verbinde EverShelf mit Home Assistant für Automationen, Push-Benachrichtigungen und REST-Sensoren.",
+ "enabled": "Home Assistant-Integration aktivieren",
+ "connection_title": "Verbindung",
+ "url_label": "Home Assistant URL",
+ "url_placeholder": "http://192.168.1.50:8123",
+ "url_hint": "Basis-URL deiner Home Assistant-Instanz (z.B. http://homeassistant.local:8123).",
+ "token_label": "Long-Lived Access Token",
+ "token_hint": "Erstelle unter HA-Profil → Sicherheit → Langlebige Zugangstoken.",
+ "token_placeholder": "eyJhbGci...",
+ "token_saved": "Token gespeichert (aus Sicherheitsgründen verborgen)",
+ "test_btn": "Verbindung testen",
+ "test_ok": "Verbunden mit {version}",
+ "test_fail": "Verbindung fehlgeschlagen: {error}",
+ "test_bad_token": "HA erreichbar, aber Token ist ungültig",
+ "testing": "Teste…",
+ "error_no_url": "Bitte zuerst die Home Assistant URL eingeben.",
+ "tts_title": "TTS auf Smart Speaker",
+ "tts_hint": "Rezeptschritte auf einem Home Assistant Media Player vorlesen.",
+ "tts_entity_label": "Media Player Entity ID",
+ "tts_entity_placeholder": "media_player.wohnzimmer",
+ "tts_entity_hint": "Entity-ID des HA-Media-Players. Zu finden unter HA: Entwicklertools → Zustände.",
+ "tts_platform_label": "TTS-Plattform",
+ "tts_platform_speak": "tts.speak (empfohlen)",
+ "tts_platform_notify": "notify.* (Benachrichtigungsdienst)",
+ "tts_apply_btn": "HA-Voreinstellung auf TTS-Tab anwenden",
+ "tts_apply_hint": "Füllt den TTS-Tab mit der Home Assistant URL und dem Token aus.",
+ "tts_preset_applied": "HA-Voreinstellung auf TTS-Tab angewendet.",
+ "webhook_title": "Webhook-Automationen",
+ "webhook_hint": "Sende Daten an Home Assistant, wenn Ereignisse in der Vorratskammer auftreten.",
+ "webhook_id_label": "Webhook-ID",
+ "webhook_id_placeholder": "evershelf_webhook_abc123",
+ "webhook_id_hint": "ID des in HA erstellten Webhooks. Kopiere aus: HA → Einstellungen → Automationen → Erstellen → Webhook-Auslöser.",
+ "webhook_events_label": "Benachrichtige bei diesen Ereignissen",
+ "event_expiry": "Ablaufende Produkte (täglich)",
+ "event_shopping": "Artikel zur Einkaufsliste hinzugefügt",
+ "event_stock": "Lagerbestand aktualisiert",
+ "expiry_days_label": "Ablaufwarnung im Voraus (Tage)",
+ "expiry_days_hint": "Sende die Ablaufwarnung N Tage vor dem Ablaufdatum.",
+ "webhook_help": "In HA: Einstellungen → Automationen → Automation erstellen → Auslöser: Webhook → ID kopieren.",
+ "notify_title": "Push-Benachrichtigungen",
+ "notify_hint": "Sende Push-Benachrichtigungen über einen Home Assistant notify-Dienst.",
+ "notify_service_label": "Notify-Dienst",
+ "notify_service_placeholder": "notify.mobile_app_mein_handy",
+ "notify_service_hint": "Name des HA-notify-Dienstes (z.B. notify.mobile_app_phone). Leer lassen zum Deaktivieren.",
+ "sensor_title": "REST-Sensoren",
+ "sensor_hint": "Zur configuration.yaml hinzufügen, um EverShelf-Sensoren in Home Assistant zu erstellen.",
+ "sensor_copy_btn": "YAML kopieren",
+ "sensor_copied": "YAML in die Zwischenablage kopiert!",
+ "save_btn": "HA-Einstellungen speichern",
+ "ha_hint": "Wenn du Home Assistant verwendest, nutze den Home Assistant-Tab für TTS, Webhooks und Sensoren."
+ }
+ },
+ "expiry": {
+ "today": "HEUTE",
+ "tomorrow": "Morgen",
+ "days": "{days} Tage",
+ "expired_days": "Seit {days}T",
+ "expired_yesterday": "Seit gestern",
+ "expired_today": "Heute",
+ "badge_today": "⚠️ Läuft heute ab!",
+ "badge_tomorrow": "⏰ Morgen",
+ "badge_tomorrow_long": "⏰ Läuft morgen ab",
+ "badge_days": "⏰ {n} Tage",
+ "badge_expired_ago": "⚠️ Seit {n}T abgel.",
+ "badge_expired": "⛔ Abgelaufen!",
+ "badge_stable": "✅ Stabil",
+ "badge_expiring_short": "⏰ Läuft in {n}T ab",
+ "badge_ok_still": "✅ Noch {n}T",
+ "badge_expires_red": "🔴 In {n}T",
+ "badge_expires_yellow": "🟡 In {n}T",
+ "badge_expired_bare": "⚠️ Abgelaufen",
+ "badge_expires_warn": "⚠️ In {n}T",
+ "badge_days_left": "⏳ ~{n}T übrig",
+ "days_approx": "~{n} Tage",
+ "weeks_approx": "~{n} Wochen",
+ "months_approx": "~{n} Monate",
+ "years_approx": "~{n} Jahre",
+ "expired_today_long": "Heute abgelaufen",
+ "expired_ago_long": "Seit {n} Tagen abgelaufen",
+ "expired_suffix": "— Abgelaufen!",
+ "expired_suffix_ok": "— Abgelaufen (noch ok)",
+ "expired_suffix_warning": "— Abgelaufen (erst prüfen)",
+ "opened_ago_long": "Seit {n} Tagen geöffnet",
+ "opened_today_long": "Heute geöffnet",
+ "opened_suffix": "— Zu lange geöffnet!",
+ "opened_suffix_ok": "— Geöffnet (noch ok)",
+ "opened_suffix_warning": "— Geöffnet (erst prüfen)",
+ "days_compact": "{n}T",
+ "badge_check_soon": "Bald prüfen"
+ },
+ "status": {
+ "ok": "OK",
+ "check": "Prüfen",
+ "discard": "Entsorgen",
+ "tip_freezer_ok": "Im Gefrierschrank: noch sicher (~{n}T Puffer)",
+ "tip_freezer_check": "Seit langem im Gefrierschrank, könnte an Qualität verloren haben. Bald verbrauchen",
+ "tip_freezer_danger": "Zu lange im Gefrierschrank, Gefrierbrand- und Qualitätsverlust-Risiko",
+ "tip_highRisk_check": "Kürzlich abgelaufen, Geruch und Aussehen vor dem Verzehr prüfen",
+ "tip_highRisk_danger": "Verderbliches Produkt abgelaufen: aus Sicherheitsgründen entsorgen",
+ "tip_medRisk_check1": "Aussehen und Geruch vor dem Verzehr prüfen",
+ "tip_medRisk_check2": "Schon eine Weile abgelaufen, vor dem Verzehr gut prüfen",
+ "tip_medRisk_danger": "Zu lange seit dem Ablaufdatum, lieber entsorgen",
+ "tip_lowRisk_ok": "Haltbares Produkt, noch sicher zu verzehren",
+ "tip_lowRisk_check": "Seit über einem Monat abgelaufen, Verpackungsintegrität prüfen",
+ "tip_lowRisk_danger": "Zu lange abgelaufen, besser kein Risiko eingehen"
+ },
+ "toast": {
+ "product_saved": "Produkt gespeichert!",
+ "product_created": "Produkt erstellt!",
+ "product_updated": "✅ Produkt aktualisiert!",
+ "product_removed": "Produkt entfernt",
+ "updated": "Aktualisiert!",
+ "quantity_confirmed": "✓ Menge bestätigt",
+ "added_to_inventory": "✅ {name} hinzugefügt!",
+ "removed_from_list": "✅ {name} von der Liste entfernt!",
+ "removed_from_list_short": "Von der Liste entfernt",
+ "added_to_shopping": "🛒 Zur Einkaufsliste hinzugefügt!",
+ "removed_from_shopping": "🛒 Von der Einkaufsliste entfernt",
+ "finished_to_bring": "🛒 Produkt aufgebraucht → zu Bring! hinzugefügt",
+ "thrown_away": "🗑️ {name} weggeworfen!",
+ "thrown_away_partial": "🗑️ {qty} {unit} von {name} weggeworfen",
+ "finished_all": "📤 {name} aufgebraucht!",
+ "vacuum_sealed": "{name} als vakuumversiegelt gespeichert",
+ "product_finished_confirmed": "✅ Entfernt — wieder hinzufügen, wenn du nachkaufst",
+ "appliance_added": "Gerät hinzugefügt",
+ "item_added": "{name} hinzugefügt"
+ },
+ "antiwaste": {
+ "title": "🌱 Anti-Verschwendungs-Bericht",
+ "grade_label": "Note",
+ "you": "Du",
+ "avg_label": "Ø",
+ "better": "🎉 Du verlierst {diff}% weniger als der {country}!",
+ "worse": "⚠️ Du verlierst mehr als der {country}. Verbesserungspotenzial!",
+ "on_par": "→ Du liegst beim {country}. Du kannst noch besser werden!",
+ "saved_money": "~{amount}/Monat gespart",
+ "saved_meals": "~{n} Mahlzeiten gerettet",
+ "saved_co2": "{n} kg CO₂ eingespart",
+ "trend_title": "Trend (letzte 3 Monate)",
+ "months_ago_2": "-60 Tage",
+ "months_ago_1": "-30 Tage",
+ "this_month": "Jetzt",
+ "country_it": "ital. Durchschnitt",
+ "country_de": "dt. Durchschnitt",
+ "country_en": "US-Durchschnitt",
+ "source": "Quellen: REDUCE, Eurostat, USDA 2021",
+ "live_on": "Live-Daten",
+ "live_off": "Offline",
+ "meals": "Mahlzeiten",
+ "annual_info": "📅 Du ~{you} kg/Jahr · Ø ~{avg} kg/Jahr",
+ "badge_rate": "Verlustquote",
+ "badge_saved_money": "gespart vs Ø",
+ "badge_wasted": "verloren",
+ "badge_better": "weniger als Ø"
+ },
+ "error": {
+ "generic": "Fehler",
+ "network": "Netzwerkfehler",
+ "no_api_key": "API-Schluessel in den Einstellungen konfigurieren",
+ "loading": "Fehler beim Laden des Produkts",
+ "not_found": "Produkt nicht gefunden",
+ "not_found_manual": "Produkt nicht gefunden. Manuell eingeben.",
+ "search": "Suchfehler. Nochmal versuchen.",
+ "search_short": "Suchfehler",
+ "save": "Fehler beim Speichern",
+ "connection": "Verbindungsfehler",
+ "camera": "Kamera nicht verfügbar",
+ "bring_add": "Fehler beim Hinzufügen zu Bring!",
+ "bring_connection": "Bring! Verbindungsfehler",
+ "identification": "Identifikationsfehler",
+ "ai_quota": "KI-Kontingent erschöpft. Bitte in ein paar Minuten erneut versuchen.",
+ "barcode_empty": "Barcode eingeben",
+ "barcode_format": "Barcode darf nur Zahlen enthalten (4-14 Ziffern)",
+ "min_chars": "Mindestens 2 Zeichen eingeben",
+ "not_in_inventory": "Produkt nicht im Bestand",
+ "appliance_exists": "Gerät bereits vorhanden",
+ "already_exists": "Bereits vorhanden",
+ "network_retry": "Verbindungsfehler. Erneut versuchen.",
+ "select_items": "Wähle mindestens ein Produkt aus",
+ "server_offline": "Serververbindung unterbrochen",
+ "server_restored": "Serververbindung wiederhergestellt",
+ "server_retry": "Erneut versuchen",
+ "unknown": "Unbekannter Fehler",
+ "prefix": "Fehler",
+ "no_inventory_entry": "Kein Inventareintrag gefunden",
+ "offline_title": "Keine Verbindung",
+ "offline_subtitle": "Die App kann den Server nicht erreichen. Überprüfe deine WLAN-Verbindung.",
+ "offline_checking": "Verbindung prüfen…",
+ "offline_restored": "Verbindung wiederhergestellt!",
+ "offline_continue": "Im Offline-Modus fortfahren",
+ "offline_reading_cache": "Lese aus lokalem Cache",
+ "offline_ops_pending": "{n} Aktionen ausstehend",
+ "offline_synced": "{n} Aktionen synchronisiert",
+ "offline_ai_disabled": "Offline nicht verfügbar",
+ "offline_cache_ready": "Offline — {n} Produkte im Cache"
+ },
+ "confirm": {
+ "remove_item": "Möchtest du dieses Produkt wirklich aus dem Bestand entfernen?",
+ "kiosk_exit": "Kioskmodus verlassen?",
+ "cancel": "Abbrechen",
+ "proceed": "Bestätigen"
+ },
+ "location": {
+ "dispensa": "Vorratskammer",
+ "frigo": "Kühlschrank",
+ "freezer": "Gefrierschrank"
+ },
+ "edit": {
+ "title": "{name} bearbeiten",
+ "unknown_hint": "Produktname und Informationen eingeben",
+ "label_name": "🏷️ Produktname",
+ "choose_location_title": "Welchen Ort?",
+ "choose_location_hint": "Wähle den zu bearbeitenden Ort:"
},
"screensaver": {
- "label": "Bildschirmschoner aktivieren",
- "card_title": "🌙 Bildschirmschoner",
- "card_hint": "Zeigt nach 5 Minuten Inaktivität eine Uhr mit nützlichen Fakten. Standardmäßig deaktiviert.",
- "timeout_1": "1 Minute",
- "timeout_2": "2 Minuten",
- "timeout_5": "5 Minuten",
- "timeout_10": "10 Minuten",
- "timeout_15": "15 Minuten",
- "timeout_30": "30 Minuten",
- "timeout_60": "1 Stunde",
- "start_after": "⏱️ Starten nach"
+ "recipe_btn": "Rezepte",
+ "scan_btn": "Produkt scannen"
+ },
+ "days": {
+ "mon": "Montag",
+ "tue": "Dienstag",
+ "wed": "Mittwoch",
+ "thu": "Donnerstag",
+ "fri": "Freitag",
+ "sat": "Samstag",
+ "sun": "Sonntag",
+ "mon_short": "Mo",
+ "tue_short": "Di",
+ "wed_short": "Mi",
+ "thu_short": "Do",
+ "fri_short": "Fr",
+ "sat_short": "Sa",
+ "sun_short": "So"
+ },
+ "meal_types": {
+ "lunch": "Mittagessen",
+ "dinner": "Abendessen",
+ "colazione": "Frühstück",
+ "merenda": "Nachmittagssnack",
+ "dolce": "Dessert",
+ "succo": "Fruchtsaft",
+ "pranzo": "Mittagessen",
+ "cena": "Abendessen"
},
"scale": {
- "title": "⚖️ Smart-Waage",
- "hint": "Verbinde eine Bluetooth-Waage über das Android-Gateway, um das Gewicht automatisch auszulesen.",
- "tab": "Smart-Waage",
- "enabled": "✅ Smart-Waage aktivieren",
- "url_label": "🌐 WebSocket-Gateway-URL",
- "url_placeholder": "ws://192.168.1.x:8765",
- "url_hint": "URL der Android-App (gleiches WLAN). z.B.:",
- "test_btn": "🔗 Verbindung testen",
- "download_btn": "📥 Android-Gateway herunterladen (APK)",
- "download_hint": "Android-App als Brücke zwischen BLE-Waage und EverShelf.",
- "download_sub": "Quellcode: evershelf-scale-gateway/ im Projektstamm",
- "live_weight": "Echtzeit-Gewicht",
- "auto_reconnect": "🔁 Verbindung: automatisch",
- "kiosk_title": "📡 BLE-Waage im Kiosk integriert",
- "kiosk_hint": "Die Waage wird direkt vom internen BLE-Gateway des Kiosks verwaltet. Um ein neues Gerät zu koppeln, verwende den Konfigurationsassistenten.",
- "kiosk_reconfigure": "🔄 BLE-Waage neu konfigurieren",
- "ble_protocols": "
🔌 Unterstützte BLE-Protokolle:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — Gewicht, Fett, BMI
- Xiaomi Mi Body Composition Scale 2
- Generisch — automatische Heuristik für 100+ Modelle
"
+ "status_connected": "Waage verbunden",
+ "status_searching": "Gateway verbunden, warte auf Waage…",
+ "status_disconnected": "Waagen-Gateway nicht erreichbar",
+ "status_error": "Verbindungsfehler zum Gateway",
+ "not_connected": "Waagen-Gateway nicht verbunden",
+ "read_btn": "⚖️ Von Waage lesen",
+ "reading_title": "Waage lesen",
+ "place_on_scale": "Produkt auf die Waage legen…",
+ "waiting_stable": "Das Gewicht wird automatisch erfasst, wenn die Messung stabil ist.",
+ "no_url": "Gateway-URL eingeben",
+ "testing": "⏳ Verbindung wird getestet…",
+ "connected_ok": "Gateway-Verbindung erfolgreich!",
+ "timeout": "Timeout: keine Antwort vom Gateway",
+ "error_connect": "Verbindung zum Gateway nicht möglich",
+ "tab": "Smart-Waage",
+ "low_weight": "Gewicht < 10 g · manuell eingeben\n(Auto-Erkennung erfordert mind. 10 g)",
+ "density_hint": "(Dichte {density} g/ml)",
+ "ml_hint": "(wird in ml umgerechnet)",
+ "weight_detected": "Gewicht erkannt — 10s Stabilität abwarten…",
+ "weight_too_low": "Gewicht zu niedrig — warten…",
+ "stable": "✓ Stabil",
+ "auto_confirm": "✅ {val} {unit} — Auto-Bestätigung in 5s (tippen zum Abbrechen)",
+ "cancelled_replace": "Abgebrochen — lege die Zutat wieder auf die Waage, um fortzufahren"
+ },
+ "prediction": {
+ "expected_qty": "Erwartet: {expected} {unit}",
+ "actual_qty": "Aktuell: {actual} {unit}",
+ "check_suggestion": "Überprüfe oder wiege die Restmenge"
+ },
+ "date": {
+ "today": "📅 Heute",
+ "yesterday": "📅 Gestern"
+ },
+ "scanner": {
+ "title_barcode": "🔖 Barcode scannen",
+ "barcode_hint": "Produktbarcode einrahmen",
+ "barcode_manual_placeholder": "Oder manuell eingeben...",
+ "barcode_use_btn": "✅ Diesen Code verwenden",
+ "ai_identifying": "🤖 Produkt wird erkannt...",
+ "ai_analyzing": "🤖 KI-Analyse läuft...",
+ "product_label_hint": "Produktetikett einrahmen",
+ "expiry_label_hint": "Ablaufdatum auf dem Produkt einrahmen",
+ "capture_btn": "📸 Aufnehmen",
+ "capture_photo_btn": "📸 Foto aufnehmen",
+ "retake_btn": "🔄 Erneut aufnehmen",
+ "camera_error_hint": "Stelle sicher, dass du HTTPS verwendest und Kameraberechtigungen erteilt hast.
Du kannst den Barcode manuell eingeben oder die KI-Identifikation verwenden.",
+ "no_barcode": "Kein Barcode",
+ "save_new_btn": "🆕 Keines davon — als neu speichern",
+ "expiry_found": "Datum gefunden",
+ "expiry_read_fail": "Datum konnte nicht gelesen werden.",
+ "expiry_raw_label": "Erkannt"
+ },
+ "lowstock": {
+ "title": "⚠️ Wird knapp!",
+ "message": "{name} wird knapp — nur noch {qty} übrig.",
+ "question": "Möchtest du es zur Einkaufsliste hinzufügen?",
+ "yes": "🛒 Ja, zu Bring! hinzufügen",
+ "no": "Nein, passt für jetzt"
+ },
+ "move": {
+ "title": "📦 Den Rest bewegen?",
+ "question": "Möchtest du {thing} von {name} an einen anderen Ort bewegen?",
+ "question_short": "Möchtest du {thing} an einen anderen Ort bewegen?",
+ "thing_opened": "die offene Packung",
+ "thing_rest": "den Rest",
+ "stay_btn": "Nein, bleibt in {location}",
+ "moved_toast": "📦 Offene Packung bewegt nach {location}",
+ "vacuum_restore": "Vakuum wiederherstellen",
+ "vacuum_seal_rest": "Rest vakuumieren"
+ },
+ "nova": {
+ "1": "Unverarbeitet",
+ "2": "Kulinarische Zutat",
+ "3": "Verarbeitet",
+ "4": "Hochverarbeitet"
+ },
+ "meal_plan_types": {
+ "pasta": "Pasta",
+ "riso": "Reis",
+ "carne": "Fleisch",
+ "pesce": "Fisch",
+ "legumi": "Hülsenfrüchte",
+ "uova": "Eier",
+ "formaggio": "Käse",
+ "pizza": "Pizza",
+ "affettati": "Aufschnitt",
+ "verdure": "Gemüse",
+ "zuppa": "Suppe",
+ "insalata": "Salat",
+ "pane": "Brot/Sandwich",
+ "dolce": "Dessert",
+ "libero": "Frei"
+ },
+ "meal_sub": {
+ "dolce_torta": "Kuchen",
+ "dolce_crema": "Creme / Pudding",
+ "dolce_crumble": "Crumble / Tarte",
+ "dolce_biscotti": "Kekse / Gebäck",
+ "dolce_frutta": "Fruchtdessert",
+ "succo_dolce": "Süß / Fruchtig",
+ "succo_energizzante": "Energetisierend",
+ "succo_detox": "Detox / Grün",
+ "succo_rinfrescante": "Erfrischend",
+ "succo_vitaminico": "Vitamin / Zitrus"
+ },
+ "meal_plan": {
+ "reset_success": "Wochenplan zurückgesetzt",
+ "not_available": "nicht im Vorrat verfügbar",
+ "suggested_by": "vom Wochenplan vorgeschlagen"
+ },
+ "nutrition": {
+ "title": "🥗 Ernährungsanalyse",
+ "score_excellent": "😄 Ausgezeichnet",
+ "score_good": "🙂 Gut",
+ "score_improve": "😬 Verbesserbar",
+ "label_health": "🌿 Gesundheit",
+ "label_variety": "🎨 Vielfalt",
+ "label_fresh": "❄️ Frisch",
+ "source": "Basierend auf {n} Produkten in deiner Vorratskammer · EverShelf",
+ "products_count": "Produkte",
+ "today_title": "🥗 Deine Vorratskammer heute",
+ "products_n": "{n} Produkte"
+ },
+ "facts": {
+ "greeting_morning": "Guten Morgen",
+ "greeting_afternoon": "Guten Tag",
+ "greeting_evening": "Guten Abend",
+ "pantry_waiting": "{greeting}! Deine Vorratskammer wartet.",
+ "expired_one": "Du hast 1 abgelaufenes Produkt in der Vorratskammer. Bitte überprüfen!",
+ "expired_many": "Du hast {n} abgelaufene Produkte in der Vorratskammer. Bitte überprüfen!",
+ "expired_list": "Abgelaufene Produkte: {names}",
+ "expired_list_more": "und {n} weitere",
+ "freezer_expired_ok": "{name} ist abgelaufen, aber im Gefrierschrank könnte es noch gut sein! Überprüfe es.",
+ "freezer_expired_old": "{name} im Gefrierschrank ist zu lange abgelaufen. Besser wegwerfen.",
+ "fridge_expired_one": "Du hast 1 abgelaufenes Produkt im Kühlschrank!",
+ "fridge_expired_many": "Du hast {n} abgelaufene Produkte im Kühlschrank!",
+ "expiring_today": "{name} läuft heute ab! Sofort verbrauchen.",
+ "expiring_tomorrow": "{name} läuft morgen ab. Denk daran!",
+ "expiring_days": "{name} läuft in {days} Tagen ab.",
+ "expiring_many": "Du hast {n} Produkte, die bald ablaufen.",
+ "expiring_this_week": "{n} Produkte laufen diese Woche ab. Plane deine Mahlzeiten entsprechend!",
+ "expiring_item_loc": "{name} ({loc}) läuft in {days} {dayslabel} ab.",
+ "expiring_this_month": "{n} Produkte laufen diesen Monat ab.",
+ "shopping_add": "Zur Liste: {names} 🛒",
+ "shopping_more": "und {n} weitere",
+ "shopping_empty": "Einkaufsliste leer. Alles aufgefüllt! ✅",
+ "in_fridge": "Im Kühlschrank: {name}.",
+ "in_freezer": "Im Gefrierschrank: {name}. Vergiss es nicht!",
+ "top_category": "Häufigste Kategorie: {icon} {cat} mit {n} Produkten.",
+ "cat_meat": "Du hast {n} Fleischprodukte. 🥩",
+ "cat_dairy": "Du hast {n} Milchprodukte zu Hause. 🥛",
+ "cat_veggies": "Du hast {n} Gemüsesorten. Super für die Gesundheit! 🥬",
+ "cat_fruit": "Du hast {n} Obstsorten. 🍎",
+ "cat_drinks": "Du hast {n} Getränke verfügbar. 🥤",
+ "cat_frozen": "Du hast {n} Tiefkühlprodukte. ❄️",
+ "cat_pasta": "Du hast {n} Nudelsorten. 🍝 Wie wäre es mit einer Carbonara?",
+ "cat_canned": "Du hast {n} Konserven in der Vorratskammer. 🥫",
+ "cat_snacks": "Du hast {n} Snacks. Widerstand leisten! 🍪",
+ "cat_condiments": "Du hast {n} Gewürze zur Verfügung. 🧂",
+ "item_random": "Wusstest du? Du hast {name} in {loc}.",
+ "item_qty": "{name}: du hast {qty}.",
+ "no_expiry_count": "{n} Produkte haben kein Ablaufdatum.",
+ "furthest_expiry": "Das Produkt mit dem spätesten Ablaufdatum ist {name}: {months} Monate.",
+ "high_qty": "Du hast einen guten Vorrat von {name}: {qty}!",
+ "low_qty_item": "{name} geht zur Neige. Auf die Einkaufsliste?",
+ "low_qty_count": "{n} Produkte sind fast aufgebraucht.",
+ "morning_bread": "Guten Morgen! Du hast Brot für das Frühstück. 🍞",
+ "morning_milk": "Gibt es Milch im Kühlschrank für den Cappuccino? ☕🥛",
+ "morning_fruit": "Guten Morgen! Frisches Obst ist ein guter Start. 🍎",
+ "noon_pasta": "Mittagszeit… Wie wäre es mit Pasta? 🍝",
+ "noon_salad": "Ein frischer Salat zum Mittagessen? Du hast {n} Gemüsesorten! 🥗",
+ "evening_meat": "Zum Abendessen könntest du das Fleisch verwenden. 🥩",
+ "evening_fish": "Wie wäre es mit Fisch zum Abendessen? 🐟",
+ "evening_expiring": "Du hast {n} Produkte, die diese Woche ablaufen — heute Abend verwenden!",
+ "night_reminder": "Gute Nacht! Denk morgen daran zu verwenden: {names}.",
+ "weekly_balance": "Wochenbilanz: +{in} hinzugefügt, −{out} verbraucht.",
+ "weekly_added": "Du hast diese Woche {n} Produkte hinzugefügt.",
+ "weekly_consumed": "Du hast diese Woche {n} Produkte verbraucht. Gut gemacht!",
+ "tip_freezer": "💡 Tiefkühlprodukte halten viel länger als das Ablaufdatum.",
+ "tip_bread": "💡 Gefrorenes Brot behält seinen Duft wochenlang.",
+ "tip_fifo": "💡 Um Verschwendung zu vermeiden, zuerst Produkte mit nahem Ablaufdatum verwenden (FIFO).",
+ "tip_meat": "💡 Fleisch im Gefrierschrank hält bis zu 6 Monate problemlos.",
+ "tip_no_refreeze": "💡 Niemals ein aufgetautes Lebensmittel wieder einfrieren. Sofort zubereiten!",
+ "tip_fridge": "💡 Ein ordentlicher Kühlschrank spart Zeit und Geld.",
+ "tip_canned": "💡 Geöffnete Konserven in den Kühlschrank und in wenigen Tagen verbrauchen.",
+ "top_brand": "Die häufigste Marke in deiner Vorratskammer ist {brand} mit {n} Produkten.",
+ "combo_pasta": "Du hast Pasta und Gewürze: bereit für ein Erstgericht! 🍝",
+ "combo_sandwich": "Brot und Fleisch: ein schnelles Sandwich ist immer eine gute Idee! 🥪",
+ "combo_balanced": "Gemüse und Fleisch: du hast alles für eine ausgewogene Mahlzeit! 🥗🥩",
+ "pantry_empty": "Die Vorratskammer ist leer! Zeit zum Einkaufen. 🛒",
+ "pantry_empty_scan": "Keine Produkte erfasst. Scanne etwas um zu beginnen!",
+ "location_distribution": "Verteilung: {parts}",
+ "day": "Tag",
+ "days": "Tage"
+ },
+ "kiosk_session": {
+ "first_item": "Erstes Produkt: {name}!",
+ "items_two_four": "{n} Artikel — Trägheit überwinden 🚀",
+ "items_five_nine": "{n} Artikel — super Tempo! 💪",
+ "items_ten_twenty": "{n} Artikel — fast Rekord 🏆",
+ "items_twenty_plus": "{n} Artikel — epischer Einkauf! 🛒🔥",
+ "duplicates_one": "1 Duplikat (gleiches Produkt zweimal)",
+ "duplicates_many": "{n} Duplikate (mehrfach genommen)",
+ "top_category": "Top-Kategorie: {cat} ({count}×)",
+ "items_fallback": "{n} Artikel hinzugefügt"
},
"kiosk": {
- "hint": "Verwandeln Sie ein Android-Tablet in ein EverShelf-Panel mit integriertem BLE-Waagen-Gateway.",
- "download_btn": "📥 EverShelf Kiosk herunterladen (APK)",
- "download_sub": "Vollbild-Kioskmodus + integriertes Waagen-Gateway. Quellcode: evershelf-kiosk/",
- "native_title": "Kiosk-Konfiguration",
- "native_hint": "Server-URL, BLE-Waage, Bildschirmschoner und Einrichtungsassistent.",
- "native_btn": "Kiosk-Konfiguration öffnen",
- "native_tap_hint": "Zahnrad oben rechts antippen",
- "native_update_hint": "Kiosk-App aktualisieren, um diese Funktion zu nutzen",
- "update_title": "Kiosk-Aktualisierung",
- "check_updates_btn": "🔍 Nach Updates suchen",
- "needs_update": "⚠️ Das installierte Kiosk unterstützt diese Funktion nicht. Aktualisiere die Kiosk-App, um sie zu aktivieren."
+ "check_btn": "🔍 Nach Updates suchen",
+ "checking": "⏳ Prüfe…",
+ "error_check": "Fehler bei der Update-Prüfung",
+ "error_start_install": "Fehler beim Starten der Installation",
+ "version_installed": "Installiert: {v}",
+ "update_available": "⬆️ Neue Version verfügbar:
{latest} (installiert: {current})",
+ "up_to_date": "✅ Du bist auf dem neuesten Stand — Version
{v}",
+ "too_old": "⚠️ Der installierte Kiosk ist zu alt für die automatische Update-Prüfung.
Drücke den Knopf unten, um die neue Version direkt herunterzuladen.",
+ "manual_install": "⚠️ Dieser Kiosk unterstützt keine automatische Installation.
Manuelle Vorgehensweise:1. Kiosk verlassen (✕ oben links)
2. EverShelf Kiosk App deinstallieren
3. Neue APK von GitHub herunterladen und installieren:",
+ "starting_download": "⏳ Download startet…",
+ "install_btn": "⬇️ Update installieren",
+ "exit_title": "Kiosk beenden",
+ "refresh_title": "Seite aktualisieren"
},
- "saved": "✅ Konfiguration gespeichert!",
- "saved_local": "✅ Konfiguration lokal gespeichert",
- "saved_local_error": "⚠️ Lokal gespeichert, Serverfehler: {error}",
- "theme": {
- "title": "🌙 Erscheinungsbild",
- "hint": "Wähle das Interface-Design.",
- "label": "🌙 Design",
- "off": "☀️ Hell",
- "on": "🌙 Dunkel",
- "auto": "🔄 Automatisch (Tageszeit)"
+ "update": {
+ "new_version": "Neue Version",
+ "btn": "Aktualisieren"
},
- "zerowaste": {
- "card_title": "♻️ Zero-Waste-Tipps",
- "card_hint": "Zeige während des Kochens Tipps zur Wiederverwendung von Abfällen (Schalen, Kochwasser usw.). Standardmäßig deaktiviert.",
- "label": "Tipps beim Kochen anzeigen"
+ "gemini": {
+ "chat_title": "Mit Gemini chatten",
+ "not_configured": "🤖 Gemini nicht konfiguriert — GEMINI_API_KEY in den Einstellungen setzen"
},
- "backup": {
- "tab": "Backup",
- "local_title": "Lokales Backup",
- "local_hint": "Täglicher Datenbank-Snapshot. Konfiguriere, wie viele Tage Backups aufbewahrt werden.",
- "enabled": "Tägliches automatisches Backup aktivieren",
- "retention_days": "Aufbewahrung (Tage)",
- "retention_info": "Backups werden aufbewahrt für",
- "backup_now": "Jetzt sichern",
- "backing_up": "Sicherung läuft…",
- "backed_up": "Sicherung abgeschlossen",
- "backup_error": "Sicherungsfehler",
- "last_backup": "Letztes Backup",
- "no_backup_yet": "Noch kein Backup erstellt",
- "list_empty": "Keine Backups verfügbar",
- "restore_btn": "Wiederherstellen",
- "restore_confirm": "Backup wiederherstellen",
- "delete_btn": "Löschen",
- "delete_confirm": "Backup löschen",
- "gdrive_title": "Google Drive",
- "gdrive_hint": "Backups automatisch via OAuth 2.0 auf Google Drive hochladen. Keine externen Bibliotheken erforderlich.",
- "gdrive_enabled": "Google Drive Backup aktivieren",
- "gdrive_folder_id": "Drive-Ordner-ID",
- "gdrive_folder_id_hint": "Kopiere die ID aus der Drive-Ordner-URL: …/folders/
ID",
- "gdrive_retention_days": "Drive-Aufbewahrung (Tage, 0=alles behalten)",
- "gdrive_test": "Verbindung testen",
- "gdrive_ok": "Verbindung erfolgreich!",
- "gdrive_error": "Verbindung fehlgeschlagen",
- "gdrive_push_now": "Jetzt auf Drive hochladen",
- "gdrive_pushing": "Wird hochgeladen…",
- "gdrive_pushed": "Auf Drive hochgeladen",
- "gdrive_wizard_hint": "Optional: täglich automatisch via OAuth 2.0 auf Google Drive sichern.",
- "gdrive_skip": "Überspringen — später in Einstellungen konfigurieren",
- "gdrive_client_id": "Client-ID",
- "gdrive_client_secret": "Client-Secret",
- "gdrive_redirect_uri_hint": "Füge
http://localhost als autorisierten Weiterleitungs-URI in der Google Cloud Console hinzu. Funktioniert auf jedem Server, auch ohne öffentliche Domain.",
- "gdrive_code_title": "Autorisierungs-URL oder Code einfügen",
- "gdrive_code_hint": "Nach der Autorisierung öffnet der Browser http://localhost und zeigt möglicherweise einen Verbindungsfehler — das ist normal. Kopiere die URL aus der Adressleiste (z.B.
http://localhost/?code=4%2F0A...) und füge sie hier ein.",
- "gdrive_code_submit": "Bestätigen",
- "gdrive_code_empty": "Bitte zuerst die URL oder den Autorisierungscode einfügen",
- "gdrive_redirect_uri_label": "Redirect-URI (in Google Cloud Console eintragen):",
- "gdrive_oauth_authorize": "Mit Google autorisieren",
- "gdrive_oauth_authorized": "Autorisiert",
- "gdrive_oauth_not_authorized": "Noch nicht autorisiert",
- "gdrive_oauth_window_opened": "Browserfenster geöffnet — autorisieren und zurückkehren",
- "gdrive_oauth_how_to": "OAuth 2.0 einrichten (Schritt für Schritt)",
- "gdrive_oauth_steps": "
Gehe zu console.cloud.google.com und wähle dein ProjektAktiviere die Google Drive API: APIs & Dienste → APIs aktivieren → Google Drive APIGehe zu APIs & Dienste → Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-IDAnwendungstyp: Webanwendung; füge die unten angezeigte URL als Autorisierter Weiterleitungs-URI hinzuKopiere Client-ID und Client-Secret in die Felder oben und speichereKlicke auf Mit Google autorisieren: melde dich an und erteile den ZugriffDas Fenster schließt sich automatisch und Backups sind bereit"
+ "appliances": {
+ "empty": "Kein Haushaltsgerät hinzugefügt"
},
- "info": {
- "tab": "Info",
- "ai_title": "Gemini AI — Token-Nutzung",
- "ai_hint": "Monatlicher Verbrauch und geschätzte Kosten für den aktuellen API-Schlüssel.",
- "loading": "Laden…",
- "total_tokens": "Token gesamt",
- "est_cost": "Gesch. Kosten",
- "input_tok": "Eingabe-Token",
- "output_tok": "Ausgabe-Token",
- "ai_calls": "Aufrufe",
- "by_action": "Aufschlüsselung nach Funktion",
- "by_model": "Aufschlüsselung nach Modell",
- "pricing_note": "Gemini Referenzpreise: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.",
- "system_title": "System",
- "db_size": "Datenbank",
- "log_size": "Protokolle",
- "log_level": "Log-Level",
- "ai_overview": "KI-Nutzungsübersicht, Inventar und Systemstatus",
- "calls_unit": "Aufrufe",
- "inv_title": "Inventar",
- "inv_active": "Aktiv",
- "inv_products": "Produkte gesamt",
- "inv_expiring": "Ablaufend (7T)",
- "inv_expired": "Abgelaufen",
- "inv_finished": "Leer",
- "act_title": "Monatliche Aktivität",
- "act_tx_month": "Bewegungen",
- "act_restock": "Einkäufe",
- "act_use": "Verbrauch",
- "act_new_products": "Neue Produkte",
- "act_tx_year": "Jährl. Bewegungen",
- "price_cache": "Preiscache",
- "cache_entries": "Produkte",
- "last_backup": "Letztes Backup",
- "bring_days": "Token läuft in {n} Tagen ab",
- "bring_expired": "Token abgelaufen",
- "year_label": "Jahr {year}",
- "currency_title": "Währung",
- "currency_hint": "Die Währung, die für alle Kosten und Preise in der App verwendet wird."
+ "about": {
+ "title": "Über",
+ "version": "Version",
+ "report_bug": "Fehler melden",
+ "report_bug_hint": "Etwas funktioniert nicht? Sende uns direkt aus der App eine Meldung.",
+ "report_bug_modal_title": "Fehler melden",
+ "report_type_bug": "Fehler",
+ "report_type_feature": "Funktion",
+ "report_type_question": "Frage",
+ "report_field_title": "Titel",
+ "report_field_title_ph": "Kurze Beschreibung des Problems",
+ "report_field_desc": "Beschreibung",
+ "report_field_desc_ph": "Problem detailliert beschreiben…",
+ "report_field_steps": "Schritte zum Reproduzieren (optional)",
+ "report_field_steps_ph": "1. Gehe zu…\n2. Tippe auf…\n3. Fehler erscheint…",
+ "report_auto_info": "Automatisch beigefügt: Version {version}, Sprache {lang}.",
+ "report_send_btn": "Bericht senden",
+ "report_bug_sending": "Wird gesendet…",
+ "report_bug_sent": "Bericht gesendet — danke!",
+ "report_bug_error": "Bericht konnte nicht gesendet werden. Verbindung prüfen.",
+ "changelog": "Changelog",
+ "github": "GitHub-Repository"
},
- "tab_general": "Allgemein",
- "shopping": {
- "tab": "Einkaufsliste",
- "title": "Einkaufsliste",
- "hint": "Konfiguriere die integrierte Einkaufsliste oder verbinde Bring!.",
- "enable_label": "Einkaufsliste aktivieren",
- "mode_label": "Anbieter",
- "mode_internal": "Intern (ohne Bring!)",
- "mode_bring": "Bring! (externe App)",
- "bring_section_title": "Bring!-Konfiguration",
- "ai_section_title": "KI-Unterstützung",
- "smart_suggestions_label": "KI-Vorschläge",
- "forecast_label": "Prognose für bald leere Produkte",
- "auto_add_label": "Automatisch hinzufügen wenn",
- "auto_add_suffix": "im Lager verbleibend (0 = nur wenn leer)"
+ "export": {
+ "title": "Inventar exportieren",
+ "hint": "Lade das aktuelle Inventar als CSV herunter oder öffne die druckfertige Version (PDF).",
+ "btn_csv": "CSV herunterladen",
+ "btn_pdf": "PDF / Drucken",
+ "btn_title": "Exportieren"
},
- "ha": {
- "tab": "Home Assistant",
- "title": "Home Assistant",
- "hint": "Verbinde EverShelf mit Home Assistant für Automationen, Push-Benachrichtigungen und REST-Sensoren.",
- "enabled": "Home Assistant-Integration aktivieren",
- "connection_title": "Verbindung",
- "url_label": "Home Assistant URL",
- "url_placeholder": "http://192.168.1.50:8123",
- "url_hint": "Basis-URL deiner Home Assistant-Instanz (z.B. http://homeassistant.local:8123).",
- "token_label": "Long-Lived Access Token",
- "token_hint": "Erstelle unter HA-Profil → Sicherheit → Langlebige Zugangstoken.",
- "token_placeholder": "eyJhbGci...",
- "token_saved": "Token gespeichert (aus Sicherheitsgründen verborgen)",
- "test_btn": "Verbindung testen",
- "test_ok": "Verbunden mit {version}",
- "test_fail": "Verbindung fehlgeschlagen: {error}",
- "test_bad_token": "HA erreichbar, aber Token ist ungültig",
- "testing": "Teste…",
- "error_no_url": "Bitte zuerst die Home Assistant URL eingeben.",
- "tts_title": "TTS auf Smart Speaker",
- "tts_hint": "Rezeptschritte auf einem Home Assistant Media Player vorlesen.",
- "tts_entity_label": "Media Player Entity ID",
- "tts_entity_placeholder": "media_player.wohnzimmer",
- "tts_entity_hint": "Entity-ID des HA-Media-Players. Zu finden unter HA: Entwicklertools → Zustände.",
- "tts_platform_label": "TTS-Plattform",
- "tts_platform_speak": "tts.speak (empfohlen)",
- "tts_platform_notify": "notify.* (Benachrichtigungsdienst)",
- "tts_apply_btn": "HA-Voreinstellung auf TTS-Tab anwenden",
- "tts_apply_hint": "Füllt den TTS-Tab mit der Home Assistant URL und dem Token aus.",
- "tts_preset_applied": "HA-Voreinstellung auf TTS-Tab angewendet.",
- "webhook_title": "Webhook-Automationen",
- "webhook_hint": "Sende Daten an Home Assistant, wenn Ereignisse in der Vorratskammer auftreten.",
- "webhook_id_label": "Webhook-ID",
- "webhook_id_placeholder": "evershelf_webhook_abc123",
- "webhook_id_hint": "ID des in HA erstellten Webhooks. Kopiere aus: HA → Einstellungen → Automationen → Erstellen → Webhook-Auslöser.",
- "webhook_events_label": "Benachrichtige bei diesen Ereignissen",
- "event_expiry": "Ablaufende Produkte (täglich)",
- "event_shopping": "Artikel zur Einkaufsliste hinzugefügt",
- "event_stock": "Lagerbestand aktualisiert",
- "expiry_days_label": "Ablaufwarnung im Voraus (Tage)",
- "expiry_days_hint": "Sende die Ablaufwarnung N Tage vor dem Ablaufdatum.",
- "webhook_help": "In HA: Einstellungen → Automationen → Automation erstellen → Auslöser: Webhook → ID kopieren.",
- "notify_title": "Push-Benachrichtigungen",
- "notify_hint": "Sende Push-Benachrichtigungen über einen Home Assistant notify-Dienst.",
- "notify_service_label": "Notify-Dienst",
- "notify_service_placeholder": "notify.mobile_app_mein_handy",
- "notify_service_hint": "Name des HA-notify-Dienstes (z.B. notify.mobile_app_phone). Leer lassen zum Deaktivieren.",
- "sensor_title": "REST-Sensoren",
- "sensor_hint": "Zur configuration.yaml hinzufügen, um EverShelf-Sensoren in Home Assistant zu erstellen.",
- "sensor_copy_btn": "YAML kopieren",
- "sensor_copied": "YAML in die Zwischenablage kopiert!",
- "save_btn": "HA-Einstellungen speichern",
- "ha_hint": "Wenn du Home Assistant verwendest, nutze den Home Assistant-Tab für TTS, Webhooks und Sensoren."
+ "startup": {
+ "connecting": "Serververbindung wird hergestellt...",
+ "check_php_memory": "PHP-Speicher",
+ "check_php_timeout": "PHP-Timeout",
+ "check_php_upload": "PHP-Upload",
+ "check_data_dir": "Datenverzeichnis",
+ "check_rate_limits": "Rate-Limits-Verzeichnis",
+ "check_backups": "Backup-Verzeichnis",
+ "check_write_test": "Schreibtest",
+ "check_disk_space": "Speicherplatz",
+ "check_db_legacy": "Legacy-DB (dispensa.db)",
+ "check_db_connect": "Datenbankverbindung",
+ "check_db_tables": "Datenbanktabellen",
+ "check_db_integrity": "Datenbankintegrität",
+ "check_db_wal": "WAL-Modus",
+ "check_db_size": "Datenbankgröße",
+ "check_db_rows": "Inventardaten",
+ "check_env": ".env-Datei",
+ "check_gemini": "Gemini-AI-Schlüssel",
+ "check_bring_creds": "Bring!-Anmeldedaten",
+ "check_bring_token": "Bring!-Token",
+ "check_tts": "Text-to-Speech-URL",
+ "check_scale": "Waagen-Gateway",
+ "check_curl_ssl": "cURL-SSL",
+ "check_internet": "Internetverbindung",
+ "fresh_install": "Neuinstallation",
+ "warnings_found": "Warnungen",
+ "all_ok": "System OK",
+ "critical_error_short": "Kritischer Fehler",
+ "critical_error": "Kritischer Fehler: Die App kann nicht gestartet werden. Prüfe die Serverlogs.",
+ "critical_error_intro": "Die App kann aufgrund folgender Probleme nicht gestartet werden:",
+ "error_network": "Server nicht erreichbar.",
+ "error_network_detail": "Der Browser kann den PHP-Server nicht erreichen.\n\nMögliche Ursachen:\n• Apache/PHP-Server läuft nicht\n• Netzwerk- oder Firewall-Problem\n• Falsche App-URL\n\nBitte Server starten und erneut versuchen.",
+ "retry": "Erneut versuchen",
+ "syncing_local": "Lokale Daten synchronisieren...",
+ "sync_done": "Lokale Daten aktualisiert"
}
- },
- "expiry": {
- "today": "HEUTE",
- "tomorrow": "Morgen",
- "days": "{days} Tage",
- "expired_days": "Seit {days}T",
- "expired_yesterday": "Seit gestern",
- "expired_today": "Heute",
- "badge_today": "⚠️ Läuft heute ab!",
- "badge_tomorrow": "⏰ Morgen",
- "badge_tomorrow_long": "⏰ Läuft morgen ab",
- "badge_days": "⏰ {n} Tage",
- "badge_expired_ago": "⚠️ Seit {n}T abgel.",
- "badge_expired": "⛔ Abgelaufen!",
- "badge_stable": "✅ Stabil",
- "badge_expiring_short": "⏰ Läuft in {n}T ab",
- "badge_ok_still": "✅ Noch {n}T",
- "badge_expires_red": "🔴 In {n}T",
- "badge_expires_yellow": "🟡 In {n}T",
- "badge_expired_bare": "⚠️ Abgelaufen",
- "badge_expires_warn": "⚠️ In {n}T",
- "badge_days_left": "⏳ ~{n}T übrig",
- "days_approx": "~{n} Tage",
- "weeks_approx": "~{n} Wochen",
- "months_approx": "~{n} Monate",
- "years_approx": "~{n} Jahre",
- "expired_today_long": "Heute abgelaufen",
- "expired_ago_long": "Seit {n} Tagen abgelaufen",
- "expired_suffix": "— Abgelaufen!",
- "expired_suffix_ok": "— Abgelaufen (noch ok)",
- "expired_suffix_warning": "— Abgelaufen (erst prüfen)",
- "opened_ago_long": "Seit {n} Tagen geöffnet",
- "opened_today_long": "Heute geöffnet",
- "opened_suffix": "— Zu lange geöffnet!",
- "opened_suffix_ok": "— Geöffnet (noch ok)",
- "opened_suffix_warning": "— Geöffnet (erst prüfen)",
- "days_compact": "{n}T",
- "badge_check_soon": "Bald prüfen"
- },
- "status": {
- "ok": "OK",
- "check": "Prüfen",
- "discard": "Entsorgen",
- "tip_freezer_ok": "Im Gefrierschrank: noch sicher (~{n}T Puffer)",
- "tip_freezer_check": "Seit langem im Gefrierschrank, könnte an Qualität verloren haben. Bald verbrauchen",
- "tip_freezer_danger": "Zu lange im Gefrierschrank, Gefrierbrand- und Qualitätsverlust-Risiko",
- "tip_highRisk_check": "Kürzlich abgelaufen, Geruch und Aussehen vor dem Verzehr prüfen",
- "tip_highRisk_danger": "Verderbliches Produkt abgelaufen: aus Sicherheitsgründen entsorgen",
- "tip_medRisk_check1": "Aussehen und Geruch vor dem Verzehr prüfen",
- "tip_medRisk_check2": "Schon eine Weile abgelaufen, vor dem Verzehr gut prüfen",
- "tip_medRisk_danger": "Zu lange seit dem Ablaufdatum, lieber entsorgen",
- "tip_lowRisk_ok": "Haltbares Produkt, noch sicher zu verzehren",
- "tip_lowRisk_check": "Seit über einem Monat abgelaufen, Verpackungsintegrität prüfen",
- "tip_lowRisk_danger": "Zu lange abgelaufen, besser kein Risiko eingehen"
- },
- "toast": {
- "product_saved": "Produkt gespeichert!",
- "product_created": "Produkt erstellt!",
- "product_updated": "✅ Produkt aktualisiert!",
- "product_removed": "Produkt entfernt",
- "updated": "Aktualisiert!",
- "quantity_confirmed": "✓ Menge bestätigt",
- "added_to_inventory": "✅ {name} hinzugefügt!",
- "removed_from_list": "✅ {name} von der Liste entfernt!",
- "removed_from_list_short": "Von der Liste entfernt",
- "added_to_shopping": "🛒 Zur Einkaufsliste hinzugefügt!",
- "removed_from_shopping": "🛒 Von der Einkaufsliste entfernt",
- "finished_to_bring": "🛒 Produkt aufgebraucht → zu Bring! hinzugefügt",
- "thrown_away": "🗑️ {name} weggeworfen!",
- "thrown_away_partial": "🗑️ {qty} {unit} von {name} weggeworfen",
- "finished_all": "📤 {name} aufgebraucht!",
- "vacuum_sealed": "{name} als vakuumversiegelt gespeichert",
- "product_finished_confirmed": "✅ Entfernt — wieder hinzufügen, wenn du nachkaufst",
- "appliance_added": "Gerät hinzugefügt",
- "item_added": "{name} hinzugefügt"
- },
- "antiwaste": {
- "title": "🌱 Anti-Verschwendungs-Bericht",
- "grade_label": "Note",
- "you": "Du",
- "avg_label": "Ø",
- "better": "🎉 Du verlierst {diff}% weniger als der {country}!",
- "worse": "⚠️ Du verlierst mehr als der {country}. Verbesserungspotenzial!",
- "on_par": "→ Du liegst beim {country}. Du kannst noch besser werden!",
- "saved_money": "~{amount}/Monat gespart",
- "saved_meals": "~{n} Mahlzeiten gerettet",
- "saved_co2": "{n} kg CO₂ eingespart",
- "trend_title": "Trend (letzte 3 Monate)",
- "months_ago_2": "-60 Tage",
- "months_ago_1": "-30 Tage",
- "this_month": "Jetzt",
- "country_it": "ital. Durchschnitt",
- "country_de": "dt. Durchschnitt",
- "country_en": "US-Durchschnitt",
- "source": "Quellen: REDUCE, Eurostat, USDA 2021",
- "live_on": "Live-Daten",
- "live_off": "Offline",
- "meals": "Mahlzeiten",
- "annual_info": "📅 Du ~{you} kg/Jahr · Ø ~{avg} kg/Jahr",
- "badge_rate": "Verlustquote",
- "badge_saved_money": "gespart vs Ø",
- "badge_wasted": "verloren",
- "badge_better": "weniger als Ø"
- },
- "error": {
- "generic": "Fehler",
- "network": "Netzwerkfehler",
- "no_api_key": "API-Schluessel in den Einstellungen konfigurieren",
- "loading": "Fehler beim Laden des Produkts",
- "not_found": "Produkt nicht gefunden",
- "not_found_manual": "Produkt nicht gefunden. Manuell eingeben.",
- "search": "Suchfehler. Nochmal versuchen.",
- "search_short": "Suchfehler",
- "save": "Fehler beim Speichern",
- "connection": "Verbindungsfehler",
- "camera": "Kamera nicht verfügbar",
- "bring_add": "Fehler beim Hinzufügen zu Bring!",
- "bring_connection": "Bring! Verbindungsfehler",
- "identification": "Identifikationsfehler",
- "ai_quota": "KI-Kontingent erschöpft. Bitte in ein paar Minuten erneut versuchen.",
- "barcode_empty": "Barcode eingeben",
- "barcode_format": "Barcode darf nur Zahlen enthalten (4-14 Ziffern)",
- "min_chars": "Mindestens 2 Zeichen eingeben",
- "not_in_inventory": "Produkt nicht im Bestand",
- "appliance_exists": "Gerät bereits vorhanden",
- "already_exists": "Bereits vorhanden",
- "network_retry": "Verbindungsfehler. Erneut versuchen.",
- "select_items": "Wähle mindestens ein Produkt aus",
- "server_offline": "Serververbindung unterbrochen",
- "server_restored": "Serververbindung wiederhergestellt",
- "server_retry": "Erneut versuchen",
- "unknown": "Unbekannter Fehler",
- "prefix": "Fehler",
- "no_inventory_entry": "Kein Inventareintrag gefunden"
- },
- "confirm": {
- "remove_item": "Möchtest du dieses Produkt wirklich aus dem Bestand entfernen?",
- "kiosk_exit": "Kioskmodus verlassen?",
- "cancel": "Abbrechen",
- "proceed": "Bestätigen"
- },
- "location": {
- "dispensa": "Vorratskammer",
- "frigo": "Kühlschrank",
- "freezer": "Gefrierschrank"
- },
- "edit": {
- "title": "{name} bearbeiten",
- "unknown_hint": "Produktname und Informationen eingeben",
- "label_name": "🏷️ Produktname",
- "choose_location_title": "Welchen Ort?",
- "choose_location_hint": "Wähle den zu bearbeitenden Ort:"
- },
- "screensaver": {
- "recipe_btn": "Rezepte",
- "scan_btn": "Produkt scannen"
- },
- "days": {
- "mon": "Montag",
- "tue": "Dienstag",
- "wed": "Mittwoch",
- "thu": "Donnerstag",
- "fri": "Freitag",
- "sat": "Samstag",
- "sun": "Sonntag",
- "mon_short": "Mo",
- "tue_short": "Di",
- "wed_short": "Mi",
- "thu_short": "Do",
- "fri_short": "Fr",
- "sat_short": "Sa",
- "sun_short": "So"
- },
- "meal_types": {
- "lunch": "Mittagessen",
- "dinner": "Abendessen",
- "colazione": "Frühstück",
- "merenda": "Nachmittagssnack",
- "dolce": "Dessert",
- "succo": "Fruchtsaft",
- "pranzo": "Mittagessen",
- "cena": "Abendessen"
- },
- "scale": {
- "status_connected": "Waage verbunden",
- "status_searching": "Gateway verbunden, warte auf Waage…",
- "status_disconnected": "Waagen-Gateway nicht erreichbar",
- "status_error": "Verbindungsfehler zum Gateway",
- "not_connected": "Waagen-Gateway nicht verbunden",
- "read_btn": "⚖️ Von Waage lesen",
- "reading_title": "Waage lesen",
- "place_on_scale": "Produkt auf die Waage legen…",
- "waiting_stable": "Das Gewicht wird automatisch erfasst, wenn die Messung stabil ist.",
- "no_url": "Gateway-URL eingeben",
- "testing": "⏳ Verbindung wird getestet…",
- "connected_ok": "Gateway-Verbindung erfolgreich!",
- "timeout": "Timeout: keine Antwort vom Gateway",
- "error_connect": "Verbindung zum Gateway nicht möglich",
- "tab": "Smart-Waage",
- "low_weight": "Gewicht < 10 g · manuell eingeben\n(Auto-Erkennung erfordert mind. 10 g)",
- "density_hint": "(Dichte {density} g/ml)",
- "ml_hint": "(wird in ml umgerechnet)",
- "weight_detected": "Gewicht erkannt — 10s Stabilität abwarten…",
- "weight_too_low": "Gewicht zu niedrig — warten…",
- "stable": "✓ Stabil",
- "auto_confirm": "✅ {val} {unit} — Auto-Bestätigung in 5s (tippen zum Abbrechen)",
- "cancelled_replace": "Abgebrochen — lege die Zutat wieder auf die Waage, um fortzufahren"
- },
- "prediction": {
- "expected_qty": "Erwartet: {expected} {unit}",
- "actual_qty": "Aktuell: {actual} {unit}",
- "check_suggestion": "Überprüfe oder wiege die Restmenge"
- },
- "date": {
- "today": "📅 Heute",
- "yesterday": "📅 Gestern"
- },
- "scanner": {
- "title_barcode": "🔖 Barcode scannen",
- "barcode_hint": "Produktbarcode einrahmen",
- "barcode_manual_placeholder": "Oder manuell eingeben...",
- "barcode_use_btn": "✅ Diesen Code verwenden",
- "ai_identifying": "🤖 Produkt wird erkannt...",
- "ai_analyzing": "🤖 KI-Analyse läuft...",
- "product_label_hint": "Produktetikett einrahmen",
- "expiry_label_hint": "Ablaufdatum auf dem Produkt einrahmen",
- "capture_btn": "📸 Aufnehmen",
- "capture_photo_btn": "📸 Foto aufnehmen",
- "retake_btn": "🔄 Erneut aufnehmen",
- "camera_error_hint": "Stelle sicher, dass du HTTPS verwendest und Kameraberechtigungen erteilt hast.
Du kannst den Barcode manuell eingeben oder die KI-Identifikation verwenden.",
- "no_barcode": "Kein Barcode",
- "save_new_btn": "🆕 Keines davon — als neu speichern",
- "expiry_found": "Datum gefunden",
- "expiry_read_fail": "Datum konnte nicht gelesen werden.",
- "expiry_raw_label": "Erkannt"
- },
- "lowstock": {
- "title": "⚠️ Wird knapp!",
- "message": "{name} wird knapp — nur noch {qty} übrig.",
- "question": "Möchtest du es zur Einkaufsliste hinzufügen?",
- "yes": "🛒 Ja, zu Bring! hinzufügen",
- "no": "Nein, passt für jetzt"
- },
- "move": {
- "title": "📦 Den Rest bewegen?",
- "question": "Möchtest du {thing} von {name} an einen anderen Ort bewegen?",
- "question_short": "Möchtest du {thing} an einen anderen Ort bewegen?",
- "thing_opened": "die offene Packung",
- "thing_rest": "den Rest",
- "stay_btn": "Nein, bleibt in {location}",
- "moved_toast": "📦 Offene Packung bewegt nach {location}",
- "vacuum_restore": "Vakuum wiederherstellen",
- "vacuum_seal_rest": "Rest vakuumieren"
- },
- "nova": {
- "1": "Unverarbeitet",
- "2": "Kulinarische Zutat",
- "3": "Verarbeitet",
- "4": "Hochverarbeitet"
- },
- "meal_plan_types": {
- "pasta": "Pasta",
- "riso": "Reis",
- "carne": "Fleisch",
- "pesce": "Fisch",
- "legumi": "Hülsenfrüchte",
- "uova": "Eier",
- "formaggio": "Käse",
- "pizza": "Pizza",
- "affettati": "Aufschnitt",
- "verdure": "Gemüse",
- "zuppa": "Suppe",
- "insalata": "Salat",
- "pane": "Brot/Sandwich",
- "dolce": "Dessert",
- "libero": "Frei"
- },
- "meal_sub": {
- "dolce_torta": "Kuchen",
- "dolce_crema": "Creme / Pudding",
- "dolce_crumble": "Crumble / Tarte",
- "dolce_biscotti": "Kekse / Gebäck",
- "dolce_frutta": "Fruchtdessert",
- "succo_dolce": "Süß / Fruchtig",
- "succo_energizzante": "Energetisierend",
- "succo_detox": "Detox / Grün",
- "succo_rinfrescante": "Erfrischend",
- "succo_vitaminico": "Vitamin / Zitrus"
- },
- "meal_plan": {
- "reset_success": "Wochenplan zurückgesetzt",
- "not_available": "nicht im Vorrat verfügbar",
- "suggested_by": "vom Wochenplan vorgeschlagen"
- },
- "nutrition": {
- "title": "🥗 Ernährungsanalyse",
- "score_excellent": "😄 Ausgezeichnet",
- "score_good": "🙂 Gut",
- "score_improve": "😬 Verbesserbar",
- "label_health": "🌿 Gesundheit",
- "label_variety": "🎨 Vielfalt",
- "label_fresh": "❄️ Frisch",
- "source": "Basierend auf {n} Produkten in deiner Vorratskammer · EverShelf",
- "products_count": "Produkte",
- "today_title": "🥗 Deine Vorratskammer heute",
- "products_n": "{n} Produkte"
- },
- "facts": {
- "greeting_morning": "Guten Morgen",
- "greeting_afternoon": "Guten Tag",
- "greeting_evening": "Guten Abend",
- "pantry_waiting": "{greeting}! Deine Vorratskammer wartet.",
- "expired_one": "Du hast 1 abgelaufenes Produkt in der Vorratskammer. Bitte überprüfen!",
- "expired_many": "Du hast {n} abgelaufene Produkte in der Vorratskammer. Bitte überprüfen!",
- "expired_list": "Abgelaufene Produkte: {names}",
- "expired_list_more": "und {n} weitere",
- "freezer_expired_ok": "{name} ist abgelaufen, aber im Gefrierschrank könnte es noch gut sein! Überprüfe es.",
- "freezer_expired_old": "{name} im Gefrierschrank ist zu lange abgelaufen. Besser wegwerfen.",
- "fridge_expired_one": "Du hast 1 abgelaufenes Produkt im Kühlschrank!",
- "fridge_expired_many": "Du hast {n} abgelaufene Produkte im Kühlschrank!",
- "expiring_today": "{name} läuft heute ab! Sofort verbrauchen.",
- "expiring_tomorrow": "{name} läuft morgen ab. Denk daran!",
- "expiring_days": "{name} läuft in {days} Tagen ab.",
- "expiring_many": "Du hast {n} Produkte, die bald ablaufen.",
- "expiring_this_week": "{n} Produkte laufen diese Woche ab. Plane deine Mahlzeiten entsprechend!",
- "expiring_item_loc": "{name} ({loc}) läuft in {days} {dayslabel} ab.",
- "expiring_this_month": "{n} Produkte laufen diesen Monat ab.",
- "shopping_add": "Zur Liste: {names} 🛒",
- "shopping_more": "und {n} weitere",
- "shopping_empty": "Einkaufsliste leer. Alles aufgefüllt! ✅",
- "in_fridge": "Im Kühlschrank: {name}.",
- "in_freezer": "Im Gefrierschrank: {name}. Vergiss es nicht!",
- "top_category": "Häufigste Kategorie: {icon} {cat} mit {n} Produkten.",
- "cat_meat": "Du hast {n} Fleischprodukte. 🥩",
- "cat_dairy": "Du hast {n} Milchprodukte zu Hause. 🥛",
- "cat_veggies": "Du hast {n} Gemüsesorten. Super für die Gesundheit! 🥬",
- "cat_fruit": "Du hast {n} Obstsorten. 🍎",
- "cat_drinks": "Du hast {n} Getränke verfügbar. 🥤",
- "cat_frozen": "Du hast {n} Tiefkühlprodukte. ❄️",
- "cat_pasta": "Du hast {n} Nudelsorten. 🍝 Wie wäre es mit einer Carbonara?",
- "cat_canned": "Du hast {n} Konserven in der Vorratskammer. 🥫",
- "cat_snacks": "Du hast {n} Snacks. Widerstand leisten! 🍪",
- "cat_condiments": "Du hast {n} Gewürze zur Verfügung. 🧂",
- "item_random": "Wusstest du? Du hast {name} in {loc}.",
- "item_qty": "{name}: du hast {qty}.",
- "no_expiry_count": "{n} Produkte haben kein Ablaufdatum.",
- "furthest_expiry": "Das Produkt mit dem spätesten Ablaufdatum ist {name}: {months} Monate.",
- "high_qty": "Du hast einen guten Vorrat von {name}: {qty}!",
- "low_qty_item": "{name} geht zur Neige. Auf die Einkaufsliste?",
- "low_qty_count": "{n} Produkte sind fast aufgebraucht.",
- "morning_bread": "Guten Morgen! Du hast Brot für das Frühstück. 🍞",
- "morning_milk": "Gibt es Milch im Kühlschrank für den Cappuccino? ☕🥛",
- "morning_fruit": "Guten Morgen! Frisches Obst ist ein guter Start. 🍎",
- "noon_pasta": "Mittagszeit… Wie wäre es mit Pasta? 🍝",
- "noon_salad": "Ein frischer Salat zum Mittagessen? Du hast {n} Gemüsesorten! 🥗",
- "evening_meat": "Zum Abendessen könntest du das Fleisch verwenden. 🥩",
- "evening_fish": "Wie wäre es mit Fisch zum Abendessen? 🐟",
- "evening_expiring": "Du hast {n} Produkte, die diese Woche ablaufen — heute Abend verwenden!",
- "night_reminder": "Gute Nacht! Denk morgen daran zu verwenden: {names}.",
- "weekly_balance": "Wochenbilanz: +{in} hinzugefügt, −{out} verbraucht.",
- "weekly_added": "Du hast diese Woche {n} Produkte hinzugefügt.",
- "weekly_consumed": "Du hast diese Woche {n} Produkte verbraucht. Gut gemacht!",
- "tip_freezer": "💡 Tiefkühlprodukte halten viel länger als das Ablaufdatum.",
- "tip_bread": "💡 Gefrorenes Brot behält seinen Duft wochenlang.",
- "tip_fifo": "💡 Um Verschwendung zu vermeiden, zuerst Produkte mit nahem Ablaufdatum verwenden (FIFO).",
- "tip_meat": "💡 Fleisch im Gefrierschrank hält bis zu 6 Monate problemlos.",
- "tip_no_refreeze": "💡 Niemals ein aufgetautes Lebensmittel wieder einfrieren. Sofort zubereiten!",
- "tip_fridge": "💡 Ein ordentlicher Kühlschrank spart Zeit und Geld.",
- "tip_canned": "💡 Geöffnete Konserven in den Kühlschrank und in wenigen Tagen verbrauchen.",
- "top_brand": "Die häufigste Marke in deiner Vorratskammer ist {brand} mit {n} Produkten.",
- "combo_pasta": "Du hast Pasta und Gewürze: bereit für ein Erstgericht! 🍝",
- "combo_sandwich": "Brot und Fleisch: ein schnelles Sandwich ist immer eine gute Idee! 🥪",
- "combo_balanced": "Gemüse und Fleisch: du hast alles für eine ausgewogene Mahlzeit! 🥗🥩",
- "pantry_empty": "Die Vorratskammer ist leer! Zeit zum Einkaufen. 🛒",
- "pantry_empty_scan": "Keine Produkte erfasst. Scanne etwas um zu beginnen!",
- "location_distribution": "Verteilung: {parts}",
- "day": "Tag",
- "days": "Tage"
- },
- "kiosk_session": {
- "first_item": "Erstes Produkt: {name}!",
- "items_two_four": "{n} Artikel — Trägheit überwinden 🚀",
- "items_five_nine": "{n} Artikel — super Tempo! 💪",
- "items_ten_twenty": "{n} Artikel — fast Rekord 🏆",
- "items_twenty_plus": "{n} Artikel — epischer Einkauf! 🛒🔥",
- "duplicates_one": "1 Duplikat (gleiches Produkt zweimal)",
- "duplicates_many": "{n} Duplikate (mehrfach genommen)",
- "top_category": "Top-Kategorie: {cat} ({count}×)",
- "items_fallback": "{n} Artikel hinzugefügt"
- },
- "kiosk": {
- "check_btn": "🔍 Nach Updates suchen",
- "checking": "⏳ Prüfe…",
- "error_check": "Fehler bei der Update-Prüfung",
- "error_start_install": "Fehler beim Starten der Installation",
- "version_installed": "Installiert: {v}",
- "update_available": "⬆️ Neue Version verfügbar:
{latest} (installiert: {current})",
- "up_to_date": "✅ Du bist auf dem neuesten Stand — Version
{v}",
- "too_old": "⚠️ Der installierte Kiosk ist zu alt für die automatische Update-Prüfung.
Drücke den Knopf unten, um die neue Version direkt herunterzuladen.",
- "manual_install": "⚠️ Dieser Kiosk unterstützt keine automatische Installation.
Manuelle Vorgehensweise:1. Kiosk verlassen (✕ oben links)
2. EverShelf Kiosk App deinstallieren
3. Neue APK von GitHub herunterladen und installieren:",
- "starting_download": "⏳ Download startet…",
- "install_btn": "⬇️ Update installieren",
- "exit_title": "Kiosk beenden",
- "refresh_title": "Seite aktualisieren"
- },
- "update": {
- "new_version": "Neue Version",
- "btn": "Aktualisieren"
- },
- "gemini": {
- "chat_title": "Mit Gemini chatten",
- "not_configured": "🤖 Gemini nicht konfiguriert — GEMINI_API_KEY in den Einstellungen setzen"
- },
- "appliances": {
- "empty": "Kein Haushaltsgerät hinzugefügt"
- },
- "about": {
- "title": "Über",
- "version": "Version",
- "report_bug": "Fehler melden",
- "report_bug_hint": "Etwas funktioniert nicht? Sende uns direkt aus der App eine Meldung.",
- "report_bug_modal_title": "Fehler melden",
- "report_type_bug": "Fehler",
- "report_type_feature": "Funktion",
- "report_type_question": "Frage",
- "report_field_title": "Titel",
- "report_field_title_ph": "Kurze Beschreibung des Problems",
- "report_field_desc": "Beschreibung",
- "report_field_desc_ph": "Problem detailliert beschreiben…",
- "report_field_steps": "Schritte zum Reproduzieren (optional)",
- "report_field_steps_ph": "1. Gehe zu…\n2. Tippe auf…\n3. Fehler erscheint…",
- "report_auto_info": "Automatisch beigefügt: Version {version}, Sprache {lang}.",
- "report_send_btn": "Bericht senden",
- "report_bug_sending": "Wird gesendet…",
- "report_bug_sent": "Bericht gesendet — danke!",
- "report_bug_error": "Bericht konnte nicht gesendet werden. Verbindung prüfen.",
- "changelog": "Changelog",
- "github": "GitHub-Repository"
- },
- "export": {
- "title": "Inventar exportieren",
- "hint": "Lade das aktuelle Inventar als CSV herunter oder öffne die druckfertige Version (PDF).",
- "btn_csv": "CSV herunterladen",
- "btn_pdf": "PDF / Drucken",
- "btn_title": "Exportieren"
- },
- "startup": {
- "connecting": "Serververbindung wird hergestellt...",
- "check_php_memory": "PHP-Speicher",
- "check_php_timeout": "PHP-Timeout",
- "check_php_upload": "PHP-Upload",
- "check_data_dir": "Datenverzeichnis",
- "check_rate_limits": "Rate-Limits-Verzeichnis",
- "check_backups": "Backup-Verzeichnis",
- "check_write_test": "Schreibtest",
- "check_disk_space": "Speicherplatz",
- "check_db_legacy": "Legacy-DB (dispensa.db)",
- "check_db_connect": "Datenbankverbindung",
- "check_db_tables": "Datenbanktabellen",
- "check_db_integrity": "Datenbankintegrität",
- "check_db_wal": "WAL-Modus",
- "check_db_size": "Datenbankgröße",
- "check_db_rows": "Inventardaten",
- "check_env": ".env-Datei",
- "check_gemini": "Gemini-AI-Schlüssel",
- "check_bring_creds": "Bring!-Anmeldedaten",
- "check_bring_token": "Bring!-Token",
- "check_tts": "Text-to-Speech-URL",
- "check_scale": "Waagen-Gateway",
- "check_curl_ssl": "cURL-SSL",
- "check_internet": "Internetverbindung",
- "fresh_install": "Neuinstallation",
- "warnings_found": "Warnungen",
- "all_ok": "System OK",
- "critical_error_short": "Kritischer Fehler",
- "critical_error": "Kritischer Fehler: Die App kann nicht gestartet werden. Prüfe die Serverlogs.",
- "critical_error_intro": "Die App kann aufgrund folgender Probleme nicht gestartet werden:",
- "error_network": "Server nicht erreichbar.",
- "error_network_detail": "Der Browser kann den PHP-Server nicht erreichen.\n\nMögliche Ursachen:\n• Apache/PHP-Server läuft nicht\n• Netzwerk- oder Firewall-Problem\n• Falsche App-URL\n\nBitte Server starten und erneut versuchen.",
- "retry": "Erneut versuchen"
- }
}
\ No newline at end of file
diff --git a/translations/en.json b/translations/en.json
index 9881abd..bc909c1 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -1,1407 +1,1419 @@
{
- "app": {
- "name": "EverShelf",
- "loading": "Loading..."
- },
- "nav": {
- "title": "EverShelf",
- "home": "Home",
- "inventory": "Pantry",
- "recipes": "Recipes",
- "shopping": "Shopping",
- "log": "Log",
- "settings": "Settings"
- },
- "btn": {
- "back": "← Back",
- "save": "💾 Save",
- "cancel": "✕ Cancel",
- "close": "Close",
- "add": "✅ Add",
- "delete": "Delete",
- "edit": "✏️ Edit",
- "use": "Use",
- "edit_item": "Edit",
- "search": "🔍 Search",
- "go": "✅ Go",
- "toggle_password": "👁️ Show/Hide",
- "load_more": "Load more...",
- "save_config": "💾 Save Configuration",
- "save_product": "💾 Save Product",
- "restart": "↺ Restart",
- "reset_default": "↺ Reset to default",
- "save_info": "💾 Save information",
- "retry": "🔄 Retry",
- "yes_short": "Yes",
- "no_short": "No"
- },
- "form": {
- "select_placeholder": "-- Select --"
- },
- "locations": {
- "dispensa": "Pantry",
- "frigo": "Fridge",
- "freezer": "Freezer",
- "altro": "Other"
- },
- "categories": {
- "latticini": "Dairy",
- "carne": "Meat",
- "pesce": "Fish",
- "frutta": "Fruit",
- "verdura": "Vegetables",
- "pasta": "Pasta & Rice",
- "pane": "Bread & Bakery",
- "surgelati": "Frozen",
- "bevande": "Beverages",
- "condimenti": "Condiments",
- "snack": "Snacks & Sweets",
- "conserve": "Canned Goods",
- "cereali": "Cereals & Legumes",
- "igiene": "Hygiene",
- "pulizia": "Household",
- "altro": "Other",
- "select": "-- Select --"
- },
- "units": {
- "pz": "pcs",
- "conf": "pkg",
- "g": "g",
- "ml": "ml",
- "pieces": "Pieces",
- "grams": "Grams",
- "box": "Package",
- "boxes": "Packages",
- "millilitres": "Millilitres",
- "from": "of"
- },
- "shopping_sections": {
- "frutta_verdura": "Fruits & Vegetables",
- "carne_pesce": "Meat & Fish",
- "latticini": "Dairy & Fresh",
- "pane_dolci": "Bread & Sweets",
- "pasta": "Pasta & Cereals",
- "conserve": "Canned & Sauces",
- "surgelati": "Frozen",
- "bevande": "Beverages",
- "pulizia_igiene": "Cleaning & Hygiene",
- "altro": "Other"
- },
- "dashboard": {
- "expired_title": "🚫 Expired",
- "expiring_title": "⏰ Expiring Soon",
- "stats_period": "📊 Last 30 days",
- "opened_title": "📦 Opened Products",
- "review_title": "🔍 To Review",
- "review_hint": "Quantities that seem unusual. Confirm if correct or modify.",
- "quick_recipe": "Quick recipe with expiring products",
- "banner_review_title": "Anomalous quantity",
- "banner_review_action_ok": "It's correct",
- "banner_review_action_finish": "🗑️ All gone",
- "banner_review_action_edit": "Correct",
- "banner_review_action_weigh": "Weigh",
- "banner_review_dismiss": "Dismiss",
- "banner_prediction_title": "Consumption to review",
- "banner_prediction_hint": "The consumption estimate adapts to recent data: confirm only if the current quantity is correct.",
- "banner_prediction_action_confirm": "Confirm {qty} {unit}",
- "banner_prediction_action_weigh": "Weigh now",
- "banner_prediction_action_edit": "Update quantity",
- "banner_expired_title": "Expired product",
- "banner_expired_today": "Expired today",
- "banner_expired_days": "Expired {days} days ago",
- "banner_expired_action_use": "Use anyway",
- "banner_expired_action_finished": "I finished it!",
- "banner_expired_action_throw": "I threw it away",
- "banner_expired_action_edit": "Fix date",
- "banner_expired_action_modify": "Edit",
- "banner_expired_action_vacuum": "Put in vacuum seal",
- "banner_anomaly_action_edit": "Fix inventory",
- "banner_anomaly_action_dismiss": "Quantity is correct",
- "banner_no_expiry_title": "Missing expiry: {name}",
- "banner_no_expiry_detail": "This product has no expiry date. Would you like to add one, or confirm it doesn't expire?",
- "banner_no_expiry_action_set": "Set expiry date",
- "banner_no_expiry_action_dismiss": "Doesn't expire ✓",
- "banner_no_expiry_toast_dismissed": "Marked as 'no expiry'",
- "banner_expiring_title": "Expiring soon",
- "banner_expiring_today": "Expires today!",
- "banner_expiring_tomorrow": "Expires tomorrow",
- "banner_expiring_days": "Expires in {days} days",
- "banner_expiring_action_use": "Use now",
- "banner_finished_title": "finished?",
- "banner_finished_detail": "I recorded that {name} reached zero stock. Is it really gone, or do you still have some?",
- "banner_finished_action_yes": "Yes, it's done",
- "banner_finished_action_no": "No, I still have some",
- "banner_review_unusual_pkg_title": "Unusual package size",
- "banner_review_unusual_pkg_detail": "You set a package of {qty} {unit} — the size seems very large. Check if correct or edit.",
- "banner_review_low_qty_title": "Very low quantity",
- "banner_review_low_qty_detail": "You only have {qty} in stock — seems very little, could be a typo. Confirm if correct.",
- "banner_review_high_qty_title": "Unusually high quantity",
- "banner_review_high_qty_detail": "You have {qty} in stock — the figure seems very high. Confirm if correct or edit.",
- "banner_prediction_rate_day": "Average ~{n} {unit}/day",
- "banner_prediction_rate_week": "Average ~{n} {unit}/week",
- "banner_prediction_days_ago": "{n} days ago you restocked",
- "banner_prediction_more": "previous estimate: {expected} {unit}{time}; current quantity: {actual} {unit}.",
- "banner_prediction_less": "estimate: {expected} {unit}{time}; current quantity: {actual} {unit}. If your usage pace changed, the forecast updates automatically.",
- "banner_finished_zero": "Inventory shows zero, but recorded movements suggest it shouldn't be empty.",
- "banner_finished_expected": "According to records you should still have {qty} {unit}.",
- "banner_finished_check": "Can you check?",
- "banner_anomaly_phantom_title": "you have more stock than expected",
- "banner_anomaly_phantom_detail": "Inventory shows {inv_qty} {unit}, but based on records you should only have {expected_qty} {unit}. Did you add stock without recording it?",
- "banner_anomaly_untracked_title": "stock not recorded as an entry",
- "banner_anomaly_untracked_detail": "You have
{inv_qty} {unit} in inventory, but recorded outflows exceed inflows — the initial stock was likely never added as an \"in\" transaction. You can correct the quantity or log the missing entries.",
- "banner_anomaly_ghost_title": "you have less stock than expected",
- "banner_anomaly_ghost_detail": "Based on recorded operations you should have {expected_qty} {unit} of {name}, but inventory shows only {inv_qty} {unit}. Did you take stock without recording it?",
- "consumed": "Consumed: {n} ({pct}%)",
- "wasted": "Wasted: {n} ({pct}%)",
- "more_opened": "and {n} more opened...",
- "banner_expired_detail": "{when} · you still have
{qty}.",
- "banner_opened_detail": "{when} in {location} · you still have
{qty}.",
- "banner_explain_title": "Ask Gemini for an explanation",
- "banner_explain_btn": "Explain",
- "banner_analyzing": "🤖 Analyzing…"
- },
- "inventory": {
- "title": "Pantry",
- "filter_all": "All",
- "search_placeholder": "🔍 Search product...",
- "recent_title": "🕐 Recently used",
- "popular_title": "⭐ Most used",
- "empty": "No products here.\nScan a product to add it!",
- "no_items_found": "No inventory items found",
- "qty_remainder_suffix": "left",
- "vacuum_badge": "🫙 Vacuum sealed",
- "opened_badge": "📭 Opened",
- "label_expiry": "📅 Expiry",
- "label_storage": "🫙 Storage",
- "label_status": "📭 Status",
- "opened_since": "Opened since {date}",
- "label_position": "📍 Location",
- "label_quantity": "📦 Quantity",
- "label_added": "📅 Added",
- "empty_text": "No products here.
Scan a product to add it!",
- "empty_db": "No products in the database.
Scan a product to get started!",
- "qty_trace": "< 1"
- },
- "scan": {
- "title": "Scan",
- "mode_shopping": "🛒 Shopping Mode",
- "mode_shopping_end": "✅ End shopping",
- "spesa_btn": "🛒 Shopping",
- "zoom": "Zoom",
- "tab_barcode": "Barcode",
- "tab_name": "Name",
- "tab_ai": "AI",
- "recents_label": "Recent",
- "torch_hint": "Torch",
- "torch_on": "Torch on",
- "torch_off": "Torch off",
- "torch_unavailable": "Torch not available on this device",
- "flip_hint": "Flip camera",
- "flip_front": "Front camera",
- "flip_back": "Rear camera",
- "num_ocr_btn": "🔢 Read numbers with AI",
- "num_ocr_searching": "Looking for barcode with AI...",
- "num_ocr_found": "Code found: {code}",
- "num_ocr_not_found": "No barcode found in image",
- "barcode_placeholder": "Enter barcode...",
- "quick_name_divider": "or type the name",
- "quick_name_placeholder": "E.g.: Apples, Zucchini, Bread...",
- "manual_entry": "✏️ Manual Entry",
- "ai_identify": "🤖 Identify with AI",
- "hint": "Scan the barcode, type the product name, or use AI to identify it",
- "debug_toggle": "🐛 Debug Log",
- "barcode_acquired": "🔖 Barcode scanned: {code}",
- "scan_barcode": "🔖 Scan Barcode",
- "create_named": "Create {name}",
- "new_without_barcode": "New product without barcode",
- "stock_in_pantry": "Already in pantry:"
- },
- "action": {
- "title": "What do you want to do?",
- "add_btn": "📥 ADD",
- "add_sub": "to pantry/fridge",
- "use_btn": "USE",
- "use_sub": "from pantry/fridge",
- "have_title": "📦 Already in stock!",
- "add_more_sub": "add more",
- "use_qty_sub": "how much you used",
- "throw_btn": "🗑️ DISCARD",
- "throw_sub": "throw away",
- "edit_sub": "expiry, location…",
- "create_recipe_btn": "Recipe",
- "related_stock_title": "Also at home"
- },
- "add": {
- "title": "Add to Pantry",
- "location_label": "📍 Where do you put it?",
- "quantity_label": "📦 Quantity",
- "conf_size_label": "📦 Each package contains:",
- "conf_size_placeholder": "e.g. 300",
- "vacuum_label": "🫙 Vacuum sealed",
- "vacuum_hint": "Expiry date will be extended automatically",
- "submit": "✅ Add",
- "purchase_type_label": "🛒 This product is...",
- "new_btn": "🆕 Just bought",
- "existing_btn": "📦 I already had it",
- "remaining_label": "📦 Remaining quantity",
- "remaining_hint": "Approximately how much is left?",
- "remaining_full": "🟢 Full",
- "remaining_half": "🟠 Half",
- "estimated_expiry": "Estimated expiry:",
- "suffix_freezer": "(freezer)",
- "suffix_vacuum": "(vacuum sealed)",
- "hint_modify": "📝 You can change the date or scan it with the camera",
- "scan_expiry_title": "📷 Scan Expiry Date",
- "product_added": "✅ {name} added!{qty}",
- "suffix_freezer_vacuum": "(freezer + vacuum sealed)",
- "history_badge_tip": "Average from {n} previous entries",
- "vacuum_question": "Vacuum sealed?",
- "vacuum_saved": "🔒 Vacuum sealed!"
- },
- "use": {
- "title": "Use / Consume",
- "location_label": "📍 From where?",
- "quantity_label": "How much did you use?",
- "change": "change",
- "partial_hint": "Or specify the quantity used:",
- "partial_piece_hint": "Did you use only a part?",
- "piece": "piece",
- "one_whole": "1 whole",
- "use_all": "🗑️ Used ALL / Finished",
- "submit": "📤 Use this quantity",
- "available": "📦 Available:",
- "opened_badge": "OPENED",
- "not_in_inventory": "⚠️ Product not in inventory.",
- "expiry_warning": "⚠️ Use first the one{loc} that expires on {date} — {when}!",
- "expiry_warning_opened": "⚠️ The one{loc} has been open for {when} — use it first!",
- "throw_title": "🗑️ Discard Product",
- "throw_all": "🗑️ Discard ALL ({qty})",
- "throw_qty_label": "How much to discard?",
- "throw_qty_hint": "or enter a quantity:",
- "throw_partial_btn": "🗑️ Discard this quantity",
- "when_expired": "expired {n} days ago",
- "when_today": "expires
today",
- "when_tomorrow": "expires
tomorrow",
- "when_days": "expires in
{n} days",
- "toast_used": "📤 Used {qty} of {name}",
- "toast_bring": "🛒 Product finished → added to Bring!",
- "toast_opened_finished": "🔓 Opened package of {name} finished!",
- "disambiguation_hint": "What do you mean by \"all done\"?",
- "disambiguation_all": "🗑️ Finish EVERYTHING ({qty})",
- "error_exceeds_stock": "⚠️ You cannot use more than you have available!",
- "use_all_confirm_title": "✅ Finish everything",
- "use_all_confirm_msg": "Confirm that you have finished the product:",
- "use_all_confirm_btn": "✅ Yes, finished",
- "throw_all_confirm_title": "🗑️ Discard everything",
- "throw_all_confirm_msg": "Do you really want to throw away the whole product?",
- "throw_all_confirm_btn": "🗑️ Yes, discard"
- },
- "product": {
- "title_new": "New Product",
- "title_edit": "Edit Product",
- "ai_fill": "📷 Take photo and identify with AI",
- "ai_fill_hint": "AI will automatically fill in the product fields",
- "name_label": "🏷️ Product Name *",
- "name_placeholder": "E.g.: Whole milk, Penne pasta...",
- "brand_label": "🏢 Brand",
- "brand_placeholder": "E.g.: Barilla, Granarolo, Mutti...",
- "category_label": "📂 Category",
- "unit_label": "📏 Unit of measure",
- "default_qty_label": "🔢 Default quantity",
- "conf_size_label": "📦 Each package contains:",
- "conf_size_placeholder": "e.g. 300",
- "notes_label": "📝 Notes",
- "notes_placeholder": "E.g.: lactose free, organic, store in fridge after opening...",
- "barcode_label": "🔖 Barcode",
- "barcode_placeholder": "Barcode (if available)",
- "barcode_hint": "⚠️ Add the barcode so next time you just need to scan it!",
- "submit": "💾 Save Product",
- "name_required": "Enter the product name",
- "conf_size_required": "Specify the package content",
- "expiry_estimated": "Estimated expiry:",
- "scan_expiry": "Scan expiry date",
- "expiry_hint": "📝 You can edit the date or scan it with the camera",
- "add_batch": "📦 + Batch with different expiry",
- "package_info": "📦 Package: {info}",
- "edit_catalog": "⚙️ Edit product info (name, brand, category…)",
- "not_recognized": "⚠️ Product not recognized",
- "edit_info": "✏️ Edit information",
- "modify_details": "EDIT\nexpiry, location…",
- "already_in_pantry": "📋 Already in pantry",
- "no_barcode": "No barcode",
- "unknown_product": "Unrecognized product",
- "edit_name_brand": "Edit name/brand",
- "weight_label": "Weight",
- "origin_label": "Origin",
- "labels_label": "Labels",
- "select_variant": "Select the exact variant or use AI data:"
- },
- "products": {
- "title": "📦 All Products",
- "search_placeholder": "🔍 Search product...",
- "empty": "No products in database.\nScan a product to get started!",
- "no_category": "No products in this category"
- },
- "recipes": {
- "title": "🍳 Recipes",
- "generate": "✨ Generate new recipe",
- "archive_empty": "No recipes saved. Generate your first recipe!",
- "dialog_title": "🍳 Recipe",
- "dialog_desc": "I will generate a healthy recipe using pantry ingredients, prioritizing expiring items.",
- "meal_label": "🕐 Which meal?",
- "persons_label": "👥 How many people?",
- "meal_type_label": "🎯 Meal type",
- "opt_fast": "⚡ Quick meal",
- "opt_light": "🥗 Light appetite",
- "opt_expiry": "⏰ Prioritize expiring items",
- "opt_healthy": "💚 Extra healthy",
- "opt_opened": "📦 Prioritize opened items",
- "opt_zero_waste": "♻️ Zero waste",
- "generate_btn": "✨ Generate Recipe",
- "loading_msg": "Preparing your recipe...",
- "start_cooking": "👨🍳 Cooking Mode",
- "regenerate": "🔄 Generate another one",
- "close_btn": "✅ Close",
- "ingredients_title": "🧾 Ingredients",
- "tools_title": "Equipment needed",
- "steps_title": "👨🍳 Steps",
- "no_steps": "No steps available",
- "generate_error": "Generation error",
- "stream_interrupted": "Generation interrupted (incomplete server response). Check logs or try again.",
- "persons_short": "serv.",
- "use_ingredient_title": "Use ingredient",
- "recipe_qty_label": "Recipe",
- "from_where_label": "From where?",
- "amount_label": "How much",
- "use_amount_btn": "Use this amount",
- "use_all_btn": "Use ALL / Finished",
- "packs_label": "Packs",
- "quantity_in_total": "Quantity in {unit} (total: {total})",
- "packs_of_have": "Packs of {size} (you have {count} packs)",
- "scale_wait_stable": "Wait 10s of stable weight for auto-fill…",
- "ingredient_scaled_toast": "📦 Ingredient deducted from pantry!",
- "finished_added_bring_toast": "🛒 Finished product → added to Bring!",
- "load_error": "Loading error"
- },
- "shopping": {
- "title": "🛒 Shopping List",
- "bring_loading": "Connecting to Bring!...",
- "bring_not_configured": "Bring! is not configured. Add your email and password in
settings.",
- "tab_to_buy": "🛍️ To buy",
- "tab_forecast": "🧠 Forecast",
- "total_label": "💰 Estimated total",
- "section_to_buy": "🛍️ To buy",
- "suggestions_title": "💡 AI Suggestions",
- "suggestions_add": "✅ Add selected to Bring!",
- "search_prices": "🔍 Search all prices",
- "suggest_btn": "Suggest what to buy",
- "smart_title": "🧠 Smart Predictions",
- "smart_empty": "No predictions available.
Add products to your pantry to receive smart predictions.",
- "smart_filter_all": "All",
- "smart_filter_critical": "🔴 Urgent",
- "smart_filter_high": "🟠 Soon",
- "smart_filter_medium": "🟡 Plan",
- "smart_filter_low": "🟢 Forecast",
- "smart_add": "🛒 Add selected to Bring!",
- "empty": "Shopping list empty!\nUse the button below to generate suggestions.",
- "already_in_list": "🛒 \"{name}\" is already in the shopping list",
- "already_in_list_short": "ℹ️ Already in the shopping list",
- "add_prompt": "Do you want to add it to the shopping list?",
- "smart_already": "📊 Smart shopping already predicts {name}",
- "all_searched": "All products have already been searched. Use 🔄 to search individual ones.",
- "search_complete": "Search complete: {count} products",
- "removed_sufficient": "🧹 {removed} product(s) with sufficient stock removed from the list",
- "suggest_buy": "🛒 Buy: {qty} {unit}",
- "suggest_buy_approx": "🛒 At least: {qty} {unit}",
- "suggest_buy_tip": "Suggested quantity based on your last 14 days of consumption",
- "suggest_buy_approx_tip": "Minimum estimate based on consumption (buy the nearest package size)",
- "bring_badge": "🛒 Already on Bring!",
- "add_urgent_toast": "🔴 {n} urgent product(s) automatically added to Bring!",
- "migration_done": "✅ {migrated} updated, {skipped} already ok",
- "added_to_bring": "🛒 {n} products added to Bring!",
- "added_to_bring_skip": "{n} already present",
- "all_on_bring": "All products were already on Bring!",
- "freq_high": "📈 Frequent",
- "freq_regular": "📊 Regular",
- "freq_occasional": "📉 Occasional",
- "out_of_stock": "Out of stock",
- "scan_toast": "📷 Scan: {name}",
- "empty_category": "No products in this category",
- "session_empty": "🛒 No products yet",
- "urgency_critical": "Urgent",
- "urgency_high": "Soon",
- "urgency_medium": "Plan",
- "urgency_low": "Forecast",
- "urgency_medium_short": "Medium",
- "urgency_low_short": "Ok",
- "tag_urgent": "🔴 Urgent",
- "tag_priority": "⭐ Priority",
- "tag_check": "✅ Check",
- "smart_already_predicted": "📊 Smart shopping already predicts
{name}{urgency}.",
- "item_removed": "✅ {name} removed from list!",
- "urgency_spec_critical": "⚡ Urgent",
- "urgency_spec_high": "🟠 Soon",
- "bring_add_n": "Add {n} to Bring!",
- "bring_add_selected": "Add selected to Bring!",
- "bring_adding": "Adding...",
- "bring_added_one": "1 product added to Bring!",
- "bring_added_many": "{n} products added to Bring!",
- "bring_skipped": "({n} already in list)",
- "force_sync": "Force Bring! sync",
- "scan_target_label": "You are looking for",
- "scan_target_found": "Found! Remove from list",
- "bring_add_one": "Add 1 product to Bring!",
- "bring_add_many": "Add {n} products to Bring!",
- "syncing": "Syncing…",
- "sync_done": "Sync completed",
- "price_searching": "Searching...",
- "search_action": "Search",
- "open_action": "Open",
- "not_found": "Not found",
- "search_price": "Search price",
- "tap_to_scan": "Tap to scan",
- "tag_title": "Tag",
- "remove_title": "Remove",
- "found_count": "{found}/{total} products found",
- "savings_offers": "· 🏷️ You save €{amount} with offers",
- "searching_progress": "Searching {current}/{total}...",
- "remove_error": "Removal error",
- "btn_fetch_prices": "Find prices",
- "price_total_label": "💰 Estimated total:",
- "price_loading": "Looking up prices…",
- "price_not_found": "price n/a",
- "suggest_loading": "Analyzing...",
- "suggest_error": "Suggestion generation error",
- "priority_high": "High",
- "priority_medium": "Medium",
- "priority_low": "Low",
- "smart_last_update": "Updated {time}",
- "names_already_updated": "All names are already up to date",
- "pantry_hint": "Already at home: {qty}"
- },
- "ai": {
- "title": "🤖 AI Identification",
- "capture": "📸 Take Photo",
- "retake": "🔄 Retake",
- "hint": "Take a photo of the product and AI will try to identify it",
- "identifying": "🤖 Identifying product...",
- "no_api_key": "⚠️ Gemini API key not configured.\n
Add GEMINI_API_KEY to the .env file on the server.",
- "fields_filled": "✅ Fields filled by AI",
- "use_data": "✅ Use AI data",
- "use_data_no_barcode": "✅ Use AI data (no barcode)"
- },
- "log": {
- "title": "📒 Operations Log",
- "type_added": "Added",
- "type_waste": "Discarded",
- "type_used": "Used",
- "type_bring": "Added to Bring!",
- "undone_badge": "Undone",
- "undo_title": "Undo this operation",
- "load_error": "Error loading log",
- "empty": "No operations recorded.",
- "undo_action_remove": "removal of",
- "undo_action_restore": "restock of",
- "undo_confirm": "Undo this operation?\n→ {action} {name}",
- "undo_success": "↩ Operation undone for {name}",
- "already_undone": "Operation already undone",
- "too_old": "Cannot undo operations older than 24 hours",
- "undo_error": "Error during undo",
- "recipe_prefix": "Recipe"
- },
- "chat": {
- "title": "Gemini Chef",
- "welcome": "Hi! I'm your kitchen assistant",
- "welcome_desc": "Ask me to make you a juice, a snack, a quick dish... I know your pantry, your appliances and your preferences!",
- "suggestion_snack": "🍿 Quick snack",
- "suggestion_juice": "🥤 Juice/Smoothie",
- "suggestion_light": "🥗 Something light",
- "suggestion_expiry": "⏰ Use expiring items",
- "clear": "New conversation",
- "placeholder": "Ask something...",
- "cleared": "Chat cleared",
- "suggestion_snack_text": "What can I make for a quick snack?",
- "suggestion_juice_text": "Make me a juice or smoothie with what I have",
- "suggestion_light_text": "I'm hungry but want something light",
- "suggestion_expiry_text": "What's about to expire and how can I use it?",
- "transfer_to_recipes": "Transfer to Recipes",
- "transferring": "Transferring...",
- "transferred": "Added to Recipes!",
- "open_recipe": "Open recipe",
- "quick_recipe_prompt": "Suggest a quick recipe FOR ONE PERSON using the products that expire first! Ignore freezer items, focus on fridge and pantry."
- },
- "cooking": {
- "close": "Close",
- "tts_btn": "Read aloud",
- "restart": "↺ Restart",
- "replay": "🔊 Replay",
- "timer": "⏱️ {time} · Timer",
- "prev": "◀ Previous",
- "next": "Next ▶",
- "ingredient_used": "✔️ Deducted",
- "ingredient_use_btn": "Use",
- "ingredient_deduct_title": "Deduct from pantry",
- "timer_expired_tts": "Timer {label} expired!",
- "timer_warning_tts": "Heads up! {label}: 10 seconds left!",
- "recipe_done_tts": "Recipe complete! Enjoy your meal!",
- "expires_chip": "exp. {date}",
- "finish": "✅ Finish",
- "step_fallback": "Step {n}",
- "zerowaste_label": "♻️ Scrap",
- "zerowaste_tip_title": "Zero-waste tip"
- },
- "settings": {
- "title": "⚙️ Settings",
- "tab_api": "API Keys",
- "tab_bring": "Bring!",
- "tab_recipe": "Recipes",
- "tab_mealplan": "Weekly Plan",
- "tab_appliances": "Appliances",
- "tab_spesa": "Online Shopping",
- "tab_camera": "Camera",
- "tab_security": "Security",
- "tab_tts": "Voice (TTS)",
- "tab_language": "Language",
- "tab_scale": "Smart Scale",
- "gemini": {
- "title": "🤖 Google Gemini AI",
- "hint": "API key for product identification, expiry dates and recipes.",
- "key_label": "Gemini API Key"
+ "app": {
+ "name": "EverShelf",
+ "loading": "Loading..."
},
- "bring": {
- "title": "🛒 Bring! Shopping List",
- "hint": "Credentials for the Bring! shopping list integration.",
- "email_label": "📧 Bring! Email",
- "password_label": "🔒 Bring! Password"
+ "nav": {
+ "title": "EverShelf",
+ "home": "Home",
+ "inventory": "Pantry",
+ "recipes": "Recipes",
+ "shopping": "Shopping",
+ "log": "Log",
+ "settings": "Settings"
},
- "price": {
- "title": "💰 Price Estimation (AI)",
- "hint": "Show estimated cost per product in the shopping list using AI.",
- "enabled_label": "Enable price estimation",
- "country_label": "🌍 Reference country",
- "currency_label": "💱 Currency",
- "update_label": "🔄 Refresh prices every",
- "update_suffix": "months"
+ "btn": {
+ "back": "← Back",
+ "save": "💾 Save",
+ "cancel": "✕ Cancel",
+ "close": "Close",
+ "add": "✅ Add",
+ "delete": "Delete",
+ "edit": "✏️ Edit",
+ "use": "Use",
+ "edit_item": "Edit",
+ "search": "🔍 Search",
+ "go": "✅ Go",
+ "toggle_password": "👁️ Show/Hide",
+ "load_more": "Load more...",
+ "save_config": "💾 Save Configuration",
+ "save_product": "💾 Save Product",
+ "restart": "↺ Restart",
+ "reset_default": "↺ Reset to default",
+ "save_info": "💾 Save information",
+ "retry": "🔄 Retry",
+ "yes_short": "Yes",
+ "no_short": "No"
},
- "recipe": {
- "title": "🍳 Recipe Preferences",
- "hint": "Configure the default options for recipe generation.",
- "persons_label": "👥 Default servings",
- "options_label": "🎯 Default recipe options",
- "fast": "⚡ Quick Meal",
- "light": "🥗 Light Meal",
- "expiry": "⏰ Expiry Priority",
- "healthy": "💚 Extra Healthy",
- "opened": "📦 Open Items Priority",
- "zerowaste": "♻️ Zero Waste",
- "dietary_label": "🚫 Intolerances / Restrictions",
- "dietary_placeholder": "E.g.: gluten free, lactose free, vegetarian..."
+ "form": {
+ "select_placeholder": "-- Select --"
},
- "mealplan": {
- "title": "📅 Weekly Meal Plan",
- "hint": "Set the meal type for each day. It will be used as a guide in recipe generation.",
- "enabled": "✅ Enable weekly meal plan",
- "legend": "🌤️ = Lunch · 🌙 = Dinner · Tap a badge to change it.",
- "types_title": "📋 Available types",
- "reset_btn": "↺ Restore defaults"
+ "locations": {
+ "dispensa": "Pantry",
+ "frigo": "Fridge",
+ "freezer": "Freezer",
+ "altro": "Other"
},
- "appliances": {
- "title": "🔌 Available Appliances",
- "hint": "Indicate the appliances you have. They will be considered in recipe generation.",
- "new_placeholder": "E.g.: Bread machine, Thermomix, Air fryer...",
- "quick_title": "Quick add:",
- "oven": "🔥 Oven",
- "microwave": "📡 Microwave",
- "air_fryer": "🍟 Air fryer",
- "bread_maker": "🍞 Bread maker",
- "bimby": "🤖 Thermomix/Cookeo",
- "mixer": "🌀 Stand mixer",
- "steamer": "♨️ Steamer",
- "pressure_cooker": "🫕 Pressure cooker",
- "toaster": "🍞 Toaster",
- "blender": "🍹 Blender",
- "empty": "No appliances added"
+ "categories": {
+ "latticini": "Dairy",
+ "carne": "Meat",
+ "pesce": "Fish",
+ "frutta": "Fruit",
+ "verdura": "Vegetables",
+ "pasta": "Pasta & Rice",
+ "pane": "Bread & Bakery",
+ "surgelati": "Frozen",
+ "bevande": "Beverages",
+ "condimenti": "Condiments",
+ "snack": "Snacks & Sweets",
+ "conserve": "Canned Goods",
+ "cereali": "Cereals & Legumes",
+ "igiene": "Hygiene",
+ "pulizia": "Household",
+ "altro": "Other",
+ "select": "-- Select --"
},
- "spesa": {
- "title": "🛍️ Online Shopping",
- "hint": "Configure the online shopping provider.",
- "provider_label": "🏪 Provider",
- "email_label": "📧 Email",
- "password_label": "🔒 Password",
- "login_btn": "🔐 Login",
- "ai_prompt_label": "🤖 AI product selection prompt",
- "ai_prompt_placeholder": "Instructions for AI when choosing between multiple products...",
- "ai_prompt_hint": "AI uses this prompt to choose the most appropriate product from results. Leave empty for default behavior.",
- "configure_first": "Configure Online Shopping in settings first",
- "missing_credentials": "Enter email and password",
- "login_in_progress": "Signing in...",
- "login_error_prefix": "Error:",
- "login_network_error_prefix": "Network error:",
- "login_success_default": "Login successful!",
- "result_name_label": "Name",
- "result_card_label": "Card",
- "result_pickup_label": "Pickup point",
- "result_points_label": "Loyalty points",
- "connected_relogin": "✅ Connected — Sign in again",
- "connected_as": "Connected as {name}"
+ "units": {
+ "pz": "pcs",
+ "conf": "pkg",
+ "g": "g",
+ "ml": "ml",
+ "pieces": "Pieces",
+ "grams": "Grams",
+ "box": "Package",
+ "boxes": "Packages",
+ "millilitres": "Millilitres",
+ "from": "of"
},
- "camera": {
- "title": "📷 Camera",
- "hint": "Choose which camera to use for barcode scanning and AI identification.",
- "device_label": "📸 Default camera",
- "back": "📱 Rear (default)",
- "front": "🤳 Front",
- "devices_hint": "If you have multiple cameras, you can select a specific one from the list above after granting permissions.",
- "detect_btn": "🔄 Detect cameras"
+ "shopping_sections": {
+ "frutta_verdura": "Fruits & Vegetables",
+ "carne_pesce": "Meat & Fish",
+ "latticini": "Dairy & Fresh",
+ "pane_dolci": "Bread & Sweets",
+ "pasta": "Pasta & Cereals",
+ "conserve": "Canned & Sauces",
+ "surgelati": "Frozen",
+ "bevande": "Beverages",
+ "pulizia_igiene": "Cleaning & Hygiene",
+ "altro": "Other"
},
- "security": {
- "title": "🔒 HTTPS Certificate",
- "hint": "If the browser shows the error \"Your connection is not private\" (ERR_CERT_AUTHORITY_INVALID), you need to install the CA certificate on the device.",
- "download_btn": "📥 Download CA Certificate",
- "token_title": "🔑 Settings Token",
- "token_label": "Access token",
- "token_hint": "If `SETTINGS_TOKEN` is configured in the server's `.env`, enter the token here before saving settings. Leave empty if not configured.",
- "token_placeholder": "(empty = no protection)",
- "token_required_hint": "🔒 This server requires a token to save settings.",
- "cert_instructions": "
Instructions for Chrome (Android):1. Download the certificate above
2. Go to
Settings → Security & Privacy → More security settings → Install from device storage3. Select the downloaded
EverShelf_CA.crt file
4. Choose \"CA\" and confirm
5. Restart Chrome
Instructions for Chrome (PC):1. Download the certificate above
2. Go to
chrome://settings/certificates (or Settings → Privacy and security → Security → Manage certificates)
3. Tab \"Authorities\" → Import → select the file
4. Check \"Trust this certificate for identifying websites\"
5. Restart Chrome"
+ "dashboard": {
+ "expired_title": "🚫 Expired",
+ "expiring_title": "⏰ Expiring Soon",
+ "stats_period": "📊 Last 30 days",
+ "opened_title": "📦 Opened Products",
+ "review_title": "🔍 To Review",
+ "review_hint": "Quantities that seem unusual. Confirm if correct or modify.",
+ "quick_recipe": "Quick recipe with expiring products",
+ "banner_review_title": "Anomalous quantity",
+ "banner_review_action_ok": "It's correct",
+ "banner_review_action_finish": "🗑️ All gone",
+ "banner_review_action_edit": "Correct",
+ "banner_review_action_weigh": "Weigh",
+ "banner_review_dismiss": "Dismiss",
+ "banner_prediction_title": "Consumption to review",
+ "banner_prediction_hint": "The consumption estimate adapts to recent data: confirm only if the current quantity is correct.",
+ "banner_prediction_action_confirm": "Confirm {qty} {unit}",
+ "banner_prediction_action_weigh": "Weigh now",
+ "banner_prediction_action_edit": "Update quantity",
+ "banner_expired_title": "Expired product",
+ "banner_expired_today": "Expired today",
+ "banner_expired_days": "Expired {days} days ago",
+ "banner_expired_action_use": "Use anyway",
+ "banner_expired_action_finished": "I finished it!",
+ "banner_expired_action_throw": "I threw it away",
+ "banner_expired_action_edit": "Fix date",
+ "banner_expired_action_modify": "Edit",
+ "banner_expired_action_vacuum": "Put in vacuum seal",
+ "banner_anomaly_action_edit": "Fix inventory",
+ "banner_anomaly_action_dismiss": "Quantity is correct",
+ "banner_no_expiry_title": "Missing expiry: {name}",
+ "banner_no_expiry_detail": "This product has no expiry date. Would you like to add one, or confirm it doesn't expire?",
+ "banner_no_expiry_action_set": "Set expiry date",
+ "banner_no_expiry_action_dismiss": "Doesn't expire ✓",
+ "banner_no_expiry_toast_dismissed": "Marked as 'no expiry'",
+ "banner_expiring_title": "Expiring soon",
+ "banner_expiring_today": "Expires today!",
+ "banner_expiring_tomorrow": "Expires tomorrow",
+ "banner_expiring_days": "Expires in {days} days",
+ "banner_expiring_action_use": "Use now",
+ "banner_finished_title": "finished?",
+ "banner_finished_detail": "I recorded that {name} reached zero stock. Is it really gone, or do you still have some?",
+ "banner_finished_action_yes": "Yes, it's done",
+ "banner_finished_action_no": "No, I still have some",
+ "banner_review_unusual_pkg_title": "Unusual package size",
+ "banner_review_unusual_pkg_detail": "You set a package of {qty} {unit} — the size seems very large. Check if correct or edit.",
+ "banner_review_low_qty_title": "Very low quantity",
+ "banner_review_low_qty_detail": "You only have {qty} in stock — seems very little, could be a typo. Confirm if correct.",
+ "banner_review_high_qty_title": "Unusually high quantity",
+ "banner_review_high_qty_detail": "You have {qty} in stock — the figure seems very high. Confirm if correct or edit.",
+ "banner_prediction_rate_day": "Average ~{n} {unit}/day",
+ "banner_prediction_rate_week": "Average ~{n} {unit}/week",
+ "banner_prediction_days_ago": "{n} days ago you restocked",
+ "banner_prediction_more": "previous estimate: {expected} {unit}{time}; current quantity: {actual} {unit}.",
+ "banner_prediction_less": "estimate: {expected} {unit}{time}; current quantity: {actual} {unit}. If your usage pace changed, the forecast updates automatically.",
+ "banner_finished_zero": "Inventory shows zero, but recorded movements suggest it shouldn't be empty.",
+ "banner_finished_expected": "According to records you should still have {qty} {unit}.",
+ "banner_finished_check": "Can you check?",
+ "banner_anomaly_phantom_title": "you have more stock than expected",
+ "banner_anomaly_phantom_detail": "Inventory shows {inv_qty} {unit}, but based on records you should only have {expected_qty} {unit}. Did you add stock without recording it?",
+ "banner_anomaly_untracked_title": "stock not recorded as an entry",
+ "banner_anomaly_untracked_detail": "You have
{inv_qty} {unit} in inventory, but recorded outflows exceed inflows — the initial stock was likely never added as an \"in\" transaction. You can correct the quantity or log the missing entries.",
+ "banner_anomaly_ghost_title": "you have less stock than expected",
+ "banner_anomaly_ghost_detail": "Based on recorded operations you should have {expected_qty} {unit} of {name}, but inventory shows only {inv_qty} {unit}. Did you take stock without recording it?",
+ "consumed": "Consumed: {n} ({pct}%)",
+ "wasted": "Wasted: {n} ({pct}%)",
+ "more_opened": "and {n} more opened...",
+ "banner_expired_detail": "{when} · you still have
{qty}.",
+ "banner_opened_detail": "{when} in {location} · you still have
{qty}.",
+ "banner_explain_title": "Ask Gemini for an explanation",
+ "banner_explain_btn": "Explain",
+ "banner_analyzing": "🤖 Analyzing…"
},
- "tts": {
- "title": "🔊 Voice & TTS",
- "hint": "Configure text-to-speech via any external REST API. Recipe steps and expired timers will be sent to the configured endpoint.",
- "enabled": "✅ Enable TTS",
- "engine_label": "⚙️ TTS Engine",
- "engine_browser": "🔇 Browser (offline, no configuration required)",
- "engine_server": "🌐 External server (Home Assistant, REST API...)",
- "voice_label": "🗣️ Voice",
- "rate_label": "⚡ Speed",
- "pitch_label": "🎵 Pitch",
- "url_label": "🌐 Endpoint URL",
- "method_label": "📡 HTTP Method",
- "auth_label": "🔐 Authentication",
- "auth_bearer": "Bearer Token",
- "auth_custom": "Custom Header",
- "auth_none": "None",
- "token_label": "🔑 Bearer Token",
- "custom_header_name": "📋 Header name",
- "custom_header_value": "📋 Header value",
- "content_type_label": "📄 Content-Type",
- "payload_key_label": "🗝️ Text field in payload",
- "payload_key_hint": "Name of the JSON field that will contain the text to read (e.g.: message, text).",
- "extra_fields_label": "➕ Extra fields (JSON)",
- "extra_fields_placeholder": "{\"entity_id\": \"media_player.living_room\"}",
- "extra_fields_hint": "Additional fields to include in the payload, in JSON format. Leave empty if not needed.",
- "test_btn": "🔊 Send Test Voice",
- "voices_loading": "Loading voices…",
- "voice_not_supported": "Voice not supported by this browser",
- "voices_none": "No voices available on this device",
- "voices_hint": "Available voices depend on the OS and browser. On macOS/iOS the Paola (Italian) voice is available. Press ↺ if the list does not load.",
- "url_missing": "⚠️ Endpoint URL missing.",
- "test_sending": "⏳ Sending…",
- "test_ok": "✅ Response {code} — check that the speaker has spoken."
+ "inventory": {
+ "title": "Pantry",
+ "filter_all": "All",
+ "search_placeholder": "🔍 Search product...",
+ "recent_title": "🕐 Recently used",
+ "popular_title": "⭐ Most used",
+ "empty": "No products here.\nScan a product to add it!",
+ "no_items_found": "No inventory items found",
+ "qty_remainder_suffix": "left",
+ "vacuum_badge": "🫙 Vacuum sealed",
+ "opened_badge": "📭 Opened",
+ "label_expiry": "📅 Expiry",
+ "label_storage": "🫙 Storage",
+ "label_status": "📭 Status",
+ "opened_since": "Opened since {date}",
+ "label_position": "📍 Location",
+ "label_quantity": "📦 Quantity",
+ "label_added": "📅 Added",
+ "empty_text": "No products here.
Scan a product to add it!",
+ "empty_db": "No products in the database.
Scan a product to get started!",
+ "qty_trace": "< 1"
},
- "language": {
- "title": "🌐 Language",
- "hint": "Select the interface language.",
- "label": "🌐 Language",
- "restart_notice": "The page will reload to apply the new language."
+ "scan": {
+ "title": "Scan",
+ "mode_shopping": "🛒 Shopping Mode",
+ "mode_shopping_end": "✅ End shopping",
+ "spesa_btn": "🛒 Shopping",
+ "zoom": "Zoom",
+ "tab_barcode": "Barcode",
+ "tab_name": "Name",
+ "tab_ai": "AI",
+ "recents_label": "Recent",
+ "torch_hint": "Torch",
+ "torch_on": "Torch on",
+ "torch_off": "Torch off",
+ "torch_unavailable": "Torch not available on this device",
+ "flip_hint": "Flip camera",
+ "flip_front": "Front camera",
+ "flip_back": "Rear camera",
+ "num_ocr_btn": "🔢 Read numbers with AI",
+ "num_ocr_searching": "Looking for barcode with AI...",
+ "num_ocr_found": "Code found: {code}",
+ "num_ocr_not_found": "No barcode found in image",
+ "barcode_placeholder": "Enter barcode...",
+ "quick_name_divider": "or type the name",
+ "quick_name_placeholder": "E.g.: Apples, Zucchini, Bread...",
+ "manual_entry": "✏️ Manual Entry",
+ "ai_identify": "🤖 Identify with AI",
+ "hint": "Scan the barcode, type the product name, or use AI to identify it",
+ "debug_toggle": "🐛 Debug Log",
+ "barcode_acquired": "🔖 Barcode scanned: {code}",
+ "scan_barcode": "🔖 Scan Barcode",
+ "create_named": "Create {name}",
+ "new_without_barcode": "New product without barcode",
+ "stock_in_pantry": "Already in pantry:"
+ },
+ "action": {
+ "title": "What do you want to do?",
+ "add_btn": "📥 ADD",
+ "add_sub": "to pantry/fridge",
+ "use_btn": "USE",
+ "use_sub": "from pantry/fridge",
+ "have_title": "📦 Already in stock!",
+ "add_more_sub": "add more",
+ "use_qty_sub": "how much you used",
+ "throw_btn": "🗑️ DISCARD",
+ "throw_sub": "throw away",
+ "edit_sub": "expiry, location…",
+ "create_recipe_btn": "Recipe",
+ "related_stock_title": "Also at home"
+ },
+ "add": {
+ "title": "Add to Pantry",
+ "location_label": "📍 Where do you put it?",
+ "quantity_label": "📦 Quantity",
+ "conf_size_label": "📦 Each package contains:",
+ "conf_size_placeholder": "e.g. 300",
+ "vacuum_label": "🫙 Vacuum sealed",
+ "vacuum_hint": "Expiry date will be extended automatically",
+ "submit": "✅ Add",
+ "purchase_type_label": "🛒 This product is...",
+ "new_btn": "🆕 Just bought",
+ "existing_btn": "📦 I already had it",
+ "remaining_label": "📦 Remaining quantity",
+ "remaining_hint": "Approximately how much is left?",
+ "remaining_full": "🟢 Full",
+ "remaining_half": "🟠 Half",
+ "estimated_expiry": "Estimated expiry:",
+ "suffix_freezer": "(freezer)",
+ "suffix_vacuum": "(vacuum sealed)",
+ "hint_modify": "📝 You can change the date or scan it with the camera",
+ "scan_expiry_title": "📷 Scan Expiry Date",
+ "product_added": "✅ {name} added!{qty}",
+ "suffix_freezer_vacuum": "(freezer + vacuum sealed)",
+ "history_badge_tip": "Average from {n} previous entries",
+ "vacuum_question": "Vacuum sealed?",
+ "vacuum_saved": "🔒 Vacuum sealed!"
+ },
+ "use": {
+ "title": "Use / Consume",
+ "location_label": "📍 From where?",
+ "quantity_label": "How much did you use?",
+ "change": "change",
+ "partial_hint": "Or specify the quantity used:",
+ "partial_piece_hint": "Did you use only a part?",
+ "piece": "piece",
+ "one_whole": "1 whole",
+ "use_all": "🗑️ Used ALL / Finished",
+ "submit": "📤 Use this quantity",
+ "available": "📦 Available:",
+ "opened_badge": "OPENED",
+ "not_in_inventory": "⚠️ Product not in inventory.",
+ "expiry_warning": "⚠️ Use first the one{loc} that expires on {date} — {when}!",
+ "expiry_warning_opened": "⚠️ The one{loc} has been open for {when} — use it first!",
+ "throw_title": "🗑️ Discard Product",
+ "throw_all": "🗑️ Discard ALL ({qty})",
+ "throw_qty_label": "How much to discard?",
+ "throw_qty_hint": "or enter a quantity:",
+ "throw_partial_btn": "🗑️ Discard this quantity",
+ "when_expired": "expired {n} days ago",
+ "when_today": "expires
today",
+ "when_tomorrow": "expires
tomorrow",
+ "when_days": "expires in
{n} days",
+ "toast_used": "📤 Used {qty} of {name}",
+ "toast_bring": "🛒 Product finished → added to Bring!",
+ "toast_opened_finished": "🔓 Opened package of {name} finished!",
+ "disambiguation_hint": "What do you mean by \"all done\"?",
+ "disambiguation_all": "🗑️ Finish EVERYTHING ({qty})",
+ "error_exceeds_stock": "⚠️ You cannot use more than you have available!",
+ "use_all_confirm_title": "✅ Finish everything",
+ "use_all_confirm_msg": "Confirm that you have finished the product:",
+ "use_all_confirm_btn": "✅ Yes, finished",
+ "throw_all_confirm_title": "🗑️ Discard everything",
+ "throw_all_confirm_msg": "Do you really want to throw away the whole product?",
+ "throw_all_confirm_btn": "🗑️ Yes, discard"
+ },
+ "product": {
+ "title_new": "New Product",
+ "title_edit": "Edit Product",
+ "ai_fill": "📷 Take photo and identify with AI",
+ "ai_fill_hint": "AI will automatically fill in the product fields",
+ "name_label": "🏷️ Product Name *",
+ "name_placeholder": "E.g.: Whole milk, Penne pasta...",
+ "brand_label": "🏢 Brand",
+ "brand_placeholder": "E.g.: Barilla, Granarolo, Mutti...",
+ "category_label": "📂 Category",
+ "unit_label": "📏 Unit of measure",
+ "default_qty_label": "🔢 Default quantity",
+ "conf_size_label": "📦 Each package contains:",
+ "conf_size_placeholder": "e.g. 300",
+ "notes_label": "📝 Notes",
+ "notes_placeholder": "E.g.: lactose free, organic, store in fridge after opening...",
+ "barcode_label": "🔖 Barcode",
+ "barcode_placeholder": "Barcode (if available)",
+ "barcode_hint": "⚠️ Add the barcode so next time you just need to scan it!",
+ "submit": "💾 Save Product",
+ "name_required": "Enter the product name",
+ "conf_size_required": "Specify the package content",
+ "expiry_estimated": "Estimated expiry:",
+ "scan_expiry": "Scan expiry date",
+ "expiry_hint": "📝 You can edit the date or scan it with the camera",
+ "add_batch": "📦 + Batch with different expiry",
+ "package_info": "📦 Package: {info}",
+ "edit_catalog": "⚙️ Edit product info (name, brand, category…)",
+ "not_recognized": "⚠️ Product not recognized",
+ "edit_info": "✏️ Edit information",
+ "modify_details": "EDIT\nexpiry, location…",
+ "already_in_pantry": "📋 Already in pantry",
+ "no_barcode": "No barcode",
+ "unknown_product": "Unrecognized product",
+ "edit_name_brand": "Edit name/brand",
+ "weight_label": "Weight",
+ "origin_label": "Origin",
+ "labels_label": "Labels",
+ "select_variant": "Select the exact variant or use AI data:"
+ },
+ "products": {
+ "title": "📦 All Products",
+ "search_placeholder": "🔍 Search product...",
+ "empty": "No products in database.\nScan a product to get started!",
+ "no_category": "No products in this category"
+ },
+ "recipes": {
+ "title": "🍳 Recipes",
+ "generate": "✨ Generate new recipe",
+ "archive_empty": "No recipes saved. Generate your first recipe!",
+ "dialog_title": "🍳 Recipe",
+ "dialog_desc": "I will generate a healthy recipe using pantry ingredients, prioritizing expiring items.",
+ "meal_label": "🕐 Which meal?",
+ "persons_label": "👥 How many people?",
+ "meal_type_label": "🎯 Meal type",
+ "opt_fast": "⚡ Quick meal",
+ "opt_light": "🥗 Light appetite",
+ "opt_expiry": "⏰ Prioritize expiring items",
+ "opt_healthy": "💚 Extra healthy",
+ "opt_opened": "📦 Prioritize opened items",
+ "opt_zero_waste": "♻️ Zero waste",
+ "generate_btn": "✨ Generate Recipe",
+ "loading_msg": "Preparing your recipe...",
+ "start_cooking": "👨🍳 Cooking Mode",
+ "regenerate": "🔄 Generate another one",
+ "close_btn": "✅ Close",
+ "ingredients_title": "🧾 Ingredients",
+ "tools_title": "Equipment needed",
+ "steps_title": "👨🍳 Steps",
+ "no_steps": "No steps available",
+ "generate_error": "Generation error",
+ "stream_interrupted": "Generation interrupted (incomplete server response). Check logs or try again.",
+ "persons_short": "serv.",
+ "use_ingredient_title": "Use ingredient",
+ "recipe_qty_label": "Recipe",
+ "from_where_label": "From where?",
+ "amount_label": "How much",
+ "use_amount_btn": "Use this amount",
+ "use_all_btn": "Use ALL / Finished",
+ "packs_label": "Packs",
+ "quantity_in_total": "Quantity in {unit} (total: {total})",
+ "packs_of_have": "Packs of {size} (you have {count} packs)",
+ "scale_wait_stable": "Wait 10s of stable weight for auto-fill…",
+ "ingredient_scaled_toast": "📦 Ingredient deducted from pantry!",
+ "finished_added_bring_toast": "🛒 Finished product → added to Bring!",
+ "load_error": "Loading error"
+ },
+ "shopping": {
+ "title": "🛒 Shopping List",
+ "bring_loading": "Connecting to Bring!...",
+ "bring_not_configured": "Bring! is not configured. Add your email and password in
settings.",
+ "tab_to_buy": "🛍️ To buy",
+ "tab_forecast": "🧠 Forecast",
+ "total_label": "💰 Estimated total",
+ "section_to_buy": "🛍️ To buy",
+ "suggestions_title": "💡 AI Suggestions",
+ "suggestions_add": "✅ Add selected to Bring!",
+ "search_prices": "🔍 Search all prices",
+ "suggest_btn": "Suggest what to buy",
+ "smart_title": "🧠 Smart Predictions",
+ "smart_empty": "No predictions available.
Add products to your pantry to receive smart predictions.",
+ "smart_filter_all": "All",
+ "smart_filter_critical": "🔴 Urgent",
+ "smart_filter_high": "🟠 Soon",
+ "smart_filter_medium": "🟡 Plan",
+ "smart_filter_low": "🟢 Forecast",
+ "smart_add": "🛒 Add selected to Bring!",
+ "empty": "Shopping list empty!\nUse the button below to generate suggestions.",
+ "already_in_list": "🛒 \"{name}\" is already in the shopping list",
+ "already_in_list_short": "ℹ️ Already in the shopping list",
+ "add_prompt": "Do you want to add it to the shopping list?",
+ "smart_already": "📊 Smart shopping already predicts {name}",
+ "all_searched": "All products have already been searched. Use 🔄 to search individual ones.",
+ "search_complete": "Search complete: {count} products",
+ "removed_sufficient": "🧹 {removed} product(s) with sufficient stock removed from the list",
+ "suggest_buy": "🛒 Buy: {qty} {unit}",
+ "suggest_buy_approx": "🛒 At least: {qty} {unit}",
+ "suggest_buy_tip": "Suggested quantity based on your last 14 days of consumption",
+ "suggest_buy_approx_tip": "Minimum estimate based on consumption (buy the nearest package size)",
+ "bring_badge": "🛒 Already on Bring!",
+ "add_urgent_toast": "🔴 {n} urgent product(s) automatically added to Bring!",
+ "migration_done": "✅ {migrated} updated, {skipped} already ok",
+ "added_to_bring": "🛒 {n} products added to Bring!",
+ "added_to_bring_skip": "{n} already present",
+ "all_on_bring": "All products were already on Bring!",
+ "freq_high": "📈 Frequent",
+ "freq_regular": "📊 Regular",
+ "freq_occasional": "📉 Occasional",
+ "out_of_stock": "Out of stock",
+ "scan_toast": "📷 Scan: {name}",
+ "empty_category": "No products in this category",
+ "session_empty": "🛒 No products yet",
+ "urgency_critical": "Urgent",
+ "urgency_high": "Soon",
+ "urgency_medium": "Plan",
+ "urgency_low": "Forecast",
+ "urgency_medium_short": "Medium",
+ "urgency_low_short": "Ok",
+ "tag_urgent": "🔴 Urgent",
+ "tag_priority": "⭐ Priority",
+ "tag_check": "✅ Check",
+ "smart_already_predicted": "📊 Smart shopping already predicts
{name}{urgency}.",
+ "item_removed": "✅ {name} removed from list!",
+ "urgency_spec_critical": "⚡ Urgent",
+ "urgency_spec_high": "🟠 Soon",
+ "bring_add_n": "Add {n} to Bring!",
+ "bring_add_selected": "Add selected to Bring!",
+ "bring_adding": "Adding...",
+ "bring_added_one": "1 product added to Bring!",
+ "bring_added_many": "{n} products added to Bring!",
+ "bring_skipped": "({n} already in list)",
+ "force_sync": "Force Bring! sync",
+ "scan_target_label": "You are looking for",
+ "scan_target_found": "Found! Remove from list",
+ "bring_add_one": "Add 1 product to Bring!",
+ "bring_add_many": "Add {n} products to Bring!",
+ "syncing": "Syncing…",
+ "sync_done": "Sync completed",
+ "price_searching": "Searching...",
+ "search_action": "Search",
+ "open_action": "Open",
+ "not_found": "Not found",
+ "search_price": "Search price",
+ "tap_to_scan": "Tap to scan",
+ "tag_title": "Tag",
+ "remove_title": "Remove",
+ "found_count": "{found}/{total} products found",
+ "savings_offers": "· 🏷️ You save €{amount} with offers",
+ "searching_progress": "Searching {current}/{total}...",
+ "remove_error": "Removal error",
+ "btn_fetch_prices": "Find prices",
+ "price_total_label": "💰 Estimated total:",
+ "price_loading": "Looking up prices…",
+ "price_not_found": "price n/a",
+ "suggest_loading": "Analyzing...",
+ "suggest_error": "Suggestion generation error",
+ "priority_high": "High",
+ "priority_medium": "Medium",
+ "priority_low": "Low",
+ "smart_last_update": "Updated {time}",
+ "names_already_updated": "All names are already up to date",
+ "pantry_hint": "Already at home: {qty}"
+ },
+ "ai": {
+ "title": "🤖 AI Identification",
+ "capture": "📸 Take Photo",
+ "retake": "🔄 Retake",
+ "hint": "Take a photo of the product and AI will try to identify it",
+ "identifying": "🤖 Identifying product...",
+ "no_api_key": "⚠️ Gemini API key not configured.\n
Add GEMINI_API_KEY to the .env file on the server.",
+ "fields_filled": "✅ Fields filled by AI",
+ "use_data": "✅ Use AI data",
+ "use_data_no_barcode": "✅ Use AI data (no barcode)"
+ },
+ "log": {
+ "title": "📒 Operations Log",
+ "type_added": "Added",
+ "type_waste": "Discarded",
+ "type_used": "Used",
+ "type_bring": "Added to Bring!",
+ "undone_badge": "Undone",
+ "undo_title": "Undo this operation",
+ "load_error": "Error loading log",
+ "empty": "No operations recorded.",
+ "undo_action_remove": "removal of",
+ "undo_action_restore": "restock of",
+ "undo_confirm": "Undo this operation?\n→ {action} {name}",
+ "undo_success": "↩ Operation undone for {name}",
+ "already_undone": "Operation already undone",
+ "too_old": "Cannot undo operations older than 24 hours",
+ "undo_error": "Error during undo",
+ "recipe_prefix": "Recipe"
+ },
+ "chat": {
+ "title": "Gemini Chef",
+ "welcome": "Hi! I'm your kitchen assistant",
+ "welcome_desc": "Ask me to make you a juice, a snack, a quick dish... I know your pantry, your appliances and your preferences!",
+ "suggestion_snack": "🍿 Quick snack",
+ "suggestion_juice": "🥤 Juice/Smoothie",
+ "suggestion_light": "🥗 Something light",
+ "suggestion_expiry": "⏰ Use expiring items",
+ "clear": "New conversation",
+ "placeholder": "Ask something...",
+ "cleared": "Chat cleared",
+ "suggestion_snack_text": "What can I make for a quick snack?",
+ "suggestion_juice_text": "Make me a juice or smoothie with what I have",
+ "suggestion_light_text": "I'm hungry but want something light",
+ "suggestion_expiry_text": "What's about to expire and how can I use it?",
+ "transfer_to_recipes": "Transfer to Recipes",
+ "transferring": "Transferring...",
+ "transferred": "Added to Recipes!",
+ "open_recipe": "Open recipe",
+ "quick_recipe_prompt": "Suggest a quick recipe FOR ONE PERSON using the products that expire first! Ignore freezer items, focus on fridge and pantry."
+ },
+ "cooking": {
+ "close": "Close",
+ "tts_btn": "Read aloud",
+ "restart": "↺ Restart",
+ "replay": "🔊 Replay",
+ "timer": "⏱️ {time} · Timer",
+ "prev": "◀ Previous",
+ "next": "Next ▶",
+ "ingredient_used": "✔️ Deducted",
+ "ingredient_use_btn": "Use",
+ "ingredient_deduct_title": "Deduct from pantry",
+ "timer_expired_tts": "Timer {label} expired!",
+ "timer_warning_tts": "Heads up! {label}: 10 seconds left!",
+ "recipe_done_tts": "Recipe complete! Enjoy your meal!",
+ "expires_chip": "exp. {date}",
+ "finish": "✅ Finish",
+ "step_fallback": "Step {n}",
+ "zerowaste_label": "♻️ Scrap",
+ "zerowaste_tip_title": "Zero-waste tip"
+ },
+ "settings": {
+ "title": "⚙️ Settings",
+ "tab_api": "API Keys",
+ "tab_bring": "Bring!",
+ "tab_recipe": "Recipes",
+ "tab_mealplan": "Weekly Plan",
+ "tab_appliances": "Appliances",
+ "tab_spesa": "Online Shopping",
+ "tab_camera": "Camera",
+ "tab_security": "Security",
+ "tab_tts": "Voice (TTS)",
+ "tab_language": "Language",
+ "tab_scale": "Smart Scale",
+ "gemini": {
+ "title": "🤖 Google Gemini AI",
+ "hint": "API key for product identification, expiry dates and recipes.",
+ "key_label": "Gemini API Key"
+ },
+ "bring": {
+ "title": "🛒 Bring! Shopping List",
+ "hint": "Credentials for the Bring! shopping list integration.",
+ "email_label": "📧 Bring! Email",
+ "password_label": "🔒 Bring! Password"
+ },
+ "price": {
+ "title": "💰 Price Estimation (AI)",
+ "hint": "Show estimated cost per product in the shopping list using AI.",
+ "enabled_label": "Enable price estimation",
+ "country_label": "🌍 Reference country",
+ "currency_label": "💱 Currency",
+ "update_label": "🔄 Refresh prices every",
+ "update_suffix": "months"
+ },
+ "recipe": {
+ "title": "🍳 Recipe Preferences",
+ "hint": "Configure the default options for recipe generation.",
+ "persons_label": "👥 Default servings",
+ "options_label": "🎯 Default recipe options",
+ "fast": "⚡ Quick Meal",
+ "light": "🥗 Light Meal",
+ "expiry": "⏰ Expiry Priority",
+ "healthy": "💚 Extra Healthy",
+ "opened": "📦 Open Items Priority",
+ "zerowaste": "♻️ Zero Waste",
+ "dietary_label": "🚫 Intolerances / Restrictions",
+ "dietary_placeholder": "E.g.: gluten free, lactose free, vegetarian..."
+ },
+ "mealplan": {
+ "title": "📅 Weekly Meal Plan",
+ "hint": "Set the meal type for each day. It will be used as a guide in recipe generation.",
+ "enabled": "✅ Enable weekly meal plan",
+ "legend": "🌤️ = Lunch · 🌙 = Dinner · Tap a badge to change it.",
+ "types_title": "📋 Available types",
+ "reset_btn": "↺ Restore defaults"
+ },
+ "appliances": {
+ "title": "🔌 Available Appliances",
+ "hint": "Indicate the appliances you have. They will be considered in recipe generation.",
+ "new_placeholder": "E.g.: Bread machine, Thermomix, Air fryer...",
+ "quick_title": "Quick add:",
+ "oven": "🔥 Oven",
+ "microwave": "📡 Microwave",
+ "air_fryer": "🍟 Air fryer",
+ "bread_maker": "🍞 Bread maker",
+ "bimby": "🤖 Thermomix/Cookeo",
+ "mixer": "🌀 Stand mixer",
+ "steamer": "♨️ Steamer",
+ "pressure_cooker": "🫕 Pressure cooker",
+ "toaster": "🍞 Toaster",
+ "blender": "🍹 Blender",
+ "empty": "No appliances added"
+ },
+ "spesa": {
+ "title": "🛍️ Online Shopping",
+ "hint": "Configure the online shopping provider.",
+ "provider_label": "🏪 Provider",
+ "email_label": "📧 Email",
+ "password_label": "🔒 Password",
+ "login_btn": "🔐 Login",
+ "ai_prompt_label": "🤖 AI product selection prompt",
+ "ai_prompt_placeholder": "Instructions for AI when choosing between multiple products...",
+ "ai_prompt_hint": "AI uses this prompt to choose the most appropriate product from results. Leave empty for default behavior.",
+ "configure_first": "Configure Online Shopping in settings first",
+ "missing_credentials": "Enter email and password",
+ "login_in_progress": "Signing in...",
+ "login_error_prefix": "Error:",
+ "login_network_error_prefix": "Network error:",
+ "login_success_default": "Login successful!",
+ "result_name_label": "Name",
+ "result_card_label": "Card",
+ "result_pickup_label": "Pickup point",
+ "result_points_label": "Loyalty points",
+ "connected_relogin": "✅ Connected — Sign in again",
+ "connected_as": "Connected as {name}"
+ },
+ "camera": {
+ "title": "📷 Camera",
+ "hint": "Choose which camera to use for barcode scanning and AI identification.",
+ "device_label": "📸 Default camera",
+ "back": "📱 Rear (default)",
+ "front": "🤳 Front",
+ "devices_hint": "If you have multiple cameras, you can select a specific one from the list above after granting permissions.",
+ "detect_btn": "🔄 Detect cameras"
+ },
+ "security": {
+ "title": "🔒 HTTPS Certificate",
+ "hint": "If the browser shows the error \"Your connection is not private\" (ERR_CERT_AUTHORITY_INVALID), you need to install the CA certificate on the device.",
+ "download_btn": "📥 Download CA Certificate",
+ "token_title": "🔑 Settings Token",
+ "token_label": "Access token",
+ "token_hint": "If `SETTINGS_TOKEN` is configured in the server's `.env`, enter the token here before saving settings. Leave empty if not configured.",
+ "token_placeholder": "(empty = no protection)",
+ "token_required_hint": "🔒 This server requires a token to save settings.",
+ "cert_instructions": "
Instructions for Chrome (Android):1. Download the certificate above
2. Go to
Settings → Security & Privacy → More security settings → Install from device storage3. Select the downloaded
EverShelf_CA.crt file
4. Choose \"CA\" and confirm
5. Restart Chrome
Instructions for Chrome (PC):1. Download the certificate above
2. Go to
chrome://settings/certificates (or Settings → Privacy and security → Security → Manage certificates)
3. Tab \"Authorities\" → Import → select the file
4. Check \"Trust this certificate for identifying websites\"
5. Restart Chrome"
+ },
+ "tts": {
+ "title": "🔊 Voice & TTS",
+ "hint": "Configure text-to-speech via any external REST API. Recipe steps and expired timers will be sent to the configured endpoint.",
+ "enabled": "✅ Enable TTS",
+ "engine_label": "⚙️ TTS Engine",
+ "engine_browser": "🔇 Browser (offline, no configuration required)",
+ "engine_server": "🌐 External server (Home Assistant, REST API...)",
+ "voice_label": "🗣️ Voice",
+ "rate_label": "⚡ Speed",
+ "pitch_label": "🎵 Pitch",
+ "url_label": "🌐 Endpoint URL",
+ "method_label": "📡 HTTP Method",
+ "auth_label": "🔐 Authentication",
+ "auth_bearer": "Bearer Token",
+ "auth_custom": "Custom Header",
+ "auth_none": "None",
+ "token_label": "🔑 Bearer Token",
+ "custom_header_name": "📋 Header name",
+ "custom_header_value": "📋 Header value",
+ "content_type_label": "📄 Content-Type",
+ "payload_key_label": "🗝️ Text field in payload",
+ "payload_key_hint": "Name of the JSON field that will contain the text to read (e.g.: message, text).",
+ "extra_fields_label": "➕ Extra fields (JSON)",
+ "extra_fields_placeholder": "{\"entity_id\": \"media_player.living_room\"}",
+ "extra_fields_hint": "Additional fields to include in the payload, in JSON format. Leave empty if not needed.",
+ "test_btn": "🔊 Send Test Voice",
+ "voices_loading": "Loading voices…",
+ "voice_not_supported": "Voice not supported by this browser",
+ "voices_none": "No voices available on this device",
+ "voices_hint": "Available voices depend on the OS and browser. On macOS/iOS the Paola (Italian) voice is available. Press ↺ if the list does not load.",
+ "url_missing": "⚠️ Endpoint URL missing.",
+ "test_sending": "⏳ Sending…",
+ "test_ok": "✅ Response {code} — check that the speaker has spoken."
+ },
+ "language": {
+ "title": "🌐 Language",
+ "hint": "Select the interface language.",
+ "label": "🌐 Language",
+ "restart_notice": "The page will reload to apply the new language."
+ },
+ "screensaver": {
+ "label": "Enable screensaver",
+ "card_title": "🌙 Screensaver",
+ "card_hint": "Shows a clock with useful facts after 5 minutes of inactivity. Disabled by default.",
+ "timeout_1": "1 minute",
+ "timeout_2": "2 minutes",
+ "timeout_5": "5 minutes",
+ "timeout_10": "10 minutes",
+ "timeout_15": "15 minutes",
+ "timeout_30": "30 minutes",
+ "timeout_60": "1 hour",
+ "start_after": "⏱️ Start after"
+ },
+ "scale": {
+ "title": "⚖️ Smart Scale",
+ "hint": "Connect a Bluetooth scale via the Android gateway to automatically read weight.",
+ "tab": "Smart Scale",
+ "enabled": "✅ Enable smart scale",
+ "url_label": "🌐 WebSocket Gateway URL",
+ "url_placeholder": "ws://192.168.1.x:8765",
+ "url_hint": "URL shown by the Android app (same Wi-Fi network). E.g.:",
+ "test_btn": "🔗 Test connection",
+ "download_btn": "📥 Download Android Gateway (APK)",
+ "download_hint": "Android app that bridges your BLE scale and EverShelf.",
+ "download_sub": "Source: evershelf-scale-gateway/ in the project root",
+ "live_weight": "real-time weight",
+ "auto_reconnect": "🔁 Reconnect: automatic",
+ "kiosk_title": "📡 BLE Scale integrated in Kiosk",
+ "kiosk_hint": "The scale is directly managed by the internal BLE Gateway of the kiosk. To pair a new device, use the configuration wizard.",
+ "kiosk_reconfigure": "🔄 Reconfigure BLE Scale",
+ "ble_protocols": "
🔌 Supported BLE protocols:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — weight, fat, BMI
- Xiaomi Mi Body Composition Scale 2
- Generic — automatic heuristic for 100+ models
"
+ },
+ "kiosk": {
+ "hint": "Turn an Android tablet into an always-on EverShelf panel with built-in BLE scale gateway.",
+ "download_btn": "📥 Download EverShelf Kiosk (APK)",
+ "download_sub": "Full-screen kiosk mode + integrated scale gateway. Source: evershelf-kiosk/",
+ "native_title": "Kiosk Configuration",
+ "native_hint": "Server URL, BLE scale, screensaver and setup wizard.",
+ "native_btn": "Open kiosk configuration",
+ "native_tap_hint": "Tap the gear button at the top right",
+ "native_update_hint": "Update the kiosk app to use this feature",
+ "update_title": "Kiosk Update",
+ "check_updates_btn": "🔍 Check for updates",
+ "needs_update": "⚠️ The installed kiosk does not support this feature. Update the kiosk app to enable it."
+ },
+ "saved": "✅ Configuration saved!",
+ "saved_local": "✅ Configuration saved locally",
+ "saved_local_error": "⚠️ Saved locally, server error: {error}",
+ "theme": {
+ "title": "🌙 Appearance",
+ "hint": "Choose the interface theme.",
+ "label": "🌙 Theme",
+ "off": "☀️ Light",
+ "on": "🌙 Dark",
+ "auto": "🔄 Automatic (time of day)"
+ },
+ "zerowaste": {
+ "card_title": "♻️ Zero-waste tips",
+ "card_hint": "During cooking, show tips on how to reuse scraps generated in each step (peels, cooking water, etc.). Disabled by default.",
+ "label": "Show tips during cooking"
+ },
+ "backup": {
+ "tab": "Backup",
+ "local_title": "Local Backup",
+ "local_hint": "Daily database snapshot. Configure how many days of backups to keep.",
+ "enabled": "Enable daily automatic backup",
+ "retention_days": "Retention (days)",
+ "retention_info": "Backups are kept for",
+ "backup_now": "Backup Now",
+ "backing_up": "Backing up…",
+ "backed_up": "Backup complete",
+ "backup_error": "Backup error",
+ "last_backup": "Last backup",
+ "no_backup_yet": "No backup has been created yet",
+ "list_empty": "No backups available",
+ "restore_btn": "Restore",
+ "restore_confirm": "Restore backup",
+ "delete_btn": "Delete",
+ "delete_confirm": "Delete backup",
+ "gdrive_title": "Google Drive",
+ "gdrive_hint": "Automatically back up to Google Drive via OAuth 2.0. No external libraries required.",
+ "gdrive_enabled": "Enable Google Drive backup",
+ "gdrive_folder_id": "Drive Folder ID",
+ "gdrive_folder_id_hint": "Copy the ID from the Drive folder URL: …/folders/
ID",
+ "gdrive_retention_days": "Drive retention (days, 0=keep all)",
+ "gdrive_test": "Test Connection",
+ "gdrive_ok": "Connection successful!",
+ "gdrive_error": "Connection failed",
+ "gdrive_push_now": "Upload to Drive Now",
+ "gdrive_pushing": "Uploading…",
+ "gdrive_pushed": "Uploaded to Drive",
+ "gdrive_wizard_hint": "Optional: automatically back up to Google Drive daily via OAuth 2.0.",
+ "gdrive_skip": "Skip — configure later in Settings",
+ "gdrive_client_id": "Client ID",
+ "gdrive_client_secret": "Client Secret",
+ "gdrive_redirect_uri_hint": "Add
http://localhost as an authorized redirect URI in Google Cloud Console. This works on any server, even without a public domain.",
+ "gdrive_code_title": "Paste the authorization URL or code",
+ "gdrive_code_hint": "After authorizing, the browser will open http://localhost and may show a connection error — that is expected. Copy the URL from the address bar (e.g.
http://localhost/?code=4%2F0A...) and paste it here.",
+ "gdrive_code_submit": "Submit",
+ "gdrive_code_empty": "Paste the URL or authorization code first",
+ "gdrive_redirect_uri_label": "Redirect URI (add this in Google Cloud Console):",
+ "gdrive_oauth_authorize": "Authorize with Google",
+ "gdrive_oauth_authorized": "Authorized",
+ "gdrive_oauth_not_authorized": "Not authorized yet",
+ "gdrive_oauth_window_opened": "Browser window opened — authorize and come back",
+ "gdrive_oauth_how_to": "How to set up OAuth 2.0 (step by step)",
+ "gdrive_oauth_steps": "
Go to console.cloud.google.com and select your projectEnable the Google Drive API: APIs & Services → Enable APIs → Google Drive APIGo to APIs & Services → Credentials → Create Credentials → OAuth client IDApplication type: Web application; add http://localhost as an Authorized redirect URICopy the Client ID and Client Secret into the fields above and saveClick Authorize with Google, sign in and grant accessThe browser will open http://localhost (a connection error is expected): copy the URL from the address bar and paste it in the field that appears below"
+ },
+ "info": {
+ "tab": "Info",
+ "ai_title": "Gemini AI — Token Usage",
+ "ai_hint": "Monthly consumption and estimated cost for the current API key.",
+ "loading": "Loading…",
+ "total_tokens": "Total tokens",
+ "est_cost": "Est. cost",
+ "input_tok": "Input tokens",
+ "output_tok": "Output tokens",
+ "ai_calls": "Calls",
+ "by_action": "Breakdown by function",
+ "by_model": "Breakdown by model",
+ "pricing_note": "Gemini reference pricing: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.",
+ "system_title": "System",
+ "db_size": "Database",
+ "log_size": "Logs",
+ "log_level": "Log level",
+ "ai_overview": "AI usage overview, inventory and system status",
+ "calls_unit": "calls",
+ "inv_title": "Inventory",
+ "inv_active": "Active",
+ "inv_products": "Total products",
+ "inv_expiring": "Expiring (7d)",
+ "inv_expired": "Expired",
+ "inv_finished": "Finished",
+ "act_title": "Monthly activity",
+ "act_tx_month": "Movements",
+ "act_restock": "Restocks",
+ "act_use": "Usages",
+ "act_new_products": "New products",
+ "act_tx_year": "Yearly movements",
+ "price_cache": "Price cache",
+ "cache_entries": "products",
+ "last_backup": "Last backup",
+ "bring_days": "token expires in {n} days",
+ "bring_expired": "token expired",
+ "year_label": "Year {year}",
+ "currency_title": "Currency",
+ "currency_hint": "The currency used for all costs and prices in the app."
+ },
+ "tab_general": "General",
+ "shopping": {
+ "tab": "Shopping list",
+ "title": "Shopping list",
+ "hint": "Configure the built-in shopping list or connect Bring!.",
+ "enable_label": "Enable shopping list",
+ "mode_label": "Provider",
+ "mode_internal": "Built-in (no Bring!)",
+ "mode_bring": "Bring! (external app)",
+ "bring_section_title": "Bring! configuration",
+ "ai_section_title": "AI assistance",
+ "smart_suggestions_label": "AI suggestions",
+ "forecast_label": "Forecast low-stock products",
+ "auto_add_label": "Auto-add to list when",
+ "auto_add_suffix": "remaining in stock (0 = only when empty)"
+ },
+ "ha": {
+ "tab": "Home Assistant",
+ "title": "Home Assistant",
+ "hint": "Connect EverShelf to Home Assistant for automations, push notifications and REST sensors.",
+ "enabled": "Enable Home Assistant integration",
+ "connection_title": "Connection",
+ "url_label": "Home Assistant URL",
+ "url_placeholder": "http://192.168.1.50:8123",
+ "url_hint": "Base URL of your Home Assistant instance (e.g. http://homeassistant.local:8123).",
+ "token_label": "Long-Lived Access Token",
+ "token_hint": "Generate from HA Profile → Security → Long-Lived Access Tokens.",
+ "token_placeholder": "eyJhbGci...",
+ "token_saved": "Token saved (hidden for security)",
+ "test_btn": "Test connection",
+ "test_ok": "Connected to {version}",
+ "test_fail": "Connection failed: {error}",
+ "test_bad_token": "HA reachable but token is invalid",
+ "testing": "Testing…",
+ "error_no_url": "Please enter the Home Assistant URL first.",
+ "tts_title": "TTS on Smart Speaker",
+ "tts_hint": "Read recipe steps aloud on a Home Assistant media player.",
+ "tts_entity_label": "Media player entity ID",
+ "tts_entity_placeholder": "media_player.living_room",
+ "tts_entity_hint": "Entity ID of the HA media player. Find it in HA: Developer Tools → States.",
+ "tts_platform_label": "TTS platform",
+ "tts_platform_speak": "tts.speak (recommended)",
+ "tts_platform_notify": "notify.* (notification service)",
+ "tts_apply_btn": "Apply HA preset to TTS tab",
+ "tts_apply_hint": "Pre-fills the TTS tab with the Home Assistant URL and token.",
+ "tts_preset_applied": "HA preset applied to TTS tab.",
+ "webhook_title": "Webhook Automations",
+ "webhook_hint": "Send data to Home Assistant when pantry events occur. Create an HA automation with a Webhook trigger and paste the generated ID here.",
+ "webhook_id_label": "Webhook ID",
+ "webhook_id_placeholder": "evershelf_webhook_abc123",
+ "webhook_id_hint": "ID of the webhook created in HA. Copy from: HA → Settings → Automations → Create → Webhook Trigger.",
+ "webhook_events_label": "Notify on these events",
+ "event_expiry": "Expiring products (daily)",
+ "event_shopping": "Item added to shopping list",
+ "event_stock": "Stock level updated",
+ "expiry_days_label": "Expiry lead time (days)",
+ "expiry_days_hint": "Send the expiry alert N days before the expiry date.",
+ "webhook_help": "In HA: Settings → Automations → Create automation → Trigger: Webhook → copy the generated ID above.",
+ "notify_title": "Push Notifications",
+ "notify_hint": "Send push notifications to your phone via a Home Assistant notify service.",
+ "notify_service_label": "Notify service",
+ "notify_service_placeholder": "notify.mobile_app_my_phone",
+ "notify_service_hint": "HA notify service name (e.g. notify.mobile_app_phone). Leave empty to disable.",
+ "sensor_title": "REST Sensors",
+ "sensor_hint": "Add to configuration.yaml to create EverShelf sensors in Home Assistant.",
+ "sensor_copy_btn": "Copy YAML",
+ "sensor_copied": "YAML copied to clipboard!",
+ "save_btn": "Save HA settings",
+ "ha_hint": "If you use Home Assistant, use the Home Assistant tab to configure TTS, webhooks and sensors."
+ }
+ },
+ "expiry": {
+ "today": "TODAY",
+ "tomorrow": "Tomorrow",
+ "days": "{days} days",
+ "expired_days": "{days}d ago",
+ "expired_yesterday": "Yesterday",
+ "expired_today": "Today",
+ "badge_today": "⚠️ Expires today!",
+ "badge_tomorrow": "⏰ Tomorrow",
+ "badge_tomorrow_long": "⏰ Expires tomorrow",
+ "badge_days": "⏰ {n} days",
+ "badge_expired_ago": "⚠️ Expired {n}d ago",
+ "badge_expired": "⛔ Expired!",
+ "badge_stable": "✅ Stable",
+ "badge_expiring_short": "⏰ Exp. in {n}d",
+ "badge_ok_still": "✅ Still {n}d",
+ "badge_expires_red": "🔴 Exp. in {n}d",
+ "badge_expires_yellow": "🟡 Exp. in {n}d",
+ "badge_expired_bare": "⚠️ Expired",
+ "badge_expires_warn": "⚠️ Exp. in {n}d",
+ "badge_days_left": "⏳ ~{n}d left",
+ "days_approx": "~{n} days",
+ "weeks_approx": "~{n} weeks",
+ "months_approx": "~{n} months",
+ "years_approx": "~{n} years",
+ "expired_today_long": "Expired today",
+ "expired_ago_long": "Expired {n} days ago",
+ "expired_suffix": "— Expired!",
+ "expired_suffix_ok": "— Expired (still ok)",
+ "expired_suffix_warning": "— Expired (check first)",
+ "opened_ago_long": "Opened {n} days ago",
+ "opened_today_long": "Opened today",
+ "opened_suffix": "— Opened too long!",
+ "opened_suffix_ok": "— Opened (still ok)",
+ "opened_suffix_warning": "— Opened (check first)",
+ "days_compact": "{n}d",
+ "badge_check_soon": "Check soon"
+ },
+ "status": {
+ "ok": "OK",
+ "check": "Check",
+ "discard": "Discard",
+ "tip_freezer_ok": "In freezer: still safe (~{n}d margin)",
+ "tip_freezer_check": "In freezer for a long time, may have lost quality. Consume soon",
+ "tip_freezer_danger": "In freezer too long, risk of freezer burn and degradation",
+ "tip_highRisk_check": "Expired recently, check smell and appearance before consuming",
+ "tip_highRisk_danger": "Perishable product expired: discard for safety",
+ "tip_medRisk_check1": "Check appearance and smell before consuming",
+ "tip_medRisk_check2": "Expired a while ago, check carefully before use",
+ "tip_medRisk_danger": "Too long since expiry, better to discard",
+ "tip_lowRisk_ok": "Long-lasting product, still safe to consume",
+ "tip_lowRisk_check": "Expired over a month ago, check package integrity",
+ "tip_lowRisk_danger": "Expired too long ago, better not to risk it"
+ },
+ "toast": {
+ "product_saved": "Product saved!",
+ "product_created": "Product created!",
+ "product_updated": "✅ Product updated!",
+ "product_removed": "Product removed",
+ "updated": "Updated!",
+ "quantity_confirmed": "✓ Quantity confirmed",
+ "added_to_inventory": "✅ {name} added!",
+ "removed_from_list": "✅ {name} removed from the list!",
+ "removed_from_list_short": "Removed from the list",
+ "added_to_shopping": "🛒 Added to the shopping list!",
+ "removed_from_shopping": "🛒 Removed from the shopping list",
+ "finished_to_bring": "🛒 Product finished → added to Bring!",
+ "thrown_away": "🗑️ {name} thrown away!",
+ "thrown_away_partial": "🗑️ Thrown away {qty} {unit} of {name}",
+ "finished_all": "📤 {name} finished!",
+ "vacuum_sealed": "{name} saved as vacuum sealed",
+ "product_finished_confirmed": "✅ Removed — add it again when you restock",
+ "appliance_added": "Appliance added",
+ "item_added": "{name} added"
+ },
+ "antiwaste": {
+ "title": "🌱 Anti-Waste Report",
+ "grade_label": "Grade",
+ "you": "You",
+ "avg_label": "Avg",
+ "better": "🎉 You lose {diff}% less than the {country}!",
+ "worse": "⚠️ You lose more than the {country}. Room for improvement!",
+ "on_par": "→ You're at the {country}. You can do better!",
+ "saved_money": "~{amount}/month saved",
+ "saved_meals": "~{n} meals saved",
+ "saved_co2": "{n} kg CO₂ avoided",
+ "trend_title": "Trend (last 3 months)",
+ "months_ago_2": "-60 days",
+ "months_ago_1": "-30 days",
+ "this_month": "Now",
+ "country_it": "Italian avg",
+ "country_de": "German avg",
+ "country_en": "US average",
+ "source": "Sources: REDUCE, Eurostat, USDA 2021",
+ "live_on": "Live data",
+ "live_off": "Offline",
+ "meals": "meals",
+ "annual_info": "📅 You ~{you} kg/yr · avg ~{avg} kg/yr",
+ "badge_rate": "loss rate",
+ "badge_saved_money": "saved vs avg",
+ "badge_wasted": "items lost",
+ "badge_better": "less than avg"
+ },
+ "error": {
+ "generic": "Error",
+ "network": "Network error",
+ "no_api_key": "Configure the API key in settings",
+ "loading": "Error loading product",
+ "not_found": "Product not found",
+ "not_found_manual": "Product not found. Enter it manually.",
+ "search": "Search error. Try again.",
+ "search_short": "Search error",
+ "save": "Error saving",
+ "connection": "Connection error",
+ "camera": "Cannot access camera",
+ "bring_add": "Error adding to Bring!",
+ "bring_connection": "Bring! connection error",
+ "identification": "Identification error",
+ "ai_quota": "AI quota exhausted. Please try again in a few minutes.",
+ "barcode_empty": "Enter a barcode",
+ "barcode_format": "Barcode must contain only numbers (4-14 digits)",
+ "min_chars": "Type at least 2 characters",
+ "not_in_inventory": "Product not in inventory",
+ "appliance_exists": "Appliance already exists",
+ "already_exists": "Already exists",
+ "network_retry": "Connection error. Try again.",
+ "select_items": "Select at least one product",
+ "server_offline": "Server connection lost",
+ "server_restored": "Server connection restored",
+ "server_retry": "Retry",
+ "unknown": "Unknown error",
+ "prefix": "Error",
+ "no_inventory_entry": "No inventory entry found",
+ "offline_title": "No connection",
+ "offline_subtitle": "The app cannot reach the server. Check your Wi-Fi connection.",
+ "offline_checking": "Checking connection…",
+ "offline_restored": "Connection restored!",
+ "offline_continue": "Continue in offline mode",
+ "offline_reading_cache": "Reading from local cache",
+ "offline_ops_pending": "{n} operations pending",
+ "offline_synced": "{n} operations synced",
+ "offline_ai_disabled": "Not available offline",
+ "offline_cache_ready": "Offline — {n} items cached"
+ },
+ "confirm": {
+ "remove_item": "Do you really want to remove this product from inventory?",
+ "kiosk_exit": "Exit kiosk mode?",
+ "cancel": "Cancel",
+ "proceed": "Confirm"
+ },
+ "location": {
+ "dispensa": "Pantry",
+ "frigo": "Fridge",
+ "freezer": "Freezer"
+ },
+ "edit": {
+ "title": "Edit {name}",
+ "unknown_hint": "Enter the product name and information",
+ "label_name": "🏷️ Product name",
+ "choose_location_title": "Which location?",
+ "choose_location_hint": "Choose the location to edit:"
},
"screensaver": {
- "label": "Enable screensaver",
- "card_title": "🌙 Screensaver",
- "card_hint": "Shows a clock with useful facts after 5 minutes of inactivity. Disabled by default.",
- "timeout_1": "1 minute",
- "timeout_2": "2 minutes",
- "timeout_5": "5 minutes",
- "timeout_10": "10 minutes",
- "timeout_15": "15 minutes",
- "timeout_30": "30 minutes",
- "timeout_60": "1 hour",
- "start_after": "⏱️ Start after"
+ "recipe_btn": "Recipes",
+ "scan_btn": "Scan product"
+ },
+ "days": {
+ "mon": "Monday",
+ "tue": "Tuesday",
+ "wed": "Wednesday",
+ "thu": "Thursday",
+ "fri": "Friday",
+ "sat": "Saturday",
+ "sun": "Sunday",
+ "mon_short": "Mon",
+ "tue_short": "Tue",
+ "wed_short": "Wed",
+ "thu_short": "Thu",
+ "fri_short": "Fri",
+ "sat_short": "Sat",
+ "sun_short": "Sun"
+ },
+ "meal_types": {
+ "lunch": "Lunch",
+ "dinner": "Dinner",
+ "colazione": "Breakfast",
+ "merenda": "Snack",
+ "dolce": "Dessert",
+ "succo": "Fruit Juice",
+ "pranzo": "Lunch",
+ "cena": "Dinner"
},
"scale": {
- "title": "⚖️ Smart Scale",
- "hint": "Connect a Bluetooth scale via the Android gateway to automatically read weight.",
- "tab": "Smart Scale",
- "enabled": "✅ Enable smart scale",
- "url_label": "🌐 WebSocket Gateway URL",
- "url_placeholder": "ws://192.168.1.x:8765",
- "url_hint": "URL shown by the Android app (same Wi-Fi network). E.g.:",
- "test_btn": "🔗 Test connection",
- "download_btn": "📥 Download Android Gateway (APK)",
- "download_hint": "Android app that bridges your BLE scale and EverShelf.",
- "download_sub": "Source: evershelf-scale-gateway/ in the project root",
- "live_weight": "real-time weight",
- "auto_reconnect": "🔁 Reconnect: automatic",
- "kiosk_title": "📡 BLE Scale integrated in Kiosk",
- "kiosk_hint": "The scale is directly managed by the internal BLE Gateway of the kiosk. To pair a new device, use the configuration wizard.",
- "kiosk_reconfigure": "🔄 Reconfigure BLE Scale",
- "ble_protocols": "
🔌 Supported BLE protocols:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — weight, fat, BMI
- Xiaomi Mi Body Composition Scale 2
- Generic — automatic heuristic for 100+ models
"
+ "status_connected": "Scale connected",
+ "status_searching": "Gateway connected, waiting for scale…",
+ "status_disconnected": "Scale gateway unreachable",
+ "status_error": "Gateway connection error",
+ "not_connected": "Scale gateway not connected",
+ "read_btn": "⚖️ Read from scale",
+ "reading_title": "Scale reading",
+ "place_on_scale": "Place the product on the scale…",
+ "waiting_stable": "Weight will be captured automatically once the reading is stable.",
+ "no_url": "Enter the gateway URL",
+ "testing": "⏳ Testing connection…",
+ "connected_ok": "Gateway connection successful!",
+ "timeout": "Timeout: no response from gateway",
+ "error_connect": "Cannot connect to gateway",
+ "tab": "Smart Scale",
+ "low_weight": "Weight < 10 g · enter manually\n(auto-reading requires at least 10 g)",
+ "density_hint": "(density {density} g/ml)",
+ "ml_hint": "(will be converted to ml)",
+ "weight_detected": "Weight detected — wait 10s for stability…",
+ "weight_too_low": "Weight too low — waiting…",
+ "stable": "✓ Stable",
+ "auto_confirm": "✅ {val} {unit} — auto-confirm in 5s (tap to cancel)",
+ "cancelled_replace": "Cancelled — replace the ingredient on the scale to resume"
+ },
+ "prediction": {
+ "expected_qty": "Expected: {expected} {unit}",
+ "actual_qty": "Current: {actual} {unit}",
+ "check_suggestion": "Check or weigh the remaining quantity"
+ },
+ "date": {
+ "today": "📅 Today",
+ "yesterday": "📅 Yesterday"
+ },
+ "scanner": {
+ "title_barcode": "🔖 Scan Barcode",
+ "barcode_hint": "Frame the product barcode",
+ "barcode_manual_placeholder": "Or enter manually...",
+ "barcode_use_btn": "✅ Use this code",
+ "ai_identifying": "🤖 Identifying product...",
+ "ai_analyzing": "🤖 AI analysis in progress...",
+ "product_label_hint": "Frame the product label",
+ "expiry_label_hint": "Frame the expiry date printed on the product",
+ "capture_btn": "📸 Capture",
+ "capture_photo_btn": "📸 Take Photo",
+ "retake_btn": "🔄 Retake",
+ "camera_error_hint": "Ensure you use HTTPS and have granted camera permissions.
You can enter the barcode manually or use AI identification.",
+ "no_barcode": "No barcode",
+ "save_new_btn": "🆕 None of these — save as new",
+ "expiry_found": "Date found",
+ "expiry_read_fail": "Cannot read the date.",
+ "expiry_raw_label": "Read"
+ },
+ "lowstock": {
+ "title": "⚠️ Running low!",
+ "message": "{name} is running low — only {qty} remaining.",
+ "question": "Do you want to add it to the shopping list?",
+ "yes": "🛒 Yes, add to Bring!",
+ "no": "No, I'm fine for now"
+ },
+ "move": {
+ "title": "📦 Move the rest?",
+ "question": "Do you want to move the {thing} of {name} to another location?",
+ "question_short": "Do you want to move the {thing} to another location?",
+ "thing_opened": "opened package",
+ "thing_rest": "rest",
+ "stay_btn": "No, stay in {location}",
+ "moved_toast": "📦 Opened package moved to {location}",
+ "vacuum_restore": "Restore vacuum sealed",
+ "vacuum_seal_rest": "Vacuum seal the rest"
+ },
+ "nova": {
+ "1": "Unprocessed",
+ "2": "Culinary ingredient",
+ "3": "Processed",
+ "4": "Ultra-processed"
+ },
+ "meal_plan_types": {
+ "pasta": "Pasta",
+ "riso": "Rice",
+ "carne": "Meat",
+ "pesce": "Fish",
+ "legumi": "Legumes",
+ "uova": "Eggs",
+ "formaggio": "Cheese",
+ "pizza": "Pizza",
+ "affettati": "Cold Cuts",
+ "verdure": "Veggies",
+ "zuppa": "Soup",
+ "insalata": "Salad",
+ "pane": "Bread/Sandwich",
+ "dolce": "Dessert",
+ "libero": "Free"
+ },
+ "meal_sub": {
+ "dolce_torta": "Cake",
+ "dolce_crema": "Cream / Pudding",
+ "dolce_crumble": "Crumble / Tart",
+ "dolce_biscotti": "Cookies / Pastries",
+ "dolce_frutta": "Fruit Dessert",
+ "succo_dolce": "Sweet / Fruity",
+ "succo_energizzante": "Energizing",
+ "succo_detox": "Detox / Green",
+ "succo_rinfrescante": "Refreshing",
+ "succo_vitaminico": "Vitamin / Citrus"
+ },
+ "meal_plan": {
+ "reset_success": "Weekly plan reset",
+ "not_available": "not available in pantry",
+ "suggested_by": "suggested by weekly plan"
+ },
+ "nutrition": {
+ "title": "🥗 Food Analysis",
+ "score_excellent": "😄 Excellent",
+ "score_good": "🙂 Good",
+ "score_improve": "😬 Improvable",
+ "label_health": "🌿 Health",
+ "label_variety": "🎨 Variety",
+ "label_fresh": "❄️ Fresh",
+ "source": "Based on {n} products in your pantry · EverShelf",
+ "products_count": "products",
+ "today_title": "🥗 Your pantry today",
+ "products_n": "{n} products"
+ },
+ "facts": {
+ "greeting_morning": "Good morning",
+ "greeting_afternoon": "Good afternoon",
+ "greeting_evening": "Good evening",
+ "pantry_waiting": "{greeting}! Your Pantry awaits.",
+ "expired_one": "You have 1 expired product in your pantry. Check it!",
+ "expired_many": "You have {n} expired products in your pantry. Check them!",
+ "expired_list": "Expired products: {names}",
+ "expired_list_more": "and {n} more",
+ "freezer_expired_ok": "{name} is expired, but being in the freezer it may still be fine! Check it.",
+ "freezer_expired_old": "{name} in the freezer has been expired too long. Better to discard it.",
+ "fridge_expired_one": "You have 1 expired product in the fridge!",
+ "fridge_expired_many": "You have {n} expired products in the fridge!",
+ "expiring_today": "{name} expires today! Use it right away.",
+ "expiring_tomorrow": "{name} expires tomorrow. Plan ahead!",
+ "expiring_days": "{name} expires in {days} days.",
+ "expiring_many": "You have {n} products expiring soon.",
+ "expiring_this_week": "{n} products expire this week. Plan your meals accordingly!",
+ "expiring_item_loc": "{name} ({loc}) expires in {days} {dayslabel}.",
+ "expiring_this_month": "{n} products will expire this month.",
+ "shopping_add": "Add to list: {names} 🛒",
+ "shopping_more": "and {n} more",
+ "shopping_empty": "Shopping list empty. All stocked up! ✅",
+ "in_fridge": "In the fridge: {name}.",
+ "in_freezer": "In the freezer: {name}. Don't forget it!",
+ "top_category": "Top category is {icon} {cat} with {n} products.",
+ "cat_meat": "You have {n} meat products. 🥩",
+ "cat_dairy": "You have {n} dairy products at home. 🥛",
+ "cat_veggies": "You have {n} types of vegetables. Great for your health! 🥬",
+ "cat_fruit": "You have {n} types of fruit. 🍎",
+ "cat_drinks": "You have {n} drinks available. 🥤",
+ "cat_frozen": "You have {n} frozen items. ❄️",
+ "cat_pasta": "You have {n} types of pasta. 🍝 How about a carbonara?",
+ "cat_canned": "You have {n} canned goods in your pantry. 🥫",
+ "cat_snacks": "You have {n} snacks. Resist the temptation! 🍪",
+ "cat_condiments": "You have {n} condiments available. 🧂",
+ "item_random": "Did you know? You have {name} in {loc}.",
+ "item_qty": "{name}: you have {qty}.",
+ "no_expiry_count": "{n} products have no expiry date set.",
+ "furthest_expiry": "The product with the furthest expiry is {name}: {months} months.",
+ "high_qty": "You have a great stock of {name}: {qty}!",
+ "low_qty_item": "{name} is running low. Add it to your shopping list?",
+ "low_qty_count": "{n} products are almost out.",
+ "morning_bread": "Good morning! You have bread for breakfast. 🍞",
+ "morning_milk": "Is there milk in the fridge for a cappuccino? ☕🥛",
+ "morning_fruit": "Good morning! Some fresh fruit is a great way to start. 🍎",
+ "noon_pasta": "Lunchtime… How about a nice bowl of pasta? 🍝",
+ "noon_salad": "A fresh salad for lunch? You have {n} vegetables! 🥗",
+ "evening_meat": "For dinner you could use the meat you have. 🥩",
+ "evening_fish": "How about fish for dinner? 🐟",
+ "evening_expiring": "You have {n} products expiring this week — use them tonight!",
+ "night_reminder": "Good night! Remember to use tomorrow: {names}.",
+ "weekly_balance": "Weekly balance: +{in} added, −{out} consumed.",
+ "weekly_added": "You added {n} products this week.",
+ "weekly_consumed": "You consumed {n} products this week. Well done!",
+ "tip_freezer": "💡 Frozen products last much longer than the expiry date.",
+ "tip_bread": "💡 Frozen bread keeps its freshness for weeks.",
+ "tip_fifo": "💡 To avoid waste, use products closest to expiry first (FIFO).",
+ "tip_meat": "💡 Meat in the freezer can last up to 6 months safely.",
+ "tip_no_refreeze": "💡 Never refreeze a thawed product. Cook it right away!",
+ "tip_fridge": "💡 A tidy fridge saves you time and money.",
+ "tip_canned": "💡 Opened canned goods should go in the fridge and be consumed within a few days.",
+ "top_brand": "The most common brand in your pantry is {brand} with {n} products.",
+ "combo_pasta": "You have pasta and condiments: ready for a first course! 🍝",
+ "combo_sandwich": "Bread and meat: a quick sandwich is always a good idea! 🥪",
+ "combo_balanced": "Vegetables and meat: you have everything for a balanced meal! 🥗🥩",
+ "pantry_empty": "The pantry is empty! Time to go shopping. 🛒",
+ "pantry_empty_scan": "No products registered. Scan something to start!",
+ "location_distribution": "Distribution: {parts}",
+ "day": "day",
+ "days": "days"
+ },
+ "kiosk_session": {
+ "first_item": "First item: {name}!",
+ "items_two_four": "{n} items — warming up 🚀",
+ "items_five_nine": "{n} items — great pace! 💪",
+ "items_ten_twenty": "{n} items — almost a record 🏆",
+ "items_twenty_plus": "{n} items — epic shopping! 🛒🔥",
+ "duplicates_one": "1 duplicate (same thing twice)",
+ "duplicates_many": "{n} duplicates (picked multiple times)",
+ "top_category": "Top category: {cat} ({count}×)",
+ "items_fallback": "{n} item{plural} added"
},
"kiosk": {
- "hint": "Turn an Android tablet into an always-on EverShelf panel with built-in BLE scale gateway.",
- "download_btn": "📥 Download EverShelf Kiosk (APK)",
- "download_sub": "Full-screen kiosk mode + integrated scale gateway. Source: evershelf-kiosk/",
- "native_title": "Kiosk Configuration",
- "native_hint": "Server URL, BLE scale, screensaver and setup wizard.",
- "native_btn": "Open kiosk configuration",
- "native_tap_hint": "Tap the gear button at the top right",
- "native_update_hint": "Update the kiosk app to use this feature",
- "update_title": "Kiosk Update",
- "check_updates_btn": "🔍 Check for updates",
- "needs_update": "⚠️ The installed kiosk does not support this feature. Update the kiosk app to enable it."
+ "check_btn": "🔍 Check for updates",
+ "checking": "⏳ Checking…",
+ "error_check": "Error during update check",
+ "error_start_install": "Error starting installation",
+ "version_installed": "Installed: {v}",
+ "update_available": "⬆️ New version available:
{latest} (installed: {current})",
+ "up_to_date": "✅ You are up to date — version
{v}",
+ "too_old": "⚠️ The installed kiosk is too old for automatic update checking.
Press the button below to download and install the new version directly.",
+ "manual_install": "⚠️ This kiosk does not support automatic installation.
Manual procedure:1. Exit the kiosk (✕ button top left)
2. Uninstall the EverShelf Kiosk app
3. Download and install the new APK from GitHub:",
+ "starting_download": "⏳ Starting download…",
+ "install_btn": "⬇️ Install update",
+ "exit_title": "Exit kiosk",
+ "refresh_title": "Refresh page"
},
- "saved": "✅ Configuration saved!",
- "saved_local": "✅ Configuration saved locally",
- "saved_local_error": "⚠️ Saved locally, server error: {error}",
- "theme": {
- "title": "🌙 Appearance",
- "hint": "Choose the interface theme.",
- "label": "🌙 Theme",
- "off": "☀️ Light",
- "on": "🌙 Dark",
- "auto": "🔄 Automatic (time of day)"
+ "update": {
+ "new_version": "New version",
+ "btn": "Update"
},
- "zerowaste": {
- "card_title": "♻️ Zero-waste tips",
- "card_hint": "During cooking, show tips on how to reuse scraps generated in each step (peels, cooking water, etc.). Disabled by default.",
- "label": "Show tips during cooking"
+ "gemini": {
+ "chat_title": "Chat with Gemini",
+ "not_configured": "🤖 Gemini not configured — set GEMINI_API_KEY in settings"
},
- "backup": {
- "tab": "Backup",
- "local_title": "Local Backup",
- "local_hint": "Daily database snapshot. Configure how many days of backups to keep.",
- "enabled": "Enable daily automatic backup",
- "retention_days": "Retention (days)",
- "retention_info": "Backups are kept for",
- "backup_now": "Backup Now",
- "backing_up": "Backing up…",
- "backed_up": "Backup complete",
- "backup_error": "Backup error",
- "last_backup": "Last backup",
- "no_backup_yet": "No backup has been created yet",
- "list_empty": "No backups available",
- "restore_btn": "Restore",
- "restore_confirm": "Restore backup",
- "delete_btn": "Delete",
- "delete_confirm": "Delete backup",
- "gdrive_title": "Google Drive",
- "gdrive_hint": "Automatically back up to Google Drive via OAuth 2.0. No external libraries required.",
- "gdrive_enabled": "Enable Google Drive backup",
- "gdrive_folder_id": "Drive Folder ID",
- "gdrive_folder_id_hint": "Copy the ID from the Drive folder URL: …/folders/
ID",
- "gdrive_retention_days": "Drive retention (days, 0=keep all)",
- "gdrive_test": "Test Connection",
- "gdrive_ok": "Connection successful!",
- "gdrive_error": "Connection failed",
- "gdrive_push_now": "Upload to Drive Now",
- "gdrive_pushing": "Uploading…",
- "gdrive_pushed": "Uploaded to Drive",
- "gdrive_wizard_hint": "Optional: automatically back up to Google Drive daily via OAuth 2.0.",
- "gdrive_skip": "Skip — configure later in Settings",
- "gdrive_client_id": "Client ID",
- "gdrive_client_secret": "Client Secret",
- "gdrive_redirect_uri_hint": "Add
http://localhost as an authorized redirect URI in Google Cloud Console. This works on any server, even without a public domain.",
- "gdrive_code_title": "Paste the authorization URL or code",
- "gdrive_code_hint": "After authorizing, the browser will open http://localhost and may show a connection error — that is expected. Copy the URL from the address bar (e.g.
http://localhost/?code=4%2F0A...) and paste it here.",
- "gdrive_code_submit": "Submit",
- "gdrive_code_empty": "Paste the URL or authorization code first",
- "gdrive_redirect_uri_label": "Redirect URI (add this in Google Cloud Console):",
- "gdrive_oauth_authorize": "Authorize with Google",
- "gdrive_oauth_authorized": "Authorized",
- "gdrive_oauth_not_authorized": "Not authorized yet",
- "gdrive_oauth_window_opened": "Browser window opened — authorize and come back",
- "gdrive_oauth_how_to": "How to set up OAuth 2.0 (step by step)",
- "gdrive_oauth_steps": "
Go to console.cloud.google.com and select your projectEnable the Google Drive API: APIs & Services → Enable APIs → Google Drive APIGo to APIs & Services → Credentials → Create Credentials → OAuth client IDApplication type: Web application; add http://localhost as an Authorized redirect URICopy the Client ID and Client Secret into the fields above and saveClick Authorize with Google, sign in and grant accessThe browser will open http://localhost (a connection error is expected): copy the URL from the address bar and paste it in the field that appears below"
+ "appliances": {
+ "empty": "No appliances added"
},
- "info": {
- "tab": "Info",
- "ai_title": "Gemini AI — Token Usage",
- "ai_hint": "Monthly consumption and estimated cost for the current API key.",
- "loading": "Loading…",
- "total_tokens": "Total tokens",
- "est_cost": "Est. cost",
- "input_tok": "Input tokens",
- "output_tok": "Output tokens",
- "ai_calls": "Calls",
- "by_action": "Breakdown by function",
- "by_model": "Breakdown by model",
- "pricing_note": "Gemini reference pricing: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.",
- "system_title": "System",
- "db_size": "Database",
- "log_size": "Logs",
- "log_level": "Log level",
- "ai_overview": "AI usage overview, inventory and system status",
- "calls_unit": "calls",
- "inv_title": "Inventory",
- "inv_active": "Active",
- "inv_products": "Total products",
- "inv_expiring": "Expiring (7d)",
- "inv_expired": "Expired",
- "inv_finished": "Finished",
- "act_title": "Monthly activity",
- "act_tx_month": "Movements",
- "act_restock": "Restocks",
- "act_use": "Usages",
- "act_new_products": "New products",
- "act_tx_year": "Yearly movements",
- "price_cache": "Price cache",
- "cache_entries": "products",
- "last_backup": "Last backup",
- "bring_days": "token expires in {n} days",
- "bring_expired": "token expired",
- "year_label": "Year {year}",
- "currency_title": "Currency",
- "currency_hint": "The currency used for all costs and prices in the app."
+ "about": {
+ "title": "About",
+ "version": "Version",
+ "report_bug": "Report a Bug",
+ "report_bug_hint": "Something not working? Send us a report directly from the app.",
+ "report_bug_modal_title": "Report a Bug",
+ "report_type_bug": "Bug",
+ "report_type_feature": "Feature",
+ "report_type_question": "Question",
+ "report_field_title": "Title",
+ "report_field_title_ph": "Brief description of the issue",
+ "report_field_desc": "Description",
+ "report_field_desc_ph": "Describe the issue in detail…",
+ "report_field_steps": "Steps to reproduce (optional)",
+ "report_field_steps_ph": "1. Go to…\n2. Tap…\n3. See the error…",
+ "report_auto_info": "Automatically attached: version {version}, language {lang}.",
+ "report_send_btn": "Send report",
+ "report_bug_sending": "Sending…",
+ "report_bug_sent": "Report sent — thank you!",
+ "report_bug_error": "Could not send the report. Check your connection.",
+ "changelog": "Changelog",
+ "github": "GitHub Repository"
},
- "tab_general": "General",
- "shopping": {
- "tab": "Shopping list",
- "title": "Shopping list",
- "hint": "Configure the built-in shopping list or connect Bring!.",
- "enable_label": "Enable shopping list",
- "mode_label": "Provider",
- "mode_internal": "Built-in (no Bring!)",
- "mode_bring": "Bring! (external app)",
- "bring_section_title": "Bring! configuration",
- "ai_section_title": "AI assistance",
- "smart_suggestions_label": "AI suggestions",
- "forecast_label": "Forecast low-stock products",
- "auto_add_label": "Auto-add to list when",
- "auto_add_suffix": "remaining in stock (0 = only when empty)"
+ "export": {
+ "title": "Export inventory",
+ "hint": "Download the current inventory as CSV or open a print-ready version (PDF).",
+ "btn_csv": "Download CSV",
+ "btn_pdf": "PDF / Print",
+ "btn_title": "Export"
},
- "ha": {
- "tab": "Home Assistant",
- "title": "Home Assistant",
- "hint": "Connect EverShelf to Home Assistant for automations, push notifications and REST sensors.",
- "enabled": "Enable Home Assistant integration",
- "connection_title": "Connection",
- "url_label": "Home Assistant URL",
- "url_placeholder": "http://192.168.1.50:8123",
- "url_hint": "Base URL of your Home Assistant instance (e.g. http://homeassistant.local:8123).",
- "token_label": "Long-Lived Access Token",
- "token_hint": "Generate from HA Profile → Security → Long-Lived Access Tokens.",
- "token_placeholder": "eyJhbGci...",
- "token_saved": "Token saved (hidden for security)",
- "test_btn": "Test connection",
- "test_ok": "Connected to {version}",
- "test_fail": "Connection failed: {error}",
- "test_bad_token": "HA reachable but token is invalid",
- "testing": "Testing…",
- "error_no_url": "Please enter the Home Assistant URL first.",
- "tts_title": "TTS on Smart Speaker",
- "tts_hint": "Read recipe steps aloud on a Home Assistant media player.",
- "tts_entity_label": "Media player entity ID",
- "tts_entity_placeholder": "media_player.living_room",
- "tts_entity_hint": "Entity ID of the HA media player. Find it in HA: Developer Tools → States.",
- "tts_platform_label": "TTS platform",
- "tts_platform_speak": "tts.speak (recommended)",
- "tts_platform_notify": "notify.* (notification service)",
- "tts_apply_btn": "Apply HA preset to TTS tab",
- "tts_apply_hint": "Pre-fills the TTS tab with the Home Assistant URL and token.",
- "tts_preset_applied": "HA preset applied to TTS tab.",
- "webhook_title": "Webhook Automations",
- "webhook_hint": "Send data to Home Assistant when pantry events occur. Create an HA automation with a Webhook trigger and paste the generated ID here.",
- "webhook_id_label": "Webhook ID",
- "webhook_id_placeholder": "evershelf_webhook_abc123",
- "webhook_id_hint": "ID of the webhook created in HA. Copy from: HA → Settings → Automations → Create → Webhook Trigger.",
- "webhook_events_label": "Notify on these events",
- "event_expiry": "Expiring products (daily)",
- "event_shopping": "Item added to shopping list",
- "event_stock": "Stock level updated",
- "expiry_days_label": "Expiry lead time (days)",
- "expiry_days_hint": "Send the expiry alert N days before the expiry date.",
- "webhook_help": "In HA: Settings → Automations → Create automation → Trigger: Webhook → copy the generated ID above.",
- "notify_title": "Push Notifications",
- "notify_hint": "Send push notifications to your phone via a Home Assistant notify service.",
- "notify_service_label": "Notify service",
- "notify_service_placeholder": "notify.mobile_app_my_phone",
- "notify_service_hint": "HA notify service name (e.g. notify.mobile_app_phone). Leave empty to disable.",
- "sensor_title": "REST Sensors",
- "sensor_hint": "Add to configuration.yaml to create EverShelf sensors in Home Assistant.",
- "sensor_copy_btn": "Copy YAML",
- "sensor_copied": "YAML copied to clipboard!",
- "save_btn": "Save HA settings",
- "ha_hint": "If you use Home Assistant, use the Home Assistant tab to configure TTS, webhooks and sensors."
+ "startup": {
+ "connecting": "Connecting to server...",
+ "check_php_memory": "PHP memory",
+ "check_php_timeout": "PHP timeout",
+ "check_php_upload": "PHP upload",
+ "check_data_dir": "Data directory",
+ "check_rate_limits": "Rate limits dir",
+ "check_backups": "Backup dir",
+ "check_write_test": "Disk write test",
+ "check_disk_space": "Disk space",
+ "check_db_legacy": "Legacy DB (dispensa.db)",
+ "check_db_connect": "Database connection",
+ "check_db_tables": "Database tables",
+ "check_db_integrity": "Database integrity",
+ "check_db_wal": "WAL mode",
+ "check_db_size": "Database size",
+ "check_db_rows": "Inventory data",
+ "check_env": ".env file",
+ "check_gemini": "Gemini AI key",
+ "check_bring_creds": "Bring! credentials",
+ "check_bring_token": "Bring! token",
+ "check_tts": "Text-to-Speech URL",
+ "check_scale": "Scale gateway",
+ "check_curl_ssl": "cURL SSL",
+ "check_internet": "Internet connection",
+ "fresh_install": "fresh install",
+ "warnings_found": "warnings found",
+ "all_ok": "System OK",
+ "critical_error_short": "Critical error",
+ "critical_error": "Critical error: the app cannot start. Check your server logs.",
+ "critical_error_intro": "The app cannot start due to the following issues:",
+ "error_network": "Cannot reach the server.",
+ "error_network_detail": "The browser cannot reach the PHP server.\n\nPossible causes:\n• Apache/PHP server is not running\n• Network or firewall issue\n• Incorrect app URL\n\nMake sure the server is started and try again.",
+ "retry": "Retry",
+ "syncing_local": "Syncing local data...",
+ "sync_done": "Local data synced"
}
- },
- "expiry": {
- "today": "TODAY",
- "tomorrow": "Tomorrow",
- "days": "{days} days",
- "expired_days": "{days}d ago",
- "expired_yesterday": "Yesterday",
- "expired_today": "Today",
- "badge_today": "⚠️ Expires today!",
- "badge_tomorrow": "⏰ Tomorrow",
- "badge_tomorrow_long": "⏰ Expires tomorrow",
- "badge_days": "⏰ {n} days",
- "badge_expired_ago": "⚠️ Expired {n}d ago",
- "badge_expired": "⛔ Expired!",
- "badge_stable": "✅ Stable",
- "badge_expiring_short": "⏰ Exp. in {n}d",
- "badge_ok_still": "✅ Still {n}d",
- "badge_expires_red": "🔴 Exp. in {n}d",
- "badge_expires_yellow": "🟡 Exp. in {n}d",
- "badge_expired_bare": "⚠️ Expired",
- "badge_expires_warn": "⚠️ Exp. in {n}d",
- "badge_days_left": "⏳ ~{n}d left",
- "days_approx": "~{n} days",
- "weeks_approx": "~{n} weeks",
- "months_approx": "~{n} months",
- "years_approx": "~{n} years",
- "expired_today_long": "Expired today",
- "expired_ago_long": "Expired {n} days ago",
- "expired_suffix": "— Expired!",
- "expired_suffix_ok": "— Expired (still ok)",
- "expired_suffix_warning": "— Expired (check first)",
- "opened_ago_long": "Opened {n} days ago",
- "opened_today_long": "Opened today",
- "opened_suffix": "— Opened too long!",
- "opened_suffix_ok": "— Opened (still ok)",
- "opened_suffix_warning": "— Opened (check first)",
- "days_compact": "{n}d",
- "badge_check_soon": "Check soon"
- },
- "status": {
- "ok": "OK",
- "check": "Check",
- "discard": "Discard",
- "tip_freezer_ok": "In freezer: still safe (~{n}d margin)",
- "tip_freezer_check": "In freezer for a long time, may have lost quality. Consume soon",
- "tip_freezer_danger": "In freezer too long, risk of freezer burn and degradation",
- "tip_highRisk_check": "Expired recently, check smell and appearance before consuming",
- "tip_highRisk_danger": "Perishable product expired: discard for safety",
- "tip_medRisk_check1": "Check appearance and smell before consuming",
- "tip_medRisk_check2": "Expired a while ago, check carefully before use",
- "tip_medRisk_danger": "Too long since expiry, better to discard",
- "tip_lowRisk_ok": "Long-lasting product, still safe to consume",
- "tip_lowRisk_check": "Expired over a month ago, check package integrity",
- "tip_lowRisk_danger": "Expired too long ago, better not to risk it"
- },
- "toast": {
- "product_saved": "Product saved!",
- "product_created": "Product created!",
- "product_updated": "✅ Product updated!",
- "product_removed": "Product removed",
- "updated": "Updated!",
- "quantity_confirmed": "✓ Quantity confirmed",
- "added_to_inventory": "✅ {name} added!",
- "removed_from_list": "✅ {name} removed from the list!",
- "removed_from_list_short": "Removed from the list",
- "added_to_shopping": "🛒 Added to the shopping list!",
- "removed_from_shopping": "🛒 Removed from the shopping list",
- "finished_to_bring": "🛒 Product finished → added to Bring!",
- "thrown_away": "🗑️ {name} thrown away!",
- "thrown_away_partial": "🗑️ Thrown away {qty} {unit} of {name}",
- "finished_all": "📤 {name} finished!",
- "vacuum_sealed": "{name} saved as vacuum sealed",
- "product_finished_confirmed": "✅ Removed — add it again when you restock",
- "appliance_added": "Appliance added",
- "item_added": "{name} added"
- },
- "antiwaste": {
- "title": "🌱 Anti-Waste Report",
- "grade_label": "Grade",
- "you": "You",
- "avg_label": "Avg",
- "better": "🎉 You lose {diff}% less than the {country}!",
- "worse": "⚠️ You lose more than the {country}. Room for improvement!",
- "on_par": "→ You're at the {country}. You can do better!",
- "saved_money": "~{amount}/month saved",
- "saved_meals": "~{n} meals saved",
- "saved_co2": "{n} kg CO₂ avoided",
- "trend_title": "Trend (last 3 months)",
- "months_ago_2": "-60 days",
- "months_ago_1": "-30 days",
- "this_month": "Now",
- "country_it": "Italian avg",
- "country_de": "German avg",
- "country_en": "US average",
- "source": "Sources: REDUCE, Eurostat, USDA 2021",
- "live_on": "Live data",
- "live_off": "Offline",
- "meals": "meals",
- "annual_info": "📅 You ~{you} kg/yr · avg ~{avg} kg/yr",
- "badge_rate": "loss rate",
- "badge_saved_money": "saved vs avg",
- "badge_wasted": "items lost",
- "badge_better": "less than avg"
- },
- "error": {
- "generic": "Error",
- "network": "Network error",
- "no_api_key": "Configure the API key in settings",
- "loading": "Error loading product",
- "not_found": "Product not found",
- "not_found_manual": "Product not found. Enter it manually.",
- "search": "Search error. Try again.",
- "search_short": "Search error",
- "save": "Error saving",
- "connection": "Connection error",
- "camera": "Cannot access camera",
- "bring_add": "Error adding to Bring!",
- "bring_connection": "Bring! connection error",
- "identification": "Identification error",
- "ai_quota": "AI quota exhausted. Please try again in a few minutes.",
- "barcode_empty": "Enter a barcode",
- "barcode_format": "Barcode must contain only numbers (4-14 digits)",
- "min_chars": "Type at least 2 characters",
- "not_in_inventory": "Product not in inventory",
- "appliance_exists": "Appliance already exists",
- "already_exists": "Already exists",
- "network_retry": "Connection error. Try again.",
- "select_items": "Select at least one product",
- "server_offline": "Server connection lost",
- "server_restored": "Server connection restored",
- "server_retry": "Retry",
- "unknown": "Unknown error",
- "prefix": "Error",
- "no_inventory_entry": "No inventory entry found"
- },
- "confirm": {
- "remove_item": "Do you really want to remove this product from inventory?",
- "kiosk_exit": "Exit kiosk mode?",
- "cancel": "Cancel",
- "proceed": "Confirm"
- },
- "location": {
- "dispensa": "Pantry",
- "frigo": "Fridge",
- "freezer": "Freezer"
- },
- "edit": {
- "title": "Edit {name}",
- "unknown_hint": "Enter the product name and information",
- "label_name": "🏷️ Product name",
- "choose_location_title": "Which location?",
- "choose_location_hint": "Choose the location to edit:"
- },
- "screensaver": {
- "recipe_btn": "Recipes",
- "scan_btn": "Scan product"
- },
- "days": {
- "mon": "Monday",
- "tue": "Tuesday",
- "wed": "Wednesday",
- "thu": "Thursday",
- "fri": "Friday",
- "sat": "Saturday",
- "sun": "Sunday",
- "mon_short": "Mon",
- "tue_short": "Tue",
- "wed_short": "Wed",
- "thu_short": "Thu",
- "fri_short": "Fri",
- "sat_short": "Sat",
- "sun_short": "Sun"
- },
- "meal_types": {
- "lunch": "Lunch",
- "dinner": "Dinner",
- "colazione": "Breakfast",
- "merenda": "Snack",
- "dolce": "Dessert",
- "succo": "Fruit Juice",
- "pranzo": "Lunch",
- "cena": "Dinner"
- },
- "scale": {
- "status_connected": "Scale connected",
- "status_searching": "Gateway connected, waiting for scale…",
- "status_disconnected": "Scale gateway unreachable",
- "status_error": "Gateway connection error",
- "not_connected": "Scale gateway not connected",
- "read_btn": "⚖️ Read from scale",
- "reading_title": "Scale reading",
- "place_on_scale": "Place the product on the scale…",
- "waiting_stable": "Weight will be captured automatically once the reading is stable.",
- "no_url": "Enter the gateway URL",
- "testing": "⏳ Testing connection…",
- "connected_ok": "Gateway connection successful!",
- "timeout": "Timeout: no response from gateway",
- "error_connect": "Cannot connect to gateway",
- "tab": "Smart Scale",
- "low_weight": "Weight < 10 g · enter manually\n(auto-reading requires at least 10 g)",
- "density_hint": "(density {density} g/ml)",
- "ml_hint": "(will be converted to ml)",
- "weight_detected": "Weight detected — wait 10s for stability…",
- "weight_too_low": "Weight too low — waiting…",
- "stable": "✓ Stable",
- "auto_confirm": "✅ {val} {unit} — auto-confirm in 5s (tap to cancel)",
- "cancelled_replace": "Cancelled — replace the ingredient on the scale to resume"
- },
- "prediction": {
- "expected_qty": "Expected: {expected} {unit}",
- "actual_qty": "Current: {actual} {unit}",
- "check_suggestion": "Check or weigh the remaining quantity"
- },
- "date": {
- "today": "📅 Today",
- "yesterday": "📅 Yesterday"
- },
- "scanner": {
- "title_barcode": "🔖 Scan Barcode",
- "barcode_hint": "Frame the product barcode",
- "barcode_manual_placeholder": "Or enter manually...",
- "barcode_use_btn": "✅ Use this code",
- "ai_identifying": "🤖 Identifying product...",
- "ai_analyzing": "🤖 AI analysis in progress...",
- "product_label_hint": "Frame the product label",
- "expiry_label_hint": "Frame the expiry date printed on the product",
- "capture_btn": "📸 Capture",
- "capture_photo_btn": "📸 Take Photo",
- "retake_btn": "🔄 Retake",
- "camera_error_hint": "Ensure you use HTTPS and have granted camera permissions.
You can enter the barcode manually or use AI identification.",
- "no_barcode": "No barcode",
- "save_new_btn": "🆕 None of these — save as new",
- "expiry_found": "Date found",
- "expiry_read_fail": "Cannot read the date.",
- "expiry_raw_label": "Read"
- },
- "lowstock": {
- "title": "⚠️ Running low!",
- "message": "{name} is running low — only {qty} remaining.",
- "question": "Do you want to add it to the shopping list?",
- "yes": "🛒 Yes, add to Bring!",
- "no": "No, I'm fine for now"
- },
- "move": {
- "title": "📦 Move the rest?",
- "question": "Do you want to move the {thing} of {name} to another location?",
- "question_short": "Do you want to move the {thing} to another location?",
- "thing_opened": "opened package",
- "thing_rest": "rest",
- "stay_btn": "No, stay in {location}",
- "moved_toast": "📦 Opened package moved to {location}",
- "vacuum_restore": "Restore vacuum sealed",
- "vacuum_seal_rest": "Vacuum seal the rest"
- },
- "nova": {
- "1": "Unprocessed",
- "2": "Culinary ingredient",
- "3": "Processed",
- "4": "Ultra-processed"
- },
- "meal_plan_types": {
- "pasta": "Pasta",
- "riso": "Rice",
- "carne": "Meat",
- "pesce": "Fish",
- "legumi": "Legumes",
- "uova": "Eggs",
- "formaggio": "Cheese",
- "pizza": "Pizza",
- "affettati": "Cold Cuts",
- "verdure": "Veggies",
- "zuppa": "Soup",
- "insalata": "Salad",
- "pane": "Bread/Sandwich",
- "dolce": "Dessert",
- "libero": "Free"
- },
- "meal_sub": {
- "dolce_torta": "Cake",
- "dolce_crema": "Cream / Pudding",
- "dolce_crumble": "Crumble / Tart",
- "dolce_biscotti": "Cookies / Pastries",
- "dolce_frutta": "Fruit Dessert",
- "succo_dolce": "Sweet / Fruity",
- "succo_energizzante": "Energizing",
- "succo_detox": "Detox / Green",
- "succo_rinfrescante": "Refreshing",
- "succo_vitaminico": "Vitamin / Citrus"
- },
- "meal_plan": {
- "reset_success": "Weekly plan reset",
- "not_available": "not available in pantry",
- "suggested_by": "suggested by weekly plan"
- },
- "nutrition": {
- "title": "🥗 Food Analysis",
- "score_excellent": "😄 Excellent",
- "score_good": "🙂 Good",
- "score_improve": "😬 Improvable",
- "label_health": "🌿 Health",
- "label_variety": "🎨 Variety",
- "label_fresh": "❄️ Fresh",
- "source": "Based on {n} products in your pantry · EverShelf",
- "products_count": "products",
- "today_title": "🥗 Your pantry today",
- "products_n": "{n} products"
- },
- "facts": {
- "greeting_morning": "Good morning",
- "greeting_afternoon": "Good afternoon",
- "greeting_evening": "Good evening",
- "pantry_waiting": "{greeting}! Your Pantry awaits.",
- "expired_one": "You have 1 expired product in your pantry. Check it!",
- "expired_many": "You have {n} expired products in your pantry. Check them!",
- "expired_list": "Expired products: {names}",
- "expired_list_more": "and {n} more",
- "freezer_expired_ok": "{name} is expired, but being in the freezer it may still be fine! Check it.",
- "freezer_expired_old": "{name} in the freezer has been expired too long. Better to discard it.",
- "fridge_expired_one": "You have 1 expired product in the fridge!",
- "fridge_expired_many": "You have {n} expired products in the fridge!",
- "expiring_today": "{name} expires today! Use it right away.",
- "expiring_tomorrow": "{name} expires tomorrow. Plan ahead!",
- "expiring_days": "{name} expires in {days} days.",
- "expiring_many": "You have {n} products expiring soon.",
- "expiring_this_week": "{n} products expire this week. Plan your meals accordingly!",
- "expiring_item_loc": "{name} ({loc}) expires in {days} {dayslabel}.",
- "expiring_this_month": "{n} products will expire this month.",
- "shopping_add": "Add to list: {names} 🛒",
- "shopping_more": "and {n} more",
- "shopping_empty": "Shopping list empty. All stocked up! ✅",
- "in_fridge": "In the fridge: {name}.",
- "in_freezer": "In the freezer: {name}. Don't forget it!",
- "top_category": "Top category is {icon} {cat} with {n} products.",
- "cat_meat": "You have {n} meat products. 🥩",
- "cat_dairy": "You have {n} dairy products at home. 🥛",
- "cat_veggies": "You have {n} types of vegetables. Great for your health! 🥬",
- "cat_fruit": "You have {n} types of fruit. 🍎",
- "cat_drinks": "You have {n} drinks available. 🥤",
- "cat_frozen": "You have {n} frozen items. ❄️",
- "cat_pasta": "You have {n} types of pasta. 🍝 How about a carbonara?",
- "cat_canned": "You have {n} canned goods in your pantry. 🥫",
- "cat_snacks": "You have {n} snacks. Resist the temptation! 🍪",
- "cat_condiments": "You have {n} condiments available. 🧂",
- "item_random": "Did you know? You have {name} in {loc}.",
- "item_qty": "{name}: you have {qty}.",
- "no_expiry_count": "{n} products have no expiry date set.",
- "furthest_expiry": "The product with the furthest expiry is {name}: {months} months.",
- "high_qty": "You have a great stock of {name}: {qty}!",
- "low_qty_item": "{name} is running low. Add it to your shopping list?",
- "low_qty_count": "{n} products are almost out.",
- "morning_bread": "Good morning! You have bread for breakfast. 🍞",
- "morning_milk": "Is there milk in the fridge for a cappuccino? ☕🥛",
- "morning_fruit": "Good morning! Some fresh fruit is a great way to start. 🍎",
- "noon_pasta": "Lunchtime… How about a nice bowl of pasta? 🍝",
- "noon_salad": "A fresh salad for lunch? You have {n} vegetables! 🥗",
- "evening_meat": "For dinner you could use the meat you have. 🥩",
- "evening_fish": "How about fish for dinner? 🐟",
- "evening_expiring": "You have {n} products expiring this week — use them tonight!",
- "night_reminder": "Good night! Remember to use tomorrow: {names}.",
- "weekly_balance": "Weekly balance: +{in} added, −{out} consumed.",
- "weekly_added": "You added {n} products this week.",
- "weekly_consumed": "You consumed {n} products this week. Well done!",
- "tip_freezer": "💡 Frozen products last much longer than the expiry date.",
- "tip_bread": "💡 Frozen bread keeps its freshness for weeks.",
- "tip_fifo": "💡 To avoid waste, use products closest to expiry first (FIFO).",
- "tip_meat": "💡 Meat in the freezer can last up to 6 months safely.",
- "tip_no_refreeze": "💡 Never refreeze a thawed product. Cook it right away!",
- "tip_fridge": "💡 A tidy fridge saves you time and money.",
- "tip_canned": "💡 Opened canned goods should go in the fridge and be consumed within a few days.",
- "top_brand": "The most common brand in your pantry is {brand} with {n} products.",
- "combo_pasta": "You have pasta and condiments: ready for a first course! 🍝",
- "combo_sandwich": "Bread and meat: a quick sandwich is always a good idea! 🥪",
- "combo_balanced": "Vegetables and meat: you have everything for a balanced meal! 🥗🥩",
- "pantry_empty": "The pantry is empty! Time to go shopping. 🛒",
- "pantry_empty_scan": "No products registered. Scan something to start!",
- "location_distribution": "Distribution: {parts}",
- "day": "day",
- "days": "days"
- },
- "kiosk_session": {
- "first_item": "First item: {name}!",
- "items_two_four": "{n} items — warming up 🚀",
- "items_five_nine": "{n} items — great pace! 💪",
- "items_ten_twenty": "{n} items — almost a record 🏆",
- "items_twenty_plus": "{n} items — epic shopping! 🛒🔥",
- "duplicates_one": "1 duplicate (same thing twice)",
- "duplicates_many": "{n} duplicates (picked multiple times)",
- "top_category": "Top category: {cat} ({count}×)",
- "items_fallback": "{n} item{plural} added"
- },
- "kiosk": {
- "check_btn": "🔍 Check for updates",
- "checking": "⏳ Checking…",
- "error_check": "Error during update check",
- "error_start_install": "Error starting installation",
- "version_installed": "Installed: {v}",
- "update_available": "⬆️ New version available:
{latest} (installed: {current})",
- "up_to_date": "✅ You are up to date — version
{v}",
- "too_old": "⚠️ The installed kiosk is too old for automatic update checking.
Press the button below to download and install the new version directly.",
- "manual_install": "⚠️ This kiosk does not support automatic installation.
Manual procedure:1. Exit the kiosk (✕ button top left)
2. Uninstall the EverShelf Kiosk app
3. Download and install the new APK from GitHub:",
- "starting_download": "⏳ Starting download…",
- "install_btn": "⬇️ Install update",
- "exit_title": "Exit kiosk",
- "refresh_title": "Refresh page"
- },
- "update": {
- "new_version": "New version",
- "btn": "Update"
- },
- "gemini": {
- "chat_title": "Chat with Gemini",
- "not_configured": "🤖 Gemini not configured — set GEMINI_API_KEY in settings"
- },
- "appliances": {
- "empty": "No appliances added"
- },
- "about": {
- "title": "About",
- "version": "Version",
- "report_bug": "Report a Bug",
- "report_bug_hint": "Something not working? Send us a report directly from the app.",
- "report_bug_modal_title": "Report a Bug",
- "report_type_bug": "Bug",
- "report_type_feature": "Feature",
- "report_type_question": "Question",
- "report_field_title": "Title",
- "report_field_title_ph": "Brief description of the issue",
- "report_field_desc": "Description",
- "report_field_desc_ph": "Describe the issue in detail…",
- "report_field_steps": "Steps to reproduce (optional)",
- "report_field_steps_ph": "1. Go to…\n2. Tap…\n3. See the error…",
- "report_auto_info": "Automatically attached: version {version}, language {lang}.",
- "report_send_btn": "Send report",
- "report_bug_sending": "Sending…",
- "report_bug_sent": "Report sent — thank you!",
- "report_bug_error": "Could not send the report. Check your connection.",
- "changelog": "Changelog",
- "github": "GitHub Repository"
- },
- "export": {
- "title": "Export inventory",
- "hint": "Download the current inventory as CSV or open a print-ready version (PDF).",
- "btn_csv": "Download CSV",
- "btn_pdf": "PDF / Print",
- "btn_title": "Export"
- },
- "startup": {
- "connecting": "Connecting to server...",
- "check_php_memory": "PHP memory",
- "check_php_timeout": "PHP timeout",
- "check_php_upload": "PHP upload",
- "check_data_dir": "Data directory",
- "check_rate_limits": "Rate limits dir",
- "check_backups": "Backup dir",
- "check_write_test": "Disk write test",
- "check_disk_space": "Disk space",
- "check_db_legacy": "Legacy DB (dispensa.db)",
- "check_db_connect": "Database connection",
- "check_db_tables": "Database tables",
- "check_db_integrity": "Database integrity",
- "check_db_wal": "WAL mode",
- "check_db_size": "Database size",
- "check_db_rows": "Inventory data",
- "check_env": ".env file",
- "check_gemini": "Gemini AI key",
- "check_bring_creds": "Bring! credentials",
- "check_bring_token": "Bring! token",
- "check_tts": "Text-to-Speech URL",
- "check_scale": "Scale gateway",
- "check_curl_ssl": "cURL SSL",
- "check_internet": "Internet connection",
- "fresh_install": "fresh install",
- "warnings_found": "warnings found",
- "all_ok": "System OK",
- "critical_error_short": "Critical error",
- "critical_error": "Critical error: the app cannot start. Check your server logs.",
- "critical_error_intro": "The app cannot start due to the following issues:",
- "error_network": "Cannot reach the server.",
- "error_network_detail": "The browser cannot reach the PHP server.\n\nPossible causes:\n• Apache/PHP server is not running\n• Network or firewall issue\n• Incorrect app URL\n\nMake sure the server is started and try again.",
- "retry": "Retry"
- }
}
\ No newline at end of file
diff --git a/translations/es.json b/translations/es.json
index a29f336..ed23122 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -1,1351 +1,1363 @@
{
- "app": {
- "name": "EverShelf",
- "loading": "Cargando..."
- },
- "nav": {
- "title": "EverShelf",
- "home": "Inicio",
- "inventory": "Despensa",
- "recipes": "Recetas",
- "shopping": "Compras",
- "log": "Registro",
- "settings": "Ajustes"
- },
- "btn": {
- "back": "← Volver",
- "save": "💾 Guardar",
- "cancel": "✕ Cancelar",
- "close": "Cerrar",
- "add": "✅ Añadir",
- "delete": "Eliminar",
- "edit": "✏️ Editar",
- "use": "Usar",
- "edit_item": "Editar",
- "search": "🔍 Buscar",
- "go": "✅ Ir",
- "toggle_password": "👁️ Mostrar/Ocultar",
- "load_more": "Cargar más...",
- "save_config": "💾 Guardar configuración",
- "save_product": "💾 Guardar producto",
- "restart": "↺ Reiniciar",
- "reset_default": "↺ Restablecer valores por defecto",
- "save_info": "💾 Guardar información",
- "retry": "🔄 Reintentar",
- "yes_short": "Sí",
- "no_short": "No"
- },
- "form": {
- "select_placeholder": "-- Seleccionar --"
- },
- "locations": {
- "dispensa": "Despensa",
- "frigo": "Nevera",
- "freezer": "Congelador",
- "altro": "Otro"
- },
- "categories": {
- "latticini": "Lácteos",
- "carne": "Carne",
- "pesce": "Pescado",
- "frutta": "Fruta",
- "verdura": "Verduras",
- "pasta": "Pasta y Arroz",
- "pane": "Pan y Panadería",
- "surgelati": "Congelados",
- "bevande": "Bebidas",
- "condimenti": "Condimentos",
- "snack": "Snacks y Dulces",
- "conserve": "Conservas",
- "cereali": "Cereales y Legumbres",
- "igiene": "Higiene",
- "pulizia": "Limpieza",
- "altro": "Otro",
- "select": "-- Seleccionar --"
- },
- "units": {
- "pz": "uds",
- "conf": "paq",
- "g": "g",
- "ml": "ml",
- "pieces": "Unidades",
- "grams": "Gramos",
- "box": "Paquete",
- "boxes": "Paquetes",
- "millilitres": "Mililitros",
- "from": "de"
- },
- "shopping_sections": {
- "frutta_verdura": "Frutas y Verduras",
- "carne_pesce": "Carne y Pescado",
- "latticini": "Lácteos y Frescos",
- "pane_dolci": "Pan y Dulces",
- "pasta": "Pasta y Cereales",
- "conserve": "Conservas y Salsas",
- "surgelati": "Congelados",
- "bevande": "Bebidas",
- "pulizia_igiene": "Limpieza e Higiene",
- "altro": "Otro"
- },
- "dashboard": {
- "expired_title": "🚫 Caducado",
- "expiring_title": "⏰ Caduca pronto",
- "stats_period": "📊 Últimos 30 días",
- "opened_title": "📦 Productos abiertos",
- "review_title": "🔍 Por revisar",
- "review_hint": "Cantidades inusuales. Confirma si son correctas o modifícalas.",
- "quick_recipe": "Receta rápida con productos que caducan",
- "banner_review_title": "Cantidad anormal",
- "banner_review_action_ok": "Es correcto",
- "banner_review_action_finish": "🗑️ Todo terminado",
- "banner_review_action_edit": "Corregir",
- "banner_review_action_weigh": "Pesar",
- "banner_review_dismiss": "Ignorar",
- "banner_prediction_title": "Consumo por revisar",
- "banner_prediction_hint": "La estimación de consumo se adapta a los datos recientes: confirma solo si la cantidad actual es correcta.",
- "banner_prediction_action_confirm": "Confirmar {qty} {unit}",
- "banner_prediction_action_weigh": "Pesar ahora",
- "banner_prediction_action_edit": "Actualizar cantidad",
- "banner_expired_title": "Producto caducado",
- "banner_expired_today": "Caducado hoy",
- "banner_expired_days": "Caducado hace {days} días",
- "banner_expired_action_use": "Usar de todas formas",
- "banner_expired_action_finished": "¡Ya lo terminé!",
- "banner_expired_action_throw": "Lo tiré",
- "banner_expired_action_edit": "Corregir fecha",
- "banner_anomaly_action_edit": "Corregir inventario",
- "banner_anomaly_action_dismiss": "La cantidad es correcta",
- "banner_no_expiry_title": "Caducidad faltante: {name}",
- "banner_no_expiry_detail": "Este producto no tiene fecha de caducidad. ¿Quieres añadir una o confirmar que no caduca?",
- "banner_no_expiry_action_set": "Establecer fecha de caducidad",
- "banner_no_expiry_action_dismiss": "No caduca ✓",
- "banner_no_expiry_toast_dismissed": "Marcado como «sin fecha de caducidad»",
- "banner_expiring_title": "Caduca pronto",
- "banner_expiring_today": "¡Caduca hoy!",
- "banner_expiring_tomorrow": "Caduca mañana",
- "banner_expiring_days": "Caduca en {days} días",
- "banner_expiring_action_use": "Usar ahora",
- "banner_finished_title": "¿terminado?",
- "banner_finished_detail": "Registré que {name} llegó a cero de stock. ¿Realmente se acabó o todavía tienes algo?",
- "banner_finished_action_yes": "Sí, se acabó",
- "banner_finished_action_no": "No, todavía tengo",
- "banner_review_unusual_pkg_title": "Tamaño de paquete inusual",
- "banner_review_unusual_pkg_detail": "Configuraste un paquete de {qty} {unit} — el tamaño parece muy grande. Comprueba si es correcto o edita.",
- "banner_review_low_qty_title": "Cantidad muy baja",
- "banner_review_low_qty_detail": "Solo tienes {qty} en stock — parece muy poco, puede ser un error de escritura. Confirma si es correcto.",
- "banner_review_high_qty_title": "Cantidad inusualmente alta",
- "banner_review_high_qty_detail": "Tienes {qty} en stock — la cifra parece muy alta. Confirma si es correcto o edita.",
- "banner_prediction_rate_day": "Media ~{n} {unit}/día",
- "banner_prediction_rate_week": "Media ~{n} {unit}/semana",
- "banner_prediction_days_ago": "Hace {n} días reabasteciste",
- "banner_prediction_more": "estimación anterior: {expected} {unit}{time}; cantidad actual: {actual} {unit}.",
- "banner_prediction_less": "estimación: {expected} {unit}{time}; cantidad actual: {actual} {unit}. Si tu ritmo de uso cambió, la previsión se actualiza automáticamente.",
- "banner_finished_zero": "El inventario muestra cero, pero los movimientos registrados sugieren que no debería estar vacío.",
- "banner_finished_expected": "Según los registros deberías tener todavía {qty} {unit}.",
- "banner_finished_check": "¿Puedes comprobarlo?",
- "banner_anomaly_phantom_title": "tienes más stock del esperado",
- "banner_anomaly_phantom_detail": "El inventario indica {inv_qty} {unit}, pero según los registros solo deberías tener {expected_qty} {unit}. ¿Añadiste stock sin registrarlo?",
- "banner_anomaly_untracked_title": "stock no registrado como entrada",
- "banner_anomaly_untracked_detail": "Tienes
{inv_qty} {unit} en inventario, pero las salidas registradas superan las entradas — el stock inicial probablemente nunca se añadió como transacción «entrada». Puedes corregir la cantidad o registrar las entradas faltantes.",
- "banner_anomaly_ghost_title": "tienes menos stock del esperado",
- "banner_anomaly_ghost_detail": "Según las operaciones registradas deberías tener {expected_qty} {unit} de {name}, pero el inventario solo muestra {inv_qty} {unit}. ¿Tomaste stock sin registrarlo?",
- "consumed": "Consumido: {n} ({pct}%)",
- "wasted": "Desperdiciado: {n} ({pct}%)",
- "more_opened": "y {n} más abiertos...",
- "banner_expired_detail": "{when} · aún tienes
{qty}.",
- "banner_opened_detail": "{when} en {location} · aún tienes
{qty}.",
- "banner_explain_title": "Pedir explicación a Gemini",
- "banner_explain_btn": "Explicar",
- "banner_analyzing": "🤖 Analizando…"
- },
- "inventory": {
- "title": "Despensa",
- "filter_all": "Todo",
- "search_placeholder": "🔍 Buscar producto...",
- "recent_title": "🕐 Usados recientemente",
- "popular_title": "⭐ Más usados",
- "empty": "No hay productos aquí.\n¡Escanea un producto para añadirlo!",
- "no_items_found": "No se encontraron artículos",
- "qty_remainder_suffix": "restante",
- "vacuum_badge": "🫙 Al vacío",
- "opened_badge": "📭 Abierto",
- "label_expiry": "📅 Caducidad",
- "label_storage": "🫙 Almacenamiento",
- "label_status": "📭 Estado",
- "opened_since": "Abierto desde el {date}",
- "label_position": "📍 Ubicación",
- "label_quantity": "📦 Cantidad",
- "label_added": "📅 Añadido",
- "empty_text": "No hay productos aquí.
¡Escanea un producto para añadirlo!",
- "empty_db": "No hay productos en la base de datos.
¡Escanea un producto para empezar!",
- "qty_trace": "< 1"
- },
- "scan": {
- "title": "Escáner",
- "mode_shopping": "🛒 Modo compras",
- "mode_shopping_end": "✅ Finalizar compras",
- "spesa_btn": "🛒 Compras",
- "zoom": "Zoom",
- "tab_barcode": "Código de barras",
- "tab_name": "Nombre",
- "tab_ai": "IA",
- "recents_label": "Recientes",
- "torch_hint": "Linterna",
- "torch_on": "Linterna encendida",
- "torch_off": "Linterna apagada",
- "torch_unavailable": "Linterna no disponible en este dispositivo",
- "flip_hint": "Cambiar cámara",
- "flip_front": "Cámara frontal",
- "flip_back": "Cámara trasera",
- "num_ocr_btn": "🔢 Leer números con IA",
- "num_ocr_searching": "Buscando código de barras con IA...",
- "num_ocr_found": "Código encontrado: {code}",
- "num_ocr_not_found": "No se encontró código de barras en la imagen",
- "barcode_placeholder": "Introduce el código de barras...",
- "quick_name_divider": "o escribe el nombre",
- "quick_name_placeholder": "Ej.: Manzanas, Calabacín, Pan...",
- "manual_entry": "✏️ Entrada manual",
- "ai_identify": "🤖 Identificar con IA",
- "hint": "Escanea el código de barras, escribe el nombre del producto o usa la IA para identificarlo",
- "debug_toggle": "🐛 Registro de depuración",
- "barcode_acquired": "🔖 Código de barras escaneado: {code}",
- "scan_barcode": "🔖 Escanear código de barras",
- "create_named": "Crear {name}",
- "new_without_barcode": "Nuevo producto sin código de barras"
- },
- "action": {
- "title": "¿Qué quieres hacer?",
- "add_btn": "📥 AÑADIR",
- "add_sub": "a la despensa/nevera",
- "use_btn": "📤 USAR / CONSUMIR",
- "use_sub": "de la despensa/nevera",
- "have_title": "📦 ¡Ya en stock!",
- "add_more_sub": "añadir más",
- "use_qty_sub": "cuánto usaste",
- "throw_btn": "🗑️ DESECHAR",
- "throw_sub": "tirar",
- "edit_sub": "caducidad, ubicación…",
- "create_recipe_btn": "Receta"
- },
- "add": {
- "title": "Añadir a la despensa",
- "location_label": "📍 ¿Dónde lo guardas?",
- "quantity_label": "📦 Cantidad",
- "conf_size_label": "📦 Cada paquete contiene:",
- "conf_size_placeholder": "ej. 300",
- "vacuum_label": "🫙 Al vacío",
- "vacuum_hint": "La fecha de caducidad se ampliará automáticamente",
- "submit": "✅ Añadir",
- "purchase_type_label": "🛒 Este producto es...",
- "new_btn": "🆕 Recién comprado",
- "existing_btn": "📦 Ya lo tenía",
- "remaining_label": "📦 Cantidad restante",
- "remaining_hint": "¿Aproximadamente cuánto queda?",
- "remaining_full": "🟢 Lleno",
- "remaining_half": "🟠 Por la mitad",
- "estimated_expiry": "Caducidad estimada:",
- "suffix_freezer": "(congelador)",
- "suffix_vacuum": "(al vacío)",
- "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}",
- "suffix_freezer_vacuum": "(congelador + al vacío)",
- "history_badge_tip": "Media de {n} entradas anteriores",
- "vacuum_question": "¿Al vacío?",
- "vacuum_saved": "🔒 ¡Al vacío!"
- },
- "use": {
- "title": "Usar / Consumir",
- "location_label": "📍 ¿De dónde?",
- "quantity_label": "¿Cuánto usaste?",
- "change": "cambiar",
- "partial_hint": "O especifica la cantidad usada:",
- "partial_piece_hint": "¿Usaste solo una parte?",
- "piece": "unidad",
- "one_whole": "1 entero",
- "use_all": "🗑️ Todo usado / Terminado",
- "submit": "📤 Usar esta cantidad",
- "available": "📦 Disponible:",
- "opened_badge": "ABIERTO",
- "not_in_inventory": "⚠️ Producto no en inventario.",
- "expiry_warning": "⚠️ ¡Usa primero el{loc} que caduca el {date} — {when}!",
- "expiry_warning_opened": "⚠️ El{loc} lleva abierto {when} — ¡úsalo primero!",
- "throw_title": "🗑️ Desechar producto",
- "throw_all": "🗑️ Desechar TODO ({qty})",
- "throw_qty_label": "¿Cuánto desechar?",
- "throw_qty_hint": "o introduce una cantidad:",
- "throw_partial_btn": "🗑️ Desechar esta cantidad",
- "when_expired": "caducado hace {n} días",
- "when_today": "caduca
hoy",
- "when_tomorrow": "caduca
mañana",
- "when_days": "caduca en
{n} días",
- "toast_used": "📤 {qty} de {name} usado",
- "toast_bring": "🛒 Producto terminado → añadido a Bring!",
- "toast_opened_finished": "🔓 ¡Paquete abierto de {name} terminado!",
- "disambiguation_hint": "¿Qué quieres decir con «todo terminado»?",
- "disambiguation_all": "🗑️ Terminar TODO ({qty})",
- "error_exceeds_stock": "⚠️ ¡No puedes usar más de lo que tienes disponible!",
- "use_all_confirm_title": "✅ Terminar todo",
- "use_all_confirm_msg": "Confirma que has terminado el producto:",
- "use_all_confirm_btn": "✅ Sí, terminado",
- "throw_all_confirm_title": "🗑️ Desechar todo",
- "throw_all_confirm_msg": "¿Realmente quieres tirar todo el producto?",
- "throw_all_confirm_btn": "🗑️ Sí, desechar"
- },
- "product": {
- "title_new": "Nuevo producto",
- "title_edit": "Editar producto",
- "ai_fill": "📷 Sacar foto e identificar con IA",
- "ai_fill_hint": "La IA rellenará automáticamente los campos del producto",
- "name_label": "🏷️ Nombre del producto *",
- "name_placeholder": "Ej.: Leche entera, Pasta penne...",
- "brand_label": "🏢 Marca",
- "brand_placeholder": "Ej.: Barilla, Danone, Heinz...",
- "category_label": "📂 Categoría",
- "unit_label": "📏 Unidad de medida",
- "default_qty_label": "🔢 Cantidad por defecto",
- "conf_size_label": "📦 Cada paquete contiene:",
- "conf_size_placeholder": "ej. 300",
- "notes_label": "📝 Notas",
- "notes_placeholder": "Ej.: sin lactosa, ecológico, guardar en nevera tras abrir...",
- "barcode_label": "🔖 Código de barras",
- "barcode_placeholder": "Código de barras (si disponible)",
- "barcode_hint": "⚠️ ¡Añade el código de barras para la próxima vez solo tendrás que escanear!",
- "submit": "💾 Guardar producto",
- "name_required": "Introduce el nombre del producto",
- "conf_size_required": "Especifica el contenido del paquete",
- "expiry_estimated": "Caducidad estimada:",
- "scan_expiry": "Escanear fecha de caducidad",
- "expiry_hint": "📝 Puedes editar la fecha o escanearla con la cámara",
- "add_batch": "📦 + Lote con fecha diferente",
- "package_info": "📦 Paquete: {info}",
- "edit_catalog": "⚙️ Editar información del producto (nombre, marca, categoría…)",
- "not_recognized": "⚠️ Producto no reconocido",
- "edit_info": "✏️ Editar información",
- "modify_details": "EDITAR\ncaducidad, ubicación…",
- "already_in_pantry": "📋 Ya en la despensa",
- "no_barcode": "Sin código de barras",
- "unknown_product": "Producto no reconocido",
- "edit_name_brand": "Editar nombre/marca",
- "weight_label": "Peso",
- "origin_label": "Origen",
- "labels_label": "Etiquetas",
- "select_variant": "Selecciona la variante exacta o usa los datos de IA:"
- },
- "products": {
- "title": "📦 Todos los productos",
- "search_placeholder": "🔍 Buscar producto...",
- "empty": "No hay productos en la base de datos.\n¡Escanea un producto para empezar!",
- "no_category": "No hay productos en esta categoría"
- },
- "recipes": {
- "title": "🍳 Recetas",
- "generate": "✨ Generar nueva receta",
- "archive_empty": "No hay recetas guardadas. ¡Genera tu primera receta!",
- "dialog_title": "🍳 Receta",
- "dialog_desc": "Generaré una receta saludable usando ingredientes de la despensa, priorizando los productos que caducan.",
- "meal_label": "🕐 ¿Qué comida?",
- "persons_label": "👥 ¿Para cuántas personas?",
- "meal_type_label": "🎯 Tipo de comida",
- "opt_fast": "⚡ Comida rápida",
- "opt_light": "🥗 Apetito ligero",
- "opt_expiry": "⏰ Priorizar productos que caducan",
- "opt_healthy": "💚 Extra saludable",
- "opt_opened": "📦 Priorizar productos abiertos",
- "opt_zero_waste": "♻️ Cero desperdicio",
- "generate_btn": "✨ Generar receta",
- "loading_msg": "Preparando tu receta...",
- "start_cooking": "👨🍳 Modo cocina",
- "regenerate": "🔄 Generar otra",
- "close_btn": "✅ Cerrar",
- "ingredients_title": "🧾 Ingredientes",
- "tools_title": "Equipo necesario",
- "steps_title": "👨🍳 Pasos",
- "no_steps": "No hay pasos disponibles",
- "generate_error": "Error de generación",
- "persons_short": "com.",
- "use_ingredient_title": "Usar ingrediente",
- "recipe_qty_label": "Receta",
- "from_where_label": "¿De dónde?",
- "amount_label": "Cuánto",
- "use_amount_btn": "Usar esta cantidad",
- "use_all_btn": "Usar TODO / Terminado",
- "packs_label": "Paquetes",
- "quantity_in_total": "Cantidad en {unit} (total: {total})",
- "packs_of_have": "Paquetes de {size} (tienes {count} paquetes)",
- "scale_wait_stable": "Espera 10s de peso estable para el relleno automático…",
- "ingredient_scaled_toast": "📦 ¡Ingrediente deducido de la despensa!",
- "finished_added_bring_toast": "🛒 Producto terminado → ¡añadido a Bring!",
- "load_error": "Error de carga"
- },
- "shopping": {
- "title": "🛒 Lista de la compra",
- "bring_loading": "Conectando a Bring!...",
- "bring_not_configured": "Bring! no está configurado. Añade tu email y contraseña en los
ajustes.",
- "tab_to_buy": "🛍️ Por comprar",
- "tab_forecast": "🧠 Previsión",
- "total_label": "💰 Total estimado",
- "section_to_buy": "🛍️ Por comprar",
- "suggestions_title": "💡 Sugerencias IA",
- "suggestions_add": "✅ Añadir selección a Bring!",
- "search_prices": "🔍 Buscar todos los precios",
- "suggest_btn": "Sugerir qué comprar",
- "smart_title": "🧠 Predicciones inteligentes",
- "smart_empty": "No hay predicciones disponibles.
Añade productos a tu despensa para recibir predicciones inteligentes.",
- "smart_filter_all": "Todo",
- "smart_filter_critical": "🔴 Urgente",
- "smart_filter_high": "🟠 Pronto",
- "smart_filter_medium": "🟡 Planificar",
- "smart_filter_low": "🟢 Previsión",
- "smart_add": "🛒 Añadir selección a Bring!",
- "empty": "¡Lista de la compra vacía!\nUsa el botón de abajo para generar sugerencias.",
- "already_in_list": "🛒 \"{name}\" ya está en la lista de la compra",
- "already_in_list_short": "ℹ️ Ya en la lista de la compra",
- "add_prompt": "¿Quieres añadirlo a la lista de la compra?",
- "smart_already": "📊 Las predicciones de compra ya predicen {name}",
- "all_searched": "Todos los productos ya han sido buscados. Usa 🔄 para buscar individualmente.",
- "search_complete": "Búsqueda completada: {count} productos",
- "removed_sufficient": "🧹 {removed} producto(s) con stock suficiente eliminado(s) de la lista",
- "suggest_buy": "🛒 Comprar: {qty} {unit}",
- "suggest_buy_approx": "🛒 Al menos: {qty} {unit}",
- "suggest_buy_tip": "Cantidad sugerida basada en tus últimos 14 días de consumo",
- "suggest_buy_approx_tip": "Estimación mínima basada en el consumo (compra el tamaño de paquete más cercano)",
- "bring_badge": "🛒 Ya en Bring!",
- "add_urgent_toast": "🔴 {n} producto(s) urgente(s) añadido(s) automáticamente a Bring!",
- "migration_done": "✅ {migrated} actualizado(s), {skipped} ya estaban ok",
- "added_to_bring": "🛒 {n} productos añadidos a Bring!",
- "added_to_bring_skip": "{n} ya presentes",
- "all_on_bring": "¡Todos los productos ya estaban en Bring!",
- "freq_high": "📈 Frecuente",
- "freq_regular": "📊 Regular",
- "freq_occasional": "📉 Ocasional",
- "out_of_stock": "Sin stock",
- "scan_toast": "📷 Escanear: {name}",
- "empty_category": "No hay productos en esta categoría",
- "session_empty": "🛒 Sin productos aún",
- "urgency_critical": "Urgente",
- "urgency_high": "Pronto",
- "urgency_medium": "Planificar",
- "urgency_low": "Previsión",
- "urgency_medium_short": "Medio",
- "urgency_low_short": "Ok",
- "tag_urgent": "🔴 Urgente",
- "tag_priority": "⭐ Prioridad",
- "tag_check": "✅ Comprobar",
- "smart_already_predicted": "📊 Las predicciones inteligentes ya predicen
{name}{urgency}.",
- "item_removed": "✅ ¡{name} eliminado de la lista!",
- "urgency_spec_critical": "⚡ Urgente",
- "urgency_spec_high": "🟠 Pronto",
- "bring_add_n": "Añadir {n} a Bring!",
- "bring_add_selected": "Añadir selección a Bring!",
- "bring_adding": "Añadiendo...",
- "bring_added_one": "1 producto añadido a Bring!",
- "bring_added_many": "{n} productos añadidos a Bring!",
- "bring_skipped": "({n} ya en la lista)",
- "force_sync": "Forzar sincronización de Bring!",
- "scan_target_label": "Estás buscando",
- "scan_target_found": "¡Encontrado! Eliminar de la lista",
- "bring_add_one": "Añadir 1 producto a Bring!",
- "bring_add_many": "Añadir {n} productos a Bring!",
- "syncing": "Sincronizando…",
- "sync_done": "Sincronización completada",
- "price_searching": "Buscando...",
- "search_action": "Buscar",
- "open_action": "Abrir",
- "not_found": "No encontrado",
- "search_price": "Buscar precio",
- "tap_to_scan": "Toca para escanear",
- "tag_title": "Etiqueta",
- "remove_title": "Eliminar",
- "found_count": "{found}/{total} productos encontrados",
- "savings_offers": "· 🏷️ Ahorras {amount}€ con ofertas",
- "searching_progress": "Buscando {current}/{total}...",
- "remove_error": "Error al eliminar",
- "btn_fetch_prices": "Buscar precios",
- "price_total_label": "💰 Total estimado:",
- "price_loading": "Buscando precios…",
- "price_not_found": "precio n/d",
- "suggest_loading": "Analizando...",
- "suggest_error": "Error al generar sugerencias",
- "priority_high": "Alta",
- "priority_medium": "Media",
- "priority_low": "Baja",
- "smart_last_update": "Actualizado {time}",
- "names_already_updated": "Todos los nombres ya están actualizados"
- },
- "ai": {
- "title": "🤖 Identificación IA",
- "capture": "📸 Sacar foto",
- "retake": "🔄 Repetir",
- "hint": "Saca una foto del producto y la IA intentará identificarlo",
- "identifying": "🤖 Identificando producto...",
- "no_api_key": "⚠️ Clave API de Gemini no configurada.\n
Añade GEMINI_API_KEY al archivo .env en el servidor.",
- "fields_filled": "✅ Campos rellenados por IA",
- "use_data": "✅ Usar datos de IA",
- "use_data_no_barcode": "✅ Usar datos de IA (sin código de barras)"
- },
- "log": {
- "title": "📒 Registro de operaciones",
- "type_added": "Añadido",
- "type_waste": "Desechado",
- "type_used": "Usado",
- "type_bring": "Añadido a Bring!",
- "undone_badge": "Deshecho",
- "undo_title": "Deshacer esta operación",
- "load_error": "Error al cargar el registro",
- "empty": "No hay operaciones registradas.",
- "undo_action_remove": "eliminación de",
- "undo_action_restore": "reabastecimiento de",
- "undo_confirm": "¿Deshacer esta operación?\n→ {action} {name}",
- "undo_success": "↩ Operación deshecha para {name}",
- "already_undone": "Operación ya deshecha",
- "too_old": "No se pueden deshacer operaciones de más de 24 horas",
- "undo_error": "Error al deshacer",
- "recipe_prefix": "Receta"
- },
- "chat": {
- "title": "Chef Gemini",
- "welcome": "¡Hola! Soy tu asistente de cocina",
- "welcome_desc": "¡Pídeme que te prepare un zumo, un snack, un plato rápido… Conozco tu despensa, tus electrodomésticos y tus preferencias!",
- "suggestion_snack": "🍿 Snack rápido",
- "suggestion_juice": "🥤 Zumo/Batido",
- "suggestion_light": "🥗 Algo ligero",
- "suggestion_expiry": "⏰ Usar productos que caducan",
- "clear": "Nueva conversación",
- "placeholder": "Pregunta algo...",
- "cleared": "Chat borrado",
- "suggestion_snack_text": "¿Qué puedo preparar como snack rápido?",
- "suggestion_juice_text": "Hazme un zumo o batido con lo que tengo",
- "suggestion_light_text": "Tengo hambre pero quiero algo ligero",
- "suggestion_expiry_text": "¿Qué está a punto de caducar y cómo puedo usarlo?",
- "transfer_to_recipes": "Transferir a recetas",
- "transferring": "Transfiriendo...",
- "transferred": "¡Añadido a recetas!",
- "open_recipe": "Abrir receta",
- "quick_recipe_prompt": "¡Sugiere una receta rápida PARA UNA PERSONA usando los productos que caducan primero! Ignora el congelador, céntrate en la nevera y la despensa."
- },
- "cooking": {
- "close": "Cerrar",
- "tts_btn": "Leer en voz alta",
- "restart": "↺ Reiniciar",
- "replay": "🔊 Repetir",
- "timer": "⏱️ {time} · Temporizador",
- "prev": "◀ Anterior",
- "next": "Siguiente ▶",
- "ingredient_used": "✔️ Deducido",
- "ingredient_use_btn": "📦 Usar",
- "ingredient_deduct_title": "Deducir de la despensa",
- "timer_expired_tts": "¡Temporizador {label} finalizado!",
- "timer_warning_tts": "¡Atención! {label}: ¡10 segundos restantes!",
- "recipe_done_tts": "¡Receta completada! ¡Buen provecho!",
- "expires_chip": "cad. {date}",
- "finish": "✅ Finalizar",
- "step_fallback": "Paso {n}",
- "zerowaste_label": "♻️ Desperdicio",
- "zerowaste_tip_title": "Consejo sin desperdicios"
- },
- "settings": {
- "title": "⚙️ Ajustes",
- "tab_api": "Claves API",
- "tab_bring": "Bring!",
- "tab_recipe": "Recetas",
- "tab_mealplan": "Plan semanal",
- "tab_appliances": "Electrodomésticos",
- "tab_spesa": "Compra online",
- "tab_camera": "Cámara",
- "tab_security": "Seguridad",
- "tab_tts": "Voz (TTS)",
- "tab_language": "Idioma",
- "tab_scale": "Báscula inteligente",
- "gemini": {
- "title": "🤖 Google Gemini IA",
- "hint": "Clave API para identificación de productos, fechas de caducidad y recetas.",
- "key_label": "Clave API Gemini"
+ "app": {
+ "name": "EverShelf",
+ "loading": "Cargando..."
},
- "bring": {
- "title": "🛒 Lista de la compra Bring!",
- "hint": "Credenciales para la integración con la lista de la compra Bring!",
- "email_label": "📧 Email Bring!",
- "password_label": "🔒 Contraseña Bring!"
+ "nav": {
+ "title": "EverShelf",
+ "home": "Inicio",
+ "inventory": "Despensa",
+ "recipes": "Recetas",
+ "shopping": "Compras",
+ "log": "Registro",
+ "settings": "Ajustes"
},
- "price": {
- "title": "💰 Estimación de precios (IA)",
- "hint": "Mostrar el coste estimado por producto en la lista de la compra usando IA.",
- "enabled_label": "Activar estimación de precios",
- "country_label": "🌍 País de referencia",
- "currency_label": "💱 Moneda",
- "update_label": "🔄 Actualizar precios cada",
- "update_suffix": "meses"
+ "btn": {
+ "back": "← Volver",
+ "save": "💾 Guardar",
+ "cancel": "✕ Cancelar",
+ "close": "Cerrar",
+ "add": "✅ Añadir",
+ "delete": "Eliminar",
+ "edit": "✏️ Editar",
+ "use": "Usar",
+ "edit_item": "Editar",
+ "search": "🔍 Buscar",
+ "go": "✅ Ir",
+ "toggle_password": "👁️ Mostrar/Ocultar",
+ "load_more": "Cargar más...",
+ "save_config": "💾 Guardar configuración",
+ "save_product": "💾 Guardar producto",
+ "restart": "↺ Reiniciar",
+ "reset_default": "↺ Restablecer valores por defecto",
+ "save_info": "💾 Guardar información",
+ "retry": "🔄 Reintentar",
+ "yes_short": "Sí",
+ "no_short": "No"
},
- "recipe": {
- "title": "🍳 Preferencias de recetas",
- "hint": "Configura las opciones predeterminadas para la generación de recetas.",
- "persons_label": "👥 Raciones por defecto",
- "options_label": "🎯 Opciones de receta por defecto",
- "fast": "⚡ Comida rápida",
- "light": "🥗 Comida ligera",
- "expiry": "⏰ Prioridad caducidad",
- "healthy": "💚 Extra saludable",
- "opened": "📦 Prioridad productos abiertos",
- "zerowaste": "♻️ Cero desperdicio",
- "dietary_label": "🚫 Intolerancias / Restricciones",
- "dietary_placeholder": "Ej.: sin gluten, sin lactosa, vegetariano..."
+ "form": {
+ "select_placeholder": "-- Seleccionar --"
},
- "mealplan": {
- "title": "📅 Plan de comidas semanal",
- "hint": "Establece el tipo de comida para cada día. Se usará como guía en la generación de recetas.",
- "enabled": "✅ Activar plan semanal",
- "legend": "🌤️ = Almuerzo · 🌙 = Cena · Toca un badge para cambiarlo.",
- "types_title": "📋 Tipos disponibles",
- "reset_btn": "↺ Restaurar valores por defecto"
+ "locations": {
+ "dispensa": "Despensa",
+ "frigo": "Nevera",
+ "freezer": "Congelador",
+ "altro": "Otro"
},
- "appliances": {
- "title": "🔌 Electrodomésticos disponibles",
- "hint": "Indica los electrodomésticos que tienes. Se tendrán en cuenta en la generación de recetas.",
- "new_placeholder": "Ej.: Panificadora, Thermomix, Freidora de aire...",
- "quick_title": "Añadir rápido:",
- "oven": "🔥 Horno",
- "microwave": "📡 Microondas",
- "air_fryer": "🍟 Freidora de aire",
- "bread_maker": "🍞 Panificadora",
- "bimby": "🤖 Thermomix/Cookeo",
- "mixer": "🌀 Robot de cocina",
- "steamer": "♨️ Vaporera",
- "pressure_cooker": "🫕 Olla a presión",
- "toaster": "🍞 Tostadora",
- "blender": "🍹 Batidora",
- "empty": "No hay electrodomésticos añadidos"
+ "categories": {
+ "latticini": "Lácteos",
+ "carne": "Carne",
+ "pesce": "Pescado",
+ "frutta": "Fruta",
+ "verdura": "Verduras",
+ "pasta": "Pasta y Arroz",
+ "pane": "Pan y Panadería",
+ "surgelati": "Congelados",
+ "bevande": "Bebidas",
+ "condimenti": "Condimentos",
+ "snack": "Snacks y Dulces",
+ "conserve": "Conservas",
+ "cereali": "Cereales y Legumbres",
+ "igiene": "Higiene",
+ "pulizia": "Limpieza",
+ "altro": "Otro",
+ "select": "-- Seleccionar --"
},
- "spesa": {
- "title": "🛍️ Compra online",
- "hint": "Configura el proveedor de compra online.",
- "provider_label": "🏪 Proveedor",
- "email_label": "📧 Email",
- "password_label": "🔒 Contraseña",
- "login_btn": "🔐 Iniciar sesión",
- "ai_prompt_label": "🤖 Prompt IA de selección de producto",
- "ai_prompt_placeholder": "Instrucciones para la IA al elegir entre varios productos...",
- "ai_prompt_hint": "La IA usa este prompt para elegir el producto más apropiado de los resultados. Deja vacío para el comportamiento por defecto.",
- "configure_first": "Configura primero la compra online en ajustes",
- "missing_credentials": "Introduce email y contraseña",
- "login_in_progress": "Iniciando sesión...",
- "login_error_prefix": "Error:",
- "login_network_error_prefix": "Error de red:",
- "login_success_default": "¡Inicio de sesión exitoso!",
- "result_name_label": "Nombre",
- "result_card_label": "Tarjeta",
- "result_pickup_label": "Punto de recogida",
- "result_points_label": "Puntos de fidelidad",
- "connected_relogin": "✅ Conectado — Iniciar sesión de nuevo",
- "connected_as": "Conectado como {name}"
+ "units": {
+ "pz": "uds",
+ "conf": "paq",
+ "g": "g",
+ "ml": "ml",
+ "pieces": "Unidades",
+ "grams": "Gramos",
+ "box": "Paquete",
+ "boxes": "Paquetes",
+ "millilitres": "Mililitros",
+ "from": "de"
},
- "camera": {
- "title": "📷 Cámara",
- "hint": "Elige qué cámara usar para el escaneo de códigos de barras e identificación IA.",
- "device_label": "📸 Cámara por defecto",
- "back": "📱 Trasera (por defecto)",
- "front": "🤳 Frontal",
- "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"
+ "shopping_sections": {
+ "frutta_verdura": "Frutas y Verduras",
+ "carne_pesce": "Carne y Pescado",
+ "latticini": "Lácteos y Frescos",
+ "pane_dolci": "Pan y Dulces",
+ "pasta": "Pasta y Cereales",
+ "conserve": "Conservas y Salsas",
+ "surgelati": "Congelados",
+ "bevande": "Bebidas",
+ "pulizia_igiene": "Limpieza e Higiene",
+ "altro": "Otro"
},
- "security": {
- "title": "🔒 Certificado HTTPS",
- "hint": "Si el navegador muestra el error «Tu conexión no es privada» (ERR_CERT_AUTHORITY_INVALID), necesitas instalar el certificado CA en el dispositivo.",
- "download_btn": "📥 Descargar certificado CA",
- "token_title": "🔑 Token de ajustes",
- "token_label": "Token de acceso",
- "token_hint": "Si `SETTINGS_TOKEN` está configurado en el `.env` del servidor, introduce el token aquí antes de guardar los ajustes. Deja vacío si no está configurado.",
- "token_placeholder": "(vacío = sin protección)",
- "token_required_hint": "🔒 Este servidor requiere un token para guardar los ajustes.",
- "cert_instructions": "
Instrucciones para Chrome (Android):1. Descarga el certificado de arriba
2. Ve a
Ajustes → Seguridad y privacidad → Más ajustes de seguridad → Instalar desde almacenamiento3. Selecciona el archivo
EverShelf_CA.crt descargado
4. Elige «CA» y confirma
5. Reinicia Chrome
Instrucciones para Chrome (PC):1. Descarga el certificado de arriba
2. Ve a
chrome://settings/certificates3. Pestaña «Autoridades» → Importar → selecciona el archivo
4. Marca «Confiar en este certificado para identificar sitios web»
5. Reinicia Chrome"
+ "dashboard": {
+ "expired_title": "🚫 Caducado",
+ "expiring_title": "⏰ Caduca pronto",
+ "stats_period": "📊 Últimos 30 días",
+ "opened_title": "📦 Productos abiertos",
+ "review_title": "🔍 Por revisar",
+ "review_hint": "Cantidades inusuales. Confirma si son correctas o modifícalas.",
+ "quick_recipe": "Receta rápida con productos que caducan",
+ "banner_review_title": "Cantidad anormal",
+ "banner_review_action_ok": "Es correcto",
+ "banner_review_action_finish": "🗑️ Todo terminado",
+ "banner_review_action_edit": "Corregir",
+ "banner_review_action_weigh": "Pesar",
+ "banner_review_dismiss": "Ignorar",
+ "banner_prediction_title": "Consumo por revisar",
+ "banner_prediction_hint": "La estimación de consumo se adapta a los datos recientes: confirma solo si la cantidad actual es correcta.",
+ "banner_prediction_action_confirm": "Confirmar {qty} {unit}",
+ "banner_prediction_action_weigh": "Pesar ahora",
+ "banner_prediction_action_edit": "Actualizar cantidad",
+ "banner_expired_title": "Producto caducado",
+ "banner_expired_today": "Caducado hoy",
+ "banner_expired_days": "Caducado hace {days} días",
+ "banner_expired_action_use": "Usar de todas formas",
+ "banner_expired_action_finished": "¡Ya lo terminé!",
+ "banner_expired_action_throw": "Lo tiré",
+ "banner_expired_action_edit": "Corregir fecha",
+ "banner_anomaly_action_edit": "Corregir inventario",
+ "banner_anomaly_action_dismiss": "La cantidad es correcta",
+ "banner_no_expiry_title": "Caducidad faltante: {name}",
+ "banner_no_expiry_detail": "Este producto no tiene fecha de caducidad. ¿Quieres añadir una o confirmar que no caduca?",
+ "banner_no_expiry_action_set": "Establecer fecha de caducidad",
+ "banner_no_expiry_action_dismiss": "No caduca ✓",
+ "banner_no_expiry_toast_dismissed": "Marcado como «sin fecha de caducidad»",
+ "banner_expiring_title": "Caduca pronto",
+ "banner_expiring_today": "¡Caduca hoy!",
+ "banner_expiring_tomorrow": "Caduca mañana",
+ "banner_expiring_days": "Caduca en {days} días",
+ "banner_expiring_action_use": "Usar ahora",
+ "banner_finished_title": "¿terminado?",
+ "banner_finished_detail": "Registré que {name} llegó a cero de stock. ¿Realmente se acabó o todavía tienes algo?",
+ "banner_finished_action_yes": "Sí, se acabó",
+ "banner_finished_action_no": "No, todavía tengo",
+ "banner_review_unusual_pkg_title": "Tamaño de paquete inusual",
+ "banner_review_unusual_pkg_detail": "Configuraste un paquete de {qty} {unit} — el tamaño parece muy grande. Comprueba si es correcto o edita.",
+ "banner_review_low_qty_title": "Cantidad muy baja",
+ "banner_review_low_qty_detail": "Solo tienes {qty} en stock — parece muy poco, puede ser un error de escritura. Confirma si es correcto.",
+ "banner_review_high_qty_title": "Cantidad inusualmente alta",
+ "banner_review_high_qty_detail": "Tienes {qty} en stock — la cifra parece muy alta. Confirma si es correcto o edita.",
+ "banner_prediction_rate_day": "Media ~{n} {unit}/día",
+ "banner_prediction_rate_week": "Media ~{n} {unit}/semana",
+ "banner_prediction_days_ago": "Hace {n} días reabasteciste",
+ "banner_prediction_more": "estimación anterior: {expected} {unit}{time}; cantidad actual: {actual} {unit}.",
+ "banner_prediction_less": "estimación: {expected} {unit}{time}; cantidad actual: {actual} {unit}. Si tu ritmo de uso cambió, la previsión se actualiza automáticamente.",
+ "banner_finished_zero": "El inventario muestra cero, pero los movimientos registrados sugieren que no debería estar vacío.",
+ "banner_finished_expected": "Según los registros deberías tener todavía {qty} {unit}.",
+ "banner_finished_check": "¿Puedes comprobarlo?",
+ "banner_anomaly_phantom_title": "tienes más stock del esperado",
+ "banner_anomaly_phantom_detail": "El inventario indica {inv_qty} {unit}, pero según los registros solo deberías tener {expected_qty} {unit}. ¿Añadiste stock sin registrarlo?",
+ "banner_anomaly_untracked_title": "stock no registrado como entrada",
+ "banner_anomaly_untracked_detail": "Tienes
{inv_qty} {unit} en inventario, pero las salidas registradas superan las entradas — el stock inicial probablemente nunca se añadió como transacción «entrada». Puedes corregir la cantidad o registrar las entradas faltantes.",
+ "banner_anomaly_ghost_title": "tienes menos stock del esperado",
+ "banner_anomaly_ghost_detail": "Según las operaciones registradas deberías tener {expected_qty} {unit} de {name}, pero el inventario solo muestra {inv_qty} {unit}. ¿Tomaste stock sin registrarlo?",
+ "consumed": "Consumido: {n} ({pct}%)",
+ "wasted": "Desperdiciado: {n} ({pct}%)",
+ "more_opened": "y {n} más abiertos...",
+ "banner_expired_detail": "{when} · aún tienes
{qty}.",
+ "banner_opened_detail": "{when} en {location} · aún tienes
{qty}.",
+ "banner_explain_title": "Pedir explicación a Gemini",
+ "banner_explain_btn": "Explicar",
+ "banner_analyzing": "🤖 Analizando…"
},
- "tts": {
- "title": "🔊 Voz & TTS",
- "hint": "Configura la síntesis de voz mediante cualquier API REST externa. Los pasos de la receta y los temporizadores expirados se enviarán al endpoint configurado.",
- "enabled": "✅ Activar TTS",
- "engine_label": "⚙️ Motor TTS",
- "engine_browser": "🔇 Navegador (sin conexión, sin configuración requerida)",
- "engine_server": "🌐 Servidor externo (Home Assistant, API REST...)",
- "voice_label": "🗣️ Voz",
- "rate_label": "⚡ Velocidad",
- "pitch_label": "🎵 Tono",
- "url_label": "🌐 URL del endpoint",
- "method_label": "📡 Método HTTP",
- "auth_label": "🔐 Autenticación",
- "auth_bearer": "Bearer Token",
- "auth_custom": "Cabecera personalizada",
- "auth_none": "Ninguna",
- "token_label": "🔑 Bearer Token",
- "custom_header_name": "📋 Nombre de cabecera",
- "custom_header_value": "📋 Valor de cabecera",
- "content_type_label": "📄 Content-Type",
- "payload_key_label": "🗝️ Campo de texto en el payload",
- "payload_key_hint": "Nombre del campo JSON que contendrá el texto a leer (ej.: message, text).",
- "extra_fields_label": "➕ Campos adicionales (JSON)",
- "extra_fields_placeholder": "{\"entity_id\": \"media_player.salon\"}",
- "extra_fields_hint": "Campos adicionales a incluir en el payload, en formato JSON. Deja vacío si no es necesario.",
- "test_btn": "🔊 Enviar voz de prueba",
- "voices_loading": "Cargando voces…",
- "voice_not_supported": "Voz no compatible con este navegador",
- "voices_none": "No hay voces disponibles en este dispositivo",
- "voices_hint": "Las voces disponibles dependen del SO y el navegador. Pulsa ↺ si la lista no carga.",
- "url_missing": "⚠️ URL del endpoint faltante.",
- "test_sending": "⏳ Enviando…",
- "test_ok": "✅ Respuesta {code} — comprueba que el altavoz haya hablado."
+ "inventory": {
+ "title": "Despensa",
+ "filter_all": "Todo",
+ "search_placeholder": "🔍 Buscar producto...",
+ "recent_title": "🕐 Usados recientemente",
+ "popular_title": "⭐ Más usados",
+ "empty": "No hay productos aquí.\n¡Escanea un producto para añadirlo!",
+ "no_items_found": "No se encontraron artículos",
+ "qty_remainder_suffix": "restante",
+ "vacuum_badge": "🫙 Al vacío",
+ "opened_badge": "📭 Abierto",
+ "label_expiry": "📅 Caducidad",
+ "label_storage": "🫙 Almacenamiento",
+ "label_status": "📭 Estado",
+ "opened_since": "Abierto desde el {date}",
+ "label_position": "📍 Ubicación",
+ "label_quantity": "📦 Cantidad",
+ "label_added": "📅 Añadido",
+ "empty_text": "No hay productos aquí.
¡Escanea un producto para añadirlo!",
+ "empty_db": "No hay productos en la base de datos.
¡Escanea un producto para empezar!",
+ "qty_trace": "< 1"
},
- "language": {
- "title": "🌐 Idioma",
- "hint": "Selecciona el idioma de la interfaz.",
- "label": "🌐 Idioma",
- "restart_notice": "La página se recargará para aplicar el nuevo idioma."
+ "scan": {
+ "title": "Escáner",
+ "mode_shopping": "🛒 Modo compras",
+ "mode_shopping_end": "✅ Finalizar compras",
+ "spesa_btn": "🛒 Compras",
+ "zoom": "Zoom",
+ "tab_barcode": "Código de barras",
+ "tab_name": "Nombre",
+ "tab_ai": "IA",
+ "recents_label": "Recientes",
+ "torch_hint": "Linterna",
+ "torch_on": "Linterna encendida",
+ "torch_off": "Linterna apagada",
+ "torch_unavailable": "Linterna no disponible en este dispositivo",
+ "flip_hint": "Cambiar cámara",
+ "flip_front": "Cámara frontal",
+ "flip_back": "Cámara trasera",
+ "num_ocr_btn": "🔢 Leer números con IA",
+ "num_ocr_searching": "Buscando código de barras con IA...",
+ "num_ocr_found": "Código encontrado: {code}",
+ "num_ocr_not_found": "No se encontró código de barras en la imagen",
+ "barcode_placeholder": "Introduce el código de barras...",
+ "quick_name_divider": "o escribe el nombre",
+ "quick_name_placeholder": "Ej.: Manzanas, Calabacín, Pan...",
+ "manual_entry": "✏️ Entrada manual",
+ "ai_identify": "🤖 Identificar con IA",
+ "hint": "Escanea el código de barras, escribe el nombre del producto o usa la IA para identificarlo",
+ "debug_toggle": "🐛 Registro de depuración",
+ "barcode_acquired": "🔖 Código de barras escaneado: {code}",
+ "scan_barcode": "🔖 Escanear código de barras",
+ "create_named": "Crear {name}",
+ "new_without_barcode": "Nuevo producto sin código de barras"
},
- "screensaver": {
- "label": "Activar protector de pantalla",
- "card_title": "🌙 Protector de pantalla",
- "card_hint": "Muestra un reloj con información útil después de 5 minutos de inactividad. Desactivado por defecto.",
- "timeout_1": "1 minuto",
- "timeout_2": "2 minutos",
- "timeout_5": "5 minutos",
- "timeout_10": "10 minutos",
- "timeout_15": "15 minutos",
- "timeout_30": "30 minutos",
- "timeout_60": "1 hora",
- "start_after": "⏱️ Iniciar tras"
+ "action": {
+ "title": "¿Qué quieres hacer?",
+ "add_btn": "📥 AÑADIR",
+ "add_sub": "a la despensa/nevera",
+ "use_btn": "📤 USAR / CONSUMIR",
+ "use_sub": "de la despensa/nevera",
+ "have_title": "📦 ¡Ya en stock!",
+ "add_more_sub": "añadir más",
+ "use_qty_sub": "cuánto usaste",
+ "throw_btn": "🗑️ DESECHAR",
+ "throw_sub": "tirar",
+ "edit_sub": "caducidad, ubicación…",
+ "create_recipe_btn": "Receta"
},
- "scale": {
- "title": "⚖️ Báscula inteligente",
- "hint": "Conecta una báscula Bluetooth mediante la pasarela Android para leer el peso automáticamente.",
- "tab": "Báscula inteligente",
- "enabled": "✅ Activar báscula inteligente",
- "url_label": "🌐 URL de la pasarela WebSocket",
- "url_placeholder": "ws://192.168.1.x:8765",
- "url_hint": "URL mostrada por la app Android (misma red Wi-Fi). Ej.:",
- "test_btn": "🔗 Probar conexión",
- "download_btn": "📥 Descargar pasarela Android (APK)",
- "download_hint": "App Android que conecta tu báscula BLE con EverShelf.",
- "download_sub": "Fuente: evershelf-scale-gateway/ en la raíz del proyecto",
- "live_weight": "peso en tiempo real",
- "auto_reconnect": "🔁 Reconexión: automática",
- "kiosk_title": "📡 Báscula BLE integrada en el kiosco",
- "kiosk_hint": "La báscula está gestionada directamente por la pasarela BLE interna del kiosco. Para vincular un nuevo dispositivo, usa el asistente de configuración.",
- "kiosk_reconfigure": "🔄 Reconfigurar báscula BLE",
- "ble_protocols": "
🔌 Protocolos BLE soportados:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — peso, grasa, IMC
- Xiaomi Mi Body Composition Scale 2
- Genérico — heurística automática para 100+ modelos
"
+ "add": {
+ "title": "Añadir a la despensa",
+ "location_label": "📍 ¿Dónde lo guardas?",
+ "quantity_label": "📦 Cantidad",
+ "conf_size_label": "📦 Cada paquete contiene:",
+ "conf_size_placeholder": "ej. 300",
+ "vacuum_label": "🫙 Al vacío",
+ "vacuum_hint": "La fecha de caducidad se ampliará automáticamente",
+ "submit": "✅ Añadir",
+ "purchase_type_label": "🛒 Este producto es...",
+ "new_btn": "🆕 Recién comprado",
+ "existing_btn": "📦 Ya lo tenía",
+ "remaining_label": "📦 Cantidad restante",
+ "remaining_hint": "¿Aproximadamente cuánto queda?",
+ "remaining_full": "🟢 Lleno",
+ "remaining_half": "🟠 Por la mitad",
+ "estimated_expiry": "Caducidad estimada:",
+ "suffix_freezer": "(congelador)",
+ "suffix_vacuum": "(al vacío)",
+ "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}",
+ "suffix_freezer_vacuum": "(congelador + al vacío)",
+ "history_badge_tip": "Media de {n} entradas anteriores",
+ "vacuum_question": "¿Al vacío?",
+ "vacuum_saved": "🔒 ¡Al vacío!"
},
- "kiosk": {
- "hint": "Convierte una tableta Android en un panel EverShelf permanente con pasarela BLE integrada.",
- "download_btn": "📥 Descargar EverShelf Kiosk (APK)",
- "download_sub": "Modo kiosco a pantalla completa + pasarela de báscula integrada. Fuente: evershelf-kiosk/",
- "native_title": "Configuración del kiosco",
- "native_hint": "URL del servidor, báscula BLE, protector de pantalla y asistente de configuración.",
- "native_btn": "Abrir configuración del kiosco",
- "native_tap_hint": "Toca el botón de engranaje en la parte superior derecha",
- "native_update_hint": "Actualiza la app del kiosco para usar esta función",
- "update_title": "Actualización del kiosco",
- "check_updates_btn": "🔍 Buscar actualizaciones",
- "needs_update": "⚠️ El kiosco instalado no admite esta función. Actualiza la app del kiosco para activarla."
+ "use": {
+ "title": "Usar / Consumir",
+ "location_label": "📍 ¿De dónde?",
+ "quantity_label": "¿Cuánto usaste?",
+ "change": "cambiar",
+ "partial_hint": "O especifica la cantidad usada:",
+ "partial_piece_hint": "¿Usaste solo una parte?",
+ "piece": "unidad",
+ "one_whole": "1 entero",
+ "use_all": "🗑️ Todo usado / Terminado",
+ "submit": "📤 Usar esta cantidad",
+ "available": "📦 Disponible:",
+ "opened_badge": "ABIERTO",
+ "not_in_inventory": "⚠️ Producto no en inventario.",
+ "expiry_warning": "⚠️ ¡Usa primero el{loc} que caduca el {date} — {when}!",
+ "expiry_warning_opened": "⚠️ El{loc} lleva abierto {when} — ¡úsalo primero!",
+ "throw_title": "🗑️ Desechar producto",
+ "throw_all": "🗑️ Desechar TODO ({qty})",
+ "throw_qty_label": "¿Cuánto desechar?",
+ "throw_qty_hint": "o introduce una cantidad:",
+ "throw_partial_btn": "🗑️ Desechar esta cantidad",
+ "when_expired": "caducado hace {n} días",
+ "when_today": "caduca
hoy",
+ "when_tomorrow": "caduca
mañana",
+ "when_days": "caduca en
{n} días",
+ "toast_used": "📤 {qty} de {name} usado",
+ "toast_bring": "🛒 Producto terminado → añadido a Bring!",
+ "toast_opened_finished": "🔓 ¡Paquete abierto de {name} terminado!",
+ "disambiguation_hint": "¿Qué quieres decir con «todo terminado»?",
+ "disambiguation_all": "🗑️ Terminar TODO ({qty})",
+ "error_exceeds_stock": "⚠️ ¡No puedes usar más de lo que tienes disponible!",
+ "use_all_confirm_title": "✅ Terminar todo",
+ "use_all_confirm_msg": "Confirma que has terminado el producto:",
+ "use_all_confirm_btn": "✅ Sí, terminado",
+ "throw_all_confirm_title": "🗑️ Desechar todo",
+ "throw_all_confirm_msg": "¿Realmente quieres tirar todo el producto?",
+ "throw_all_confirm_btn": "🗑️ Sí, desechar"
},
- "saved": "✅ ¡Configuración guardada!",
- "saved_local": "✅ Configuración guardada localmente",
- "saved_local_error": "⚠️ Guardado localmente, error del servidor: {error}",
- "theme": {
- "title": "🌙 Apariencia",
- "hint": "Elige el tema de la interfaz.",
- "label": "🌙 Tema",
- "off": "☀️ Claro",
- "on": "🌙 Oscuro",
- "auto": "🔄 Automático (sistema)"
+ "product": {
+ "title_new": "Nuevo producto",
+ "title_edit": "Editar producto",
+ "ai_fill": "📷 Sacar foto e identificar con IA",
+ "ai_fill_hint": "La IA rellenará automáticamente los campos del producto",
+ "name_label": "🏷️ Nombre del producto *",
+ "name_placeholder": "Ej.: Leche entera, Pasta penne...",
+ "brand_label": "🏢 Marca",
+ "brand_placeholder": "Ej.: Barilla, Danone, Heinz...",
+ "category_label": "📂 Categoría",
+ "unit_label": "📏 Unidad de medida",
+ "default_qty_label": "🔢 Cantidad por defecto",
+ "conf_size_label": "📦 Cada paquete contiene:",
+ "conf_size_placeholder": "ej. 300",
+ "notes_label": "📝 Notas",
+ "notes_placeholder": "Ej.: sin lactosa, ecológico, guardar en nevera tras abrir...",
+ "barcode_label": "🔖 Código de barras",
+ "barcode_placeholder": "Código de barras (si disponible)",
+ "barcode_hint": "⚠️ ¡Añade el código de barras para la próxima vez solo tendrás que escanear!",
+ "submit": "💾 Guardar producto",
+ "name_required": "Introduce el nombre del producto",
+ "conf_size_required": "Especifica el contenido del paquete",
+ "expiry_estimated": "Caducidad estimada:",
+ "scan_expiry": "Escanear fecha de caducidad",
+ "expiry_hint": "📝 Puedes editar la fecha o escanearla con la cámara",
+ "add_batch": "📦 + Lote con fecha diferente",
+ "package_info": "📦 Paquete: {info}",
+ "edit_catalog": "⚙️ Editar información del producto (nombre, marca, categoría…)",
+ "not_recognized": "⚠️ Producto no reconocido",
+ "edit_info": "✏️ Editar información",
+ "modify_details": "EDITAR\ncaducidad, ubicación…",
+ "already_in_pantry": "📋 Ya en la despensa",
+ "no_barcode": "Sin código de barras",
+ "unknown_product": "Producto no reconocido",
+ "edit_name_brand": "Editar nombre/marca",
+ "weight_label": "Peso",
+ "origin_label": "Origen",
+ "labels_label": "Etiquetas",
+ "select_variant": "Selecciona la variante exacta o usa los datos de IA:"
},
- "zerowaste": {
- "card_title": "♻️ Consejos sin desperdicios",
- "card_hint": "Durante la cocción, muestra consejos sobre cómo reutilizar los restos generados en cada paso (peladuras, agua de cocción, etc.). Desactivado por defecto.",
- "label": "Mostrar consejos durante la cocción"
+ "products": {
+ "title": "📦 Todos los productos",
+ "search_placeholder": "🔍 Buscar producto...",
+ "empty": "No hay productos en la base de datos.\n¡Escanea un producto para empezar!",
+ "no_category": "No hay productos en esta categoría"
},
- "backup": {
- "tab": "Copia de seguridad",
- "local_title": "Copia local",
- "local_hint": "Instantánea diaria de la base de datos. Configura cuántos días de copias de seguridad conservar.",
- "enabled": "Activar copia de seguridad diaria automática",
- "retention_days": "Retención (días)",
- "retention_info": "Las copias se conservan durante",
- "backup_now": "Hacer copia ahora",
- "backing_up": "Haciendo copia…",
- "backed_up": "Copia completada",
- "backup_error": "Error en la copia",
- "last_backup": "Última copia",
- "no_backup_yet": "Aún no se ha creado ninguna copia",
- "list_empty": "No hay copias disponibles",
- "restore_btn": "Restaurar",
- "restore_confirm": "Restaurar la copia",
- "delete_btn": "Eliminar",
- "delete_confirm": "Eliminar la copia",
- "gdrive_title": "Google Drive",
- "gdrive_hint": "Copias de seguridad automáticas en Google Drive via OAuth 2.0. No se requieren bibliotecas externas.",
- "gdrive_enabled": "Activar copia en Google Drive",
- "gdrive_folder_id": "ID de carpeta de Drive",
- "gdrive_folder_id_hint": "Copia el ID desde la URL de la carpeta de Drive: …/folders/
ID",
- "gdrive_retention_days": "Retención en Drive (días, 0=mantener todo)",
- "gdrive_test": "Probar conexión",
- "gdrive_ok": "Conexión exitosa!",
- "gdrive_error": "Conexión fallida",
- "gdrive_push_now": "Subir a Drive ahora",
- "gdrive_pushing": "Subiendo…",
- "gdrive_pushed": "Subido a Drive",
- "gdrive_wizard_hint": "Opcional: copia de seguridad diaria automática en Google Drive via OAuth 2.0.",
- "gdrive_skip": "Omitir — configurar después en Ajustes",
- "gdrive_client_id": "Client ID",
- "gdrive_client_secret": "Client Secret",
- "gdrive_redirect_uri_hint": "Agrega
http://localhost como URI de redireccionamiento autorizado en Google Cloud Console. Funciona en cualquier servidor, incluso sin dominio público.",
- "gdrive_code_title": "Pegar la URL o el código de autorización",
- "gdrive_code_hint": "Tras autorizar, el navegador abrirá http://localhost y puede mostrar un error de conexión — es normal. Copia la URL de la barra de direcciones (ej.
http://localhost/?code=4%2F0A...) y pégala aquí.",
- "gdrive_code_submit": "Confirmar",
- "gdrive_code_empty": "Pega primero la URL o el código de autorización",
- "gdrive_redirect_uri_label": "URI de redirección (agregar en Google Cloud Console):",
- "gdrive_oauth_authorize": "Autorizar con Google",
- "gdrive_oauth_authorized": "Autorizado",
- "gdrive_oauth_not_authorized": "Aún no autorizado",
- "gdrive_oauth_window_opened": "Ventana abierta — autoriza y regresa aquí",
- "gdrive_oauth_how_to": "Cómo configurar OAuth 2.0 (paso a paso)",
- "gdrive_oauth_steps": "
Ve a console.cloud.google.com y selecciona tu proyectoHabilita la API de Google Drive: API y servicios → Habilitar API → Google Drive APIVe a API y servicios → Credenciales → Crear credenciales → ID de cliente OAuthTipo de aplicación: Aplicación web; agrega la URL mostrada abajo como URI de redirección autorizadoCopia el Client ID y el Client Secret en los campos de arriba y guardaHaz clic en Autorizar con Google: inicia sesión en tu cuenta de Google y concede accesoLa ventana se cierra automáticamente al finalizar y las copias de seguridad están listas"
+ "recipes": {
+ "title": "🍳 Recetas",
+ "generate": "✨ Generar nueva receta",
+ "archive_empty": "No hay recetas guardadas. ¡Genera tu primera receta!",
+ "dialog_title": "🍳 Receta",
+ "dialog_desc": "Generaré una receta saludable usando ingredientes de la despensa, priorizando los productos que caducan.",
+ "meal_label": "🕐 ¿Qué comida?",
+ "persons_label": "👥 ¿Para cuántas personas?",
+ "meal_type_label": "🎯 Tipo de comida",
+ "opt_fast": "⚡ Comida rápida",
+ "opt_light": "🥗 Apetito ligero",
+ "opt_expiry": "⏰ Priorizar productos que caducan",
+ "opt_healthy": "💚 Extra saludable",
+ "opt_opened": "📦 Priorizar productos abiertos",
+ "opt_zero_waste": "♻️ Cero desperdicio",
+ "generate_btn": "✨ Generar receta",
+ "loading_msg": "Preparando tu receta...",
+ "start_cooking": "👨🍳 Modo cocina",
+ "regenerate": "🔄 Generar otra",
+ "close_btn": "✅ Cerrar",
+ "ingredients_title": "🧾 Ingredientes",
+ "tools_title": "Equipo necesario",
+ "steps_title": "👨🍳 Pasos",
+ "no_steps": "No hay pasos disponibles",
+ "generate_error": "Error de generación",
+ "persons_short": "com.",
+ "use_ingredient_title": "Usar ingrediente",
+ "recipe_qty_label": "Receta",
+ "from_where_label": "¿De dónde?",
+ "amount_label": "Cuánto",
+ "use_amount_btn": "Usar esta cantidad",
+ "use_all_btn": "Usar TODO / Terminado",
+ "packs_label": "Paquetes",
+ "quantity_in_total": "Cantidad en {unit} (total: {total})",
+ "packs_of_have": "Paquetes de {size} (tienes {count} paquetes)",
+ "scale_wait_stable": "Espera 10s de peso estable para el relleno automático…",
+ "ingredient_scaled_toast": "📦 ¡Ingrediente deducido de la despensa!",
+ "finished_added_bring_toast": "🛒 Producto terminado → ¡añadido a Bring!",
+ "load_error": "Error de carga"
},
"shopping": {
- "tab": "Lista de la compra",
- "title": "Lista de la compra",
- "hint": "Configura la lista de la compra integrada o conecta Bring!.",
- "enable_label": "Activar lista de la compra",
- "mode_label": "Proveedor",
- "mode_internal": "Integrado (sin Bring!)",
- "mode_bring": "Bring! (app externa)",
- "bring_section_title": "Configuración de Bring!",
- "ai_section_title": "Asistencia IA",
- "smart_suggestions_label": "Sugerencias IA",
- "forecast_label": "Previsión de productos por agotar",
- "auto_add_label": "Añadir automáticamente cuando",
- "auto_add_suffix": "restante en stock (0 = solo cuando se agota)"
+ "title": "🛒 Lista de la compra",
+ "bring_loading": "Conectando a Bring!...",
+ "bring_not_configured": "Bring! no está configurado. Añade tu email y contraseña en los
ajustes.",
+ "tab_to_buy": "🛍️ Por comprar",
+ "tab_forecast": "🧠 Previsión",
+ "total_label": "💰 Total estimado",
+ "section_to_buy": "🛍️ Por comprar",
+ "suggestions_title": "💡 Sugerencias IA",
+ "suggestions_add": "✅ Añadir selección a Bring!",
+ "search_prices": "🔍 Buscar todos los precios",
+ "suggest_btn": "Sugerir qué comprar",
+ "smart_title": "🧠 Predicciones inteligentes",
+ "smart_empty": "No hay predicciones disponibles.
Añade productos a tu despensa para recibir predicciones inteligentes.",
+ "smart_filter_all": "Todo",
+ "smart_filter_critical": "🔴 Urgente",
+ "smart_filter_high": "🟠 Pronto",
+ "smart_filter_medium": "🟡 Planificar",
+ "smart_filter_low": "🟢 Previsión",
+ "smart_add": "🛒 Añadir selección a Bring!",
+ "empty": "¡Lista de la compra vacía!\nUsa el botón de abajo para generar sugerencias.",
+ "already_in_list": "🛒 \"{name}\" ya está en la lista de la compra",
+ "already_in_list_short": "ℹ️ Ya en la lista de la compra",
+ "add_prompt": "¿Quieres añadirlo a la lista de la compra?",
+ "smart_already": "📊 Las predicciones de compra ya predicen {name}",
+ "all_searched": "Todos los productos ya han sido buscados. Usa 🔄 para buscar individualmente.",
+ "search_complete": "Búsqueda completada: {count} productos",
+ "removed_sufficient": "🧹 {removed} producto(s) con stock suficiente eliminado(s) de la lista",
+ "suggest_buy": "🛒 Comprar: {qty} {unit}",
+ "suggest_buy_approx": "🛒 Al menos: {qty} {unit}",
+ "suggest_buy_tip": "Cantidad sugerida basada en tus últimos 14 días de consumo",
+ "suggest_buy_approx_tip": "Estimación mínima basada en el consumo (compra el tamaño de paquete más cercano)",
+ "bring_badge": "🛒 Ya en Bring!",
+ "add_urgent_toast": "🔴 {n} producto(s) urgente(s) añadido(s) automáticamente a Bring!",
+ "migration_done": "✅ {migrated} actualizado(s), {skipped} ya estaban ok",
+ "added_to_bring": "🛒 {n} productos añadidos a Bring!",
+ "added_to_bring_skip": "{n} ya presentes",
+ "all_on_bring": "¡Todos los productos ya estaban en Bring!",
+ "freq_high": "📈 Frecuente",
+ "freq_regular": "📊 Regular",
+ "freq_occasional": "📉 Ocasional",
+ "out_of_stock": "Sin stock",
+ "scan_toast": "📷 Escanear: {name}",
+ "empty_category": "No hay productos en esta categoría",
+ "session_empty": "🛒 Sin productos aún",
+ "urgency_critical": "Urgente",
+ "urgency_high": "Pronto",
+ "urgency_medium": "Planificar",
+ "urgency_low": "Previsión",
+ "urgency_medium_short": "Medio",
+ "urgency_low_short": "Ok",
+ "tag_urgent": "🔴 Urgente",
+ "tag_priority": "⭐ Prioridad",
+ "tag_check": "✅ Comprobar",
+ "smart_already_predicted": "📊 Las predicciones inteligentes ya predicen
{name}{urgency}.",
+ "item_removed": "✅ ¡{name} eliminado de la lista!",
+ "urgency_spec_critical": "⚡ Urgente",
+ "urgency_spec_high": "🟠 Pronto",
+ "bring_add_n": "Añadir {n} a Bring!",
+ "bring_add_selected": "Añadir selección a Bring!",
+ "bring_adding": "Añadiendo...",
+ "bring_added_one": "1 producto añadido a Bring!",
+ "bring_added_many": "{n} productos añadidos a Bring!",
+ "bring_skipped": "({n} ya en la lista)",
+ "force_sync": "Forzar sincronización de Bring!",
+ "scan_target_label": "Estás buscando",
+ "scan_target_found": "¡Encontrado! Eliminar de la lista",
+ "bring_add_one": "Añadir 1 producto a Bring!",
+ "bring_add_many": "Añadir {n} productos a Bring!",
+ "syncing": "Sincronizando…",
+ "sync_done": "Sincronización completada",
+ "price_searching": "Buscando...",
+ "search_action": "Buscar",
+ "open_action": "Abrir",
+ "not_found": "No encontrado",
+ "search_price": "Buscar precio",
+ "tap_to_scan": "Toca para escanear",
+ "tag_title": "Etiqueta",
+ "remove_title": "Eliminar",
+ "found_count": "{found}/{total} productos encontrados",
+ "savings_offers": "· 🏷️ Ahorras {amount}€ con ofertas",
+ "searching_progress": "Buscando {current}/{total}...",
+ "remove_error": "Error al eliminar",
+ "btn_fetch_prices": "Buscar precios",
+ "price_total_label": "💰 Total estimado:",
+ "price_loading": "Buscando precios…",
+ "price_not_found": "precio n/d",
+ "suggest_loading": "Analizando...",
+ "suggest_error": "Error al generar sugerencias",
+ "priority_high": "Alta",
+ "priority_medium": "Media",
+ "priority_low": "Baja",
+ "smart_last_update": "Actualizado {time}",
+ "names_already_updated": "Todos los nombres ya están actualizados"
},
- "ha": {
- "tab": "Home Assistant",
- "title": "Home Assistant",
- "hint": "Conecta EverShelf a Home Assistant para automatizaciones, notificaciones push y sensores REST.",
- "enabled": "Activar integración con Home Assistant",
- "connection_title": "Conexión",
- "url_label": "URL de Home Assistant",
- "url_placeholder": "http://192.168.1.50:8123",
- "url_hint": "URL base de tu instancia de Home Assistant.",
- "token_label": "Token de acceso de larga duración",
- "token_hint": "Genera desde Perfil HA → Seguridad → Tokens de acceso de larga duración.",
- "token_placeholder": "eyJhbGci...",
- "token_saved": "Token guardado (oculto por seguridad)",
- "test_btn": "Probar conexión",
- "test_ok": "Conectado a {version}",
- "test_fail": "Conexión fallida: {error}",
- "test_bad_token": "HA accesible pero el token no es válido",
- "testing": "Probando…",
- "error_no_url": "Por favor, introduce primero la URL de Home Assistant.",
- "tts_title": "TTS en altavoz inteligente",
- "tts_hint": "Lee los pasos de la receta en un reproductor de medios de Home Assistant.",
- "tts_entity_label": "Entity ID del reproductor multimedia",
- "tts_entity_placeholder": "media_player.salon",
- "tts_entity_hint": "ID de entidad del reproductor multimedia HA. Encuéntralo en HA: Herramientas para desarrolladores → Estados.",
- "tts_platform_label": "Plataforma TTS",
- "tts_platform_speak": "tts.speak (recomendado)",
- "tts_platform_notify": "notify.* (servicio de notificaciones)",
- "tts_apply_btn": "Aplicar preset HA a la pestaña TTS",
- "tts_apply_hint": "Pre-rellena la pestaña TTS con la URL y el token de Home Assistant.",
- "tts_preset_applied": "Preset HA aplicado a la pestaña TTS.",
- "webhook_title": "Automatizaciones Webhook",
- "webhook_hint": "Envía datos a Home Assistant cuando ocurren eventos en la despensa.",
- "webhook_id_label": "ID de Webhook",
- "webhook_id_placeholder": "evershelf_webhook_abc123",
- "webhook_id_hint": "ID del webhook creado en HA. Copia desde: HA → Ajustes → Automatizaciones → Crear → Disparador Webhook.",
- "webhook_events_label": "Notificar en estos eventos",
- "event_expiry": "Productos próximos a caducar (diario)",
- "event_shopping": "Artículo añadido a la lista de compras",
- "event_stock": "Nivel de stock actualizado",
- "expiry_days_label": "Antelación de caducidad (días)",
- "expiry_days_hint": "Enviar alerta de caducidad N días antes de la fecha.",
- "webhook_help": "En HA: Ajustes → Automatizaciones → Crear automatización → Disparador: Webhook → copia el ID generado.",
- "notify_title": "Notificaciones push",
- "notify_hint": "Envía notificaciones push a tu teléfono mediante un servicio notify de Home Assistant.",
- "notify_service_label": "Servicio notify",
- "notify_service_placeholder": "notify.mobile_app_mi_telefono",
- "notify_service_hint": "Nombre del servicio notify de HA. Déjalo vacío para desactivar.",
- "sensor_title": "Sensores REST",
- "sensor_hint": "Añade a configuration.yaml para crear sensores de EverShelf en Home Assistant.",
- "sensor_copy_btn": "Copiar YAML",
- "sensor_copied": "¡YAML copiado al portapapeles!",
- "save_btn": "Guardar ajustes HA",
- "ha_hint": "Si usas Home Assistant, utiliza la pestaña Home Assistant para configurar TTS, webhooks y sensores."
+ "ai": {
+ "title": "🤖 Identificación IA",
+ "capture": "📸 Sacar foto",
+ "retake": "🔄 Repetir",
+ "hint": "Saca una foto del producto y la IA intentará identificarlo",
+ "identifying": "🤖 Identificando producto...",
+ "no_api_key": "⚠️ Clave API de Gemini no configurada.\n
Añade GEMINI_API_KEY al archivo .env en el servidor.",
+ "fields_filled": "✅ Campos rellenados por IA",
+ "use_data": "✅ Usar datos de IA",
+ "use_data_no_barcode": "✅ Usar datos de IA (sin código de barras)"
+ },
+ "log": {
+ "title": "📒 Registro de operaciones",
+ "type_added": "Añadido",
+ "type_waste": "Desechado",
+ "type_used": "Usado",
+ "type_bring": "Añadido a Bring!",
+ "undone_badge": "Deshecho",
+ "undo_title": "Deshacer esta operación",
+ "load_error": "Error al cargar el registro",
+ "empty": "No hay operaciones registradas.",
+ "undo_action_remove": "eliminación de",
+ "undo_action_restore": "reabastecimiento de",
+ "undo_confirm": "¿Deshacer esta operación?\n→ {action} {name}",
+ "undo_success": "↩ Operación deshecha para {name}",
+ "already_undone": "Operación ya deshecha",
+ "too_old": "No se pueden deshacer operaciones de más de 24 horas",
+ "undo_error": "Error al deshacer",
+ "recipe_prefix": "Receta"
+ },
+ "chat": {
+ "title": "Chef Gemini",
+ "welcome": "¡Hola! Soy tu asistente de cocina",
+ "welcome_desc": "¡Pídeme que te prepare un zumo, un snack, un plato rápido… Conozco tu despensa, tus electrodomésticos y tus preferencias!",
+ "suggestion_snack": "🍿 Snack rápido",
+ "suggestion_juice": "🥤 Zumo/Batido",
+ "suggestion_light": "🥗 Algo ligero",
+ "suggestion_expiry": "⏰ Usar productos que caducan",
+ "clear": "Nueva conversación",
+ "placeholder": "Pregunta algo...",
+ "cleared": "Chat borrado",
+ "suggestion_snack_text": "¿Qué puedo preparar como snack rápido?",
+ "suggestion_juice_text": "Hazme un zumo o batido con lo que tengo",
+ "suggestion_light_text": "Tengo hambre pero quiero algo ligero",
+ "suggestion_expiry_text": "¿Qué está a punto de caducar y cómo puedo usarlo?",
+ "transfer_to_recipes": "Transferir a recetas",
+ "transferring": "Transfiriendo...",
+ "transferred": "¡Añadido a recetas!",
+ "open_recipe": "Abrir receta",
+ "quick_recipe_prompt": "¡Sugiere una receta rápida PARA UNA PERSONA usando los productos que caducan primero! Ignora el congelador, céntrate en la nevera y la despensa."
+ },
+ "cooking": {
+ "close": "Cerrar",
+ "tts_btn": "Leer en voz alta",
+ "restart": "↺ Reiniciar",
+ "replay": "🔊 Repetir",
+ "timer": "⏱️ {time} · Temporizador",
+ "prev": "◀ Anterior",
+ "next": "Siguiente ▶",
+ "ingredient_used": "✔️ Deducido",
+ "ingredient_use_btn": "📦 Usar",
+ "ingredient_deduct_title": "Deducir de la despensa",
+ "timer_expired_tts": "¡Temporizador {label} finalizado!",
+ "timer_warning_tts": "¡Atención! {label}: ¡10 segundos restantes!",
+ "recipe_done_tts": "¡Receta completada! ¡Buen provecho!",
+ "expires_chip": "cad. {date}",
+ "finish": "✅ Finalizar",
+ "step_fallback": "Paso {n}",
+ "zerowaste_label": "♻️ Desperdicio",
+ "zerowaste_tip_title": "Consejo sin desperdicios"
+ },
+ "settings": {
+ "title": "⚙️ Ajustes",
+ "tab_api": "Claves API",
+ "tab_bring": "Bring!",
+ "tab_recipe": "Recetas",
+ "tab_mealplan": "Plan semanal",
+ "tab_appliances": "Electrodomésticos",
+ "tab_spesa": "Compra online",
+ "tab_camera": "Cámara",
+ "tab_security": "Seguridad",
+ "tab_tts": "Voz (TTS)",
+ "tab_language": "Idioma",
+ "tab_scale": "Báscula inteligente",
+ "gemini": {
+ "title": "🤖 Google Gemini IA",
+ "hint": "Clave API para identificación de productos, fechas de caducidad y recetas.",
+ "key_label": "Clave API Gemini"
+ },
+ "bring": {
+ "title": "🛒 Lista de la compra Bring!",
+ "hint": "Credenciales para la integración con la lista de la compra Bring!",
+ "email_label": "📧 Email Bring!",
+ "password_label": "🔒 Contraseña Bring!"
+ },
+ "price": {
+ "title": "💰 Estimación de precios (IA)",
+ "hint": "Mostrar el coste estimado por producto en la lista de la compra usando IA.",
+ "enabled_label": "Activar estimación de precios",
+ "country_label": "🌍 País de referencia",
+ "currency_label": "💱 Moneda",
+ "update_label": "🔄 Actualizar precios cada",
+ "update_suffix": "meses"
+ },
+ "recipe": {
+ "title": "🍳 Preferencias de recetas",
+ "hint": "Configura las opciones predeterminadas para la generación de recetas.",
+ "persons_label": "👥 Raciones por defecto",
+ "options_label": "🎯 Opciones de receta por defecto",
+ "fast": "⚡ Comida rápida",
+ "light": "🥗 Comida ligera",
+ "expiry": "⏰ Prioridad caducidad",
+ "healthy": "💚 Extra saludable",
+ "opened": "📦 Prioridad productos abiertos",
+ "zerowaste": "♻️ Cero desperdicio",
+ "dietary_label": "🚫 Intolerancias / Restricciones",
+ "dietary_placeholder": "Ej.: sin gluten, sin lactosa, vegetariano..."
+ },
+ "mealplan": {
+ "title": "📅 Plan de comidas semanal",
+ "hint": "Establece el tipo de comida para cada día. Se usará como guía en la generación de recetas.",
+ "enabled": "✅ Activar plan semanal",
+ "legend": "🌤️ = Almuerzo · 🌙 = Cena · Toca un badge para cambiarlo.",
+ "types_title": "📋 Tipos disponibles",
+ "reset_btn": "↺ Restaurar valores por defecto"
+ },
+ "appliances": {
+ "title": "🔌 Electrodomésticos disponibles",
+ "hint": "Indica los electrodomésticos que tienes. Se tendrán en cuenta en la generación de recetas.",
+ "new_placeholder": "Ej.: Panificadora, Thermomix, Freidora de aire...",
+ "quick_title": "Añadir rápido:",
+ "oven": "🔥 Horno",
+ "microwave": "📡 Microondas",
+ "air_fryer": "🍟 Freidora de aire",
+ "bread_maker": "🍞 Panificadora",
+ "bimby": "🤖 Thermomix/Cookeo",
+ "mixer": "🌀 Robot de cocina",
+ "steamer": "♨️ Vaporera",
+ "pressure_cooker": "🫕 Olla a presión",
+ "toaster": "🍞 Tostadora",
+ "blender": "🍹 Batidora",
+ "empty": "No hay electrodomésticos añadidos"
+ },
+ "spesa": {
+ "title": "🛍️ Compra online",
+ "hint": "Configura el proveedor de compra online.",
+ "provider_label": "🏪 Proveedor",
+ "email_label": "📧 Email",
+ "password_label": "🔒 Contraseña",
+ "login_btn": "🔐 Iniciar sesión",
+ "ai_prompt_label": "🤖 Prompt IA de selección de producto",
+ "ai_prompt_placeholder": "Instrucciones para la IA al elegir entre varios productos...",
+ "ai_prompt_hint": "La IA usa este prompt para elegir el producto más apropiado de los resultados. Deja vacío para el comportamiento por defecto.",
+ "configure_first": "Configura primero la compra online en ajustes",
+ "missing_credentials": "Introduce email y contraseña",
+ "login_in_progress": "Iniciando sesión...",
+ "login_error_prefix": "Error:",
+ "login_network_error_prefix": "Error de red:",
+ "login_success_default": "¡Inicio de sesión exitoso!",
+ "result_name_label": "Nombre",
+ "result_card_label": "Tarjeta",
+ "result_pickup_label": "Punto de recogida",
+ "result_points_label": "Puntos de fidelidad",
+ "connected_relogin": "✅ Conectado — Iniciar sesión de nuevo",
+ "connected_as": "Conectado como {name}"
+ },
+ "camera": {
+ "title": "📷 Cámara",
+ "hint": "Elige qué cámara usar para el escaneo de códigos de barras e identificación IA.",
+ "device_label": "📸 Cámara por defecto",
+ "back": "📱 Trasera (por defecto)",
+ "front": "🤳 Frontal",
+ "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"
+ },
+ "security": {
+ "title": "🔒 Certificado HTTPS",
+ "hint": "Si el navegador muestra el error «Tu conexión no es privada» (ERR_CERT_AUTHORITY_INVALID), necesitas instalar el certificado CA en el dispositivo.",
+ "download_btn": "📥 Descargar certificado CA",
+ "token_title": "🔑 Token de ajustes",
+ "token_label": "Token de acceso",
+ "token_hint": "Si `SETTINGS_TOKEN` está configurado en el `.env` del servidor, introduce el token aquí antes de guardar los ajustes. Deja vacío si no está configurado.",
+ "token_placeholder": "(vacío = sin protección)",
+ "token_required_hint": "🔒 Este servidor requiere un token para guardar los ajustes.",
+ "cert_instructions": "
Instrucciones para Chrome (Android):1. Descarga el certificado de arriba
2. Ve a
Ajustes → Seguridad y privacidad → Más ajustes de seguridad → Instalar desde almacenamiento3. Selecciona el archivo
EverShelf_CA.crt descargado
4. Elige «CA» y confirma
5. Reinicia Chrome
Instrucciones para Chrome (PC):1. Descarga el certificado de arriba
2. Ve a
chrome://settings/certificates3. Pestaña «Autoridades» → Importar → selecciona el archivo
4. Marca «Confiar en este certificado para identificar sitios web»
5. Reinicia Chrome"
+ },
+ "tts": {
+ "title": "🔊 Voz & TTS",
+ "hint": "Configura la síntesis de voz mediante cualquier API REST externa. Los pasos de la receta y los temporizadores expirados se enviarán al endpoint configurado.",
+ "enabled": "✅ Activar TTS",
+ "engine_label": "⚙️ Motor TTS",
+ "engine_browser": "🔇 Navegador (sin conexión, sin configuración requerida)",
+ "engine_server": "🌐 Servidor externo (Home Assistant, API REST...)",
+ "voice_label": "🗣️ Voz",
+ "rate_label": "⚡ Velocidad",
+ "pitch_label": "🎵 Tono",
+ "url_label": "🌐 URL del endpoint",
+ "method_label": "📡 Método HTTP",
+ "auth_label": "🔐 Autenticación",
+ "auth_bearer": "Bearer Token",
+ "auth_custom": "Cabecera personalizada",
+ "auth_none": "Ninguna",
+ "token_label": "🔑 Bearer Token",
+ "custom_header_name": "📋 Nombre de cabecera",
+ "custom_header_value": "📋 Valor de cabecera",
+ "content_type_label": "📄 Content-Type",
+ "payload_key_label": "🗝️ Campo de texto en el payload",
+ "payload_key_hint": "Nombre del campo JSON que contendrá el texto a leer (ej.: message, text).",
+ "extra_fields_label": "➕ Campos adicionales (JSON)",
+ "extra_fields_placeholder": "{\"entity_id\": \"media_player.salon\"}",
+ "extra_fields_hint": "Campos adicionales a incluir en el payload, en formato JSON. Deja vacío si no es necesario.",
+ "test_btn": "🔊 Enviar voz de prueba",
+ "voices_loading": "Cargando voces…",
+ "voice_not_supported": "Voz no compatible con este navegador",
+ "voices_none": "No hay voces disponibles en este dispositivo",
+ "voices_hint": "Las voces disponibles dependen del SO y el navegador. Pulsa ↺ si la lista no carga.",
+ "url_missing": "⚠️ URL del endpoint faltante.",
+ "test_sending": "⏳ Enviando…",
+ "test_ok": "✅ Respuesta {code} — comprueba que el altavoz haya hablado."
+ },
+ "language": {
+ "title": "🌐 Idioma",
+ "hint": "Selecciona el idioma de la interfaz.",
+ "label": "🌐 Idioma",
+ "restart_notice": "La página se recargará para aplicar el nuevo idioma."
+ },
+ "screensaver": {
+ "label": "Activar protector de pantalla",
+ "card_title": "🌙 Protector de pantalla",
+ "card_hint": "Muestra un reloj con información útil después de 5 minutos de inactividad. Desactivado por defecto.",
+ "timeout_1": "1 minuto",
+ "timeout_2": "2 minutos",
+ "timeout_5": "5 minutos",
+ "timeout_10": "10 minutos",
+ "timeout_15": "15 minutos",
+ "timeout_30": "30 minutos",
+ "timeout_60": "1 hora",
+ "start_after": "⏱️ Iniciar tras"
+ },
+ "scale": {
+ "title": "⚖️ Báscula inteligente",
+ "hint": "Conecta una báscula Bluetooth mediante la pasarela Android para leer el peso automáticamente.",
+ "tab": "Báscula inteligente",
+ "enabled": "✅ Activar báscula inteligente",
+ "url_label": "🌐 URL de la pasarela WebSocket",
+ "url_placeholder": "ws://192.168.1.x:8765",
+ "url_hint": "URL mostrada por la app Android (misma red Wi-Fi). Ej.:",
+ "test_btn": "🔗 Probar conexión",
+ "download_btn": "📥 Descargar pasarela Android (APK)",
+ "download_hint": "App Android que conecta tu báscula BLE con EverShelf.",
+ "download_sub": "Fuente: evershelf-scale-gateway/ en la raíz del proyecto",
+ "live_weight": "peso en tiempo real",
+ "auto_reconnect": "🔁 Reconexión: automática",
+ "kiosk_title": "📡 Báscula BLE integrada en el kiosco",
+ "kiosk_hint": "La báscula está gestionada directamente por la pasarela BLE interna del kiosco. Para vincular un nuevo dispositivo, usa el asistente de configuración.",
+ "kiosk_reconfigure": "🔄 Reconfigurar báscula BLE",
+ "ble_protocols": "
🔌 Protocolos BLE soportados:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — peso, grasa, IMC
- Xiaomi Mi Body Composition Scale 2
- Genérico — heurística automática para 100+ modelos
"
+ },
+ "kiosk": {
+ "hint": "Convierte una tableta Android en un panel EverShelf permanente con pasarela BLE integrada.",
+ "download_btn": "📥 Descargar EverShelf Kiosk (APK)",
+ "download_sub": "Modo kiosco a pantalla completa + pasarela de báscula integrada. Fuente: evershelf-kiosk/",
+ "native_title": "Configuración del kiosco",
+ "native_hint": "URL del servidor, báscula BLE, protector de pantalla y asistente de configuración.",
+ "native_btn": "Abrir configuración del kiosco",
+ "native_tap_hint": "Toca el botón de engranaje en la parte superior derecha",
+ "native_update_hint": "Actualiza la app del kiosco para usar esta función",
+ "update_title": "Actualización del kiosco",
+ "check_updates_btn": "🔍 Buscar actualizaciones",
+ "needs_update": "⚠️ El kiosco instalado no admite esta función. Actualiza la app del kiosco para activarla."
+ },
+ "saved": "✅ ¡Configuración guardada!",
+ "saved_local": "✅ Configuración guardada localmente",
+ "saved_local_error": "⚠️ Guardado localmente, error del servidor: {error}",
+ "theme": {
+ "title": "🌙 Apariencia",
+ "hint": "Elige el tema de la interfaz.",
+ "label": "🌙 Tema",
+ "off": "☀️ Claro",
+ "on": "🌙 Oscuro",
+ "auto": "🔄 Automático (sistema)"
+ },
+ "zerowaste": {
+ "card_title": "♻️ Consejos sin desperdicios",
+ "card_hint": "Durante la cocción, muestra consejos sobre cómo reutilizar los restos generados en cada paso (peladuras, agua de cocción, etc.). Desactivado por defecto.",
+ "label": "Mostrar consejos durante la cocción"
+ },
+ "backup": {
+ "tab": "Copia de seguridad",
+ "local_title": "Copia local",
+ "local_hint": "Instantánea diaria de la base de datos. Configura cuántos días de copias de seguridad conservar.",
+ "enabled": "Activar copia de seguridad diaria automática",
+ "retention_days": "Retención (días)",
+ "retention_info": "Las copias se conservan durante",
+ "backup_now": "Hacer copia ahora",
+ "backing_up": "Haciendo copia…",
+ "backed_up": "Copia completada",
+ "backup_error": "Error en la copia",
+ "last_backup": "Última copia",
+ "no_backup_yet": "Aún no se ha creado ninguna copia",
+ "list_empty": "No hay copias disponibles",
+ "restore_btn": "Restaurar",
+ "restore_confirm": "Restaurar la copia",
+ "delete_btn": "Eliminar",
+ "delete_confirm": "Eliminar la copia",
+ "gdrive_title": "Google Drive",
+ "gdrive_hint": "Copias de seguridad automáticas en Google Drive via OAuth 2.0. No se requieren bibliotecas externas.",
+ "gdrive_enabled": "Activar copia en Google Drive",
+ "gdrive_folder_id": "ID de carpeta de Drive",
+ "gdrive_folder_id_hint": "Copia el ID desde la URL de la carpeta de Drive: …/folders/
ID",
+ "gdrive_retention_days": "Retención en Drive (días, 0=mantener todo)",
+ "gdrive_test": "Probar conexión",
+ "gdrive_ok": "Conexión exitosa!",
+ "gdrive_error": "Conexión fallida",
+ "gdrive_push_now": "Subir a Drive ahora",
+ "gdrive_pushing": "Subiendo…",
+ "gdrive_pushed": "Subido a Drive",
+ "gdrive_wizard_hint": "Opcional: copia de seguridad diaria automática en Google Drive via OAuth 2.0.",
+ "gdrive_skip": "Omitir — configurar después en Ajustes",
+ "gdrive_client_id": "Client ID",
+ "gdrive_client_secret": "Client Secret",
+ "gdrive_redirect_uri_hint": "Agrega
http://localhost como URI de redireccionamiento autorizado en Google Cloud Console. Funciona en cualquier servidor, incluso sin dominio público.",
+ "gdrive_code_title": "Pegar la URL o el código de autorización",
+ "gdrive_code_hint": "Tras autorizar, el navegador abrirá http://localhost y puede mostrar un error de conexión — es normal. Copia la URL de la barra de direcciones (ej.
http://localhost/?code=4%2F0A...) y pégala aquí.",
+ "gdrive_code_submit": "Confirmar",
+ "gdrive_code_empty": "Pega primero la URL o el código de autorización",
+ "gdrive_redirect_uri_label": "URI de redirección (agregar en Google Cloud Console):",
+ "gdrive_oauth_authorize": "Autorizar con Google",
+ "gdrive_oauth_authorized": "Autorizado",
+ "gdrive_oauth_not_authorized": "Aún no autorizado",
+ "gdrive_oauth_window_opened": "Ventana abierta — autoriza y regresa aquí",
+ "gdrive_oauth_how_to": "Cómo configurar OAuth 2.0 (paso a paso)",
+ "gdrive_oauth_steps": "
Ve a console.cloud.google.com y selecciona tu proyectoHabilita la API de Google Drive: API y servicios → Habilitar API → Google Drive APIVe a API y servicios → Credenciales → Crear credenciales → ID de cliente OAuthTipo de aplicación: Aplicación web; agrega la URL mostrada abajo como URI de redirección autorizadoCopia el Client ID y el Client Secret en los campos de arriba y guardaHaz clic en Autorizar con Google: inicia sesión en tu cuenta de Google y concede accesoLa ventana se cierra automáticamente al finalizar y las copias de seguridad están listas"
+ },
+ "shopping": {
+ "tab": "Lista de la compra",
+ "title": "Lista de la compra",
+ "hint": "Configura la lista de la compra integrada o conecta Bring!.",
+ "enable_label": "Activar lista de la compra",
+ "mode_label": "Proveedor",
+ "mode_internal": "Integrado (sin Bring!)",
+ "mode_bring": "Bring! (app externa)",
+ "bring_section_title": "Configuración de Bring!",
+ "ai_section_title": "Asistencia IA",
+ "smart_suggestions_label": "Sugerencias IA",
+ "forecast_label": "Previsión de productos por agotar",
+ "auto_add_label": "Añadir automáticamente cuando",
+ "auto_add_suffix": "restante en stock (0 = solo cuando se agota)"
+ },
+ "ha": {
+ "tab": "Home Assistant",
+ "title": "Home Assistant",
+ "hint": "Conecta EverShelf a Home Assistant para automatizaciones, notificaciones push y sensores REST.",
+ "enabled": "Activar integración con Home Assistant",
+ "connection_title": "Conexión",
+ "url_label": "URL de Home Assistant",
+ "url_placeholder": "http://192.168.1.50:8123",
+ "url_hint": "URL base de tu instancia de Home Assistant.",
+ "token_label": "Token de acceso de larga duración",
+ "token_hint": "Genera desde Perfil HA → Seguridad → Tokens de acceso de larga duración.",
+ "token_placeholder": "eyJhbGci...",
+ "token_saved": "Token guardado (oculto por seguridad)",
+ "test_btn": "Probar conexión",
+ "test_ok": "Conectado a {version}",
+ "test_fail": "Conexión fallida: {error}",
+ "test_bad_token": "HA accesible pero el token no es válido",
+ "testing": "Probando…",
+ "error_no_url": "Por favor, introduce primero la URL de Home Assistant.",
+ "tts_title": "TTS en altavoz inteligente",
+ "tts_hint": "Lee los pasos de la receta en un reproductor de medios de Home Assistant.",
+ "tts_entity_label": "Entity ID del reproductor multimedia",
+ "tts_entity_placeholder": "media_player.salon",
+ "tts_entity_hint": "ID de entidad del reproductor multimedia HA. Encuéntralo en HA: Herramientas para desarrolladores → Estados.",
+ "tts_platform_label": "Plataforma TTS",
+ "tts_platform_speak": "tts.speak (recomendado)",
+ "tts_platform_notify": "notify.* (servicio de notificaciones)",
+ "tts_apply_btn": "Aplicar preset HA a la pestaña TTS",
+ "tts_apply_hint": "Pre-rellena la pestaña TTS con la URL y el token de Home Assistant.",
+ "tts_preset_applied": "Preset HA aplicado a la pestaña TTS.",
+ "webhook_title": "Automatizaciones Webhook",
+ "webhook_hint": "Envía datos a Home Assistant cuando ocurren eventos en la despensa.",
+ "webhook_id_label": "ID de Webhook",
+ "webhook_id_placeholder": "evershelf_webhook_abc123",
+ "webhook_id_hint": "ID del webhook creado en HA. Copia desde: HA → Ajustes → Automatizaciones → Crear → Disparador Webhook.",
+ "webhook_events_label": "Notificar en estos eventos",
+ "event_expiry": "Productos próximos a caducar (diario)",
+ "event_shopping": "Artículo añadido a la lista de compras",
+ "event_stock": "Nivel de stock actualizado",
+ "expiry_days_label": "Antelación de caducidad (días)",
+ "expiry_days_hint": "Enviar alerta de caducidad N días antes de la fecha.",
+ "webhook_help": "En HA: Ajustes → Automatizaciones → Crear automatización → Disparador: Webhook → copia el ID generado.",
+ "notify_title": "Notificaciones push",
+ "notify_hint": "Envía notificaciones push a tu teléfono mediante un servicio notify de Home Assistant.",
+ "notify_service_label": "Servicio notify",
+ "notify_service_placeholder": "notify.mobile_app_mi_telefono",
+ "notify_service_hint": "Nombre del servicio notify de HA. Déjalo vacío para desactivar.",
+ "sensor_title": "Sensores REST",
+ "sensor_hint": "Añade a configuration.yaml para crear sensores de EverShelf en Home Assistant.",
+ "sensor_copy_btn": "Copiar YAML",
+ "sensor_copied": "¡YAML copiado al portapapeles!",
+ "save_btn": "Guardar ajustes HA",
+ "ha_hint": "Si usas Home Assistant, utiliza la pestaña Home Assistant para configurar TTS, webhooks y sensores."
+ }
+ },
+ "expiry": {
+ "today": "HOY",
+ "tomorrow": "Mañana",
+ "days": "{days} días",
+ "expired_days": "hace {days}d",
+ "expired_yesterday": "Ayer",
+ "expired_today": "Hoy",
+ "badge_today": "⚠️ ¡Caduca hoy!",
+ "badge_tomorrow": "⏰ Mañana",
+ "badge_tomorrow_long": "⏰ Caduca mañana",
+ "badge_days": "⏰ {n} días",
+ "badge_expired_ago": "⚠️ Caducado hace {n}d",
+ "badge_expired": "⛔ ¡Caducado!",
+ "badge_stable": "✅ Estable",
+ "badge_expiring_short": "⏰ Cad. en {n}d",
+ "badge_ok_still": "✅ Aún {n}d",
+ "badge_expires_red": "🔴 Cad. en {n}d",
+ "badge_expires_yellow": "🟡 Cad. en {n}d",
+ "badge_expired_bare": "⚠️ Caducado",
+ "badge_expires_warn": "⚠️ Cad. en {n}d",
+ "badge_days_left": "⏳ ~{n}d restantes",
+ "days_approx": "~{n} días",
+ "weeks_approx": "~{n} semanas",
+ "months_approx": "~{n} meses",
+ "years_approx": "~{n} años",
+ "expired_today_long": "Caducado hoy",
+ "expired_ago_long": "Caducado hace {n} días",
+ "expired_suffix": "— ¡Caducado!",
+ "expired_suffix_ok": "— Caducado (aún ok)",
+ "expired_suffix_warning": "— Caducado (comprobar primero)",
+ "opened_ago_long": "Abierto hace {n} días",
+ "opened_today_long": "Abierto hoy",
+ "opened_suffix": "— ¡Abierto demasiado tiempo!",
+ "opened_suffix_ok": "— Abierto (aún ok)",
+ "opened_suffix_warning": "— Abierto (comprobar primero)",
+ "days_compact": "{n}d",
+ "badge_check_soon": "Comprobar pronto"
+ },
+ "status": {
+ "ok": "OK",
+ "check": "Comprobar",
+ "discard": "Desechar",
+ "tip_freezer_ok": "En congelador: aún seguro (~{n}d de margen)",
+ "tip_freezer_check": "En el congelador mucho tiempo, puede haber perdido calidad. Consumir pronto",
+ "tip_freezer_danger": "En el congelador demasiado tiempo, riesgo de quemadura por congelación y degradación",
+ "tip_highRisk_check": "Caducado recientemente, comprueba el olor y el aspecto antes de consumir",
+ "tip_highRisk_danger": "Producto perecedero caducado: desechar por seguridad",
+ "tip_medRisk_check1": "Comprueba el aspecto y el olor antes de consumir",
+ "tip_medRisk_check2": "Caducado hace tiempo, comprueba cuidadosamente antes de usar",
+ "tip_medRisk_danger": "Demasiado tiempo desde la caducidad, mejor desechar",
+ "tip_lowRisk_ok": "Producto de larga duración, aún seguro para consumir",
+ "tip_lowRisk_check": "Caducado hace más de un mes, comprueba la integridad del envase",
+ "tip_lowRisk_danger": "Caducado hace demasiado tiempo, mejor no arriesgarse"
+ },
+ "toast": {
+ "product_saved": "¡Producto guardado!",
+ "product_created": "¡Producto creado!",
+ "product_updated": "✅ ¡Producto actualizado!",
+ "product_removed": "Producto eliminado",
+ "updated": "¡Actualizado!",
+ "quantity_confirmed": "✓ Cantidad confirmada",
+ "added_to_inventory": "✅ ¡{name} añadido!",
+ "removed_from_list": "✅ ¡{name} eliminado de la lista!",
+ "removed_from_list_short": "Eliminado de la lista",
+ "added_to_shopping": "🛒 ¡Añadido a la lista de la compra!",
+ "removed_from_shopping": "🛒 Eliminado de la lista de la compra",
+ "finished_to_bring": "🛒 Producto terminado → ¡añadido a Bring!",
+ "thrown_away": "🗑️ ¡{name} tirado!",
+ "thrown_away_partial": "🗑️ {qty} {unit} de {name} tirado(s)",
+ "finished_all": "📤 ¡{name} terminado!",
+ "product_finished_confirmed": "✅ Eliminado — añádelo de nuevo cuando reabastezcas",
+ "appliance_added": "Electrodoméstico añadido",
+ "item_added": "{name} añadido"
+ },
+ "antiwaste": {
+ "title": "🌱 Informe anti-desperdicio",
+ "grade_label": "Nota",
+ "you": "Tú",
+ "avg_label": "Media",
+ "better": "🎉 ¡Desperdicias un {diff}% menos que la media {country}!",
+ "worse": "⚠️ Desperdicias más que la media {country}. ¡Hay margen de mejora!",
+ "on_par": "→ Estás en la media {country}. ¡Puedes hacerlo mejor!",
+ "saved_money": "~{amount}/mes ahorrado",
+ "saved_meals": "~{n} comidas salvadas",
+ "saved_co2": "{n} kg CO₂ evitados",
+ "trend_title": "Tendencia (últimos 3 meses)",
+ "months_ago_2": "-60 días",
+ "months_ago_1": "-30 días",
+ "this_month": "Ahora",
+ "country_it": "Media italiana",
+ "country_de": "Media alemana",
+ "country_en": "Media estadounidense",
+ "source": "Fuentes: REDUCE, Eurostat, USDA 2021",
+ "live_on": "Datos en vivo",
+ "live_off": "Sin conexión",
+ "meals": "comidas",
+ "annual_info": "📅 Tú ~{you} kg/año · media ~{avg} kg/año",
+ "badge_rate": "tasa de pérdida",
+ "badge_saved_money": "ahorrado vs media",
+ "badge_wasted": "artículos perdidos",
+ "badge_better": "menos que la media"
+ },
+ "error": {
+ "generic": "Error",
+ "network": "Error de red",
+ "no_api_key": "Configura la clave API en los ajustes",
+ "loading": "Error al cargar el producto",
+ "not_found": "Producto no encontrado",
+ "not_found_manual": "Producto no encontrado. Introdúcelo manualmente.",
+ "search": "Error de búsqueda. Inténtalo de nuevo.",
+ "search_short": "Error de búsqueda",
+ "save": "Error al guardar",
+ "connection": "Error de conexión",
+ "camera": "No se puede acceder a la cámara",
+ "bring_add": "Error al añadir a Bring!",
+ "bring_connection": "Error de conexión con Bring!",
+ "identification": "Error de identificación",
+ "ai_quota": "Cuota de IA agotada. Inténtalo de nuevo en unos minutos.",
+ "barcode_empty": "Introduce un código de barras",
+ "barcode_format": "El código de barras solo puede contener números (4-14 dígitos)",
+ "min_chars": "Escribe al menos 2 caracteres",
+ "not_in_inventory": "Producto no en inventario",
+ "appliance_exists": "El electrodoméstico ya existe",
+ "already_exists": "Ya existe",
+ "network_retry": "Error de conexión. Inténtalo de nuevo.",
+ "select_items": "Selecciona al menos un producto",
+ "server_offline": "Conexión con el servidor perdida",
+ "server_restored": "Conexión con el servidor restaurada",
+ "server_retry": "Reintentar",
+ "unknown": "Error desconocido",
+ "prefix": "Error",
+ "no_inventory_entry": "No se encontró ninguna entrada de inventario",
+ "offline_title": "Sin conexión",
+ "offline_subtitle": "La app no puede conectar con el servidor. Verifica tu conexión Wi-Fi.",
+ "offline_checking": "Verificando conexión…",
+ "offline_restored": "¡Conexión restaurada!",
+ "offline_continue": "Continuar en modo sin conexión",
+ "offline_reading_cache": "Leyendo desde caché local",
+ "offline_ops_pending": "{n} operaciones pendientes",
+ "offline_synced": "{n} operaciones sincronizadas",
+ "offline_ai_disabled": "No disponible sin conexión",
+ "offline_cache_ready": "Offline — {n} productos en caché"
+ },
+ "confirm": {
+ "remove_item": "¿Realmente quieres eliminar este producto del inventario?",
+ "kiosk_exit": "¿Salir del modo kiosco?",
+ "cancel": "Cancelar",
+ "proceed": "Confirmar"
+ },
+ "location": {
+ "dispensa": "Despensa",
+ "frigo": "Nevera",
+ "freezer": "Congelador"
+ },
+ "edit": {
+ "title": "Editar {name}",
+ "unknown_hint": "Introduce el nombre del producto y la información",
+ "label_name": "🏷️ Nombre del producto",
+ "choose_location_title": "¿Qué ubicación?",
+ "choose_location_hint": "Elige la ubicación a editar:"
+ },
+ "screensaver": {
+ "recipe_btn": "Recetas",
+ "scan_btn": "Escanear producto"
+ },
+ "days": {
+ "mon": "Lunes",
+ "tue": "Martes",
+ "wed": "Miércoles",
+ "thu": "Jueves",
+ "fri": "Viernes",
+ "sat": "Sábado",
+ "sun": "Domingo",
+ "mon_short": "Lun",
+ "tue_short": "Mar",
+ "wed_short": "Mié",
+ "thu_short": "Jue",
+ "fri_short": "Vie",
+ "sat_short": "Sáb",
+ "sun_short": "Dom"
+ },
+ "meal_types": {
+ "lunch": "Almuerzo",
+ "dinner": "Cena",
+ "colazione": "Desayuno",
+ "merenda": "Merienda",
+ "dolce": "Postre",
+ "succo": "Zumo de fruta",
+ "pranzo": "Almuerzo",
+ "cena": "Cena"
+ },
+ "scale": {
+ "status_connected": "Báscula conectada",
+ "status_searching": "Pasarela conectada, esperando báscula…",
+ "status_disconnected": "Pasarela de báscula inaccesible",
+ "status_error": "Error de conexión con la pasarela",
+ "not_connected": "Pasarela de báscula no conectada",
+ "read_btn": "⚖️ Leer desde báscula",
+ "reading_title": "Lectura de báscula",
+ "place_on_scale": "Coloca el producto en la báscula…",
+ "waiting_stable": "El peso se capturará automáticamente cuando la lectura sea estable.",
+ "no_url": "Introduce la URL de la pasarela",
+ "testing": "⏳ Probando conexión…",
+ "connected_ok": "¡Conexión con la pasarela exitosa!",
+ "timeout": "Tiempo de espera agotado: sin respuesta de la pasarela",
+ "error_connect": "No se puede conectar a la pasarela",
+ "tab": "Báscula inteligente",
+ "low_weight": "Peso < 10 g · introduce manualmente\n(la lectura automática requiere al menos 10 g)",
+ "density_hint": "(densidad {density} g/ml)",
+ "ml_hint": "(se convertirá a ml)",
+ "weight_detected": "Peso detectado — espera 10s de estabilidad…",
+ "weight_too_low": "Peso demasiado bajo — esperando…",
+ "stable": "✓ Estable",
+ "auto_confirm": "✅ {val} {unit} — confirmación automática en 5s (toca para cancelar)",
+ "cancelled_replace": "Cancelado — vuelve a colocar el ingrediente en la báscula para continuar"
+ },
+ "prediction": {
+ "expected_qty": "Esperado: {expected} {unit}",
+ "actual_qty": "Actual: {actual} {unit}",
+ "check_suggestion": "Comprueba o pesa la cantidad restante"
+ },
+ "date": {
+ "today": "📅 Hoy",
+ "yesterday": "📅 Ayer"
+ },
+ "scanner": {
+ "title_barcode": "🔖 Escanear código de barras",
+ "barcode_hint": "Encuadra el código de barras del producto",
+ "barcode_manual_placeholder": "O introduce manualmente...",
+ "barcode_use_btn": "✅ Usar este código",
+ "ai_identifying": "🤖 Identificando producto...",
+ "ai_analyzing": "🤖 Análisis IA en curso...",
+ "product_label_hint": "Encuadra la etiqueta del producto",
+ "expiry_label_hint": "Encuadra la fecha de caducidad impresa en el producto",
+ "capture_btn": "📸 Capturar",
+ "capture_photo_btn": "📸 Sacar foto",
+ "retake_btn": "🔄 Repetir",
+ "camera_error_hint": "Asegúrate de usar HTTPS y haber concedido los permisos de cámara.
Puedes introducir el código de barras manualmente o usar la identificación IA.",
+ "no_barcode": "Sin código de barras",
+ "save_new_btn": "🆕 Ninguno de estos — guardar como nuevo"
+ },
+ "lowstock": {
+ "title": "⚠️ ¡Stock bajo!",
+ "message": "{name} se está agotando — solo quedan {qty}.",
+ "question": "¿Quieres añadirlo a la lista de la compra?",
+ "yes": "🛒 Sí, añadir a Bring!",
+ "no": "No, por ahora estoy bien"
+ },
+ "move": {
+ "title": "📦 ¿Mover el resto?",
+ "question": "¿Quieres mover {thing} de {name} a otra ubicación?",
+ "question_short": "¿Quieres mover {thing} a otra ubicación?",
+ "thing_opened": "el paquete abierto",
+ "thing_rest": "el resto",
+ "stay_btn": "No, quedarse en {location}",
+ "moved_toast": "📦 Paquete abierto movido a {location}",
+ "vacuum_restore": "🫙 Restaurar al vacío",
+ "vacuum_seal_rest": "🔒 Sellar el resto al vacío"
+ },
+ "nova": {
+ "1": "Sin procesar",
+ "2": "Ingrediente culinario",
+ "3": "Procesado",
+ "4": "Ultraprocesado"
+ },
+ "meal_plan_types": {
+ "pasta": "Pasta",
+ "riso": "Arroz",
+ "carne": "Carne",
+ "pesce": "Pescado",
+ "legumi": "Legumbres",
+ "uova": "Huevos",
+ "formaggio": "Queso",
+ "pizza": "Pizza",
+ "affettati": "Fiambres",
+ "verdure": "Verduras",
+ "zuppa": "Sopa",
+ "insalata": "Ensalada",
+ "pane": "Pan/Bocadillo",
+ "dolce": "Postre",
+ "libero": "Libre"
+ },
+ "meal_sub": {
+ "dolce_torta": "Tarta",
+ "dolce_crema": "Crema / Pudín",
+ "dolce_crumble": "Crumble / Tarta",
+ "dolce_biscotti": "Galletas / Pastelería",
+ "dolce_frutta": "Postre de fruta",
+ "succo_dolce": "Dulce / Afrutado",
+ "succo_energizzante": "Energizante",
+ "succo_detox": "Detox / Verde",
+ "succo_rinfrescante": "Refrescante",
+ "succo_vitaminico": "Vitamínico / Cítricos"
+ },
+ "meal_plan": {
+ "reset_success": "Plan semanal restablecido",
+ "not_available": "no disponible en la despensa",
+ "suggested_by": "sugerido por el plan semanal"
+ },
+ "nutrition": {
+ "title": "🥗 Análisis alimentario",
+ "score_excellent": "😄 Excelente",
+ "score_good": "🙂 Bien",
+ "score_improve": "😬 Mejorable",
+ "label_health": "🌿 Salud",
+ "label_variety": "🎨 Variedad",
+ "label_fresh": "❄️ Fresco",
+ "source": "Basado en {n} productos en tu despensa · EverShelf",
+ "products_count": "productos",
+ "today_title": "🥗 Tu despensa hoy",
+ "products_n": "{n} productos"
+ },
+ "facts": {
+ "greeting_morning": "Buenos días",
+ "greeting_afternoon": "Buenas tardes",
+ "greeting_evening": "Buenas noches",
+ "pantry_waiting": "¡{greeting}! Tu despensa te espera.",
+ "expired_one": "Tienes 1 producto caducado en tu despensa. ¡Compruébalo!",
+ "expired_many": "Tienes {n} productos caducados en tu despensa. ¡Compruébalos!",
+ "expired_list": "Productos caducados: {names}",
+ "expired_list_more": "y {n} más",
+ "freezer_expired_ok": "{name} está caducado, pero al estar en el congelador puede estar bien. ¡Compruébalo!",
+ "freezer_expired_old": "{name} en el congelador lleva demasiado tiempo caducado. Mejor tirarlo.",
+ "fridge_expired_one": "¡Tienes 1 producto caducado en la nevera!",
+ "fridge_expired_many": "¡Tienes {n} productos caducados en la nevera!",
+ "expiring_today": "¡{name} caduca hoy! Úsalo enseguida.",
+ "expiring_tomorrow": "{name} caduca mañana. ¡Planifica!",
+ "expiring_days": "{name} caduca en {days} días.",
+ "expiring_many": "Tienes {n} productos que caducan pronto.",
+ "expiring_this_week": "¡{n} productos caducan esta semana. Planifica tus comidas en consecuencia!",
+ "expiring_item_loc": "{name} ({loc}) caduca en {days} {dayslabel}.",
+ "expiring_this_month": "{n} productos caducarán este mes.",
+ "shopping_add": "Añadir a la lista: {names} 🛒",
+ "shopping_more": "y {n} más",
+ "shopping_empty": "¡Lista de la compra vacía. Todo en stock! ✅",
+ "in_fridge": "En la nevera: {name}.",
+ "in_freezer": "En el congelador: {name}. ¡No lo olvides!",
+ "top_category": "La categoría principal es {icon} {cat} con {n} productos.",
+ "cat_meat": "Tienes {n} productos cárnicos. 🥩",
+ "cat_dairy": "Tienes {n} productos lácteos en casa. 🥛",
+ "cat_veggies": "Tienes {n} tipos de verduras. ¡Genial para la salud! 🥬",
+ "cat_fruit": "Tienes {n} tipos de fruta. 🍎",
+ "cat_drinks": "Tienes {n} bebidas disponibles. 🥤",
+ "cat_frozen": "Tienes {n} artículos congelados. ❄️",
+ "cat_pasta": "Tienes {n} tipos de pasta. 🍝 ¿Y si hacemos una carbonara?",
+ "cat_canned": "Tienes {n} conservas en la despensa. 🥫",
+ "cat_snacks": "Tienes {n} snacks. ¡Resiste la tentación! 🍪",
+ "cat_condiments": "Tienes {n} condimentos disponibles. 🧂",
+ "item_random": "¿Sabías que tienes {name} en {loc}?",
+ "item_qty": "{name}: tienes {qty}.",
+ "no_expiry_count": "{n} productos no tienen fecha de caducidad.",
+ "furthest_expiry": "El producto con la fecha de caducidad más lejana es {name}: {months} meses.",
+ "high_qty": "¡Tienes un buen stock de {name}: {qty}!",
+ "low_qty_item": "{name} se está agotando. ¿Añadirlo a tu lista de la compra?",
+ "low_qty_count": "{n} productos están casi agotados.",
+ "morning_bread": "¡Buenos días! Tienes pan para el desayuno. 🍞",
+ "morning_milk": "¿Hay leche en la nevera para un café con leche? ☕🥛",
+ "morning_fruit": "¡Buenos días! Algo de fruta fresca es un gran comienzo. 🍎",
+ "noon_pasta": "Es hora de comer… ¿Y si preparamos un buen plato de pasta? 🍝",
+ "noon_salad": "¿Una ensalada fresca para comer? ¡Tienes {n} verduras! 🥗",
+ "evening_meat": "Para cenar podrías usar la carne que tienes. 🥩",
+ "evening_fish": "¿Qué tal pescado para cenar? 🐟",
+ "evening_expiring": "Tienes {n} productos que caducan esta semana — ¡úsalos esta noche!",
+ "night_reminder": "¡Buenas noches! Recuerda usar mañana: {names}.",
+ "weekly_balance": "Balance semanal: +{in} añadidos, −{out} consumidos.",
+ "weekly_added": "Has añadido {n} productos esta semana.",
+ "weekly_consumed": "Has consumido {n} productos esta semana. ¡Bien hecho!",
+ "tip_freezer": "💡 Los productos congelados duran mucho más que la fecha de caducidad.",
+ "tip_bread": "💡 El pan congelado conserva su frescura durante semanas.",
+ "tip_fifo": "💡 Para evitar desperdicios, usa primero los productos más cercanos a la caducidad (FIFO).",
+ "tip_meat": "💡 La carne en el congelador puede durar hasta 6 meses con seguridad.",
+ "tip_no_refreeze": "💡 Nunca vuelvas a congelar un producto descongelado. ¡Cocínalo enseguida!",
+ "tip_fridge": "💡 Una nevera ordenada te ahorra tiempo y dinero.",
+ "tip_canned": "💡 Las conservas abiertas deben ir a la nevera y consumirse en pocos días.",
+ "top_brand": "La marca más común en tu despensa es {brand} con {n} productos.",
+ "combo_pasta": "Tienes pasta y condimentos: ¡listo para un primer plato! 🍝",
+ "combo_sandwich": "Pan y carne: ¡un sándwich rápido siempre es buena idea! 🥪",
+ "combo_balanced": "Verduras y carne: ¡tienes todo para una comida equilibrada! 🥗🥩",
+ "pantry_empty": "¡La despensa está vacía! Es hora de ir al supermercado. 🛒",
+ "pantry_empty_scan": "No hay productos registrados. ¡Escanea algo para empezar!",
+ "location_distribution": "Distribución: {parts}",
+ "day": "día",
+ "days": "días"
+ },
+ "kiosk_session": {
+ "first_item": "¡Primer artículo: {name}!",
+ "items_two_four": "{n} artículos — arrancando 🚀",
+ "items_five_nine": "{n} artículos — ¡buen ritmo! 💪",
+ "items_ten_twenty": "{n} artículos — casi un récord 🏆",
+ "items_twenty_plus": "{n} artículos — ¡compra épica! 🛒🔥",
+ "duplicates_one": "1 duplicado (mismo artículo dos veces)",
+ "duplicates_many": "{n} duplicados (cogido varias veces)",
+ "top_category": "Categoría principal: {cat} ({count}×)",
+ "items_fallback": "{n} artículo{plural} añadido{plural}"
+ },
+ "kiosk": {
+ "check_btn": "🔍 Buscar actualizaciones",
+ "checking": "⏳ Comprobando…",
+ "error_check": "Error durante la comprobación de actualizaciones",
+ "error_start_install": "Error al iniciar la instalación",
+ "version_installed": "Instalado: {v}",
+ "update_available": "⬆️ Nueva versión disponible:
{latest} (instalada: {current})",
+ "up_to_date": "✅ Estás actualizado — versión
{v}",
+ "too_old": "⚠️ El kiosco instalado es demasiado antiguo para la comprobación automática de actualizaciones.
Pulsa el botón de abajo para descargar e instalar la nueva versión directamente.",
+ "manual_install": "⚠️ Este kiosco no admite instalación automática.
Procedimiento manual:1. Sal del kiosco (botón ✕ arriba a la izquierda)
2. Desinstala la app EverShelf Kiosk
3. Descarga e instala el nuevo APK desde GitHub:",
+ "starting_download": "⏳ Iniciando descarga…",
+ "install_btn": "⬇️ Instalar actualización",
+ "exit_title": "Salir del kiosco",
+ "refresh_title": "Actualizar página"
+ },
+ "update": {
+ "new_version": "Nueva versión",
+ "btn": "Actualizar"
+ },
+ "gemini": {
+ "chat_title": "Chat con Gemini",
+ "not_configured": "🤖 Gemini no configurado — establece GEMINI_API_KEY en los ajustes"
+ },
+ "appliances": {
+ "empty": "No hay electrodomésticos añadidos"
+ },
+ "about": {
+ "title": "Acerca de",
+ "version": "Versión",
+ "report_bug": "Reportar un error",
+ "report_bug_hint": "¿Algo no funciona? Envíanos un informe directamente desde la app.",
+ "report_bug_modal_title": "Reportar un error",
+ "report_type_bug": "Error",
+ "report_type_feature": "Función",
+ "report_type_question": "Pregunta",
+ "report_field_title": "Título",
+ "report_field_title_ph": "Breve descripción del problema",
+ "report_field_desc": "Descripción",
+ "report_field_desc_ph": "Describe el problema en detalle…",
+ "report_field_steps": "Pasos para reproducir (opcional)",
+ "report_field_steps_ph": "1. Ir a…\n2. Tocar…\n3. Ver el error…",
+ "report_auto_info": "Adjuntado automáticamente: versión {version}, idioma {lang}.",
+ "report_send_btn": "Enviar informe",
+ "report_bug_sending": "Enviando…",
+ "report_bug_sent": "¡Informe enviado — gracias!",
+ "report_bug_error": "No se pudo enviar el informe. Comprueba tu conexión.",
+ "changelog": "Registro de cambios",
+ "github": "Repositorio GitHub"
+ },
+ "export": {
+ "title": "Exportar inventario",
+ "hint": "Descarga el inventario actual en CSV o abre la versión imprimible (PDF).",
+ "btn_csv": "Descargar CSV",
+ "btn_pdf": "PDF / Imprimir",
+ "btn_title": "Exportar"
+ },
+ "startup": {
+ "connecting": "Conectando al servidor...",
+ "check_php_memory": "Memoria PHP",
+ "check_php_timeout": "Tiempo de espera PHP",
+ "check_php_upload": "Upload PHP",
+ "check_data_dir": "Carpeta de datos",
+ "check_rate_limits": "Dir. rate limits",
+ "check_backups": "Dir. copias de seguridad",
+ "check_write_test": "Prueba escritura disco",
+ "check_disk_space": "Espacio en disco",
+ "check_db_connect": "Conexión base de datos",
+ "check_db_tables": "Tablas de la BD",
+ "check_db_integrity": "Integridad BD",
+ "check_db_wal": "Modo WAL",
+ "check_db_size": "Tamaño de la BD",
+ "check_db_rows": "Datos del inventario",
+ "check_env": "Archivo .env",
+ "check_gemini": "Clave Gemini AI",
+ "check_bring_creds": "Credenciales Bring!",
+ "check_bring_token": "Token de Bring!",
+ "check_curl_ssl": "cURL SSL",
+ "check_internet": "Conexión a internet",
+ "fresh_install": "instalación nueva",
+ "warnings_found": "avisos detectados",
+ "all_ok": "Sistema OK",
+ "critical_error_short": "Error crítico",
+ "critical_error": "Error crítico: la aplicación no puede iniciarse. Revisa los registros del servidor.",
+ "error_network": "No se puede contactar con el servidor. Comprueba tu conexión de red.",
+ "retry": "Reintentar",
+ "syncing_local": "Sincronizando datos locales...",
+ "sync_done": "Datos locales sincronizados"
}
- },
- "expiry": {
- "today": "HOY",
- "tomorrow": "Mañana",
- "days": "{days} días",
- "expired_days": "hace {days}d",
- "expired_yesterday": "Ayer",
- "expired_today": "Hoy",
- "badge_today": "⚠️ ¡Caduca hoy!",
- "badge_tomorrow": "⏰ Mañana",
- "badge_tomorrow_long": "⏰ Caduca mañana",
- "badge_days": "⏰ {n} días",
- "badge_expired_ago": "⚠️ Caducado hace {n}d",
- "badge_expired": "⛔ ¡Caducado!",
- "badge_stable": "✅ Estable",
- "badge_expiring_short": "⏰ Cad. en {n}d",
- "badge_ok_still": "✅ Aún {n}d",
- "badge_expires_red": "🔴 Cad. en {n}d",
- "badge_expires_yellow": "🟡 Cad. en {n}d",
- "badge_expired_bare": "⚠️ Caducado",
- "badge_expires_warn": "⚠️ Cad. en {n}d",
- "badge_days_left": "⏳ ~{n}d restantes",
- "days_approx": "~{n} días",
- "weeks_approx": "~{n} semanas",
- "months_approx": "~{n} meses",
- "years_approx": "~{n} años",
- "expired_today_long": "Caducado hoy",
- "expired_ago_long": "Caducado hace {n} días",
- "expired_suffix": "— ¡Caducado!",
- "expired_suffix_ok": "— Caducado (aún ok)",
- "expired_suffix_warning": "— Caducado (comprobar primero)",
- "opened_ago_long": "Abierto hace {n} días",
- "opened_today_long": "Abierto hoy",
- "opened_suffix": "— ¡Abierto demasiado tiempo!",
- "opened_suffix_ok": "— Abierto (aún ok)",
- "opened_suffix_warning": "— Abierto (comprobar primero)",
- "days_compact": "{n}d",
- "badge_check_soon": "Comprobar pronto"
- },
- "status": {
- "ok": "OK",
- "check": "Comprobar",
- "discard": "Desechar",
- "tip_freezer_ok": "En congelador: aún seguro (~{n}d de margen)",
- "tip_freezer_check": "En el congelador mucho tiempo, puede haber perdido calidad. Consumir pronto",
- "tip_freezer_danger": "En el congelador demasiado tiempo, riesgo de quemadura por congelación y degradación",
- "tip_highRisk_check": "Caducado recientemente, comprueba el olor y el aspecto antes de consumir",
- "tip_highRisk_danger": "Producto perecedero caducado: desechar por seguridad",
- "tip_medRisk_check1": "Comprueba el aspecto y el olor antes de consumir",
- "tip_medRisk_check2": "Caducado hace tiempo, comprueba cuidadosamente antes de usar",
- "tip_medRisk_danger": "Demasiado tiempo desde la caducidad, mejor desechar",
- "tip_lowRisk_ok": "Producto de larga duración, aún seguro para consumir",
- "tip_lowRisk_check": "Caducado hace más de un mes, comprueba la integridad del envase",
- "tip_lowRisk_danger": "Caducado hace demasiado tiempo, mejor no arriesgarse"
- },
- "toast": {
- "product_saved": "¡Producto guardado!",
- "product_created": "¡Producto creado!",
- "product_updated": "✅ ¡Producto actualizado!",
- "product_removed": "Producto eliminado",
- "updated": "¡Actualizado!",
- "quantity_confirmed": "✓ Cantidad confirmada",
- "added_to_inventory": "✅ ¡{name} añadido!",
- "removed_from_list": "✅ ¡{name} eliminado de la lista!",
- "removed_from_list_short": "Eliminado de la lista",
- "added_to_shopping": "🛒 ¡Añadido a la lista de la compra!",
- "removed_from_shopping": "🛒 Eliminado de la lista de la compra",
- "finished_to_bring": "🛒 Producto terminado → ¡añadido a Bring!",
- "thrown_away": "🗑️ ¡{name} tirado!",
- "thrown_away_partial": "🗑️ {qty} {unit} de {name} tirado(s)",
- "finished_all": "📤 ¡{name} terminado!",
- "product_finished_confirmed": "✅ Eliminado — añádelo de nuevo cuando reabastezcas",
- "appliance_added": "Electrodoméstico añadido",
- "item_added": "{name} añadido"
- },
- "antiwaste": {
- "title": "🌱 Informe anti-desperdicio",
- "grade_label": "Nota",
- "you": "Tú",
- "avg_label": "Media",
- "better": "🎉 ¡Desperdicias un {diff}% menos que la media {country}!",
- "worse": "⚠️ Desperdicias más que la media {country}. ¡Hay margen de mejora!",
- "on_par": "→ Estás en la media {country}. ¡Puedes hacerlo mejor!",
- "saved_money": "~{amount}/mes ahorrado",
- "saved_meals": "~{n} comidas salvadas",
- "saved_co2": "{n} kg CO₂ evitados",
- "trend_title": "Tendencia (últimos 3 meses)",
- "months_ago_2": "-60 días",
- "months_ago_1": "-30 días",
- "this_month": "Ahora",
- "country_it": "Media italiana",
- "country_de": "Media alemana",
- "country_en": "Media estadounidense",
- "source": "Fuentes: REDUCE, Eurostat, USDA 2021",
- "live_on": "Datos en vivo",
- "live_off": "Sin conexión",
- "meals": "comidas",
- "annual_info": "📅 Tú ~{you} kg/año · media ~{avg} kg/año",
- "badge_rate": "tasa de pérdida",
- "badge_saved_money": "ahorrado vs media",
- "badge_wasted": "artículos perdidos",
- "badge_better": "menos que la media"
- },
- "error": {
- "generic": "Error",
- "network": "Error de red",
- "no_api_key": "Configura la clave API en los ajustes",
- "loading": "Error al cargar el producto",
- "not_found": "Producto no encontrado",
- "not_found_manual": "Producto no encontrado. Introdúcelo manualmente.",
- "search": "Error de búsqueda. Inténtalo de nuevo.",
- "search_short": "Error de búsqueda",
- "save": "Error al guardar",
- "connection": "Error de conexión",
- "camera": "No se puede acceder a la cámara",
- "bring_add": "Error al añadir a Bring!",
- "bring_connection": "Error de conexión con Bring!",
- "identification": "Error de identificación",
- "ai_quota": "Cuota de IA agotada. Inténtalo de nuevo en unos minutos.",
- "barcode_empty": "Introduce un código de barras",
- "barcode_format": "El código de barras solo puede contener números (4-14 dígitos)",
- "min_chars": "Escribe al menos 2 caracteres",
- "not_in_inventory": "Producto no en inventario",
- "appliance_exists": "El electrodoméstico ya existe",
- "already_exists": "Ya existe",
- "network_retry": "Error de conexión. Inténtalo de nuevo.",
- "select_items": "Selecciona al menos un producto",
- "server_offline": "Conexión con el servidor perdida",
- "server_restored": "Conexión con el servidor restaurada",
- "server_retry": "Reintentar",
- "unknown": "Error desconocido",
- "prefix": "Error",
- "no_inventory_entry": "No se encontró ninguna entrada de inventario"
- },
- "confirm": {
- "remove_item": "¿Realmente quieres eliminar este producto del inventario?",
- "kiosk_exit": "¿Salir del modo kiosco?",
- "cancel": "Cancelar",
- "proceed": "Confirmar"
- },
- "location": {
- "dispensa": "Despensa",
- "frigo": "Nevera",
- "freezer": "Congelador"
- },
- "edit": {
- "title": "Editar {name}",
- "unknown_hint": "Introduce el nombre del producto y la información",
- "label_name": "🏷️ Nombre del producto",
- "choose_location_title": "¿Qué ubicación?",
- "choose_location_hint": "Elige la ubicación a editar:"
- },
- "screensaver": {
- "recipe_btn": "Recetas",
- "scan_btn": "Escanear producto"
- },
- "days": {
- "mon": "Lunes",
- "tue": "Martes",
- "wed": "Miércoles",
- "thu": "Jueves",
- "fri": "Viernes",
- "sat": "Sábado",
- "sun": "Domingo",
- "mon_short": "Lun",
- "tue_short": "Mar",
- "wed_short": "Mié",
- "thu_short": "Jue",
- "fri_short": "Vie",
- "sat_short": "Sáb",
- "sun_short": "Dom"
- },
- "meal_types": {
- "lunch": "Almuerzo",
- "dinner": "Cena",
- "colazione": "Desayuno",
- "merenda": "Merienda",
- "dolce": "Postre",
- "succo": "Zumo de fruta",
- "pranzo": "Almuerzo",
- "cena": "Cena"
- },
- "scale": {
- "status_connected": "Báscula conectada",
- "status_searching": "Pasarela conectada, esperando báscula…",
- "status_disconnected": "Pasarela de báscula inaccesible",
- "status_error": "Error de conexión con la pasarela",
- "not_connected": "Pasarela de báscula no conectada",
- "read_btn": "⚖️ Leer desde báscula",
- "reading_title": "Lectura de báscula",
- "place_on_scale": "Coloca el producto en la báscula…",
- "waiting_stable": "El peso se capturará automáticamente cuando la lectura sea estable.",
- "no_url": "Introduce la URL de la pasarela",
- "testing": "⏳ Probando conexión…",
- "connected_ok": "¡Conexión con la pasarela exitosa!",
- "timeout": "Tiempo de espera agotado: sin respuesta de la pasarela",
- "error_connect": "No se puede conectar a la pasarela",
- "tab": "Báscula inteligente",
- "low_weight": "Peso < 10 g · introduce manualmente\n(la lectura automática requiere al menos 10 g)",
- "density_hint": "(densidad {density} g/ml)",
- "ml_hint": "(se convertirá a ml)",
- "weight_detected": "Peso detectado — espera 10s de estabilidad…",
- "weight_too_low": "Peso demasiado bajo — esperando…",
- "stable": "✓ Estable",
- "auto_confirm": "✅ {val} {unit} — confirmación automática en 5s (toca para cancelar)",
- "cancelled_replace": "Cancelado — vuelve a colocar el ingrediente en la báscula para continuar"
- },
- "prediction": {
- "expected_qty": "Esperado: {expected} {unit}",
- "actual_qty": "Actual: {actual} {unit}",
- "check_suggestion": "Comprueba o pesa la cantidad restante"
- },
- "date": {
- "today": "📅 Hoy",
- "yesterday": "📅 Ayer"
- },
- "scanner": {
- "title_barcode": "🔖 Escanear código de barras",
- "barcode_hint": "Encuadra el código de barras del producto",
- "barcode_manual_placeholder": "O introduce manualmente...",
- "barcode_use_btn": "✅ Usar este código",
- "ai_identifying": "🤖 Identificando producto...",
- "ai_analyzing": "🤖 Análisis IA en curso...",
- "product_label_hint": "Encuadra la etiqueta del producto",
- "expiry_label_hint": "Encuadra la fecha de caducidad impresa en el producto",
- "capture_btn": "📸 Capturar",
- "capture_photo_btn": "📸 Sacar foto",
- "retake_btn": "🔄 Repetir",
- "camera_error_hint": "Asegúrate de usar HTTPS y haber concedido los permisos de cámara.
Puedes introducir el código de barras manualmente o usar la identificación IA.",
- "no_barcode": "Sin código de barras",
- "save_new_btn": "🆕 Ninguno de estos — guardar como nuevo"
- },
- "lowstock": {
- "title": "⚠️ ¡Stock bajo!",
- "message": "{name} se está agotando — solo quedan {qty}.",
- "question": "¿Quieres añadirlo a la lista de la compra?",
- "yes": "🛒 Sí, añadir a Bring!",
- "no": "No, por ahora estoy bien"
- },
- "move": {
- "title": "📦 ¿Mover el resto?",
- "question": "¿Quieres mover {thing} de {name} a otra ubicación?",
- "question_short": "¿Quieres mover {thing} a otra ubicación?",
- "thing_opened": "el paquete abierto",
- "thing_rest": "el resto",
- "stay_btn": "No, quedarse en {location}",
- "moved_toast": "📦 Paquete abierto movido a {location}",
- "vacuum_restore": "🫙 Restaurar al vacío",
- "vacuum_seal_rest": "🔒 Sellar el resto al vacío"
- },
- "nova": {
- "1": "Sin procesar",
- "2": "Ingrediente culinario",
- "3": "Procesado",
- "4": "Ultraprocesado"
- },
- "meal_plan_types": {
- "pasta": "Pasta",
- "riso": "Arroz",
- "carne": "Carne",
- "pesce": "Pescado",
- "legumi": "Legumbres",
- "uova": "Huevos",
- "formaggio": "Queso",
- "pizza": "Pizza",
- "affettati": "Fiambres",
- "verdure": "Verduras",
- "zuppa": "Sopa",
- "insalata": "Ensalada",
- "pane": "Pan/Bocadillo",
- "dolce": "Postre",
- "libero": "Libre"
- },
- "meal_sub": {
- "dolce_torta": "Tarta",
- "dolce_crema": "Crema / Pudín",
- "dolce_crumble": "Crumble / Tarta",
- "dolce_biscotti": "Galletas / Pastelería",
- "dolce_frutta": "Postre de fruta",
- "succo_dolce": "Dulce / Afrutado",
- "succo_energizzante": "Energizante",
- "succo_detox": "Detox / Verde",
- "succo_rinfrescante": "Refrescante",
- "succo_vitaminico": "Vitamínico / Cítricos"
- },
- "meal_plan": {
- "reset_success": "Plan semanal restablecido",
- "not_available": "no disponible en la despensa",
- "suggested_by": "sugerido por el plan semanal"
- },
- "nutrition": {
- "title": "🥗 Análisis alimentario",
- "score_excellent": "😄 Excelente",
- "score_good": "🙂 Bien",
- "score_improve": "😬 Mejorable",
- "label_health": "🌿 Salud",
- "label_variety": "🎨 Variedad",
- "label_fresh": "❄️ Fresco",
- "source": "Basado en {n} productos en tu despensa · EverShelf",
- "products_count": "productos",
- "today_title": "🥗 Tu despensa hoy",
- "products_n": "{n} productos"
- },
- "facts": {
- "greeting_morning": "Buenos días",
- "greeting_afternoon": "Buenas tardes",
- "greeting_evening": "Buenas noches",
- "pantry_waiting": "¡{greeting}! Tu despensa te espera.",
- "expired_one": "Tienes 1 producto caducado en tu despensa. ¡Compruébalo!",
- "expired_many": "Tienes {n} productos caducados en tu despensa. ¡Compruébalos!",
- "expired_list": "Productos caducados: {names}",
- "expired_list_more": "y {n} más",
- "freezer_expired_ok": "{name} está caducado, pero al estar en el congelador puede estar bien. ¡Compruébalo!",
- "freezer_expired_old": "{name} en el congelador lleva demasiado tiempo caducado. Mejor tirarlo.",
- "fridge_expired_one": "¡Tienes 1 producto caducado en la nevera!",
- "fridge_expired_many": "¡Tienes {n} productos caducados en la nevera!",
- "expiring_today": "¡{name} caduca hoy! Úsalo enseguida.",
- "expiring_tomorrow": "{name} caduca mañana. ¡Planifica!",
- "expiring_days": "{name} caduca en {days} días.",
- "expiring_many": "Tienes {n} productos que caducan pronto.",
- "expiring_this_week": "¡{n} productos caducan esta semana. Planifica tus comidas en consecuencia!",
- "expiring_item_loc": "{name} ({loc}) caduca en {days} {dayslabel}.",
- "expiring_this_month": "{n} productos caducarán este mes.",
- "shopping_add": "Añadir a la lista: {names} 🛒",
- "shopping_more": "y {n} más",
- "shopping_empty": "¡Lista de la compra vacía. Todo en stock! ✅",
- "in_fridge": "En la nevera: {name}.",
- "in_freezer": "En el congelador: {name}. ¡No lo olvides!",
- "top_category": "La categoría principal es {icon} {cat} con {n} productos.",
- "cat_meat": "Tienes {n} productos cárnicos. 🥩",
- "cat_dairy": "Tienes {n} productos lácteos en casa. 🥛",
- "cat_veggies": "Tienes {n} tipos de verduras. ¡Genial para la salud! 🥬",
- "cat_fruit": "Tienes {n} tipos de fruta. 🍎",
- "cat_drinks": "Tienes {n} bebidas disponibles. 🥤",
- "cat_frozen": "Tienes {n} artículos congelados. ❄️",
- "cat_pasta": "Tienes {n} tipos de pasta. 🍝 ¿Y si hacemos una carbonara?",
- "cat_canned": "Tienes {n} conservas en la despensa. 🥫",
- "cat_snacks": "Tienes {n} snacks. ¡Resiste la tentación! 🍪",
- "cat_condiments": "Tienes {n} condimentos disponibles. 🧂",
- "item_random": "¿Sabías que tienes {name} en {loc}?",
- "item_qty": "{name}: tienes {qty}.",
- "no_expiry_count": "{n} productos no tienen fecha de caducidad.",
- "furthest_expiry": "El producto con la fecha de caducidad más lejana es {name}: {months} meses.",
- "high_qty": "¡Tienes un buen stock de {name}: {qty}!",
- "low_qty_item": "{name} se está agotando. ¿Añadirlo a tu lista de la compra?",
- "low_qty_count": "{n} productos están casi agotados.",
- "morning_bread": "¡Buenos días! Tienes pan para el desayuno. 🍞",
- "morning_milk": "¿Hay leche en la nevera para un café con leche? ☕🥛",
- "morning_fruit": "¡Buenos días! Algo de fruta fresca es un gran comienzo. 🍎",
- "noon_pasta": "Es hora de comer… ¿Y si preparamos un buen plato de pasta? 🍝",
- "noon_salad": "¿Una ensalada fresca para comer? ¡Tienes {n} verduras! 🥗",
- "evening_meat": "Para cenar podrías usar la carne que tienes. 🥩",
- "evening_fish": "¿Qué tal pescado para cenar? 🐟",
- "evening_expiring": "Tienes {n} productos que caducan esta semana — ¡úsalos esta noche!",
- "night_reminder": "¡Buenas noches! Recuerda usar mañana: {names}.",
- "weekly_balance": "Balance semanal: +{in} añadidos, −{out} consumidos.",
- "weekly_added": "Has añadido {n} productos esta semana.",
- "weekly_consumed": "Has consumido {n} productos esta semana. ¡Bien hecho!",
- "tip_freezer": "💡 Los productos congelados duran mucho más que la fecha de caducidad.",
- "tip_bread": "💡 El pan congelado conserva su frescura durante semanas.",
- "tip_fifo": "💡 Para evitar desperdicios, usa primero los productos más cercanos a la caducidad (FIFO).",
- "tip_meat": "💡 La carne en el congelador puede durar hasta 6 meses con seguridad.",
- "tip_no_refreeze": "💡 Nunca vuelvas a congelar un producto descongelado. ¡Cocínalo enseguida!",
- "tip_fridge": "💡 Una nevera ordenada te ahorra tiempo y dinero.",
- "tip_canned": "💡 Las conservas abiertas deben ir a la nevera y consumirse en pocos días.",
- "top_brand": "La marca más común en tu despensa es {brand} con {n} productos.",
- "combo_pasta": "Tienes pasta y condimentos: ¡listo para un primer plato! 🍝",
- "combo_sandwich": "Pan y carne: ¡un sándwich rápido siempre es buena idea! 🥪",
- "combo_balanced": "Verduras y carne: ¡tienes todo para una comida equilibrada! 🥗🥩",
- "pantry_empty": "¡La despensa está vacía! Es hora de ir al supermercado. 🛒",
- "pantry_empty_scan": "No hay productos registrados. ¡Escanea algo para empezar!",
- "location_distribution": "Distribución: {parts}",
- "day": "día",
- "days": "días"
- },
- "kiosk_session": {
- "first_item": "¡Primer artículo: {name}!",
- "items_two_four": "{n} artículos — arrancando 🚀",
- "items_five_nine": "{n} artículos — ¡buen ritmo! 💪",
- "items_ten_twenty": "{n} artículos — casi un récord 🏆",
- "items_twenty_plus": "{n} artículos — ¡compra épica! 🛒🔥",
- "duplicates_one": "1 duplicado (mismo artículo dos veces)",
- "duplicates_many": "{n} duplicados (cogido varias veces)",
- "top_category": "Categoría principal: {cat} ({count}×)",
- "items_fallback": "{n} artículo{plural} añadido{plural}"
- },
- "kiosk": {
- "check_btn": "🔍 Buscar actualizaciones",
- "checking": "⏳ Comprobando…",
- "error_check": "Error durante la comprobación de actualizaciones",
- "error_start_install": "Error al iniciar la instalación",
- "version_installed": "Instalado: {v}",
- "update_available": "⬆️ Nueva versión disponible:
{latest} (instalada: {current})",
- "up_to_date": "✅ Estás actualizado — versión
{v}",
- "too_old": "⚠️ El kiosco instalado es demasiado antiguo para la comprobación automática de actualizaciones.
Pulsa el botón de abajo para descargar e instalar la nueva versión directamente.",
- "manual_install": "⚠️ Este kiosco no admite instalación automática.
Procedimiento manual:1. Sal del kiosco (botón ✕ arriba a la izquierda)
2. Desinstala la app EverShelf Kiosk
3. Descarga e instala el nuevo APK desde GitHub:",
- "starting_download": "⏳ Iniciando descarga…",
- "install_btn": "⬇️ Instalar actualización",
- "exit_title": "Salir del kiosco",
- "refresh_title": "Actualizar página"
- },
- "update": {
- "new_version": "Nueva versión",
- "btn": "Actualizar"
- },
- "gemini": {
- "chat_title": "Chat con Gemini",
- "not_configured": "🤖 Gemini no configurado — establece GEMINI_API_KEY en los ajustes"
- },
- "appliances": {
- "empty": "No hay electrodomésticos añadidos"
- },
- "about": {
- "title": "Acerca de",
- "version": "Versión",
- "report_bug": "Reportar un error",
- "report_bug_hint": "¿Algo no funciona? Envíanos un informe directamente desde la app.",
- "report_bug_modal_title": "Reportar un error",
- "report_type_bug": "Error",
- "report_type_feature": "Función",
- "report_type_question": "Pregunta",
- "report_field_title": "Título",
- "report_field_title_ph": "Breve descripción del problema",
- "report_field_desc": "Descripción",
- "report_field_desc_ph": "Describe el problema en detalle…",
- "report_field_steps": "Pasos para reproducir (opcional)",
- "report_field_steps_ph": "1. Ir a…\n2. Tocar…\n3. Ver el error…",
- "report_auto_info": "Adjuntado automáticamente: versión {version}, idioma {lang}.",
- "report_send_btn": "Enviar informe",
- "report_bug_sending": "Enviando…",
- "report_bug_sent": "¡Informe enviado — gracias!",
- "report_bug_error": "No se pudo enviar el informe. Comprueba tu conexión.",
- "changelog": "Registro de cambios",
- "github": "Repositorio GitHub"
- },
- "export": {
- "title": "Exportar inventario",
- "hint": "Descarga el inventario actual en CSV o abre la versión imprimible (PDF).",
- "btn_csv": "Descargar CSV",
- "btn_pdf": "PDF / Imprimir",
- "btn_title": "Exportar"
- },
- "startup": {
- "connecting": "Conectando al servidor...",
- "check_php_memory": "Memoria PHP",
- "check_php_timeout": "Tiempo de espera PHP",
- "check_php_upload": "Upload PHP",
- "check_data_dir": "Carpeta de datos",
- "check_rate_limits": "Dir. rate limits",
- "check_backups": "Dir. copias de seguridad",
- "check_write_test": "Prueba escritura disco",
- "check_disk_space": "Espacio en disco",
- "check_db_connect": "Conexión base de datos",
- "check_db_tables": "Tablas de la BD",
- "check_db_integrity": "Integridad BD",
- "check_db_wal": "Modo WAL",
- "check_db_size": "Tamaño de la BD",
- "check_db_rows": "Datos del inventario",
- "check_env": "Archivo .env",
- "check_gemini": "Clave Gemini AI",
- "check_bring_creds": "Credenciales Bring!",
- "check_bring_token": "Token de Bring!",
- "check_curl_ssl": "cURL SSL",
- "check_internet": "Conexión a internet",
- "fresh_install": "instalación nueva",
- "warnings_found": "avisos detectados",
- "all_ok": "Sistema OK",
- "critical_error_short": "Error crítico",
- "critical_error": "Error crítico: la aplicación no puede iniciarse. Revisa los registros del servidor.",
- "error_network": "No se puede contactar con el servidor. Comprueba tu conexión de red.",
- "retry": "Reintentar"
- }
}
\ No newline at end of file
diff --git a/translations/fr.json b/translations/fr.json
index dda794e..0144695 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -1,1351 +1,1363 @@
{
- "app": {
- "name": "EverShelf",
- "loading": "Chargement..."
- },
- "nav": {
- "title": "EverShelf",
- "home": "Accueil",
- "inventory": "Garde-manger",
- "recipes": "Recettes",
- "shopping": "Courses",
- "log": "Journal",
- "settings": "Paramètres"
- },
- "btn": {
- "back": "← Retour",
- "save": "💾 Enregistrer",
- "cancel": "✕ Annuler",
- "close": "Fermer",
- "add": "✅ Ajouter",
- "delete": "Supprimer",
- "edit": "✏️ Modifier",
- "use": "Utiliser",
- "edit_item": "Modifier",
- "search": "🔍 Rechercher",
- "go": "✅ Valider",
- "toggle_password": "👁️ Afficher/Masquer",
- "load_more": "Charger plus...",
- "save_config": "💾 Enregistrer la configuration",
- "save_product": "💾 Enregistrer le produit",
- "restart": "↺ Redémarrer",
- "reset_default": "↺ Rétablir les valeurs par défaut",
- "save_info": "💾 Enregistrer les informations",
- "retry": "🔄 Réessayer",
- "yes_short": "Oui",
- "no_short": "Non"
- },
- "form": {
- "select_placeholder": "-- Sélectionner --"
- },
- "locations": {
- "dispensa": "Garde-manger",
- "frigo": "Réfrigérateur",
- "freezer": "Congélateur",
- "altro": "Autre"
- },
- "categories": {
- "latticini": "Produits laitiers",
- "carne": "Viande",
- "pesce": "Poisson",
- "frutta": "Fruits",
- "verdura": "Légumes",
- "pasta": "Pâtes & Riz",
- "pane": "Pain & Boulangerie",
- "surgelati": "Surgelés",
- "bevande": "Boissons",
- "condimenti": "Condiments",
- "snack": "Snacks & Sucreries",
- "conserve": "Conserves",
- "cereali": "Céréales & Légumineuses",
- "igiene": "Hygiène",
- "pulizia": "Entretien",
- "altro": "Autre",
- "select": "-- Sélectionner --"
- },
- "units": {
- "pz": "pcs",
- "conf": "pkg",
- "g": "g",
- "ml": "ml",
- "pieces": "Pièces",
- "grams": "Grammes",
- "box": "Paquet",
- "boxes": "Paquets",
- "millilitres": "Millilitres",
- "from": "de"
- },
- "shopping_sections": {
- "frutta_verdura": "Fruits & Légumes",
- "carne_pesce": "Viande & Poisson",
- "latticini": "Produits laitiers & Frais",
- "pane_dolci": "Pain & Pâtisseries",
- "pasta": "Pâtes & Céréales",
- "conserve": "Conserves & Sauces",
- "surgelati": "Surgelés",
- "bevande": "Boissons",
- "pulizia_igiene": "Nettoyage & Hygiène",
- "altro": "Autre"
- },
- "dashboard": {
- "expired_title": "🚫 Périmé",
- "expiring_title": "⏰ Expire bientôt",
- "stats_period": "📊 30 derniers jours",
- "opened_title": "📦 Produits ouverts",
- "review_title": "🔍 À vérifier",
- "review_hint": "Quantités inhabituelles. Confirmez si elles sont correctes ou modifiez-les.",
- "quick_recipe": "Recette rapide avec les produits qui expirent",
- "banner_review_title": "Quantité anormale",
- "banner_review_action_ok": "C'est correct",
- "banner_review_action_finish": "🗑️ Tout fini",
- "banner_review_action_edit": "Corriger",
- "banner_review_action_weigh": "Peser",
- "banner_review_dismiss": "Ignorer",
- "banner_prediction_title": "Consommation à vérifier",
- "banner_prediction_hint": "L'estimation de consommation s'adapte aux données récentes : confirmez uniquement si la quantité actuelle est correcte.",
- "banner_prediction_action_confirm": "Confirmer {qty} {unit}",
- "banner_prediction_action_weigh": "Peser maintenant",
- "banner_prediction_action_edit": "Mettre à jour la quantité",
- "banner_expired_title": "Produit périmé",
- "banner_expired_today": "Périmé aujourd'hui",
- "banner_expired_days": "Périmé il y a {days} jours",
- "banner_expired_action_use": "Utiliser quand même",
- "banner_expired_action_finished": "Je l'ai terminé !",
- "banner_expired_action_throw": "Je l'ai jeté",
- "banner_expired_action_edit": "Corriger la date",
- "banner_anomaly_action_edit": "Corriger l'inventaire",
- "banner_anomaly_action_dismiss": "La quantité est correcte",
- "banner_no_expiry_title": "Date manquante : {name}",
- "banner_no_expiry_detail": "Ce produit n'a pas de date de péremption. Voulez-vous en ajouter une ou confirmer qu'il ne périme pas ?",
- "banner_no_expiry_action_set": "Définir une date de péremption",
- "banner_no_expiry_action_dismiss": "Ne périme pas ✓",
- "banner_no_expiry_toast_dismissed": "Marqué comme « sans date limite »",
- "banner_expiring_title": "Expire bientôt",
- "banner_expiring_today": "Expire aujourd'hui !",
- "banner_expiring_tomorrow": "Expire demain",
- "banner_expiring_days": "Expire dans {days} jours",
- "banner_expiring_action_use": "Utiliser maintenant",
- "banner_finished_title": "terminé ?",
- "banner_finished_detail": "J'ai enregistré que {name} a atteint zéro stock. Est-il vraiment épuisé ou en avez-vous encore ?",
- "banner_finished_action_yes": "Oui, c'est fini",
- "banner_finished_action_no": "Non, j'en ai encore",
- "banner_review_unusual_pkg_title": "Taille de paquet inhabituelle",
- "banner_review_unusual_pkg_detail": "Vous avez défini un paquet de {qty} {unit} — la taille semble très grande. Vérifiez si c'est correct ou modifiez.",
- "banner_review_low_qty_title": "Quantité très faible",
- "banner_review_low_qty_detail": "Vous n'avez que {qty} en stock — cela semble très peu, peut-être une erreur de saisie. Confirmez si c'est correct.",
- "banner_review_high_qty_title": "Quantité inhabituellement élevée",
- "banner_review_high_qty_detail": "Vous avez {qty} en stock — le chiffre semble très élevé. Confirmez si c'est correct ou modifiez.",
- "banner_prediction_rate_day": "Moyenne ~{n} {unit}/jour",
- "banner_prediction_rate_week": "Moyenne ~{n} {unit}/semaine",
- "banner_prediction_days_ago": "Il y a {n} jours vous avez réapprovisionné",
- "banner_prediction_more": "estimation précédente : {expected} {unit}{time} ; quantité actuelle : {actual} {unit}.",
- "banner_prediction_less": "estimation : {expected} {unit}{time} ; quantité actuelle : {actual} {unit}. Si votre rythme d'utilisation a changé, la prévision se met à jour automatiquement.",
- "banner_finished_zero": "L'inventaire indique zéro, mais les mouvements enregistrés suggèrent qu'il ne devrait pas être vide.",
- "banner_finished_expected": "D'après les enregistrements vous devriez avoir encore {qty} {unit}.",
- "banner_finished_check": "Pouvez-vous vérifier ?",
- "banner_anomaly_phantom_title": "vous avez plus de stock que prévu",
- "banner_anomaly_phantom_detail": "L'inventaire indique {inv_qty} {unit}, mais selon les enregistrements vous ne devriez avoir que {expected_qty} {unit}. Avez-vous ajouté du stock sans l'enregistrer ?",
- "banner_anomaly_untracked_title": "stock non enregistré comme entrée",
- "banner_anomaly_untracked_detail": "Vous avez
{inv_qty} {unit} en inventaire, mais les sorties enregistrées dépassent les entrées — le stock initial n'a probablement jamais été ajouté comme transaction « entrée ». Vous pouvez corriger la quantité ou saisir les entrées manquantes.",
- "banner_anomaly_ghost_title": "vous avez moins de stock que prévu",
- "banner_anomaly_ghost_detail": "D'après les opérations enregistrées vous devriez avoir {expected_qty} {unit} de {name}, mais l'inventaire n'en montre que {inv_qty} {unit}. Avez-vous pris du stock sans l'enregistrer ?",
- "consumed": "Consommé : {n} ({pct}%)",
- "wasted": "Gaspillé : {n} ({pct}%)",
- "more_opened": "et {n} autres ouverts...",
- "banner_expired_detail": "{when} · il vous reste encore
{qty}.",
- "banner_opened_detail": "{when} dans {location} · il vous reste encore
{qty}.",
- "banner_explain_title": "Demander une explication à Gemini",
- "banner_explain_btn": "Expliquer",
- "banner_analyzing": "🤖 Analyse en cours…"
- },
- "inventory": {
- "title": "Garde-manger",
- "filter_all": "Tout",
- "search_placeholder": "🔍 Rechercher un produit...",
- "recent_title": "🕐 Utilisés récemment",
- "popular_title": "⭐ Les plus utilisés",
- "empty": "Aucun produit ici.\nScannez un produit pour l'ajouter !",
- "no_items_found": "Aucun article trouvé",
- "qty_remainder_suffix": "restant",
- "vacuum_badge": "🫙 Sous vide",
- "opened_badge": "📭 Ouvert",
- "label_expiry": "📅 Péremption",
- "label_storage": "🫙 Conservation",
- "label_status": "📭 État",
- "opened_since": "Ouvert depuis le {date}",
- "label_position": "📍 Emplacement",
- "label_quantity": "📦 Quantité",
- "label_added": "📅 Ajouté",
- "empty_text": "Aucun produit ici.
Scannez un produit pour l'ajouter !",
- "empty_db": "Aucun produit dans la base de données.
Scannez un produit pour commencer !",
- "qty_trace": "< 1"
- },
- "scan": {
- "title": "Scanner",
- "mode_shopping": "🛒 Mode courses",
- "mode_shopping_end": "✅ Terminer les courses",
- "spesa_btn": "🛒 Courses",
- "zoom": "Zoom",
- "tab_barcode": "Code-barres",
- "tab_name": "Nom",
- "tab_ai": "IA",
- "recents_label": "Récents",
- "torch_hint": "Torche",
- "torch_on": "Torche activée",
- "torch_off": "Torche désactivée",
- "torch_unavailable": "Torche non disponible sur cet appareil",
- "flip_hint": "Retourner la caméra",
- "flip_front": "Caméra frontale",
- "flip_back": "Caméra arrière",
- "num_ocr_btn": "🔢 Lire les chiffres avec l'IA",
- "num_ocr_searching": "Recherche du code-barres avec l'IA...",
- "num_ocr_found": "Code trouvé : {code}",
- "num_ocr_not_found": "Aucun code-barres trouvé dans l'image",
- "barcode_placeholder": "Entrez le code-barres...",
- "quick_name_divider": "ou tapez le nom",
- "quick_name_placeholder": "Ex. : Pommes, Courgettes, Pain...",
- "manual_entry": "✏️ Saisie manuelle",
- "ai_identify": "🤖 Identifier avec l'IA",
- "hint": "Scannez le code-barres, tapez le nom du produit ou utilisez l'IA pour l'identifier",
- "debug_toggle": "🐛 Journal de débogage",
- "barcode_acquired": "🔖 Code-barres scanné : {code}",
- "scan_barcode": "🔖 Scanner le code-barres",
- "create_named": "Créer {name}",
- "new_without_barcode": "Nouveau produit sans code-barres"
- },
- "action": {
- "title": "Que voulez-vous faire ?",
- "add_btn": "📥 AJOUTER",
- "add_sub": "au garde-manger/réfrigérateur",
- "use_btn": "📤 UTILISER / CONSOMMER",
- "use_sub": "depuis le garde-manger/réfrigérateur",
- "have_title": "📦 Déjà en stock !",
- "add_more_sub": "en ajouter",
- "use_qty_sub": "combien vous avez utilisé",
- "throw_btn": "🗑️ JETER",
- "throw_sub": "jeter",
- "edit_sub": "péremption, emplacement…",
- "create_recipe_btn": "Recette"
- },
- "add": {
- "title": "Ajouter au garde-manger",
- "location_label": "📍 Où le rangez-vous ?",
- "quantity_label": "📦 Quantité",
- "conf_size_label": "📦 Chaque paquet contient :",
- "conf_size_placeholder": "ex. 300",
- "vacuum_label": "🫙 Sous vide",
- "vacuum_hint": "La date de péremption sera automatiquement prolongée",
- "submit": "✅ Ajouter",
- "purchase_type_label": "🛒 Ce produit est...",
- "new_btn": "🆕 Vient d'être acheté",
- "existing_btn": "📦 Je l'avais déjà",
- "remaining_label": "📦 Quantité restante",
- "remaining_hint": "Approximativement combien en reste-t-il ?",
- "remaining_full": "🟢 Plein",
- "remaining_half": "🟠 À moitié",
- "estimated_expiry": "Date de péremption estimée :",
- "suffix_freezer": "(congélateur)",
- "suffix_vacuum": "(sous vide)",
- "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}",
- "suffix_freezer_vacuum": "(congélateur + sous vide)",
- "history_badge_tip": "Moyenne de {n} entrées précédentes",
- "vacuum_question": "Sous vide ?",
- "vacuum_saved": "🔒 Sous vide !"
- },
- "use": {
- "title": "Utiliser / Consommer",
- "location_label": "📍 Depuis où ?",
- "quantity_label": "Combien avez-vous utilisé ?",
- "change": "modifier",
- "partial_hint": "Ou précisez la quantité utilisée :",
- "partial_piece_hint": "Avez-vous utilisé seulement une partie ?",
- "piece": "pièce",
- "one_whole": "1 entier",
- "use_all": "🗑️ Tout utilisé / Terminé",
- "submit": "📤 Utiliser cette quantité",
- "available": "📦 Disponible :",
- "opened_badge": "OUVERT",
- "not_in_inventory": "⚠️ Produit absent de l'inventaire.",
- "expiry_warning": "⚠️ Utilisez en premier celui{loc} qui expire le {date} — {when} !",
- "expiry_warning_opened": "⚠️ Celui{loc} est ouvert depuis {when} — utilisez-le en premier !",
- "throw_title": "🗑️ Jeter le produit",
- "throw_all": "🗑️ Tout jeter ({qty})",
- "throw_qty_label": "Quelle quantité jeter ?",
- "throw_qty_hint": "ou entrez une quantité :",
- "throw_partial_btn": "🗑️ Jeter cette quantité",
- "when_expired": "périmé il y a {n} jours",
- "when_today": "expire
aujourd'hui",
- "when_tomorrow": "expire
demain",
- "when_days": "expire dans
{n} jours",
- "toast_used": "📤 {qty} de {name} utilisé",
- "toast_bring": "🛒 Produit terminé → ajouté à Bring !",
- "toast_opened_finished": "🔓 Emballage ouvert de {name} terminé !",
- "disambiguation_hint": "Que voulez-vous dire par « tout fini » ?",
- "disambiguation_all": "🗑️ Tout finir ({qty})",
- "error_exceeds_stock": "⚠️ Vous ne pouvez pas utiliser plus que ce que vous avez disponible !",
- "use_all_confirm_title": "✅ Tout terminer",
- "use_all_confirm_msg": "Confirmez que vous avez terminé le produit :",
- "use_all_confirm_btn": "✅ Oui, terminé",
- "throw_all_confirm_title": "🗑️ Tout jeter",
- "throw_all_confirm_msg": "Voulez-vous vraiment jeter tout le produit ?",
- "throw_all_confirm_btn": "🗑️ Oui, jeter"
- },
- "product": {
- "title_new": "Nouveau produit",
- "title_edit": "Modifier le produit",
- "ai_fill": "📷 Prendre une photo et identifier avec l'IA",
- "ai_fill_hint": "L'IA remplira automatiquement les champs du produit",
- "name_label": "🏷️ Nom du produit *",
- "name_placeholder": "Ex. : Lait entier, Pâtes penne...",
- "brand_label": "🏢 Marque",
- "brand_placeholder": "Ex. : Barilla, Danone, Heinz...",
- "category_label": "📂 Catégorie",
- "unit_label": "📏 Unité de mesure",
- "default_qty_label": "🔢 Quantité par défaut",
- "conf_size_label": "📦 Chaque paquet contient :",
- "conf_size_placeholder": "ex. 300",
- "notes_label": "📝 Notes",
- "notes_placeholder": "Ex. : sans lactose, biologique, conserver au réfrigérateur après ouverture...",
- "barcode_label": "🔖 Code-barres",
- "barcode_placeholder": "Code-barres (si disponible)",
- "barcode_hint": "⚠️ Ajoutez le code-barres pour la prochaine fois, il suffira de scanner !",
- "submit": "💾 Enregistrer le produit",
- "name_required": "Entrez le nom du produit",
- "conf_size_required": "Précisez le contenu du paquet",
- "expiry_estimated": "Date de péremption estimée :",
- "scan_expiry": "Scanner la date de péremption",
- "expiry_hint": "📝 Vous pouvez modifier la date ou la scanner avec la caméra",
- "add_batch": "📦 + Lot avec une date différente",
- "package_info": "📦 Paquet : {info}",
- "edit_catalog": "⚙️ Modifier les infos produit (nom, marque, catégorie…)",
- "not_recognized": "⚠️ Produit non reconnu",
- "edit_info": "✏️ Modifier les informations",
- "modify_details": "MODIFIER\npéremption, emplacement…",
- "already_in_pantry": "📋 Déjà dans le garde-manger",
- "no_barcode": "Pas de code-barres",
- "unknown_product": "Produit non reconnu",
- "edit_name_brand": "Modifier nom/marque",
- "weight_label": "Poids",
- "origin_label": "Origine",
- "labels_label": "Labels",
- "select_variant": "Sélectionnez la variante exacte ou utilisez les données IA :"
- },
- "products": {
- "title": "📦 Tous les produits",
- "search_placeholder": "🔍 Rechercher un produit...",
- "empty": "Aucun produit dans la base de données.\nScannez un produit pour commencer !",
- "no_category": "Aucun produit dans cette catégorie"
- },
- "recipes": {
- "title": "🍳 Recettes",
- "generate": "✨ Générer une nouvelle recette",
- "archive_empty": "Aucune recette enregistrée. Générez votre première recette !",
- "dialog_title": "🍳 Recette",
- "dialog_desc": "Je vais générer une recette saine en utilisant les ingrédients du garde-manger, en priorisant les produits qui expirent.",
- "meal_label": "🕐 Quel repas ?",
- "persons_label": "👥 Pour combien de personnes ?",
- "meal_type_label": "🎯 Type de repas",
- "opt_fast": "⚡ Repas rapide",
- "opt_light": "🥗 Appétit léger",
- "opt_expiry": "⏰ Prioriser les produits qui expirent",
- "opt_healthy": "💚 Extra sain",
- "opt_opened": "📦 Prioriser les produits ouverts",
- "opt_zero_waste": "♻️ Zéro déchet",
- "generate_btn": "✨ Générer la recette",
- "loading_msg": "Préparation de votre recette...",
- "start_cooking": "👨🍳 Mode cuisine",
- "regenerate": "🔄 En générer une autre",
- "close_btn": "✅ Fermer",
- "ingredients_title": "🧾 Ingrédients",
- "tools_title": "Matériel nécessaire",
- "steps_title": "👨🍳 Étapes",
- "no_steps": "Aucune étape disponible",
- "generate_error": "Erreur de génération",
- "persons_short": "pers.",
- "use_ingredient_title": "Utiliser l'ingrédient",
- "recipe_qty_label": "Recette",
- "from_where_label": "Depuis où ?",
- "amount_label": "Combien",
- "use_amount_btn": "Utiliser cette quantité",
- "use_all_btn": "Tout utiliser / Terminé",
- "packs_label": "Paquets",
- "quantity_in_total": "Quantité en {unit} (total : {total})",
- "packs_of_have": "Paquets de {size} (vous en avez {count})",
- "scale_wait_stable": "Attendez 10s de poids stable pour le remplissage automatique…",
- "ingredient_scaled_toast": "📦 Ingrédient déduit du garde-manger !",
- "finished_added_bring_toast": "🛒 Produit terminé → ajouté à Bring !",
- "load_error": "Erreur de chargement"
- },
- "shopping": {
- "title": "🛒 Liste de courses",
- "bring_loading": "Connexion à Bring !...",
- "bring_not_configured": "Bring ! n'est pas configuré. Ajoutez votre e-mail et mot de passe dans les
paramètres.",
- "tab_to_buy": "🛍️ À acheter",
- "tab_forecast": "🧠 Prévision",
- "total_label": "💰 Total estimé",
- "section_to_buy": "🛍️ À acheter",
- "suggestions_title": "💡 Suggestions IA",
- "suggestions_add": "✅ Ajouter la sélection à Bring !",
- "search_prices": "🔍 Rechercher tous les prix",
- "suggest_btn": "Suggérer des achats",
- "smart_title": "🧠 Prévisions intelligentes",
- "smart_empty": "Aucune prévision disponible.
Ajoutez des produits à votre garde-manger pour recevoir des prévisions intelligentes.",
- "smart_filter_all": "Tout",
- "smart_filter_critical": "🔴 Urgent",
- "smart_filter_high": "🟠 Bientôt",
- "smart_filter_medium": "🟡 Planifier",
- "smart_filter_low": "🟢 Prévision",
- "smart_add": "🛒 Ajouter la sélection à Bring !",
- "empty": "Liste de courses vide !\nUtilisez le bouton ci-dessous pour générer des suggestions.",
- "already_in_list": "🛒 \"{name}\" est déjà dans la liste de courses",
- "already_in_list_short": "ℹ️ Déjà dans la liste de courses",
- "add_prompt": "Voulez-vous l'ajouter à la liste de courses ?",
- "smart_already": "📊 Les prévisions achats prédisent déjà {name}",
- "all_searched": "Tous les produits ont déjà été recherchés. Utilisez 🔄 pour rechercher individuellement.",
- "search_complete": "Recherche terminée : {count} produits",
- "removed_sufficient": "🧹 {removed} produit(s) avec stock suffisant retiré(s) de la liste",
- "suggest_buy": "🛒 Acheter : {qty} {unit}",
- "suggest_buy_approx": "🛒 Au minimum : {qty} {unit}",
- "suggest_buy_tip": "Quantité suggérée basée sur vos 14 derniers jours de consommation",
- "suggest_buy_approx_tip": "Estimation minimale basée sur la consommation (achetez le format le plus proche)",
- "bring_badge": "🛒 Déjà sur Bring !",
- "add_urgent_toast": "🔴 {n} produit(s) urgent(s) automatiquement ajouté(s) à Bring !",
- "migration_done": "✅ {migrated} mis à jour, {skipped} déjà ok",
- "added_to_bring": "🛒 {n} produits ajoutés à Bring !",
- "added_to_bring_skip": "{n} déjà présents",
- "all_on_bring": "Tous les produits étaient déjà sur Bring !",
- "freq_high": "📈 Fréquent",
- "freq_regular": "📊 Régulier",
- "freq_occasional": "📉 Occasionnel",
- "out_of_stock": "Rupture de stock",
- "scan_toast": "📷 Scanner : {name}",
- "empty_category": "Aucun produit dans cette catégorie",
- "session_empty": "🛒 Aucun produit encore",
- "urgency_critical": "Urgent",
- "urgency_high": "Bientôt",
- "urgency_medium": "Planifier",
- "urgency_low": "Prévision",
- "urgency_medium_short": "Moyen",
- "urgency_low_short": "Ok",
- "tag_urgent": "🔴 Urgent",
- "tag_priority": "⭐ Priorité",
- "tag_check": "✅ Vérifier",
- "smart_already_predicted": "📊 Les prévisions achats prédisent déjà
{name}{urgency}.",
- "item_removed": "✅ {name} retiré de la liste !",
- "urgency_spec_critical": "⚡ Urgent",
- "urgency_spec_high": "🟠 Bientôt",
- "bring_add_n": "Ajouter {n} à Bring !",
- "bring_add_selected": "Ajouter la sélection à Bring !",
- "bring_adding": "Ajout en cours...",
- "bring_added_one": "1 produit ajouté à Bring !",
- "bring_added_many": "{n} produits ajoutés à Bring !",
- "bring_skipped": "({n} déjà dans la liste)",
- "force_sync": "Forcer la synchronisation Bring !",
- "scan_target_label": "Vous cherchez",
- "scan_target_found": "Trouvé ! Retirer de la liste",
- "bring_add_one": "Ajouter 1 produit à Bring !",
- "bring_add_many": "Ajouter {n} produits à Bring !",
- "syncing": "Synchronisation…",
- "sync_done": "Synchronisation terminée",
- "price_searching": "Recherche en cours...",
- "search_action": "Rechercher",
- "open_action": "Ouvrir",
- "not_found": "Non trouvé",
- "search_price": "Rechercher le prix",
- "tap_to_scan": "Appuyez pour scanner",
- "tag_title": "Tag",
- "remove_title": "Retirer",
- "found_count": "{found}/{total} produits trouvés",
- "savings_offers": "· 🏷️ Vous économisez {amount}€ avec les offres",
- "searching_progress": "Recherche {current}/{total}...",
- "remove_error": "Erreur de suppression",
- "btn_fetch_prices": "Trouver les prix",
- "price_total_label": "💰 Total estimé :",
- "price_loading": "Recherche des prix…",
- "price_not_found": "prix n/d",
- "suggest_loading": "Analyse en cours...",
- "suggest_error": "Erreur de génération des suggestions",
- "priority_high": "Élevée",
- "priority_medium": "Moyenne",
- "priority_low": "Faible",
- "smart_last_update": "Mis à jour {time}",
- "names_already_updated": "Tous les noms sont déjà à jour"
- },
- "ai": {
- "title": "🤖 Identification IA",
- "capture": "📸 Prendre une photo",
- "retake": "🔄 Reprendre",
- "hint": "Prenez une photo du produit et l'IA essaiera de l'identifier",
- "identifying": "🤖 Identification du produit...",
- "no_api_key": "⚠️ Clé API Gemini non configurée.\n
Ajoutez GEMINI_API_KEY au fichier .env sur le serveur.",
- "fields_filled": "✅ Champs remplis par l'IA",
- "use_data": "✅ Utiliser les données IA",
- "use_data_no_barcode": "✅ Utiliser les données IA (sans code-barres)"
- },
- "log": {
- "title": "📒 Journal des opérations",
- "type_added": "Ajouté",
- "type_waste": "Jeté",
- "type_used": "Utilisé",
- "type_bring": "Ajouté à Bring !",
- "undone_badge": "Annulé",
- "undo_title": "Annuler cette opération",
- "load_error": "Erreur de chargement du journal",
- "empty": "Aucune opération enregistrée.",
- "undo_action_remove": "suppression de",
- "undo_action_restore": "réapprovisionnement de",
- "undo_confirm": "Annuler cette opération ?\n→ {action} {name}",
- "undo_success": "↩ Opération annulée pour {name}",
- "already_undone": "Opération déjà annulée",
- "too_old": "Impossible d'annuler des opérations de plus de 24 heures",
- "undo_error": "Erreur lors de l'annulation",
- "recipe_prefix": "Recette"
- },
- "chat": {
- "title": "Chef Gemini",
- "welcome": "Bonjour ! Je suis votre assistant cuisine",
- "welcome_desc": "Demandez-moi de préparer un jus, un snack, un plat rapide… Je connais votre garde-manger, vos appareils et vos préférences !",
- "suggestion_snack": "🍿 Snack rapide",
- "suggestion_juice": "🥤 Jus/Smoothie",
- "suggestion_light": "🥗 Quelque chose de léger",
- "suggestion_expiry": "⏰ Utiliser les produits qui expirent",
- "clear": "Nouvelle conversation",
- "placeholder": "Demandez quelque chose...",
- "cleared": "Chat effacé",
- "suggestion_snack_text": "Que puis-je préparer comme snack rapide ?",
- "suggestion_juice_text": "Faites-moi un jus ou un smoothie avec ce que j'ai",
- "suggestion_light_text": "J'ai faim mais je veux quelque chose de léger",
- "suggestion_expiry_text": "Qu'est-ce qui va bientôt expirer et comment l'utiliser ?",
- "transfer_to_recipes": "Transférer aux recettes",
- "transferring": "Transfert en cours...",
- "transferred": "Ajouté aux recettes !",
- "open_recipe": "Ouvrir la recette",
- "quick_recipe_prompt": "Suggérez une recette rapide POUR UNE PERSONNE en utilisant les produits qui expirent en premier ! Ignorez les congélateurs, concentrez-vous sur le réfrigérateur et le garde-manger."
- },
- "cooking": {
- "close": "Fermer",
- "tts_btn": "Lire à voix haute",
- "restart": "↺ Recommencer",
- "replay": "🔊 Rejouer",
- "timer": "⏱️ {time} · Minuterie",
- "prev": "◀ Précédent",
- "next": "Suivant ▶",
- "ingredient_used": "✔️ Déduit",
- "ingredient_use_btn": "📦 Utiliser",
- "ingredient_deduct_title": "Déduire du garde-manger",
- "timer_expired_tts": "Minuterie {label} terminée !",
- "timer_warning_tts": "Attention ! {label} : 10 secondes restantes !",
- "recipe_done_tts": "Recette terminée ! Bon appétit !",
- "expires_chip": "exp. {date}",
- "finish": "✅ Terminer",
- "step_fallback": "Étape {n}",
- "zerowaste_label": "♻️ Déchet",
- "zerowaste_tip_title": "Conseil zéro déchet"
- },
- "settings": {
- "title": "⚙️ Paramètres",
- "tab_api": "Clés API",
- "tab_bring": "Bring !",
- "tab_recipe": "Recettes",
- "tab_mealplan": "Plan hebdomadaire",
- "tab_appliances": "Appareils",
- "tab_spesa": "Courses en ligne",
- "tab_camera": "Caméra",
- "tab_security": "Sécurité",
- "tab_tts": "Voix (TTS)",
- "tab_language": "Langue",
- "tab_scale": "Balance connectée",
- "gemini": {
- "title": "🤖 Google Gemini IA",
- "hint": "Clé API pour l'identification des produits, les dates de péremption et les recettes.",
- "key_label": "Clé API Gemini"
+ "app": {
+ "name": "EverShelf",
+ "loading": "Chargement..."
},
- "bring": {
- "title": "🛒 Liste de courses Bring !",
- "hint": "Identifiants pour l'intégration de la liste de courses Bring !",
- "email_label": "📧 E-mail Bring !",
- "password_label": "🔒 Mot de passe Bring !"
+ "nav": {
+ "title": "EverShelf",
+ "home": "Accueil",
+ "inventory": "Garde-manger",
+ "recipes": "Recettes",
+ "shopping": "Courses",
+ "log": "Journal",
+ "settings": "Paramètres"
},
- "price": {
- "title": "💰 Estimation des prix (IA)",
- "hint": "Afficher le coût estimé par produit dans la liste de courses à l'aide de l'IA.",
- "enabled_label": "Activer l'estimation des prix",
- "country_label": "🌍 Pays de référence",
- "currency_label": "💱 Devise",
- "update_label": "🔄 Actualiser les prix tous les",
- "update_suffix": "mois"
+ "btn": {
+ "back": "← Retour",
+ "save": "💾 Enregistrer",
+ "cancel": "✕ Annuler",
+ "close": "Fermer",
+ "add": "✅ Ajouter",
+ "delete": "Supprimer",
+ "edit": "✏️ Modifier",
+ "use": "Utiliser",
+ "edit_item": "Modifier",
+ "search": "🔍 Rechercher",
+ "go": "✅ Valider",
+ "toggle_password": "👁️ Afficher/Masquer",
+ "load_more": "Charger plus...",
+ "save_config": "💾 Enregistrer la configuration",
+ "save_product": "💾 Enregistrer le produit",
+ "restart": "↺ Redémarrer",
+ "reset_default": "↺ Rétablir les valeurs par défaut",
+ "save_info": "💾 Enregistrer les informations",
+ "retry": "🔄 Réessayer",
+ "yes_short": "Oui",
+ "no_short": "Non"
},
- "recipe": {
- "title": "🍳 Préférences de recettes",
- "hint": "Configurez les options par défaut pour la génération de recettes.",
- "persons_label": "👥 Portions par défaut",
- "options_label": "🎯 Options de recette par défaut",
- "fast": "⚡ Repas rapide",
- "light": "🥗 Repas léger",
- "expiry": "⏰ Priorité péremption",
- "healthy": "💚 Extra sain",
- "opened": "📦 Priorité produits ouverts",
- "zerowaste": "♻️ Zéro déchet",
- "dietary_label": "🚫 Intolérances / Restrictions",
- "dietary_placeholder": "Ex. : sans gluten, sans lactose, végétarien..."
+ "form": {
+ "select_placeholder": "-- Sélectionner --"
},
- "mealplan": {
- "title": "📅 Plan de repas hebdomadaire",
- "hint": "Définissez le type de repas pour chaque jour. Il sera utilisé comme guide pour la génération de recettes.",
- "enabled": "✅ Activer le plan hebdomadaire",
- "legend": "🌤️ = Déjeuner · 🌙 = Dîner · Appuyez sur un badge pour le modifier.",
- "types_title": "📋 Types disponibles",
- "reset_btn": "↺ Restaurer les valeurs par défaut"
+ "locations": {
+ "dispensa": "Garde-manger",
+ "frigo": "Réfrigérateur",
+ "freezer": "Congélateur",
+ "altro": "Autre"
},
- "appliances": {
- "title": "🔌 Appareils disponibles",
- "hint": "Indiquez les appareils que vous possédez. Ils seront pris en compte lors de la génération de recettes.",
- "new_placeholder": "Ex. : Machine à pain, Thermomix, Friteuse à air...",
- "quick_title": "Ajout rapide :",
- "oven": "🔥 Four",
- "microwave": "📡 Micro-ondes",
- "air_fryer": "🍟 Friteuse à air",
- "bread_maker": "🍞 Machine à pain",
- "bimby": "🤖 Thermomix/Cookeo",
- "mixer": "🌀 Robot pâtissier",
- "steamer": "♨️ Cuiseur vapeur",
- "pressure_cooker": "🫕 Cocotte-minute",
- "toaster": "🍞 Grille-pain",
- "blender": "🍹 Mixeur",
- "empty": "Aucun appareil ajouté"
+ "categories": {
+ "latticini": "Produits laitiers",
+ "carne": "Viande",
+ "pesce": "Poisson",
+ "frutta": "Fruits",
+ "verdura": "Légumes",
+ "pasta": "Pâtes & Riz",
+ "pane": "Pain & Boulangerie",
+ "surgelati": "Surgelés",
+ "bevande": "Boissons",
+ "condimenti": "Condiments",
+ "snack": "Snacks & Sucreries",
+ "conserve": "Conserves",
+ "cereali": "Céréales & Légumineuses",
+ "igiene": "Hygiène",
+ "pulizia": "Entretien",
+ "altro": "Autre",
+ "select": "-- Sélectionner --"
},
- "spesa": {
- "title": "🛍️ Courses en ligne",
- "hint": "Configurez le fournisseur de courses en ligne.",
- "provider_label": "🏪 Fournisseur",
- "email_label": "📧 E-mail",
- "password_label": "🔒 Mot de passe",
- "login_btn": "🔐 Connexion",
- "ai_prompt_label": "🤖 Prompt IA de sélection de produit",
- "ai_prompt_placeholder": "Instructions pour l'IA lors du choix entre plusieurs produits...",
- "ai_prompt_hint": "L'IA utilise ce prompt pour choisir le produit le plus approprié parmi les résultats. Laissez vide pour le comportement par défaut.",
- "configure_first": "Configurez d'abord les courses en ligne dans les paramètres",
- "missing_credentials": "Entrez l'e-mail et le mot de passe",
- "login_in_progress": "Connexion en cours...",
- "login_error_prefix": "Erreur :",
- "login_network_error_prefix": "Erreur réseau :",
- "login_success_default": "Connexion réussie !",
- "result_name_label": "Nom",
- "result_card_label": "Carte",
- "result_pickup_label": "Point de retrait",
- "result_points_label": "Points fidélité",
- "connected_relogin": "✅ Connecté — Se reconnecter",
- "connected_as": "Connecté en tant que {name}"
+ "units": {
+ "pz": "pcs",
+ "conf": "pkg",
+ "g": "g",
+ "ml": "ml",
+ "pieces": "Pièces",
+ "grams": "Grammes",
+ "box": "Paquet",
+ "boxes": "Paquets",
+ "millilitres": "Millilitres",
+ "from": "de"
},
- "camera": {
- "title": "📷 Caméra",
- "hint": "Choisissez la caméra à utiliser pour le scan de code-barres et l'identification IA.",
- "device_label": "📸 Caméra par défaut",
- "back": "📱 Arrière (par défaut)",
- "front": "🤳 Frontale",
- "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"
+ "shopping_sections": {
+ "frutta_verdura": "Fruits & Légumes",
+ "carne_pesce": "Viande & Poisson",
+ "latticini": "Produits laitiers & Frais",
+ "pane_dolci": "Pain & Pâtisseries",
+ "pasta": "Pâtes & Céréales",
+ "conserve": "Conserves & Sauces",
+ "surgelati": "Surgelés",
+ "bevande": "Boissons",
+ "pulizia_igiene": "Nettoyage & Hygiène",
+ "altro": "Autre"
},
- "security": {
- "title": "🔒 Certificat HTTPS",
- "hint": "Si le navigateur affiche l'erreur « Votre connexion n'est pas privée » (ERR_CERT_AUTHORITY_INVALID), vous devez installer le certificat CA sur l'appareil.",
- "download_btn": "📥 Télécharger le certificat CA",
- "token_title": "🔑 Token de paramètres",
- "token_label": "Token d'accès",
- "token_hint": "Si `SETTINGS_TOKEN` est configuré dans le `.env` du serveur, entrez le token ici avant de sauvegarder les paramètres. Laissez vide si non configuré.",
- "token_placeholder": "(vide = pas de protection)",
- "token_required_hint": "🔒 Ce serveur nécessite un token pour sauvegarder les paramètres.",
- "cert_instructions": "
Instructions pour Chrome (Android) :1. Téléchargez le certificat ci-dessus
2. Allez dans
Paramètres → Sécurité & Confidentialité → Plus de paramètres de sécurité → Installer depuis le stockage3. Sélectionnez le fichier
EverShelf_CA.crt téléchargé
4. Choisissez « CA » et confirmez
5. Redémarrez Chrome
Instructions pour Chrome (PC) :1. Téléchargez le certificat ci-dessus
2. Allez dans
chrome://settings/certificates3. Onglet « Autorités » → Importer → sélectionnez le fichier
4. Cochez « Approuver ce certificat pour identifier les sites web »
5. Redémarrez Chrome"
+ "dashboard": {
+ "expired_title": "🚫 Périmé",
+ "expiring_title": "⏰ Expire bientôt",
+ "stats_period": "📊 30 derniers jours",
+ "opened_title": "📦 Produits ouverts",
+ "review_title": "🔍 À vérifier",
+ "review_hint": "Quantités inhabituelles. Confirmez si elles sont correctes ou modifiez-les.",
+ "quick_recipe": "Recette rapide avec les produits qui expirent",
+ "banner_review_title": "Quantité anormale",
+ "banner_review_action_ok": "C'est correct",
+ "banner_review_action_finish": "🗑️ Tout fini",
+ "banner_review_action_edit": "Corriger",
+ "banner_review_action_weigh": "Peser",
+ "banner_review_dismiss": "Ignorer",
+ "banner_prediction_title": "Consommation à vérifier",
+ "banner_prediction_hint": "L'estimation de consommation s'adapte aux données récentes : confirmez uniquement si la quantité actuelle est correcte.",
+ "banner_prediction_action_confirm": "Confirmer {qty} {unit}",
+ "banner_prediction_action_weigh": "Peser maintenant",
+ "banner_prediction_action_edit": "Mettre à jour la quantité",
+ "banner_expired_title": "Produit périmé",
+ "banner_expired_today": "Périmé aujourd'hui",
+ "banner_expired_days": "Périmé il y a {days} jours",
+ "banner_expired_action_use": "Utiliser quand même",
+ "banner_expired_action_finished": "Je l'ai terminé !",
+ "banner_expired_action_throw": "Je l'ai jeté",
+ "banner_expired_action_edit": "Corriger la date",
+ "banner_anomaly_action_edit": "Corriger l'inventaire",
+ "banner_anomaly_action_dismiss": "La quantité est correcte",
+ "banner_no_expiry_title": "Date manquante : {name}",
+ "banner_no_expiry_detail": "Ce produit n'a pas de date de péremption. Voulez-vous en ajouter une ou confirmer qu'il ne périme pas ?",
+ "banner_no_expiry_action_set": "Définir une date de péremption",
+ "banner_no_expiry_action_dismiss": "Ne périme pas ✓",
+ "banner_no_expiry_toast_dismissed": "Marqué comme « sans date limite »",
+ "banner_expiring_title": "Expire bientôt",
+ "banner_expiring_today": "Expire aujourd'hui !",
+ "banner_expiring_tomorrow": "Expire demain",
+ "banner_expiring_days": "Expire dans {days} jours",
+ "banner_expiring_action_use": "Utiliser maintenant",
+ "banner_finished_title": "terminé ?",
+ "banner_finished_detail": "J'ai enregistré que {name} a atteint zéro stock. Est-il vraiment épuisé ou en avez-vous encore ?",
+ "banner_finished_action_yes": "Oui, c'est fini",
+ "banner_finished_action_no": "Non, j'en ai encore",
+ "banner_review_unusual_pkg_title": "Taille de paquet inhabituelle",
+ "banner_review_unusual_pkg_detail": "Vous avez défini un paquet de {qty} {unit} — la taille semble très grande. Vérifiez si c'est correct ou modifiez.",
+ "banner_review_low_qty_title": "Quantité très faible",
+ "banner_review_low_qty_detail": "Vous n'avez que {qty} en stock — cela semble très peu, peut-être une erreur de saisie. Confirmez si c'est correct.",
+ "banner_review_high_qty_title": "Quantité inhabituellement élevée",
+ "banner_review_high_qty_detail": "Vous avez {qty} en stock — le chiffre semble très élevé. Confirmez si c'est correct ou modifiez.",
+ "banner_prediction_rate_day": "Moyenne ~{n} {unit}/jour",
+ "banner_prediction_rate_week": "Moyenne ~{n} {unit}/semaine",
+ "banner_prediction_days_ago": "Il y a {n} jours vous avez réapprovisionné",
+ "banner_prediction_more": "estimation précédente : {expected} {unit}{time} ; quantité actuelle : {actual} {unit}.",
+ "banner_prediction_less": "estimation : {expected} {unit}{time} ; quantité actuelle : {actual} {unit}. Si votre rythme d'utilisation a changé, la prévision se met à jour automatiquement.",
+ "banner_finished_zero": "L'inventaire indique zéro, mais les mouvements enregistrés suggèrent qu'il ne devrait pas être vide.",
+ "banner_finished_expected": "D'après les enregistrements vous devriez avoir encore {qty} {unit}.",
+ "banner_finished_check": "Pouvez-vous vérifier ?",
+ "banner_anomaly_phantom_title": "vous avez plus de stock que prévu",
+ "banner_anomaly_phantom_detail": "L'inventaire indique {inv_qty} {unit}, mais selon les enregistrements vous ne devriez avoir que {expected_qty} {unit}. Avez-vous ajouté du stock sans l'enregistrer ?",
+ "banner_anomaly_untracked_title": "stock non enregistré comme entrée",
+ "banner_anomaly_untracked_detail": "Vous avez
{inv_qty} {unit} en inventaire, mais les sorties enregistrées dépassent les entrées — le stock initial n'a probablement jamais été ajouté comme transaction « entrée ». Vous pouvez corriger la quantité ou saisir les entrées manquantes.",
+ "banner_anomaly_ghost_title": "vous avez moins de stock que prévu",
+ "banner_anomaly_ghost_detail": "D'après les opérations enregistrées vous devriez avoir {expected_qty} {unit} de {name}, mais l'inventaire n'en montre que {inv_qty} {unit}. Avez-vous pris du stock sans l'enregistrer ?",
+ "consumed": "Consommé : {n} ({pct}%)",
+ "wasted": "Gaspillé : {n} ({pct}%)",
+ "more_opened": "et {n} autres ouverts...",
+ "banner_expired_detail": "{when} · il vous reste encore
{qty}.",
+ "banner_opened_detail": "{when} dans {location} · il vous reste encore
{qty}.",
+ "banner_explain_title": "Demander une explication à Gemini",
+ "banner_explain_btn": "Expliquer",
+ "banner_analyzing": "🤖 Analyse en cours…"
},
- "tts": {
- "title": "🔊 Voix & TTS",
- "hint": "Configurez la synthèse vocale via une API REST externe. Les étapes de recette et les minuteries expirées seront envoyées à l'endpoint configuré.",
- "enabled": "✅ Activer la TTS",
- "engine_label": "⚙️ Moteur TTS",
- "engine_browser": "🔇 Navigateur (hors ligne, aucune configuration requise)",
- "engine_server": "🌐 Serveur externe (Home Assistant, API REST...)",
- "voice_label": "🗣️ Voix",
- "rate_label": "⚡ Vitesse",
- "pitch_label": "🎵 Tonalité",
- "url_label": "🌐 URL de l'endpoint",
- "method_label": "📡 Méthode HTTP",
- "auth_label": "🔐 Authentification",
- "auth_bearer": "Bearer Token",
- "auth_custom": "En-tête personnalisé",
- "auth_none": "Aucune",
- "token_label": "🔑 Bearer Token",
- "custom_header_name": "📋 Nom de l'en-tête",
- "custom_header_value": "📋 Valeur de l'en-tête",
- "content_type_label": "📄 Content-Type",
- "payload_key_label": "🗝️ Champ texte dans le payload",
- "payload_key_hint": "Nom du champ JSON qui contiendra le texte à lire (ex. : message, text).",
- "extra_fields_label": "➕ Champs supplémentaires (JSON)",
- "extra_fields_placeholder": "{\"entity_id\": \"media_player.salon\"}",
- "extra_fields_hint": "Champs supplémentaires à inclure dans le payload, en format JSON. Laissez vide si non nécessaire.",
- "test_btn": "🔊 Envoyer une voix de test",
- "voices_loading": "Chargement des voix…",
- "voice_not_supported": "Voix non supportée par ce navigateur",
- "voices_none": "Aucune voix disponible sur cet appareil",
- "voices_hint": "Les voix disponibles dépendent du système d'exploitation et du navigateur. Appuyez sur ↺ si la liste ne se charge pas.",
- "url_missing": "⚠️ URL de l'endpoint manquante.",
- "test_sending": "⏳ Envoi…",
- "test_ok": "✅ Réponse {code} — vérifiez que le haut-parleur a parlé."
+ "inventory": {
+ "title": "Garde-manger",
+ "filter_all": "Tout",
+ "search_placeholder": "🔍 Rechercher un produit...",
+ "recent_title": "🕐 Utilisés récemment",
+ "popular_title": "⭐ Les plus utilisés",
+ "empty": "Aucun produit ici.\nScannez un produit pour l'ajouter !",
+ "no_items_found": "Aucun article trouvé",
+ "qty_remainder_suffix": "restant",
+ "vacuum_badge": "🫙 Sous vide",
+ "opened_badge": "📭 Ouvert",
+ "label_expiry": "📅 Péremption",
+ "label_storage": "🫙 Conservation",
+ "label_status": "📭 État",
+ "opened_since": "Ouvert depuis le {date}",
+ "label_position": "📍 Emplacement",
+ "label_quantity": "📦 Quantité",
+ "label_added": "📅 Ajouté",
+ "empty_text": "Aucun produit ici.
Scannez un produit pour l'ajouter !",
+ "empty_db": "Aucun produit dans la base de données.
Scannez un produit pour commencer !",
+ "qty_trace": "< 1"
},
- "language": {
- "title": "🌐 Langue",
- "hint": "Sélectionnez la langue de l'interface.",
- "label": "🌐 Langue",
- "restart_notice": "La page sera rechargée pour appliquer la nouvelle langue."
+ "scan": {
+ "title": "Scanner",
+ "mode_shopping": "🛒 Mode courses",
+ "mode_shopping_end": "✅ Terminer les courses",
+ "spesa_btn": "🛒 Courses",
+ "zoom": "Zoom",
+ "tab_barcode": "Code-barres",
+ "tab_name": "Nom",
+ "tab_ai": "IA",
+ "recents_label": "Récents",
+ "torch_hint": "Torche",
+ "torch_on": "Torche activée",
+ "torch_off": "Torche désactivée",
+ "torch_unavailable": "Torche non disponible sur cet appareil",
+ "flip_hint": "Retourner la caméra",
+ "flip_front": "Caméra frontale",
+ "flip_back": "Caméra arrière",
+ "num_ocr_btn": "🔢 Lire les chiffres avec l'IA",
+ "num_ocr_searching": "Recherche du code-barres avec l'IA...",
+ "num_ocr_found": "Code trouvé : {code}",
+ "num_ocr_not_found": "Aucun code-barres trouvé dans l'image",
+ "barcode_placeholder": "Entrez le code-barres...",
+ "quick_name_divider": "ou tapez le nom",
+ "quick_name_placeholder": "Ex. : Pommes, Courgettes, Pain...",
+ "manual_entry": "✏️ Saisie manuelle",
+ "ai_identify": "🤖 Identifier avec l'IA",
+ "hint": "Scannez le code-barres, tapez le nom du produit ou utilisez l'IA pour l'identifier",
+ "debug_toggle": "🐛 Journal de débogage",
+ "barcode_acquired": "🔖 Code-barres scanné : {code}",
+ "scan_barcode": "🔖 Scanner le code-barres",
+ "create_named": "Créer {name}",
+ "new_without_barcode": "Nouveau produit sans code-barres"
},
- "screensaver": {
- "label": "Activer l'économiseur d'écran",
- "card_title": "🌙 Économiseur d'écran",
- "card_hint": "Affiche une horloge avec des informations utiles après 5 minutes d'inactivité. Désactivé par défaut.",
- "timeout_1": "1 minute",
- "timeout_2": "2 minutes",
- "timeout_5": "5 minutes",
- "timeout_10": "10 minutes",
- "timeout_15": "15 minutes",
- "timeout_30": "30 minutes",
- "timeout_60": "1 heure",
- "start_after": "⏱️ Démarrer après"
+ "action": {
+ "title": "Que voulez-vous faire ?",
+ "add_btn": "📥 AJOUTER",
+ "add_sub": "au garde-manger/réfrigérateur",
+ "use_btn": "📤 UTILISER / CONSOMMER",
+ "use_sub": "depuis le garde-manger/réfrigérateur",
+ "have_title": "📦 Déjà en stock !",
+ "add_more_sub": "en ajouter",
+ "use_qty_sub": "combien vous avez utilisé",
+ "throw_btn": "🗑️ JETER",
+ "throw_sub": "jeter",
+ "edit_sub": "péremption, emplacement…",
+ "create_recipe_btn": "Recette"
},
- "scale": {
- "title": "⚖️ Balance connectée",
- "hint": "Connectez une balance Bluetooth via la passerelle Android pour lire automatiquement le poids.",
- "tab": "Balance connectée",
- "enabled": "✅ Activer la balance connectée",
- "url_label": "🌐 URL de la passerelle WebSocket",
- "url_placeholder": "ws://192.168.1.x:8765",
- "url_hint": "URL affichée par l'application Android (même réseau Wi-Fi). Ex. :",
- "test_btn": "🔗 Tester la connexion",
- "download_btn": "📥 Télécharger la passerelle Android (APK)",
- "download_hint": "Application Android qui connecte votre balance BLE et EverShelf.",
- "download_sub": "Source : evershelf-scale-gateway/ dans la racine du projet",
- "live_weight": "poids en temps réel",
- "auto_reconnect": "🔁 Reconnexion : automatique",
- "kiosk_title": "📡 Balance BLE intégrée dans le kiosque",
- "kiosk_hint": "La balance est directement gérée par la passerelle BLE interne du kiosque. Pour associer un nouvel appareil, utilisez l'assistant de configuration.",
- "kiosk_reconfigure": "🔄 Reconfigurer la balance BLE",
- "ble_protocols": "
🔌 Protocoles BLE supportés :
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — poids, graisse, IMC
- Xiaomi Mi Body Composition Scale 2
- Générique — heuristique automatique pour 100+ modèles
"
+ "add": {
+ "title": "Ajouter au garde-manger",
+ "location_label": "📍 Où le rangez-vous ?",
+ "quantity_label": "📦 Quantité",
+ "conf_size_label": "📦 Chaque paquet contient :",
+ "conf_size_placeholder": "ex. 300",
+ "vacuum_label": "🫙 Sous vide",
+ "vacuum_hint": "La date de péremption sera automatiquement prolongée",
+ "submit": "✅ Ajouter",
+ "purchase_type_label": "🛒 Ce produit est...",
+ "new_btn": "🆕 Vient d'être acheté",
+ "existing_btn": "📦 Je l'avais déjà",
+ "remaining_label": "📦 Quantité restante",
+ "remaining_hint": "Approximativement combien en reste-t-il ?",
+ "remaining_full": "🟢 Plein",
+ "remaining_half": "🟠 À moitié",
+ "estimated_expiry": "Date de péremption estimée :",
+ "suffix_freezer": "(congélateur)",
+ "suffix_vacuum": "(sous vide)",
+ "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}",
+ "suffix_freezer_vacuum": "(congélateur + sous vide)",
+ "history_badge_tip": "Moyenne de {n} entrées précédentes",
+ "vacuum_question": "Sous vide ?",
+ "vacuum_saved": "🔒 Sous vide !"
},
- "kiosk": {
- "hint": "Transformez une tablette Android en panneau EverShelf permanent avec passerelle BLE intégrée.",
- "download_btn": "📥 Télécharger EverShelf Kiosk (APK)",
- "download_sub": "Mode kiosque plein écran + passerelle de balance intégrée. Source : evershelf-kiosk/",
- "native_title": "Configuration du kiosque",
- "native_hint": "URL du serveur, balance BLE, économiseur d'écran et assistant de configuration.",
- "native_btn": "Ouvrir la configuration du kiosque",
- "native_tap_hint": "Appuyez sur le bouton engrenage en haut à droite",
- "native_update_hint": "Mettez à jour l'application kiosque pour utiliser cette fonctionnalité",
- "update_title": "Mise à jour du kiosque",
- "check_updates_btn": "🔍 Vérifier les mises à jour",
- "needs_update": "⚠️ Le kiosque installé ne supporte pas cette fonctionnalité. Mettez à jour l'application kiosque pour l'activer."
+ "use": {
+ "title": "Utiliser / Consommer",
+ "location_label": "📍 Depuis où ?",
+ "quantity_label": "Combien avez-vous utilisé ?",
+ "change": "modifier",
+ "partial_hint": "Ou précisez la quantité utilisée :",
+ "partial_piece_hint": "Avez-vous utilisé seulement une partie ?",
+ "piece": "pièce",
+ "one_whole": "1 entier",
+ "use_all": "🗑️ Tout utilisé / Terminé",
+ "submit": "📤 Utiliser cette quantité",
+ "available": "📦 Disponible :",
+ "opened_badge": "OUVERT",
+ "not_in_inventory": "⚠️ Produit absent de l'inventaire.",
+ "expiry_warning": "⚠️ Utilisez en premier celui{loc} qui expire le {date} — {when} !",
+ "expiry_warning_opened": "⚠️ Celui{loc} est ouvert depuis {when} — utilisez-le en premier !",
+ "throw_title": "🗑️ Jeter le produit",
+ "throw_all": "🗑️ Tout jeter ({qty})",
+ "throw_qty_label": "Quelle quantité jeter ?",
+ "throw_qty_hint": "ou entrez une quantité :",
+ "throw_partial_btn": "🗑️ Jeter cette quantité",
+ "when_expired": "périmé il y a {n} jours",
+ "when_today": "expire
aujourd'hui",
+ "when_tomorrow": "expire
demain",
+ "when_days": "expire dans
{n} jours",
+ "toast_used": "📤 {qty} de {name} utilisé",
+ "toast_bring": "🛒 Produit terminé → ajouté à Bring !",
+ "toast_opened_finished": "🔓 Emballage ouvert de {name} terminé !",
+ "disambiguation_hint": "Que voulez-vous dire par « tout fini » ?",
+ "disambiguation_all": "🗑️ Tout finir ({qty})",
+ "error_exceeds_stock": "⚠️ Vous ne pouvez pas utiliser plus que ce que vous avez disponible !",
+ "use_all_confirm_title": "✅ Tout terminer",
+ "use_all_confirm_msg": "Confirmez que vous avez terminé le produit :",
+ "use_all_confirm_btn": "✅ Oui, terminé",
+ "throw_all_confirm_title": "🗑️ Tout jeter",
+ "throw_all_confirm_msg": "Voulez-vous vraiment jeter tout le produit ?",
+ "throw_all_confirm_btn": "🗑️ Oui, jeter"
},
- "saved": "✅ Configuration enregistrée !",
- "saved_local": "✅ Configuration enregistrée localement",
- "saved_local_error": "⚠️ Enregistré localement, erreur serveur : {error}",
- "theme": {
- "title": "🌙 Apparence",
- "hint": "Choisissez le thème de l'interface.",
- "label": "🌙 Thème",
- "off": "☀️ Clair",
- "on": "🌙 Sombre",
- "auto": "🔄 Automatique (système)"
+ "product": {
+ "title_new": "Nouveau produit",
+ "title_edit": "Modifier le produit",
+ "ai_fill": "📷 Prendre une photo et identifier avec l'IA",
+ "ai_fill_hint": "L'IA remplira automatiquement les champs du produit",
+ "name_label": "🏷️ Nom du produit *",
+ "name_placeholder": "Ex. : Lait entier, Pâtes penne...",
+ "brand_label": "🏢 Marque",
+ "brand_placeholder": "Ex. : Barilla, Danone, Heinz...",
+ "category_label": "📂 Catégorie",
+ "unit_label": "📏 Unité de mesure",
+ "default_qty_label": "🔢 Quantité par défaut",
+ "conf_size_label": "📦 Chaque paquet contient :",
+ "conf_size_placeholder": "ex. 300",
+ "notes_label": "📝 Notes",
+ "notes_placeholder": "Ex. : sans lactose, biologique, conserver au réfrigérateur après ouverture...",
+ "barcode_label": "🔖 Code-barres",
+ "barcode_placeholder": "Code-barres (si disponible)",
+ "barcode_hint": "⚠️ Ajoutez le code-barres pour la prochaine fois, il suffira de scanner !",
+ "submit": "💾 Enregistrer le produit",
+ "name_required": "Entrez le nom du produit",
+ "conf_size_required": "Précisez le contenu du paquet",
+ "expiry_estimated": "Date de péremption estimée :",
+ "scan_expiry": "Scanner la date de péremption",
+ "expiry_hint": "📝 Vous pouvez modifier la date ou la scanner avec la caméra",
+ "add_batch": "📦 + Lot avec une date différente",
+ "package_info": "📦 Paquet : {info}",
+ "edit_catalog": "⚙️ Modifier les infos produit (nom, marque, catégorie…)",
+ "not_recognized": "⚠️ Produit non reconnu",
+ "edit_info": "✏️ Modifier les informations",
+ "modify_details": "MODIFIER\npéremption, emplacement…",
+ "already_in_pantry": "📋 Déjà dans le garde-manger",
+ "no_barcode": "Pas de code-barres",
+ "unknown_product": "Produit non reconnu",
+ "edit_name_brand": "Modifier nom/marque",
+ "weight_label": "Poids",
+ "origin_label": "Origine",
+ "labels_label": "Labels",
+ "select_variant": "Sélectionnez la variante exacte ou utilisez les données IA :"
},
- "zerowaste": {
- "card_title": "♻️ Conseils zéro déchet",
- "card_hint": "Pendant la cuisson, affichez des conseils pour réutiliser les déchets produits à chaque étape (épluchures, eau de cuisson, etc.). Désactivé par défaut.",
- "label": "Afficher les conseils pendant la cuisson"
+ "products": {
+ "title": "📦 Tous les produits",
+ "search_placeholder": "🔍 Rechercher un produit...",
+ "empty": "Aucun produit dans la base de données.\nScannez un produit pour commencer !",
+ "no_category": "Aucun produit dans cette catégorie"
},
- "backup": {
- "tab": "Sauvegarde",
- "local_title": "Sauvegarde locale",
- "local_hint": "Instantané quotidien de la base de données. Configurez le nombre de jours de rétention.",
- "enabled": "Activer la sauvegarde automatique quotidienne",
- "retention_days": "Rétention (jours)",
- "retention_info": "Les sauvegardes sont conservées pendant",
- "backup_now": "Sauvegarder maintenant",
- "backing_up": "Sauvegarde en cours…",
- "backed_up": "Sauvegarde terminée",
- "backup_error": "Erreur de sauvegarde",
- "last_backup": "Dernière sauvegarde",
- "no_backup_yet": "Aucune sauvegarde créée",
- "list_empty": "Aucune sauvegarde disponible",
- "restore_btn": "Restaurer",
- "restore_confirm": "Restaurer la sauvegarde",
- "delete_btn": "Supprimer",
- "delete_confirm": "Supprimer la sauvegarde",
- "gdrive_title": "Google Drive",
- "gdrive_hint": "Sauvegardez automatiquement sur Google Drive via OAuth 2.0. Aucune bibliothèque externe requise.",
- "gdrive_enabled": "Activer la sauvegarde Google Drive",
- "gdrive_folder_id": "ID du dossier Drive",
- "gdrive_folder_id_hint": "Copiez l'ID depuis l'URL du dossier Drive : …/folders/
ID",
- "gdrive_retention_days": "Rétention Drive (jours, 0=tout garder)",
- "gdrive_test": "Tester la connexion",
- "gdrive_ok": "Connexion réussie !",
- "gdrive_error": "Échec de la connexion",
- "gdrive_push_now": "Téléverser sur Drive maintenant",
- "gdrive_pushing": "Téléversement en cours…",
- "gdrive_pushed": "Téléversé sur Drive",
- "gdrive_wizard_hint": "Optionnel : sauvegarde quotidienne automatique sur Google Drive via OAuth 2.0.",
- "gdrive_skip": "Passer — configurer plus tard dans Paramètres",
- "gdrive_client_id": "Client ID",
- "gdrive_client_secret": "Client Secret",
- "gdrive_redirect_uri_hint": "Ajoute
http://localhost comme URI de redirection autorisé dans la Google Cloud Console. Fonctionne sur n'importe quel serveur, même sans domaine public.",
- "gdrive_code_title": "Coller l'URL ou le code d'autorisation",
- "gdrive_code_hint": "Après autorisation, le navigateur ouvre http://localhost et peut afficher une erreur de connexion — c'est normal. Copie l'URL dans la barre d'adresse (ex.
http://localhost/?code=4%2F0A...) et colle-la ici.",
- "gdrive_code_submit": "Confirmer",
- "gdrive_code_empty": "Coller d'abord l'URL ou le code d'autorisation",
- "gdrive_redirect_uri_label": "URI de redirection (ajouter dans Google Cloud Console) :",
- "gdrive_oauth_authorize": "Autoriser avec Google",
- "gdrive_oauth_authorized": "Autorisé",
- "gdrive_oauth_not_authorized": "Pas encore autorisé",
- "gdrive_oauth_window_opened": "Fenêtre ouverte — autorisez et revenez ici",
- "gdrive_oauth_how_to": "Configurer OAuth 2.0 (étape par étape)",
- "gdrive_oauth_steps": "
Allez sur console.cloud.google.com et sélectionnez votre projetActivez l’API Google Drive : API et services → Activer les API → Google Drive APIAllez dans API et services → Identifiants → Créer des identifiants → ID client OAuthType d’application : Application Web ; ajoutez l’URL affichée ci-dessous comme URI de redirection autoriséCopiez le Client ID et le Client Secret dans les champs ci-dessus et enregistrezCliquez sur Autoriser avec Google : connectez-vous et accordez l’accèsLa fenêtre se ferme automatiquement une fois terminé et les sauvegardes sont prêtes"
+ "recipes": {
+ "title": "🍳 Recettes",
+ "generate": "✨ Générer une nouvelle recette",
+ "archive_empty": "Aucune recette enregistrée. Générez votre première recette !",
+ "dialog_title": "🍳 Recette",
+ "dialog_desc": "Je vais générer une recette saine en utilisant les ingrédients du garde-manger, en priorisant les produits qui expirent.",
+ "meal_label": "🕐 Quel repas ?",
+ "persons_label": "👥 Pour combien de personnes ?",
+ "meal_type_label": "🎯 Type de repas",
+ "opt_fast": "⚡ Repas rapide",
+ "opt_light": "🥗 Appétit léger",
+ "opt_expiry": "⏰ Prioriser les produits qui expirent",
+ "opt_healthy": "💚 Extra sain",
+ "opt_opened": "📦 Prioriser les produits ouverts",
+ "opt_zero_waste": "♻️ Zéro déchet",
+ "generate_btn": "✨ Générer la recette",
+ "loading_msg": "Préparation de votre recette...",
+ "start_cooking": "👨🍳 Mode cuisine",
+ "regenerate": "🔄 En générer une autre",
+ "close_btn": "✅ Fermer",
+ "ingredients_title": "🧾 Ingrédients",
+ "tools_title": "Matériel nécessaire",
+ "steps_title": "👨🍳 Étapes",
+ "no_steps": "Aucune étape disponible",
+ "generate_error": "Erreur de génération",
+ "persons_short": "pers.",
+ "use_ingredient_title": "Utiliser l'ingrédient",
+ "recipe_qty_label": "Recette",
+ "from_where_label": "Depuis où ?",
+ "amount_label": "Combien",
+ "use_amount_btn": "Utiliser cette quantité",
+ "use_all_btn": "Tout utiliser / Terminé",
+ "packs_label": "Paquets",
+ "quantity_in_total": "Quantité en {unit} (total : {total})",
+ "packs_of_have": "Paquets de {size} (vous en avez {count})",
+ "scale_wait_stable": "Attendez 10s de poids stable pour le remplissage automatique…",
+ "ingredient_scaled_toast": "📦 Ingrédient déduit du garde-manger !",
+ "finished_added_bring_toast": "🛒 Produit terminé → ajouté à Bring !",
+ "load_error": "Erreur de chargement"
},
"shopping": {
- "tab": "Liste de courses",
- "title": "Liste de courses",
- "hint": "Configurez la liste de courses intégrée ou connectez Bring!.",
- "enable_label": "Activer la liste de courses",
- "mode_label": "Fournisseur",
- "mode_internal": "Intégré (sans Bring!)",
- "mode_bring": "Bring! (application externe)",
- "bring_section_title": "Configuration Bring!",
- "ai_section_title": "Assistance IA",
- "smart_suggestions_label": "Suggestions IA",
- "forecast_label": "Prévision des produits bientôt épuisés",
- "auto_add_label": "Ajouter automatiquement quand",
- "auto_add_suffix": "restant en stock (0 = seulement quand épuisé)"
+ "title": "🛒 Liste de courses",
+ "bring_loading": "Connexion à Bring !...",
+ "bring_not_configured": "Bring ! n'est pas configuré. Ajoutez votre e-mail et mot de passe dans les
paramètres.",
+ "tab_to_buy": "🛍️ À acheter",
+ "tab_forecast": "🧠 Prévision",
+ "total_label": "💰 Total estimé",
+ "section_to_buy": "🛍️ À acheter",
+ "suggestions_title": "💡 Suggestions IA",
+ "suggestions_add": "✅ Ajouter la sélection à Bring !",
+ "search_prices": "🔍 Rechercher tous les prix",
+ "suggest_btn": "Suggérer des achats",
+ "smart_title": "🧠 Prévisions intelligentes",
+ "smart_empty": "Aucune prévision disponible.
Ajoutez des produits à votre garde-manger pour recevoir des prévisions intelligentes.",
+ "smart_filter_all": "Tout",
+ "smart_filter_critical": "🔴 Urgent",
+ "smart_filter_high": "🟠 Bientôt",
+ "smart_filter_medium": "🟡 Planifier",
+ "smart_filter_low": "🟢 Prévision",
+ "smart_add": "🛒 Ajouter la sélection à Bring !",
+ "empty": "Liste de courses vide !\nUtilisez le bouton ci-dessous pour générer des suggestions.",
+ "already_in_list": "🛒 \"{name}\" est déjà dans la liste de courses",
+ "already_in_list_short": "ℹ️ Déjà dans la liste de courses",
+ "add_prompt": "Voulez-vous l'ajouter à la liste de courses ?",
+ "smart_already": "📊 Les prévisions achats prédisent déjà {name}",
+ "all_searched": "Tous les produits ont déjà été recherchés. Utilisez 🔄 pour rechercher individuellement.",
+ "search_complete": "Recherche terminée : {count} produits",
+ "removed_sufficient": "🧹 {removed} produit(s) avec stock suffisant retiré(s) de la liste",
+ "suggest_buy": "🛒 Acheter : {qty} {unit}",
+ "suggest_buy_approx": "🛒 Au minimum : {qty} {unit}",
+ "suggest_buy_tip": "Quantité suggérée basée sur vos 14 derniers jours de consommation",
+ "suggest_buy_approx_tip": "Estimation minimale basée sur la consommation (achetez le format le plus proche)",
+ "bring_badge": "🛒 Déjà sur Bring !",
+ "add_urgent_toast": "🔴 {n} produit(s) urgent(s) automatiquement ajouté(s) à Bring !",
+ "migration_done": "✅ {migrated} mis à jour, {skipped} déjà ok",
+ "added_to_bring": "🛒 {n} produits ajoutés à Bring !",
+ "added_to_bring_skip": "{n} déjà présents",
+ "all_on_bring": "Tous les produits étaient déjà sur Bring !",
+ "freq_high": "📈 Fréquent",
+ "freq_regular": "📊 Régulier",
+ "freq_occasional": "📉 Occasionnel",
+ "out_of_stock": "Rupture de stock",
+ "scan_toast": "📷 Scanner : {name}",
+ "empty_category": "Aucun produit dans cette catégorie",
+ "session_empty": "🛒 Aucun produit encore",
+ "urgency_critical": "Urgent",
+ "urgency_high": "Bientôt",
+ "urgency_medium": "Planifier",
+ "urgency_low": "Prévision",
+ "urgency_medium_short": "Moyen",
+ "urgency_low_short": "Ok",
+ "tag_urgent": "🔴 Urgent",
+ "tag_priority": "⭐ Priorité",
+ "tag_check": "✅ Vérifier",
+ "smart_already_predicted": "📊 Les prévisions achats prédisent déjà
{name}{urgency}.",
+ "item_removed": "✅ {name} retiré de la liste !",
+ "urgency_spec_critical": "⚡ Urgent",
+ "urgency_spec_high": "🟠 Bientôt",
+ "bring_add_n": "Ajouter {n} à Bring !",
+ "bring_add_selected": "Ajouter la sélection à Bring !",
+ "bring_adding": "Ajout en cours...",
+ "bring_added_one": "1 produit ajouté à Bring !",
+ "bring_added_many": "{n} produits ajoutés à Bring !",
+ "bring_skipped": "({n} déjà dans la liste)",
+ "force_sync": "Forcer la synchronisation Bring !",
+ "scan_target_label": "Vous cherchez",
+ "scan_target_found": "Trouvé ! Retirer de la liste",
+ "bring_add_one": "Ajouter 1 produit à Bring !",
+ "bring_add_many": "Ajouter {n} produits à Bring !",
+ "syncing": "Synchronisation…",
+ "sync_done": "Synchronisation terminée",
+ "price_searching": "Recherche en cours...",
+ "search_action": "Rechercher",
+ "open_action": "Ouvrir",
+ "not_found": "Non trouvé",
+ "search_price": "Rechercher le prix",
+ "tap_to_scan": "Appuyez pour scanner",
+ "tag_title": "Tag",
+ "remove_title": "Retirer",
+ "found_count": "{found}/{total} produits trouvés",
+ "savings_offers": "· 🏷️ Vous économisez {amount}€ avec les offres",
+ "searching_progress": "Recherche {current}/{total}...",
+ "remove_error": "Erreur de suppression",
+ "btn_fetch_prices": "Trouver les prix",
+ "price_total_label": "💰 Total estimé :",
+ "price_loading": "Recherche des prix…",
+ "price_not_found": "prix n/d",
+ "suggest_loading": "Analyse en cours...",
+ "suggest_error": "Erreur de génération des suggestions",
+ "priority_high": "Élevée",
+ "priority_medium": "Moyenne",
+ "priority_low": "Faible",
+ "smart_last_update": "Mis à jour {time}",
+ "names_already_updated": "Tous les noms sont déjà à jour"
},
- "ha": {
- "tab": "Home Assistant",
- "title": "Home Assistant",
- "hint": "Connectez EverShelf à Home Assistant pour les automations, les notifications push et les capteurs REST.",
- "enabled": "Activer l'intégration Home Assistant",
- "connection_title": "Connexion",
- "url_label": "URL Home Assistant",
- "url_placeholder": "http://192.168.1.50:8123",
- "url_hint": "URL de base de votre instance Home Assistant.",
- "token_label": "Jeton d'accès longue durée",
- "token_hint": "Générez depuis Profil HA → Sécurité → Jetons d'accès longue durée.",
- "token_placeholder": "eyJhbGci...",
- "token_saved": "Jeton enregistré (masqué pour des raisons de sécurité)",
- "test_btn": "Tester la connexion",
- "test_ok": "Connecté à {version}",
- "test_fail": "Connexion échouée : {error}",
- "test_bad_token": "HA accessible mais le jeton est invalide",
- "testing": "Test en cours…",
- "error_no_url": "Veuillez d'abord saisir l'URL de Home Assistant.",
- "tts_title": "TTS sur enceinte connectée",
- "tts_hint": "Lisez les étapes de recette sur un media player Home Assistant.",
- "tts_entity_label": "Entity ID du lecteur multimédia",
- "tts_entity_placeholder": "media_player.salon",
- "tts_entity_hint": "Entity ID du lecteur multimédia HA. Disponible dans HA : Outils développeur → États.",
- "tts_platform_label": "Plateforme TTS",
- "tts_platform_speak": "tts.speak (recommandé)",
- "tts_platform_notify": "notify.* (service de notification)",
- "tts_apply_btn": "Appliquer le preset HA à l'onglet TTS",
- "tts_apply_hint": "Pré-remplit l'onglet TTS avec l'URL et le jeton de Home Assistant.",
- "tts_preset_applied": "Preset HA appliqué à l'onglet TTS.",
- "webhook_title": "Automations Webhook",
- "webhook_hint": "Envoyez des données à Home Assistant lors d'événements dans le garde-manger.",
- "webhook_id_label": "ID Webhook",
- "webhook_id_placeholder": "evershelf_webhook_abc123",
- "webhook_id_hint": "ID du webhook créé dans HA. Copiez depuis : HA → Paramètres → Automations → Créer → Déclencheur Webhook.",
- "webhook_events_label": "Notifier pour ces événements",
- "event_expiry": "Produits expirant bientôt (quotidien)",
- "event_shopping": "Article ajouté à la liste de courses",
- "event_stock": "Niveau de stock mis à jour",
- "expiry_days_label": "Préavis d'expiration (jours)",
- "expiry_days_hint": "Envoyer l'alerte d'expiration N jours avant la date d'expiration.",
- "webhook_help": "Dans HA : Paramètres → Automations → Créer → Déclencheur : Webhook → copier l'ID généré.",
- "notify_title": "Notifications push",
- "notify_hint": "Envoyez des notifications push sur votre téléphone via un service notify de Home Assistant.",
- "notify_service_label": "Service notify",
- "notify_service_placeholder": "notify.mobile_app_mon_telephone",
- "notify_service_hint": "Nom du service notify HA. Laissez vide pour désactiver.",
- "sensor_title": "Capteurs REST",
- "sensor_hint": "Ajoutez à configuration.yaml pour créer des capteurs EverShelf dans Home Assistant.",
- "sensor_copy_btn": "Copier le YAML",
- "sensor_copied": "YAML copié dans le presse-papiers !",
- "save_btn": "Enregistrer les paramètres HA",
- "ha_hint": "Si vous utilisez Home Assistant, utilisez l'onglet Home Assistant pour configurer TTS, webhooks et capteurs."
+ "ai": {
+ "title": "🤖 Identification IA",
+ "capture": "📸 Prendre une photo",
+ "retake": "🔄 Reprendre",
+ "hint": "Prenez une photo du produit et l'IA essaiera de l'identifier",
+ "identifying": "🤖 Identification du produit...",
+ "no_api_key": "⚠️ Clé API Gemini non configurée.\n
Ajoutez GEMINI_API_KEY au fichier .env sur le serveur.",
+ "fields_filled": "✅ Champs remplis par l'IA",
+ "use_data": "✅ Utiliser les données IA",
+ "use_data_no_barcode": "✅ Utiliser les données IA (sans code-barres)"
+ },
+ "log": {
+ "title": "📒 Journal des opérations",
+ "type_added": "Ajouté",
+ "type_waste": "Jeté",
+ "type_used": "Utilisé",
+ "type_bring": "Ajouté à Bring !",
+ "undone_badge": "Annulé",
+ "undo_title": "Annuler cette opération",
+ "load_error": "Erreur de chargement du journal",
+ "empty": "Aucune opération enregistrée.",
+ "undo_action_remove": "suppression de",
+ "undo_action_restore": "réapprovisionnement de",
+ "undo_confirm": "Annuler cette opération ?\n→ {action} {name}",
+ "undo_success": "↩ Opération annulée pour {name}",
+ "already_undone": "Opération déjà annulée",
+ "too_old": "Impossible d'annuler des opérations de plus de 24 heures",
+ "undo_error": "Erreur lors de l'annulation",
+ "recipe_prefix": "Recette"
+ },
+ "chat": {
+ "title": "Chef Gemini",
+ "welcome": "Bonjour ! Je suis votre assistant cuisine",
+ "welcome_desc": "Demandez-moi de préparer un jus, un snack, un plat rapide… Je connais votre garde-manger, vos appareils et vos préférences !",
+ "suggestion_snack": "🍿 Snack rapide",
+ "suggestion_juice": "🥤 Jus/Smoothie",
+ "suggestion_light": "🥗 Quelque chose de léger",
+ "suggestion_expiry": "⏰ Utiliser les produits qui expirent",
+ "clear": "Nouvelle conversation",
+ "placeholder": "Demandez quelque chose...",
+ "cleared": "Chat effacé",
+ "suggestion_snack_text": "Que puis-je préparer comme snack rapide ?",
+ "suggestion_juice_text": "Faites-moi un jus ou un smoothie avec ce que j'ai",
+ "suggestion_light_text": "J'ai faim mais je veux quelque chose de léger",
+ "suggestion_expiry_text": "Qu'est-ce qui va bientôt expirer et comment l'utiliser ?",
+ "transfer_to_recipes": "Transférer aux recettes",
+ "transferring": "Transfert en cours...",
+ "transferred": "Ajouté aux recettes !",
+ "open_recipe": "Ouvrir la recette",
+ "quick_recipe_prompt": "Suggérez une recette rapide POUR UNE PERSONNE en utilisant les produits qui expirent en premier ! Ignorez les congélateurs, concentrez-vous sur le réfrigérateur et le garde-manger."
+ },
+ "cooking": {
+ "close": "Fermer",
+ "tts_btn": "Lire à voix haute",
+ "restart": "↺ Recommencer",
+ "replay": "🔊 Rejouer",
+ "timer": "⏱️ {time} · Minuterie",
+ "prev": "◀ Précédent",
+ "next": "Suivant ▶",
+ "ingredient_used": "✔️ Déduit",
+ "ingredient_use_btn": "📦 Utiliser",
+ "ingredient_deduct_title": "Déduire du garde-manger",
+ "timer_expired_tts": "Minuterie {label} terminée !",
+ "timer_warning_tts": "Attention ! {label} : 10 secondes restantes !",
+ "recipe_done_tts": "Recette terminée ! Bon appétit !",
+ "expires_chip": "exp. {date}",
+ "finish": "✅ Terminer",
+ "step_fallback": "Étape {n}",
+ "zerowaste_label": "♻️ Déchet",
+ "zerowaste_tip_title": "Conseil zéro déchet"
+ },
+ "settings": {
+ "title": "⚙️ Paramètres",
+ "tab_api": "Clés API",
+ "tab_bring": "Bring !",
+ "tab_recipe": "Recettes",
+ "tab_mealplan": "Plan hebdomadaire",
+ "tab_appliances": "Appareils",
+ "tab_spesa": "Courses en ligne",
+ "tab_camera": "Caméra",
+ "tab_security": "Sécurité",
+ "tab_tts": "Voix (TTS)",
+ "tab_language": "Langue",
+ "tab_scale": "Balance connectée",
+ "gemini": {
+ "title": "🤖 Google Gemini IA",
+ "hint": "Clé API pour l'identification des produits, les dates de péremption et les recettes.",
+ "key_label": "Clé API Gemini"
+ },
+ "bring": {
+ "title": "🛒 Liste de courses Bring !",
+ "hint": "Identifiants pour l'intégration de la liste de courses Bring !",
+ "email_label": "📧 E-mail Bring !",
+ "password_label": "🔒 Mot de passe Bring !"
+ },
+ "price": {
+ "title": "💰 Estimation des prix (IA)",
+ "hint": "Afficher le coût estimé par produit dans la liste de courses à l'aide de l'IA.",
+ "enabled_label": "Activer l'estimation des prix",
+ "country_label": "🌍 Pays de référence",
+ "currency_label": "💱 Devise",
+ "update_label": "🔄 Actualiser les prix tous les",
+ "update_suffix": "mois"
+ },
+ "recipe": {
+ "title": "🍳 Préférences de recettes",
+ "hint": "Configurez les options par défaut pour la génération de recettes.",
+ "persons_label": "👥 Portions par défaut",
+ "options_label": "🎯 Options de recette par défaut",
+ "fast": "⚡ Repas rapide",
+ "light": "🥗 Repas léger",
+ "expiry": "⏰ Priorité péremption",
+ "healthy": "💚 Extra sain",
+ "opened": "📦 Priorité produits ouverts",
+ "zerowaste": "♻️ Zéro déchet",
+ "dietary_label": "🚫 Intolérances / Restrictions",
+ "dietary_placeholder": "Ex. : sans gluten, sans lactose, végétarien..."
+ },
+ "mealplan": {
+ "title": "📅 Plan de repas hebdomadaire",
+ "hint": "Définissez le type de repas pour chaque jour. Il sera utilisé comme guide pour la génération de recettes.",
+ "enabled": "✅ Activer le plan hebdomadaire",
+ "legend": "🌤️ = Déjeuner · 🌙 = Dîner · Appuyez sur un badge pour le modifier.",
+ "types_title": "📋 Types disponibles",
+ "reset_btn": "↺ Restaurer les valeurs par défaut"
+ },
+ "appliances": {
+ "title": "🔌 Appareils disponibles",
+ "hint": "Indiquez les appareils que vous possédez. Ils seront pris en compte lors de la génération de recettes.",
+ "new_placeholder": "Ex. : Machine à pain, Thermomix, Friteuse à air...",
+ "quick_title": "Ajout rapide :",
+ "oven": "🔥 Four",
+ "microwave": "📡 Micro-ondes",
+ "air_fryer": "🍟 Friteuse à air",
+ "bread_maker": "🍞 Machine à pain",
+ "bimby": "🤖 Thermomix/Cookeo",
+ "mixer": "🌀 Robot pâtissier",
+ "steamer": "♨️ Cuiseur vapeur",
+ "pressure_cooker": "🫕 Cocotte-minute",
+ "toaster": "🍞 Grille-pain",
+ "blender": "🍹 Mixeur",
+ "empty": "Aucun appareil ajouté"
+ },
+ "spesa": {
+ "title": "🛍️ Courses en ligne",
+ "hint": "Configurez le fournisseur de courses en ligne.",
+ "provider_label": "🏪 Fournisseur",
+ "email_label": "📧 E-mail",
+ "password_label": "🔒 Mot de passe",
+ "login_btn": "🔐 Connexion",
+ "ai_prompt_label": "🤖 Prompt IA de sélection de produit",
+ "ai_prompt_placeholder": "Instructions pour l'IA lors du choix entre plusieurs produits...",
+ "ai_prompt_hint": "L'IA utilise ce prompt pour choisir le produit le plus approprié parmi les résultats. Laissez vide pour le comportement par défaut.",
+ "configure_first": "Configurez d'abord les courses en ligne dans les paramètres",
+ "missing_credentials": "Entrez l'e-mail et le mot de passe",
+ "login_in_progress": "Connexion en cours...",
+ "login_error_prefix": "Erreur :",
+ "login_network_error_prefix": "Erreur réseau :",
+ "login_success_default": "Connexion réussie !",
+ "result_name_label": "Nom",
+ "result_card_label": "Carte",
+ "result_pickup_label": "Point de retrait",
+ "result_points_label": "Points fidélité",
+ "connected_relogin": "✅ Connecté — Se reconnecter",
+ "connected_as": "Connecté en tant que {name}"
+ },
+ "camera": {
+ "title": "📷 Caméra",
+ "hint": "Choisissez la caméra à utiliser pour le scan de code-barres et l'identification IA.",
+ "device_label": "📸 Caméra par défaut",
+ "back": "📱 Arrière (par défaut)",
+ "front": "🤳 Frontale",
+ "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"
+ },
+ "security": {
+ "title": "🔒 Certificat HTTPS",
+ "hint": "Si le navigateur affiche l'erreur « Votre connexion n'est pas privée » (ERR_CERT_AUTHORITY_INVALID), vous devez installer le certificat CA sur l'appareil.",
+ "download_btn": "📥 Télécharger le certificat CA",
+ "token_title": "🔑 Token de paramètres",
+ "token_label": "Token d'accès",
+ "token_hint": "Si `SETTINGS_TOKEN` est configuré dans le `.env` du serveur, entrez le token ici avant de sauvegarder les paramètres. Laissez vide si non configuré.",
+ "token_placeholder": "(vide = pas de protection)",
+ "token_required_hint": "🔒 Ce serveur nécessite un token pour sauvegarder les paramètres.",
+ "cert_instructions": "
Instructions pour Chrome (Android) :1. Téléchargez le certificat ci-dessus
2. Allez dans
Paramètres → Sécurité & Confidentialité → Plus de paramètres de sécurité → Installer depuis le stockage3. Sélectionnez le fichier
EverShelf_CA.crt téléchargé
4. Choisissez « CA » et confirmez
5. Redémarrez Chrome
Instructions pour Chrome (PC) :1. Téléchargez le certificat ci-dessus
2. Allez dans
chrome://settings/certificates3. Onglet « Autorités » → Importer → sélectionnez le fichier
4. Cochez « Approuver ce certificat pour identifier les sites web »
5. Redémarrez Chrome"
+ },
+ "tts": {
+ "title": "🔊 Voix & TTS",
+ "hint": "Configurez la synthèse vocale via une API REST externe. Les étapes de recette et les minuteries expirées seront envoyées à l'endpoint configuré.",
+ "enabled": "✅ Activer la TTS",
+ "engine_label": "⚙️ Moteur TTS",
+ "engine_browser": "🔇 Navigateur (hors ligne, aucune configuration requise)",
+ "engine_server": "🌐 Serveur externe (Home Assistant, API REST...)",
+ "voice_label": "🗣️ Voix",
+ "rate_label": "⚡ Vitesse",
+ "pitch_label": "🎵 Tonalité",
+ "url_label": "🌐 URL de l'endpoint",
+ "method_label": "📡 Méthode HTTP",
+ "auth_label": "🔐 Authentification",
+ "auth_bearer": "Bearer Token",
+ "auth_custom": "En-tête personnalisé",
+ "auth_none": "Aucune",
+ "token_label": "🔑 Bearer Token",
+ "custom_header_name": "📋 Nom de l'en-tête",
+ "custom_header_value": "📋 Valeur de l'en-tête",
+ "content_type_label": "📄 Content-Type",
+ "payload_key_label": "🗝️ Champ texte dans le payload",
+ "payload_key_hint": "Nom du champ JSON qui contiendra le texte à lire (ex. : message, text).",
+ "extra_fields_label": "➕ Champs supplémentaires (JSON)",
+ "extra_fields_placeholder": "{\"entity_id\": \"media_player.salon\"}",
+ "extra_fields_hint": "Champs supplémentaires à inclure dans le payload, en format JSON. Laissez vide si non nécessaire.",
+ "test_btn": "🔊 Envoyer une voix de test",
+ "voices_loading": "Chargement des voix…",
+ "voice_not_supported": "Voix non supportée par ce navigateur",
+ "voices_none": "Aucune voix disponible sur cet appareil",
+ "voices_hint": "Les voix disponibles dépendent du système d'exploitation et du navigateur. Appuyez sur ↺ si la liste ne se charge pas.",
+ "url_missing": "⚠️ URL de l'endpoint manquante.",
+ "test_sending": "⏳ Envoi…",
+ "test_ok": "✅ Réponse {code} — vérifiez que le haut-parleur a parlé."
+ },
+ "language": {
+ "title": "🌐 Langue",
+ "hint": "Sélectionnez la langue de l'interface.",
+ "label": "🌐 Langue",
+ "restart_notice": "La page sera rechargée pour appliquer la nouvelle langue."
+ },
+ "screensaver": {
+ "label": "Activer l'économiseur d'écran",
+ "card_title": "🌙 Économiseur d'écran",
+ "card_hint": "Affiche une horloge avec des informations utiles après 5 minutes d'inactivité. Désactivé par défaut.",
+ "timeout_1": "1 minute",
+ "timeout_2": "2 minutes",
+ "timeout_5": "5 minutes",
+ "timeout_10": "10 minutes",
+ "timeout_15": "15 minutes",
+ "timeout_30": "30 minutes",
+ "timeout_60": "1 heure",
+ "start_after": "⏱️ Démarrer après"
+ },
+ "scale": {
+ "title": "⚖️ Balance connectée",
+ "hint": "Connectez une balance Bluetooth via la passerelle Android pour lire automatiquement le poids.",
+ "tab": "Balance connectée",
+ "enabled": "✅ Activer la balance connectée",
+ "url_label": "🌐 URL de la passerelle WebSocket",
+ "url_placeholder": "ws://192.168.1.x:8765",
+ "url_hint": "URL affichée par l'application Android (même réseau Wi-Fi). Ex. :",
+ "test_btn": "🔗 Tester la connexion",
+ "download_btn": "📥 Télécharger la passerelle Android (APK)",
+ "download_hint": "Application Android qui connecte votre balance BLE et EverShelf.",
+ "download_sub": "Source : evershelf-scale-gateway/ dans la racine du projet",
+ "live_weight": "poids en temps réel",
+ "auto_reconnect": "🔁 Reconnexion : automatique",
+ "kiosk_title": "📡 Balance BLE intégrée dans le kiosque",
+ "kiosk_hint": "La balance est directement gérée par la passerelle BLE interne du kiosque. Pour associer un nouvel appareil, utilisez l'assistant de configuration.",
+ "kiosk_reconfigure": "🔄 Reconfigurer la balance BLE",
+ "ble_protocols": "
🔌 Protocoles BLE supportés :
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — poids, graisse, IMC
- Xiaomi Mi Body Composition Scale 2
- Générique — heuristique automatique pour 100+ modèles
"
+ },
+ "kiosk": {
+ "hint": "Transformez une tablette Android en panneau EverShelf permanent avec passerelle BLE intégrée.",
+ "download_btn": "📥 Télécharger EverShelf Kiosk (APK)",
+ "download_sub": "Mode kiosque plein écran + passerelle de balance intégrée. Source : evershelf-kiosk/",
+ "native_title": "Configuration du kiosque",
+ "native_hint": "URL du serveur, balance BLE, économiseur d'écran et assistant de configuration.",
+ "native_btn": "Ouvrir la configuration du kiosque",
+ "native_tap_hint": "Appuyez sur le bouton engrenage en haut à droite",
+ "native_update_hint": "Mettez à jour l'application kiosque pour utiliser cette fonctionnalité",
+ "update_title": "Mise à jour du kiosque",
+ "check_updates_btn": "🔍 Vérifier les mises à jour",
+ "needs_update": "⚠️ Le kiosque installé ne supporte pas cette fonctionnalité. Mettez à jour l'application kiosque pour l'activer."
+ },
+ "saved": "✅ Configuration enregistrée !",
+ "saved_local": "✅ Configuration enregistrée localement",
+ "saved_local_error": "⚠️ Enregistré localement, erreur serveur : {error}",
+ "theme": {
+ "title": "🌙 Apparence",
+ "hint": "Choisissez le thème de l'interface.",
+ "label": "🌙 Thème",
+ "off": "☀️ Clair",
+ "on": "🌙 Sombre",
+ "auto": "🔄 Automatique (système)"
+ },
+ "zerowaste": {
+ "card_title": "♻️ Conseils zéro déchet",
+ "card_hint": "Pendant la cuisson, affichez des conseils pour réutiliser les déchets produits à chaque étape (épluchures, eau de cuisson, etc.). Désactivé par défaut.",
+ "label": "Afficher les conseils pendant la cuisson"
+ },
+ "backup": {
+ "tab": "Sauvegarde",
+ "local_title": "Sauvegarde locale",
+ "local_hint": "Instantané quotidien de la base de données. Configurez le nombre de jours de rétention.",
+ "enabled": "Activer la sauvegarde automatique quotidienne",
+ "retention_days": "Rétention (jours)",
+ "retention_info": "Les sauvegardes sont conservées pendant",
+ "backup_now": "Sauvegarder maintenant",
+ "backing_up": "Sauvegarde en cours…",
+ "backed_up": "Sauvegarde terminée",
+ "backup_error": "Erreur de sauvegarde",
+ "last_backup": "Dernière sauvegarde",
+ "no_backup_yet": "Aucune sauvegarde créée",
+ "list_empty": "Aucune sauvegarde disponible",
+ "restore_btn": "Restaurer",
+ "restore_confirm": "Restaurer la sauvegarde",
+ "delete_btn": "Supprimer",
+ "delete_confirm": "Supprimer la sauvegarde",
+ "gdrive_title": "Google Drive",
+ "gdrive_hint": "Sauvegardez automatiquement sur Google Drive via OAuth 2.0. Aucune bibliothèque externe requise.",
+ "gdrive_enabled": "Activer la sauvegarde Google Drive",
+ "gdrive_folder_id": "ID du dossier Drive",
+ "gdrive_folder_id_hint": "Copiez l'ID depuis l'URL du dossier Drive : …/folders/
ID",
+ "gdrive_retention_days": "Rétention Drive (jours, 0=tout garder)",
+ "gdrive_test": "Tester la connexion",
+ "gdrive_ok": "Connexion réussie !",
+ "gdrive_error": "Échec de la connexion",
+ "gdrive_push_now": "Téléverser sur Drive maintenant",
+ "gdrive_pushing": "Téléversement en cours…",
+ "gdrive_pushed": "Téléversé sur Drive",
+ "gdrive_wizard_hint": "Optionnel : sauvegarde quotidienne automatique sur Google Drive via OAuth 2.0.",
+ "gdrive_skip": "Passer — configurer plus tard dans Paramètres",
+ "gdrive_client_id": "Client ID",
+ "gdrive_client_secret": "Client Secret",
+ "gdrive_redirect_uri_hint": "Ajoute
http://localhost comme URI de redirection autorisé dans la Google Cloud Console. Fonctionne sur n'importe quel serveur, même sans domaine public.",
+ "gdrive_code_title": "Coller l'URL ou le code d'autorisation",
+ "gdrive_code_hint": "Après autorisation, le navigateur ouvre http://localhost et peut afficher une erreur de connexion — c'est normal. Copie l'URL dans la barre d'adresse (ex.
http://localhost/?code=4%2F0A...) et colle-la ici.",
+ "gdrive_code_submit": "Confirmer",
+ "gdrive_code_empty": "Coller d'abord l'URL ou le code d'autorisation",
+ "gdrive_redirect_uri_label": "URI de redirection (ajouter dans Google Cloud Console) :",
+ "gdrive_oauth_authorize": "Autoriser avec Google",
+ "gdrive_oauth_authorized": "Autorisé",
+ "gdrive_oauth_not_authorized": "Pas encore autorisé",
+ "gdrive_oauth_window_opened": "Fenêtre ouverte — autorisez et revenez ici",
+ "gdrive_oauth_how_to": "Configurer OAuth 2.0 (étape par étape)",
+ "gdrive_oauth_steps": "
Allez sur console.cloud.google.com et sélectionnez votre projetActivez l’API Google Drive : API et services → Activer les API → Google Drive APIAllez dans API et services → Identifiants → Créer des identifiants → ID client OAuthType d’application : Application Web ; ajoutez l’URL affichée ci-dessous comme URI de redirection autoriséCopiez le Client ID et le Client Secret dans les champs ci-dessus et enregistrezCliquez sur Autoriser avec Google : connectez-vous et accordez l’accèsLa fenêtre se ferme automatiquement une fois terminé et les sauvegardes sont prêtes"
+ },
+ "shopping": {
+ "tab": "Liste de courses",
+ "title": "Liste de courses",
+ "hint": "Configurez la liste de courses intégrée ou connectez Bring!.",
+ "enable_label": "Activer la liste de courses",
+ "mode_label": "Fournisseur",
+ "mode_internal": "Intégré (sans Bring!)",
+ "mode_bring": "Bring! (application externe)",
+ "bring_section_title": "Configuration Bring!",
+ "ai_section_title": "Assistance IA",
+ "smart_suggestions_label": "Suggestions IA",
+ "forecast_label": "Prévision des produits bientôt épuisés",
+ "auto_add_label": "Ajouter automatiquement quand",
+ "auto_add_suffix": "restant en stock (0 = seulement quand épuisé)"
+ },
+ "ha": {
+ "tab": "Home Assistant",
+ "title": "Home Assistant",
+ "hint": "Connectez EverShelf à Home Assistant pour les automations, les notifications push et les capteurs REST.",
+ "enabled": "Activer l'intégration Home Assistant",
+ "connection_title": "Connexion",
+ "url_label": "URL Home Assistant",
+ "url_placeholder": "http://192.168.1.50:8123",
+ "url_hint": "URL de base de votre instance Home Assistant.",
+ "token_label": "Jeton d'accès longue durée",
+ "token_hint": "Générez depuis Profil HA → Sécurité → Jetons d'accès longue durée.",
+ "token_placeholder": "eyJhbGci...",
+ "token_saved": "Jeton enregistré (masqué pour des raisons de sécurité)",
+ "test_btn": "Tester la connexion",
+ "test_ok": "Connecté à {version}",
+ "test_fail": "Connexion échouée : {error}",
+ "test_bad_token": "HA accessible mais le jeton est invalide",
+ "testing": "Test en cours…",
+ "error_no_url": "Veuillez d'abord saisir l'URL de Home Assistant.",
+ "tts_title": "TTS sur enceinte connectée",
+ "tts_hint": "Lisez les étapes de recette sur un media player Home Assistant.",
+ "tts_entity_label": "Entity ID du lecteur multimédia",
+ "tts_entity_placeholder": "media_player.salon",
+ "tts_entity_hint": "Entity ID du lecteur multimédia HA. Disponible dans HA : Outils développeur → États.",
+ "tts_platform_label": "Plateforme TTS",
+ "tts_platform_speak": "tts.speak (recommandé)",
+ "tts_platform_notify": "notify.* (service de notification)",
+ "tts_apply_btn": "Appliquer le preset HA à l'onglet TTS",
+ "tts_apply_hint": "Pré-remplit l'onglet TTS avec l'URL et le jeton de Home Assistant.",
+ "tts_preset_applied": "Preset HA appliqué à l'onglet TTS.",
+ "webhook_title": "Automations Webhook",
+ "webhook_hint": "Envoyez des données à Home Assistant lors d'événements dans le garde-manger.",
+ "webhook_id_label": "ID Webhook",
+ "webhook_id_placeholder": "evershelf_webhook_abc123",
+ "webhook_id_hint": "ID du webhook créé dans HA. Copiez depuis : HA → Paramètres → Automations → Créer → Déclencheur Webhook.",
+ "webhook_events_label": "Notifier pour ces événements",
+ "event_expiry": "Produits expirant bientôt (quotidien)",
+ "event_shopping": "Article ajouté à la liste de courses",
+ "event_stock": "Niveau de stock mis à jour",
+ "expiry_days_label": "Préavis d'expiration (jours)",
+ "expiry_days_hint": "Envoyer l'alerte d'expiration N jours avant la date d'expiration.",
+ "webhook_help": "Dans HA : Paramètres → Automations → Créer → Déclencheur : Webhook → copier l'ID généré.",
+ "notify_title": "Notifications push",
+ "notify_hint": "Envoyez des notifications push sur votre téléphone via un service notify de Home Assistant.",
+ "notify_service_label": "Service notify",
+ "notify_service_placeholder": "notify.mobile_app_mon_telephone",
+ "notify_service_hint": "Nom du service notify HA. Laissez vide pour désactiver.",
+ "sensor_title": "Capteurs REST",
+ "sensor_hint": "Ajoutez à configuration.yaml pour créer des capteurs EverShelf dans Home Assistant.",
+ "sensor_copy_btn": "Copier le YAML",
+ "sensor_copied": "YAML copié dans le presse-papiers !",
+ "save_btn": "Enregistrer les paramètres HA",
+ "ha_hint": "Si vous utilisez Home Assistant, utilisez l'onglet Home Assistant pour configurer TTS, webhooks et capteurs."
+ }
+ },
+ "expiry": {
+ "today": "AUJOURD'HUI",
+ "tomorrow": "Demain",
+ "days": "{days} jours",
+ "expired_days": "il y a {days}j",
+ "expired_yesterday": "Hier",
+ "expired_today": "Aujourd'hui",
+ "badge_today": "⚠️ Expire aujourd'hui !",
+ "badge_tomorrow": "⏰ Demain",
+ "badge_tomorrow_long": "⏰ Expire demain",
+ "badge_days": "⏰ {n} jours",
+ "badge_expired_ago": "⚠️ Périmé il y a {n}j",
+ "badge_expired": "⛔ Périmé !",
+ "badge_stable": "✅ Stable",
+ "badge_expiring_short": "⏰ Exp. dans {n}j",
+ "badge_ok_still": "✅ Encore {n}j",
+ "badge_expires_red": "🔴 Exp. dans {n}j",
+ "badge_expires_yellow": "🟡 Exp. dans {n}j",
+ "badge_expired_bare": "⚠️ Périmé",
+ "badge_expires_warn": "⚠️ Exp. dans {n}j",
+ "badge_days_left": "⏳ ~{n}j restants",
+ "days_approx": "~{n} jours",
+ "weeks_approx": "~{n} semaines",
+ "months_approx": "~{n} mois",
+ "years_approx": "~{n} ans",
+ "expired_today_long": "Périmé aujourd'hui",
+ "expired_ago_long": "Périmé il y a {n} jours",
+ "expired_suffix": "— Périmé !",
+ "expired_suffix_ok": "— Périmé (encore ok)",
+ "expired_suffix_warning": "— Périmé (vérifier d'abord)",
+ "opened_ago_long": "Ouvert il y a {n} jours",
+ "opened_today_long": "Ouvert aujourd'hui",
+ "opened_suffix": "— Ouvert depuis trop longtemps !",
+ "opened_suffix_ok": "— Ouvert (encore ok)",
+ "opened_suffix_warning": "— Ouvert (vérifier d'abord)",
+ "days_compact": "{n}j",
+ "badge_check_soon": "Vérifier prochainement"
+ },
+ "status": {
+ "ok": "OK",
+ "check": "Vérifier",
+ "discard": "Jeter",
+ "tip_freezer_ok": "Au congélateur : encore sûr (~{n}j de marge)",
+ "tip_freezer_check": "Au congélateur depuis longtemps, peut avoir perdu en qualité. Consommer rapidement",
+ "tip_freezer_danger": "Au congélateur trop longtemps, risque de brûlure de congélation et de dégradation",
+ "tip_highRisk_check": "Périmé récemment, vérifiez l'odeur et l'aspect avant de consommer",
+ "tip_highRisk_danger": "Produit périssable périmé : jeter pour la sécurité",
+ "tip_medRisk_check1": "Vérifiez l'aspect et l'odeur avant de consommer",
+ "tip_medRisk_check2": "Périmé depuis un moment, vérifiez soigneusement avant utilisation",
+ "tip_medRisk_danger": "Trop longtemps depuis la péremption, mieux vaut jeter",
+ "tip_lowRisk_ok": "Produit longue conservation, encore sûr à consommer",
+ "tip_lowRisk_check": "Périmé depuis plus d'un mois, vérifiez l'intégrité de l'emballage",
+ "tip_lowRisk_danger": "Périmé depuis trop longtemps, mieux vaut ne pas risquer"
+ },
+ "toast": {
+ "product_saved": "Produit enregistré !",
+ "product_created": "Produit créé !",
+ "product_updated": "✅ Produit mis à jour !",
+ "product_removed": "Produit supprimé",
+ "updated": "Mis à jour !",
+ "quantity_confirmed": "✓ Quantité confirmée",
+ "added_to_inventory": "✅ {name} ajouté !",
+ "removed_from_list": "✅ {name} retiré de la liste !",
+ "removed_from_list_short": "Retiré de la liste",
+ "added_to_shopping": "🛒 Ajouté à la liste de courses !",
+ "removed_from_shopping": "🛒 Retiré de la liste de courses",
+ "finished_to_bring": "🛒 Produit terminé → ajouté à Bring !",
+ "thrown_away": "🗑️ {name} jeté !",
+ "thrown_away_partial": "🗑️ {qty} {unit} de {name} jeté(s)",
+ "finished_all": "📤 {name} terminé !",
+ "product_finished_confirmed": "✅ Supprimé — ajoutez-le à nouveau lors du réapprovisionnement",
+ "appliance_added": "Appareil ajouté",
+ "item_added": "{name} ajouté"
+ },
+ "antiwaste": {
+ "title": "🌱 Rapport anti-gaspi",
+ "grade_label": "Note",
+ "you": "Vous",
+ "avg_label": "Moy.",
+ "better": "🎉 Vous gaspillez {diff}% de moins que la moyenne {country} !",
+ "worse": "⚠️ Vous gaspillez plus que la moyenne {country}. Des progrès sont possibles !",
+ "on_par": "→ Vous êtes dans la moyenne {country}. Vous pouvez faire mieux !",
+ "saved_money": "~{amount}/mois économisé",
+ "saved_meals": "~{n} repas sauvés",
+ "saved_co2": "{n} kg CO₂ évités",
+ "trend_title": "Tendance (3 derniers mois)",
+ "months_ago_2": "-60 jours",
+ "months_ago_1": "-30 jours",
+ "this_month": "Maintenant",
+ "country_it": "Moy. italienne",
+ "country_de": "Moy. allemande",
+ "country_en": "Moy. américaine",
+ "source": "Sources : REDUCE, Eurostat, USDA 2021",
+ "live_on": "Données en direct",
+ "live_off": "Hors ligne",
+ "meals": "repas",
+ "annual_info": "📅 Vous ~{you} kg/an · moy. ~{avg} kg/an",
+ "badge_rate": "taux de perte",
+ "badge_saved_money": "économisé vs moy.",
+ "badge_wasted": "articles perdus",
+ "badge_better": "moins que la moy."
+ },
+ "error": {
+ "generic": "Erreur",
+ "network": "Erreur réseau",
+ "no_api_key": "Configurez la clé API dans les paramètres",
+ "loading": "Erreur de chargement du produit",
+ "not_found": "Produit introuvable",
+ "not_found_manual": "Produit introuvable. Entrez-le manuellement.",
+ "search": "Erreur de recherche. Réessayez.",
+ "search_short": "Erreur de recherche",
+ "save": "Erreur d'enregistrement",
+ "connection": "Erreur de connexion",
+ "camera": "Impossible d'accéder à la caméra",
+ "bring_add": "Erreur d'ajout à Bring !",
+ "bring_connection": "Erreur de connexion à Bring !",
+ "identification": "Erreur d'identification",
+ "ai_quota": "Quota IA épuisé. Réessayez dans quelques minutes.",
+ "barcode_empty": "Entrez un code-barres",
+ "barcode_format": "Le code-barres ne doit contenir que des chiffres (4-14 chiffres)",
+ "min_chars": "Tapez au moins 2 caractères",
+ "not_in_inventory": "Produit absent de l'inventaire",
+ "appliance_exists": "L'appareil existe déjà",
+ "already_exists": "Existe déjà",
+ "network_retry": "Erreur de connexion. Réessayez.",
+ "select_items": "Sélectionnez au moins un produit",
+ "server_offline": "Connexion au serveur perdue",
+ "server_restored": "Connexion au serveur rétablie",
+ "server_retry": "Réessayer",
+ "unknown": "Erreur inconnue",
+ "prefix": "Erreur",
+ "no_inventory_entry": "Aucune entrée d'inventaire trouvée",
+ "offline_title": "Aucune connexion",
+ "offline_subtitle": "L'app ne peut pas atteindre le serveur. Vérifiez votre connexion Wi-Fi.",
+ "offline_checking": "Vérification de la connexion…",
+ "offline_restored": "Connexion rétablie !",
+ "offline_continue": "Continuer en mode hors ligne",
+ "offline_reading_cache": "Lecture depuis le cache local",
+ "offline_ops_pending": "{n} opérations en attente",
+ "offline_synced": "{n} opérations synchronisées",
+ "offline_ai_disabled": "Indisponible hors ligne",
+ "offline_cache_ready": "Offline — {n} produits en cache"
+ },
+ "confirm": {
+ "remove_item": "Voulez-vous vraiment supprimer ce produit de l'inventaire ?",
+ "kiosk_exit": "Quitter le mode kiosque ?",
+ "cancel": "Annuler",
+ "proceed": "Confirmer"
+ },
+ "location": {
+ "dispensa": "Garde-manger",
+ "frigo": "Réfrigérateur",
+ "freezer": "Congélateur"
+ },
+ "edit": {
+ "title": "Modifier {name}",
+ "unknown_hint": "Entrez le nom du produit et les informations",
+ "label_name": "🏷️ Nom du produit",
+ "choose_location_title": "Quel emplacement ?",
+ "choose_location_hint": "Choisissez l'emplacement à modifier :"
+ },
+ "screensaver": {
+ "recipe_btn": "Recettes",
+ "scan_btn": "Scanner un produit"
+ },
+ "days": {
+ "mon": "Lundi",
+ "tue": "Mardi",
+ "wed": "Mercredi",
+ "thu": "Jeudi",
+ "fri": "Vendredi",
+ "sat": "Samedi",
+ "sun": "Dimanche",
+ "mon_short": "Lun",
+ "tue_short": "Mar",
+ "wed_short": "Mer",
+ "thu_short": "Jeu",
+ "fri_short": "Ven",
+ "sat_short": "Sam",
+ "sun_short": "Dim"
+ },
+ "meal_types": {
+ "lunch": "Déjeuner",
+ "dinner": "Dîner",
+ "colazione": "Petit-déjeuner",
+ "merenda": "Goûter",
+ "dolce": "Dessert",
+ "succo": "Jus de fruits",
+ "pranzo": "Déjeuner",
+ "cena": "Dîner"
+ },
+ "scale": {
+ "status_connected": "Balance connectée",
+ "status_searching": "Passerelle connectée, attente de la balance…",
+ "status_disconnected": "Passerelle de balance inaccessible",
+ "status_error": "Erreur de connexion à la passerelle",
+ "not_connected": "Passerelle de balance non connectée",
+ "read_btn": "⚖️ Lire depuis la balance",
+ "reading_title": "Lecture de la balance",
+ "place_on_scale": "Posez le produit sur la balance…",
+ "waiting_stable": "Le poids sera capturé automatiquement une fois la lecture stable.",
+ "no_url": "Entrez l'URL de la passerelle",
+ "testing": "⏳ Test de connexion…",
+ "connected_ok": "Connexion à la passerelle réussie !",
+ "timeout": "Délai dépassé : pas de réponse de la passerelle",
+ "error_connect": "Impossible de se connecter à la passerelle",
+ "tab": "Balance connectée",
+ "low_weight": "Poids < 10 g · entrez manuellement\n(la lecture automatique nécessite au moins 10 g)",
+ "density_hint": "(densité {density} g/ml)",
+ "ml_hint": "(sera converti en ml)",
+ "weight_detected": "Poids détecté — attendez 10s de stabilité…",
+ "weight_too_low": "Poids trop faible — attente…",
+ "stable": "✓ Stable",
+ "auto_confirm": "✅ {val} {unit} — confirmation automatique dans 5s (appuyez pour annuler)",
+ "cancelled_replace": "Annulé — replacez l'ingrédient sur la balance pour reprendre"
+ },
+ "prediction": {
+ "expected_qty": "Attendu : {expected} {unit}",
+ "actual_qty": "Actuel : {actual} {unit}",
+ "check_suggestion": "Vérifiez ou pesez la quantité restante"
+ },
+ "date": {
+ "today": "📅 Aujourd'hui",
+ "yesterday": "📅 Hier"
+ },
+ "scanner": {
+ "title_barcode": "🔖 Scanner le code-barres",
+ "barcode_hint": "Cadrez le code-barres du produit",
+ "barcode_manual_placeholder": "Ou entrez manuellement...",
+ "barcode_use_btn": "✅ Utiliser ce code",
+ "ai_identifying": "🤖 Identification du produit...",
+ "ai_analyzing": "🤖 Analyse IA en cours...",
+ "product_label_hint": "Cadrez l'étiquette du produit",
+ "expiry_label_hint": "Cadrez la date de péremption imprimée sur le produit",
+ "capture_btn": "📸 Capturer",
+ "capture_photo_btn": "📸 Prendre une photo",
+ "retake_btn": "🔄 Reprendre",
+ "camera_error_hint": "Assurez-vous d'utiliser HTTPS et d'avoir accordé les permissions caméra.
Vous pouvez entrer le code-barres manuellement ou utiliser l'identification IA.",
+ "no_barcode": "Pas de code-barres",
+ "save_new_btn": "🆕 Aucun de ceux-ci — enregistrer comme nouveau"
+ },
+ "lowstock": {
+ "title": "⚠️ Stock faible !",
+ "message": "{name} est presque épuisé — il ne reste que {qty}.",
+ "question": "Voulez-vous l'ajouter à la liste de courses ?",
+ "yes": "🛒 Oui, ajouter à Bring !",
+ "no": "Non, pour l'instant ça va"
+ },
+ "move": {
+ "title": "📦 Déplacer le reste ?",
+ "question": "Voulez-vous déplacer {thing} de {name} vers un autre emplacement ?",
+ "question_short": "Voulez-vous déplacer {thing} vers un autre emplacement ?",
+ "thing_opened": "l'emballage ouvert",
+ "thing_rest": "le reste",
+ "stay_btn": "Non, rester dans {location}",
+ "moved_toast": "📦 Emballage ouvert déplacé vers {location}",
+ "vacuum_restore": "🫙 Restaurer sous vide",
+ "vacuum_seal_rest": "🔒 Mettre le reste sous vide"
+ },
+ "nova": {
+ "1": "Non transformé",
+ "2": "Ingrédient culinaire",
+ "3": "Transformé",
+ "4": "Ultra-transformé"
+ },
+ "meal_plan_types": {
+ "pasta": "Pâtes",
+ "riso": "Riz",
+ "carne": "Viande",
+ "pesce": "Poisson",
+ "legumi": "Légumineuses",
+ "uova": "Œufs",
+ "formaggio": "Fromage",
+ "pizza": "Pizza",
+ "affettati": "Charcuterie",
+ "verdure": "Légumes",
+ "zuppa": "Soupe",
+ "insalata": "Salade",
+ "pane": "Pain/Sandwich",
+ "dolce": "Dessert",
+ "libero": "Libre"
+ },
+ "meal_sub": {
+ "dolce_torta": "Gâteau",
+ "dolce_crema": "Crème / Pudding",
+ "dolce_crumble": "Crumble / Tarte",
+ "dolce_biscotti": "Biscuits / Pâtisseries",
+ "dolce_frutta": "Dessert aux fruits",
+ "succo_dolce": "Sucré / Fruité",
+ "succo_energizzante": "Énergisant",
+ "succo_detox": "Détox / Vert",
+ "succo_rinfrescante": "Rafraîchissant",
+ "succo_vitaminico": "Vitaminé / Agrumes"
+ },
+ "meal_plan": {
+ "reset_success": "Plan hebdomadaire réinitialisé",
+ "not_available": "non disponible dans le garde-manger",
+ "suggested_by": "suggéré par le plan hebdomadaire"
+ },
+ "nutrition": {
+ "title": "🥗 Analyse alimentaire",
+ "score_excellent": "😄 Excellent",
+ "score_good": "🙂 Bien",
+ "score_improve": "😬 Améliorable",
+ "label_health": "🌿 Santé",
+ "label_variety": "🎨 Variété",
+ "label_fresh": "❄️ Frais",
+ "source": "Basé sur {n} produits dans votre garde-manger · EverShelf",
+ "products_count": "produits",
+ "today_title": "🥗 Votre garde-manger aujourd'hui",
+ "products_n": "{n} produits"
+ },
+ "facts": {
+ "greeting_morning": "Bonjour",
+ "greeting_afternoon": "Bon après-midi",
+ "greeting_evening": "Bonsoir",
+ "pantry_waiting": "{greeting} ! Votre garde-manger vous attend.",
+ "expired_one": "Vous avez 1 produit périmé dans votre garde-manger. Vérifiez-le !",
+ "expired_many": "Vous avez {n} produits périmés dans votre garde-manger. Vérifiez-les !",
+ "expired_list": "Produits périmés : {names}",
+ "expired_list_more": "et {n} autres",
+ "freezer_expired_ok": "{name} est périmé, mais étant au congélateur il peut encore être bon ! Vérifiez.",
+ "freezer_expired_old": "{name} au congélateur est périmé depuis trop longtemps. Mieux vaut le jeter.",
+ "fridge_expired_one": "Vous avez 1 produit périmé dans le réfrigérateur !",
+ "fridge_expired_many": "Vous avez {n} produits périmés dans le réfrigérateur !",
+ "expiring_today": "{name} expire aujourd'hui ! Utilisez-le immédiatement.",
+ "expiring_tomorrow": "{name} expire demain. Planifiez !",
+ "expiring_days": "{name} expire dans {days} jours.",
+ "expiring_many": "Vous avez {n} produits qui expirent bientôt.",
+ "expiring_this_week": "{n} produits expirent cette semaine. Planifiez vos repas en conséquence !",
+ "expiring_item_loc": "{name} ({loc}) expire dans {days} {dayslabel}.",
+ "expiring_this_month": "{n} produits expireront ce mois-ci.",
+ "shopping_add": "Ajouter à la liste : {names} 🛒",
+ "shopping_more": "et {n} autres",
+ "shopping_empty": "Liste de courses vide. Tout est en stock ! ✅",
+ "in_fridge": "Dans le réfrigérateur : {name}.",
+ "in_freezer": "Dans le congélateur : {name}. N'oubliez pas !",
+ "top_category": "Catégorie principale : {icon} {cat} avec {n} produits.",
+ "cat_meat": "Vous avez {n} produits carnés. 🥩",
+ "cat_dairy": "Vous avez {n} produits laitiers chez vous. 🥛",
+ "cat_veggies": "Vous avez {n} types de légumes. Super pour la santé ! 🥬",
+ "cat_fruit": "Vous avez {n} types de fruits. 🍎",
+ "cat_drinks": "Vous avez {n} boissons disponibles. 🥤",
+ "cat_frozen": "Vous avez {n} articles surgelés. ❄️",
+ "cat_pasta": "Vous avez {n} types de pâtes. 🍝 Et si on faisait une carbonara ?",
+ "cat_canned": "Vous avez {n} conserves dans le garde-manger. 🥫",
+ "cat_snacks": "Vous avez {n} snacks. Résistez à la tentation ! 🍪",
+ "cat_condiments": "Vous avez {n} condiments disponibles. 🧂",
+ "item_random": "Le saviez-vous ? Vous avez {name} dans {loc}.",
+ "item_qty": "{name} : vous en avez {qty}.",
+ "no_expiry_count": "{n} produits n'ont pas de date de péremption.",
+ "furthest_expiry": "Le produit avec la date de péremption la plus lointaine est {name} : {months} mois.",
+ "high_qty": "Vous avez un beau stock de {name} : {qty} !",
+ "low_qty_item": "{name} est presque épuisé. L'ajouter à votre liste de courses ?",
+ "low_qty_count": "{n} produits sont presque épuisés.",
+ "morning_bread": "Bonjour ! Vous avez du pain pour le petit-déjeuner. 🍞",
+ "morning_milk": "Y a-t-il du lait dans le réfrigérateur pour un cappuccino ? ☕🥛",
+ "morning_fruit": "Bonjour ! Des fruits frais sont un excellent début de journée. 🍎",
+ "noon_pasta": "C'est l'heure du déjeuner… Et si on faisait un bon bol de pâtes ? 🍝",
+ "noon_salad": "Une salade fraîche pour le déjeuner ? Vous avez {n} légumes ! 🥗",
+ "evening_meat": "Pour le dîner, vous pourriez utiliser la viande que vous avez. 🥩",
+ "evening_fish": "Et si on mangeait du poisson ce soir ? 🐟",
+ "evening_expiring": "Vous avez {n} produits qui expirent cette semaine — utilisez-les ce soir !",
+ "night_reminder": "Bonne nuit ! N'oubliez pas d'utiliser demain : {names}.",
+ "weekly_balance": "Bilan hebdomadaire : +{in} ajoutés, −{out} consommés.",
+ "weekly_added": "Vous avez ajouté {n} produits cette semaine.",
+ "weekly_consumed": "Vous avez consommé {n} produits cette semaine. Bravo !",
+ "tip_freezer": "💡 Les produits surgelés durent bien plus longtemps que la date de péremption.",
+ "tip_bread": "💡 Le pain congelé garde sa fraîcheur pendant des semaines.",
+ "tip_fifo": "💡 Pour éviter le gaspillage, utilisez en premier les produits les plus proches de la péremption (FIFO).",
+ "tip_meat": "💡 La viande au congélateur peut se conserver jusqu'à 6 mois en toute sécurité.",
+ "tip_no_refreeze": "💡 Ne jamais recongeler un produit décongelé. Cuisinez-le immédiatement !",
+ "tip_fridge": "💡 Un réfrigérateur bien rangé vous fait gagner du temps et de l'argent.",
+ "tip_canned": "💡 Les conserves ouvertes doivent aller au réfrigérateur et être consommées en quelques jours.",
+ "top_brand": "La marque la plus courante dans votre garde-manger est {brand} avec {n} produits.",
+ "combo_pasta": "Vous avez des pâtes et des condiments : prêt pour un premier plat ! 🍝",
+ "combo_sandwich": "Pain et viande : un sandwich rapide est toujours une bonne idée ! 🥪",
+ "combo_balanced": "Légumes et viande : vous avez tout pour un repas équilibré ! 🥗🥩",
+ "pantry_empty": "Le garde-manger est vide ! Il est temps de faire les courses. 🛒",
+ "pantry_empty_scan": "Aucun produit enregistré. Scannez quelque chose pour commencer !",
+ "location_distribution": "Distribution : {parts}",
+ "day": "jour",
+ "days": "jours"
+ },
+ "kiosk_session": {
+ "first_item": "Premier article : {name} !",
+ "items_two_four": "{n} articles — on démarre 🚀",
+ "items_five_nine": "{n} articles — bon rythme ! 💪",
+ "items_ten_twenty": "{n} articles — presque un record 🏆",
+ "items_twenty_plus": "{n} articles — courses épiques ! 🛒🔥",
+ "duplicates_one": "1 doublon (même article deux fois)",
+ "duplicates_many": "{n} doublons (pris plusieurs fois)",
+ "top_category": "Catégorie principale : {cat} ({count}×)",
+ "items_fallback": "{n} article{plural} ajouté{plural}"
+ },
+ "kiosk": {
+ "check_btn": "🔍 Vérifier les mises à jour",
+ "checking": "⏳ Vérification…",
+ "error_check": "Erreur lors de la vérification des mises à jour",
+ "error_start_install": "Erreur au démarrage de l'installation",
+ "version_installed": "Installé : {v}",
+ "update_available": "⬆️ Nouvelle version disponible :
{latest} (installée : {current})",
+ "up_to_date": "✅ Vous êtes à jour — version
{v}",
+ "too_old": "⚠️ Le kiosque installé est trop ancien pour la vérification automatique des mises à jour.
Appuyez sur le bouton ci-dessous pour télécharger et installer la nouvelle version directement.",
+ "manual_install": "⚠️ Ce kiosque ne supporte pas l'installation automatique.
Procédure manuelle :1. Quittez le kiosque (bouton ✕ en haut à gauche)
2. Désinstallez l'application EverShelf Kiosk
3. Téléchargez et installez le nouvel APK depuis GitHub :",
+ "starting_download": "⏳ Démarrage du téléchargement…",
+ "install_btn": "⬇️ Installer la mise à jour",
+ "exit_title": "Quitter le kiosque",
+ "refresh_title": "Actualiser la page"
+ },
+ "update": {
+ "new_version": "Nouvelle version",
+ "btn": "Mettre à jour"
+ },
+ "gemini": {
+ "chat_title": "Discussion avec Gemini",
+ "not_configured": "🤖 Gemini non configuré — définissez GEMINI_API_KEY dans les paramètres"
+ },
+ "appliances": {
+ "empty": "Aucun appareil ajouté"
+ },
+ "about": {
+ "title": "À propos",
+ "version": "Version",
+ "report_bug": "Signaler un bug",
+ "report_bug_hint": "Quelque chose ne fonctionne pas ? Envoyez-nous un rapport directement depuis l'application.",
+ "report_bug_modal_title": "Signaler un bug",
+ "report_type_bug": "Bug",
+ "report_type_feature": "Fonctionnalité",
+ "report_type_question": "Question",
+ "report_field_title": "Titre",
+ "report_field_title_ph": "Brève description du problème",
+ "report_field_desc": "Description",
+ "report_field_desc_ph": "Décrivez le problème en détail…",
+ "report_field_steps": "Étapes pour reproduire (optionnel)",
+ "report_field_steps_ph": "1. Aller à…\n2. Appuyer sur…\n3. Voir l'erreur…",
+ "report_auto_info": "Joint automatiquement : version {version}, langue {lang}.",
+ "report_send_btn": "Envoyer le rapport",
+ "report_bug_sending": "Envoi…",
+ "report_bug_sent": "Rapport envoyé — merci !",
+ "report_bug_error": "Impossible d'envoyer le rapport. Vérifiez votre connexion.",
+ "changelog": "Journal des modifications",
+ "github": "Dépôt GitHub"
+ },
+ "export": {
+ "title": "Exporter l'inventaire",
+ "hint": "Téléchargez l'inventaire actuel en CSV ou ouvrez la version imprimable (PDF).",
+ "btn_csv": "Télécharger CSV",
+ "btn_pdf": "PDF / Imprimer",
+ "btn_title": "Exporter"
+ },
+ "startup": {
+ "connecting": "Connexion au serveur...",
+ "check_php_memory": "Mémoire PHP",
+ "check_php_timeout": "Délai PHP",
+ "check_php_upload": "Upload PHP",
+ "check_data_dir": "Dossier de données",
+ "check_rate_limits": "Dossier rate limits",
+ "check_backups": "Dossier sauvegardes",
+ "check_write_test": "Test d'écriture disque",
+ "check_disk_space": "Espace disque",
+ "check_db_connect": "Connexion base de données",
+ "check_db_tables": "Tables de la BDD",
+ "check_db_integrity": "Intégrité BDD",
+ "check_db_wal": "Mode WAL",
+ "check_db_size": "Taille de la BDD",
+ "check_db_rows": "Données inventaire",
+ "check_env": "Fichier .env",
+ "check_gemini": "Clé Gemini AI",
+ "check_bring_creds": "Identifiants Bring!",
+ "check_bring_token": "Token Bring!",
+ "check_curl_ssl": "cURL SSL",
+ "check_internet": "Connexion internet",
+ "fresh_install": "nouvelle installation",
+ "warnings_found": "avertissements détectés",
+ "all_ok": "Système OK",
+ "critical_error_short": "Erreur critique",
+ "critical_error": "Erreur critique : l'application ne peut pas démarrer. Vérifiez les logs.",
+ "error_network": "Impossible de contacter le serveur. Vérifiez votre connexion réseau.",
+ "retry": "Réessayer",
+ "syncing_local": "Synchronisation des données locales...",
+ "sync_done": "Données locales synchronisées"
}
- },
- "expiry": {
- "today": "AUJOURD'HUI",
- "tomorrow": "Demain",
- "days": "{days} jours",
- "expired_days": "il y a {days}j",
- "expired_yesterday": "Hier",
- "expired_today": "Aujourd'hui",
- "badge_today": "⚠️ Expire aujourd'hui !",
- "badge_tomorrow": "⏰ Demain",
- "badge_tomorrow_long": "⏰ Expire demain",
- "badge_days": "⏰ {n} jours",
- "badge_expired_ago": "⚠️ Périmé il y a {n}j",
- "badge_expired": "⛔ Périmé !",
- "badge_stable": "✅ Stable",
- "badge_expiring_short": "⏰ Exp. dans {n}j",
- "badge_ok_still": "✅ Encore {n}j",
- "badge_expires_red": "🔴 Exp. dans {n}j",
- "badge_expires_yellow": "🟡 Exp. dans {n}j",
- "badge_expired_bare": "⚠️ Périmé",
- "badge_expires_warn": "⚠️ Exp. dans {n}j",
- "badge_days_left": "⏳ ~{n}j restants",
- "days_approx": "~{n} jours",
- "weeks_approx": "~{n} semaines",
- "months_approx": "~{n} mois",
- "years_approx": "~{n} ans",
- "expired_today_long": "Périmé aujourd'hui",
- "expired_ago_long": "Périmé il y a {n} jours",
- "expired_suffix": "— Périmé !",
- "expired_suffix_ok": "— Périmé (encore ok)",
- "expired_suffix_warning": "— Périmé (vérifier d'abord)",
- "opened_ago_long": "Ouvert il y a {n} jours",
- "opened_today_long": "Ouvert aujourd'hui",
- "opened_suffix": "— Ouvert depuis trop longtemps !",
- "opened_suffix_ok": "— Ouvert (encore ok)",
- "opened_suffix_warning": "— Ouvert (vérifier d'abord)",
- "days_compact": "{n}j",
- "badge_check_soon": "Vérifier prochainement"
- },
- "status": {
- "ok": "OK",
- "check": "Vérifier",
- "discard": "Jeter",
- "tip_freezer_ok": "Au congélateur : encore sûr (~{n}j de marge)",
- "tip_freezer_check": "Au congélateur depuis longtemps, peut avoir perdu en qualité. Consommer rapidement",
- "tip_freezer_danger": "Au congélateur trop longtemps, risque de brûlure de congélation et de dégradation",
- "tip_highRisk_check": "Périmé récemment, vérifiez l'odeur et l'aspect avant de consommer",
- "tip_highRisk_danger": "Produit périssable périmé : jeter pour la sécurité",
- "tip_medRisk_check1": "Vérifiez l'aspect et l'odeur avant de consommer",
- "tip_medRisk_check2": "Périmé depuis un moment, vérifiez soigneusement avant utilisation",
- "tip_medRisk_danger": "Trop longtemps depuis la péremption, mieux vaut jeter",
- "tip_lowRisk_ok": "Produit longue conservation, encore sûr à consommer",
- "tip_lowRisk_check": "Périmé depuis plus d'un mois, vérifiez l'intégrité de l'emballage",
- "tip_lowRisk_danger": "Périmé depuis trop longtemps, mieux vaut ne pas risquer"
- },
- "toast": {
- "product_saved": "Produit enregistré !",
- "product_created": "Produit créé !",
- "product_updated": "✅ Produit mis à jour !",
- "product_removed": "Produit supprimé",
- "updated": "Mis à jour !",
- "quantity_confirmed": "✓ Quantité confirmée",
- "added_to_inventory": "✅ {name} ajouté !",
- "removed_from_list": "✅ {name} retiré de la liste !",
- "removed_from_list_short": "Retiré de la liste",
- "added_to_shopping": "🛒 Ajouté à la liste de courses !",
- "removed_from_shopping": "🛒 Retiré de la liste de courses",
- "finished_to_bring": "🛒 Produit terminé → ajouté à Bring !",
- "thrown_away": "🗑️ {name} jeté !",
- "thrown_away_partial": "🗑️ {qty} {unit} de {name} jeté(s)",
- "finished_all": "📤 {name} terminé !",
- "product_finished_confirmed": "✅ Supprimé — ajoutez-le à nouveau lors du réapprovisionnement",
- "appliance_added": "Appareil ajouté",
- "item_added": "{name} ajouté"
- },
- "antiwaste": {
- "title": "🌱 Rapport anti-gaspi",
- "grade_label": "Note",
- "you": "Vous",
- "avg_label": "Moy.",
- "better": "🎉 Vous gaspillez {diff}% de moins que la moyenne {country} !",
- "worse": "⚠️ Vous gaspillez plus que la moyenne {country}. Des progrès sont possibles !",
- "on_par": "→ Vous êtes dans la moyenne {country}. Vous pouvez faire mieux !",
- "saved_money": "~{amount}/mois économisé",
- "saved_meals": "~{n} repas sauvés",
- "saved_co2": "{n} kg CO₂ évités",
- "trend_title": "Tendance (3 derniers mois)",
- "months_ago_2": "-60 jours",
- "months_ago_1": "-30 jours",
- "this_month": "Maintenant",
- "country_it": "Moy. italienne",
- "country_de": "Moy. allemande",
- "country_en": "Moy. américaine",
- "source": "Sources : REDUCE, Eurostat, USDA 2021",
- "live_on": "Données en direct",
- "live_off": "Hors ligne",
- "meals": "repas",
- "annual_info": "📅 Vous ~{you} kg/an · moy. ~{avg} kg/an",
- "badge_rate": "taux de perte",
- "badge_saved_money": "économisé vs moy.",
- "badge_wasted": "articles perdus",
- "badge_better": "moins que la moy."
- },
- "error": {
- "generic": "Erreur",
- "network": "Erreur réseau",
- "no_api_key": "Configurez la clé API dans les paramètres",
- "loading": "Erreur de chargement du produit",
- "not_found": "Produit introuvable",
- "not_found_manual": "Produit introuvable. Entrez-le manuellement.",
- "search": "Erreur de recherche. Réessayez.",
- "search_short": "Erreur de recherche",
- "save": "Erreur d'enregistrement",
- "connection": "Erreur de connexion",
- "camera": "Impossible d'accéder à la caméra",
- "bring_add": "Erreur d'ajout à Bring !",
- "bring_connection": "Erreur de connexion à Bring !",
- "identification": "Erreur d'identification",
- "ai_quota": "Quota IA épuisé. Réessayez dans quelques minutes.",
- "barcode_empty": "Entrez un code-barres",
- "barcode_format": "Le code-barres ne doit contenir que des chiffres (4-14 chiffres)",
- "min_chars": "Tapez au moins 2 caractères",
- "not_in_inventory": "Produit absent de l'inventaire",
- "appliance_exists": "L'appareil existe déjà",
- "already_exists": "Existe déjà",
- "network_retry": "Erreur de connexion. Réessayez.",
- "select_items": "Sélectionnez au moins un produit",
- "server_offline": "Connexion au serveur perdue",
- "server_restored": "Connexion au serveur rétablie",
- "server_retry": "Réessayer",
- "unknown": "Erreur inconnue",
- "prefix": "Erreur",
- "no_inventory_entry": "Aucune entrée d'inventaire trouvée"
- },
- "confirm": {
- "remove_item": "Voulez-vous vraiment supprimer ce produit de l'inventaire ?",
- "kiosk_exit": "Quitter le mode kiosque ?",
- "cancel": "Annuler",
- "proceed": "Confirmer"
- },
- "location": {
- "dispensa": "Garde-manger",
- "frigo": "Réfrigérateur",
- "freezer": "Congélateur"
- },
- "edit": {
- "title": "Modifier {name}",
- "unknown_hint": "Entrez le nom du produit et les informations",
- "label_name": "🏷️ Nom du produit",
- "choose_location_title": "Quel emplacement ?",
- "choose_location_hint": "Choisissez l'emplacement à modifier :"
- },
- "screensaver": {
- "recipe_btn": "Recettes",
- "scan_btn": "Scanner un produit"
- },
- "days": {
- "mon": "Lundi",
- "tue": "Mardi",
- "wed": "Mercredi",
- "thu": "Jeudi",
- "fri": "Vendredi",
- "sat": "Samedi",
- "sun": "Dimanche",
- "mon_short": "Lun",
- "tue_short": "Mar",
- "wed_short": "Mer",
- "thu_short": "Jeu",
- "fri_short": "Ven",
- "sat_short": "Sam",
- "sun_short": "Dim"
- },
- "meal_types": {
- "lunch": "Déjeuner",
- "dinner": "Dîner",
- "colazione": "Petit-déjeuner",
- "merenda": "Goûter",
- "dolce": "Dessert",
- "succo": "Jus de fruits",
- "pranzo": "Déjeuner",
- "cena": "Dîner"
- },
- "scale": {
- "status_connected": "Balance connectée",
- "status_searching": "Passerelle connectée, attente de la balance…",
- "status_disconnected": "Passerelle de balance inaccessible",
- "status_error": "Erreur de connexion à la passerelle",
- "not_connected": "Passerelle de balance non connectée",
- "read_btn": "⚖️ Lire depuis la balance",
- "reading_title": "Lecture de la balance",
- "place_on_scale": "Posez le produit sur la balance…",
- "waiting_stable": "Le poids sera capturé automatiquement une fois la lecture stable.",
- "no_url": "Entrez l'URL de la passerelle",
- "testing": "⏳ Test de connexion…",
- "connected_ok": "Connexion à la passerelle réussie !",
- "timeout": "Délai dépassé : pas de réponse de la passerelle",
- "error_connect": "Impossible de se connecter à la passerelle",
- "tab": "Balance connectée",
- "low_weight": "Poids < 10 g · entrez manuellement\n(la lecture automatique nécessite au moins 10 g)",
- "density_hint": "(densité {density} g/ml)",
- "ml_hint": "(sera converti en ml)",
- "weight_detected": "Poids détecté — attendez 10s de stabilité…",
- "weight_too_low": "Poids trop faible — attente…",
- "stable": "✓ Stable",
- "auto_confirm": "✅ {val} {unit} — confirmation automatique dans 5s (appuyez pour annuler)",
- "cancelled_replace": "Annulé — replacez l'ingrédient sur la balance pour reprendre"
- },
- "prediction": {
- "expected_qty": "Attendu : {expected} {unit}",
- "actual_qty": "Actuel : {actual} {unit}",
- "check_suggestion": "Vérifiez ou pesez la quantité restante"
- },
- "date": {
- "today": "📅 Aujourd'hui",
- "yesterday": "📅 Hier"
- },
- "scanner": {
- "title_barcode": "🔖 Scanner le code-barres",
- "barcode_hint": "Cadrez le code-barres du produit",
- "barcode_manual_placeholder": "Ou entrez manuellement...",
- "barcode_use_btn": "✅ Utiliser ce code",
- "ai_identifying": "🤖 Identification du produit...",
- "ai_analyzing": "🤖 Analyse IA en cours...",
- "product_label_hint": "Cadrez l'étiquette du produit",
- "expiry_label_hint": "Cadrez la date de péremption imprimée sur le produit",
- "capture_btn": "📸 Capturer",
- "capture_photo_btn": "📸 Prendre une photo",
- "retake_btn": "🔄 Reprendre",
- "camera_error_hint": "Assurez-vous d'utiliser HTTPS et d'avoir accordé les permissions caméra.
Vous pouvez entrer le code-barres manuellement ou utiliser l'identification IA.",
- "no_barcode": "Pas de code-barres",
- "save_new_btn": "🆕 Aucun de ceux-ci — enregistrer comme nouveau"
- },
- "lowstock": {
- "title": "⚠️ Stock faible !",
- "message": "{name} est presque épuisé — il ne reste que {qty}.",
- "question": "Voulez-vous l'ajouter à la liste de courses ?",
- "yes": "🛒 Oui, ajouter à Bring !",
- "no": "Non, pour l'instant ça va"
- },
- "move": {
- "title": "📦 Déplacer le reste ?",
- "question": "Voulez-vous déplacer {thing} de {name} vers un autre emplacement ?",
- "question_short": "Voulez-vous déplacer {thing} vers un autre emplacement ?",
- "thing_opened": "l'emballage ouvert",
- "thing_rest": "le reste",
- "stay_btn": "Non, rester dans {location}",
- "moved_toast": "📦 Emballage ouvert déplacé vers {location}",
- "vacuum_restore": "🫙 Restaurer sous vide",
- "vacuum_seal_rest": "🔒 Mettre le reste sous vide"
- },
- "nova": {
- "1": "Non transformé",
- "2": "Ingrédient culinaire",
- "3": "Transformé",
- "4": "Ultra-transformé"
- },
- "meal_plan_types": {
- "pasta": "Pâtes",
- "riso": "Riz",
- "carne": "Viande",
- "pesce": "Poisson",
- "legumi": "Légumineuses",
- "uova": "Œufs",
- "formaggio": "Fromage",
- "pizza": "Pizza",
- "affettati": "Charcuterie",
- "verdure": "Légumes",
- "zuppa": "Soupe",
- "insalata": "Salade",
- "pane": "Pain/Sandwich",
- "dolce": "Dessert",
- "libero": "Libre"
- },
- "meal_sub": {
- "dolce_torta": "Gâteau",
- "dolce_crema": "Crème / Pudding",
- "dolce_crumble": "Crumble / Tarte",
- "dolce_biscotti": "Biscuits / Pâtisseries",
- "dolce_frutta": "Dessert aux fruits",
- "succo_dolce": "Sucré / Fruité",
- "succo_energizzante": "Énergisant",
- "succo_detox": "Détox / Vert",
- "succo_rinfrescante": "Rafraîchissant",
- "succo_vitaminico": "Vitaminé / Agrumes"
- },
- "meal_plan": {
- "reset_success": "Plan hebdomadaire réinitialisé",
- "not_available": "non disponible dans le garde-manger",
- "suggested_by": "suggéré par le plan hebdomadaire"
- },
- "nutrition": {
- "title": "🥗 Analyse alimentaire",
- "score_excellent": "😄 Excellent",
- "score_good": "🙂 Bien",
- "score_improve": "😬 Améliorable",
- "label_health": "🌿 Santé",
- "label_variety": "🎨 Variété",
- "label_fresh": "❄️ Frais",
- "source": "Basé sur {n} produits dans votre garde-manger · EverShelf",
- "products_count": "produits",
- "today_title": "🥗 Votre garde-manger aujourd'hui",
- "products_n": "{n} produits"
- },
- "facts": {
- "greeting_morning": "Bonjour",
- "greeting_afternoon": "Bon après-midi",
- "greeting_evening": "Bonsoir",
- "pantry_waiting": "{greeting} ! Votre garde-manger vous attend.",
- "expired_one": "Vous avez 1 produit périmé dans votre garde-manger. Vérifiez-le !",
- "expired_many": "Vous avez {n} produits périmés dans votre garde-manger. Vérifiez-les !",
- "expired_list": "Produits périmés : {names}",
- "expired_list_more": "et {n} autres",
- "freezer_expired_ok": "{name} est périmé, mais étant au congélateur il peut encore être bon ! Vérifiez.",
- "freezer_expired_old": "{name} au congélateur est périmé depuis trop longtemps. Mieux vaut le jeter.",
- "fridge_expired_one": "Vous avez 1 produit périmé dans le réfrigérateur !",
- "fridge_expired_many": "Vous avez {n} produits périmés dans le réfrigérateur !",
- "expiring_today": "{name} expire aujourd'hui ! Utilisez-le immédiatement.",
- "expiring_tomorrow": "{name} expire demain. Planifiez !",
- "expiring_days": "{name} expire dans {days} jours.",
- "expiring_many": "Vous avez {n} produits qui expirent bientôt.",
- "expiring_this_week": "{n} produits expirent cette semaine. Planifiez vos repas en conséquence !",
- "expiring_item_loc": "{name} ({loc}) expire dans {days} {dayslabel}.",
- "expiring_this_month": "{n} produits expireront ce mois-ci.",
- "shopping_add": "Ajouter à la liste : {names} 🛒",
- "shopping_more": "et {n} autres",
- "shopping_empty": "Liste de courses vide. Tout est en stock ! ✅",
- "in_fridge": "Dans le réfrigérateur : {name}.",
- "in_freezer": "Dans le congélateur : {name}. N'oubliez pas !",
- "top_category": "Catégorie principale : {icon} {cat} avec {n} produits.",
- "cat_meat": "Vous avez {n} produits carnés. 🥩",
- "cat_dairy": "Vous avez {n} produits laitiers chez vous. 🥛",
- "cat_veggies": "Vous avez {n} types de légumes. Super pour la santé ! 🥬",
- "cat_fruit": "Vous avez {n} types de fruits. 🍎",
- "cat_drinks": "Vous avez {n} boissons disponibles. 🥤",
- "cat_frozen": "Vous avez {n} articles surgelés. ❄️",
- "cat_pasta": "Vous avez {n} types de pâtes. 🍝 Et si on faisait une carbonara ?",
- "cat_canned": "Vous avez {n} conserves dans le garde-manger. 🥫",
- "cat_snacks": "Vous avez {n} snacks. Résistez à la tentation ! 🍪",
- "cat_condiments": "Vous avez {n} condiments disponibles. 🧂",
- "item_random": "Le saviez-vous ? Vous avez {name} dans {loc}.",
- "item_qty": "{name} : vous en avez {qty}.",
- "no_expiry_count": "{n} produits n'ont pas de date de péremption.",
- "furthest_expiry": "Le produit avec la date de péremption la plus lointaine est {name} : {months} mois.",
- "high_qty": "Vous avez un beau stock de {name} : {qty} !",
- "low_qty_item": "{name} est presque épuisé. L'ajouter à votre liste de courses ?",
- "low_qty_count": "{n} produits sont presque épuisés.",
- "morning_bread": "Bonjour ! Vous avez du pain pour le petit-déjeuner. 🍞",
- "morning_milk": "Y a-t-il du lait dans le réfrigérateur pour un cappuccino ? ☕🥛",
- "morning_fruit": "Bonjour ! Des fruits frais sont un excellent début de journée. 🍎",
- "noon_pasta": "C'est l'heure du déjeuner… Et si on faisait un bon bol de pâtes ? 🍝",
- "noon_salad": "Une salade fraîche pour le déjeuner ? Vous avez {n} légumes ! 🥗",
- "evening_meat": "Pour le dîner, vous pourriez utiliser la viande que vous avez. 🥩",
- "evening_fish": "Et si on mangeait du poisson ce soir ? 🐟",
- "evening_expiring": "Vous avez {n} produits qui expirent cette semaine — utilisez-les ce soir !",
- "night_reminder": "Bonne nuit ! N'oubliez pas d'utiliser demain : {names}.",
- "weekly_balance": "Bilan hebdomadaire : +{in} ajoutés, −{out} consommés.",
- "weekly_added": "Vous avez ajouté {n} produits cette semaine.",
- "weekly_consumed": "Vous avez consommé {n} produits cette semaine. Bravo !",
- "tip_freezer": "💡 Les produits surgelés durent bien plus longtemps que la date de péremption.",
- "tip_bread": "💡 Le pain congelé garde sa fraîcheur pendant des semaines.",
- "tip_fifo": "💡 Pour éviter le gaspillage, utilisez en premier les produits les plus proches de la péremption (FIFO).",
- "tip_meat": "💡 La viande au congélateur peut se conserver jusqu'à 6 mois en toute sécurité.",
- "tip_no_refreeze": "💡 Ne jamais recongeler un produit décongelé. Cuisinez-le immédiatement !",
- "tip_fridge": "💡 Un réfrigérateur bien rangé vous fait gagner du temps et de l'argent.",
- "tip_canned": "💡 Les conserves ouvertes doivent aller au réfrigérateur et être consommées en quelques jours.",
- "top_brand": "La marque la plus courante dans votre garde-manger est {brand} avec {n} produits.",
- "combo_pasta": "Vous avez des pâtes et des condiments : prêt pour un premier plat ! 🍝",
- "combo_sandwich": "Pain et viande : un sandwich rapide est toujours une bonne idée ! 🥪",
- "combo_balanced": "Légumes et viande : vous avez tout pour un repas équilibré ! 🥗🥩",
- "pantry_empty": "Le garde-manger est vide ! Il est temps de faire les courses. 🛒",
- "pantry_empty_scan": "Aucun produit enregistré. Scannez quelque chose pour commencer !",
- "location_distribution": "Distribution : {parts}",
- "day": "jour",
- "days": "jours"
- },
- "kiosk_session": {
- "first_item": "Premier article : {name} !",
- "items_two_four": "{n} articles — on démarre 🚀",
- "items_five_nine": "{n} articles — bon rythme ! 💪",
- "items_ten_twenty": "{n} articles — presque un record 🏆",
- "items_twenty_plus": "{n} articles — courses épiques ! 🛒🔥",
- "duplicates_one": "1 doublon (même article deux fois)",
- "duplicates_many": "{n} doublons (pris plusieurs fois)",
- "top_category": "Catégorie principale : {cat} ({count}×)",
- "items_fallback": "{n} article{plural} ajouté{plural}"
- },
- "kiosk": {
- "check_btn": "🔍 Vérifier les mises à jour",
- "checking": "⏳ Vérification…",
- "error_check": "Erreur lors de la vérification des mises à jour",
- "error_start_install": "Erreur au démarrage de l'installation",
- "version_installed": "Installé : {v}",
- "update_available": "⬆️ Nouvelle version disponible :
{latest} (installée : {current})",
- "up_to_date": "✅ Vous êtes à jour — version
{v}",
- "too_old": "⚠️ Le kiosque installé est trop ancien pour la vérification automatique des mises à jour.
Appuyez sur le bouton ci-dessous pour télécharger et installer la nouvelle version directement.",
- "manual_install": "⚠️ Ce kiosque ne supporte pas l'installation automatique.
Procédure manuelle :1. Quittez le kiosque (bouton ✕ en haut à gauche)
2. Désinstallez l'application EverShelf Kiosk
3. Téléchargez et installez le nouvel APK depuis GitHub :",
- "starting_download": "⏳ Démarrage du téléchargement…",
- "install_btn": "⬇️ Installer la mise à jour",
- "exit_title": "Quitter le kiosque",
- "refresh_title": "Actualiser la page"
- },
- "update": {
- "new_version": "Nouvelle version",
- "btn": "Mettre à jour"
- },
- "gemini": {
- "chat_title": "Discussion avec Gemini",
- "not_configured": "🤖 Gemini non configuré — définissez GEMINI_API_KEY dans les paramètres"
- },
- "appliances": {
- "empty": "Aucun appareil ajouté"
- },
- "about": {
- "title": "À propos",
- "version": "Version",
- "report_bug": "Signaler un bug",
- "report_bug_hint": "Quelque chose ne fonctionne pas ? Envoyez-nous un rapport directement depuis l'application.",
- "report_bug_modal_title": "Signaler un bug",
- "report_type_bug": "Bug",
- "report_type_feature": "Fonctionnalité",
- "report_type_question": "Question",
- "report_field_title": "Titre",
- "report_field_title_ph": "Brève description du problème",
- "report_field_desc": "Description",
- "report_field_desc_ph": "Décrivez le problème en détail…",
- "report_field_steps": "Étapes pour reproduire (optionnel)",
- "report_field_steps_ph": "1. Aller à…\n2. Appuyer sur…\n3. Voir l'erreur…",
- "report_auto_info": "Joint automatiquement : version {version}, langue {lang}.",
- "report_send_btn": "Envoyer le rapport",
- "report_bug_sending": "Envoi…",
- "report_bug_sent": "Rapport envoyé — merci !",
- "report_bug_error": "Impossible d'envoyer le rapport. Vérifiez votre connexion.",
- "changelog": "Journal des modifications",
- "github": "Dépôt GitHub"
- },
- "export": {
- "title": "Exporter l'inventaire",
- "hint": "Téléchargez l'inventaire actuel en CSV ou ouvrez la version imprimable (PDF).",
- "btn_csv": "Télécharger CSV",
- "btn_pdf": "PDF / Imprimer",
- "btn_title": "Exporter"
- },
- "startup": {
- "connecting": "Connexion au serveur...",
- "check_php_memory": "Mémoire PHP",
- "check_php_timeout": "Délai PHP",
- "check_php_upload": "Upload PHP",
- "check_data_dir": "Dossier de données",
- "check_rate_limits": "Dossier rate limits",
- "check_backups": "Dossier sauvegardes",
- "check_write_test": "Test d'écriture disque",
- "check_disk_space": "Espace disque",
- "check_db_connect": "Connexion base de données",
- "check_db_tables": "Tables de la BDD",
- "check_db_integrity": "Intégrité BDD",
- "check_db_wal": "Mode WAL",
- "check_db_size": "Taille de la BDD",
- "check_db_rows": "Données inventaire",
- "check_env": "Fichier .env",
- "check_gemini": "Clé Gemini AI",
- "check_bring_creds": "Identifiants Bring!",
- "check_bring_token": "Token Bring!",
- "check_curl_ssl": "cURL SSL",
- "check_internet": "Connexion internet",
- "fresh_install": "nouvelle installation",
- "warnings_found": "avertissements détectés",
- "all_ok": "Système OK",
- "critical_error_short": "Erreur critique",
- "critical_error": "Erreur critique : l'application ne peut pas démarrer. Vérifiez les logs.",
- "error_network": "Impossible de contacter le serveur. Vérifiez votre connexion réseau.",
- "retry": "Réessayer"
- }
}
\ No newline at end of file
diff --git a/translations/it.json b/translations/it.json
index 4be2ea0..e1c1260 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -1,1407 +1,1419 @@
{
- "app": {
- "name": "EverShelf",
- "loading": "Caricamento..."
- },
- "nav": {
- "title": "EverShelf",
- "home": "Home",
- "inventory": "Dispensa",
- "recipes": "Ricette",
- "shopping": "Spesa",
- "log": "Storico",
- "settings": "Config"
- },
- "btn": {
- "back": "← Indietro",
- "save": "💾 Salva",
- "cancel": "✕ Annulla",
- "close": "Chiudi",
- "add": "✅ Aggiungi",
- "delete": "Elimina",
- "edit": "✏️ Modifica",
- "use": "Usa",
- "edit_item": "Modifica",
- "search": "🔍 Cerca",
- "go": "✅ Vai",
- "toggle_password": "👁️ Mostra/Nascondi",
- "load_more": "Carica altri...",
- "save_config": "💾 Salva Configurazione",
- "save_product": "💾 Salva Prodotto",
- "restart": "↺ Ricomincia",
- "reset_default": "↺ Ripristina default",
- "save_info": "💾 Salva informazioni",
- "retry": "🔄 Riprova",
- "yes_short": "Sì",
- "no_short": "No"
- },
- "form": {
- "select_placeholder": "-- Seleziona --"
- },
- "locations": {
- "dispensa": "Dispensa",
- "frigo": "Frigo",
- "freezer": "Freezer",
- "altro": "Altro"
- },
- "categories": {
- "latticini": "Latticini",
- "carne": "Carne",
- "pesce": "Pesce",
- "frutta": "Frutta",
- "verdura": "Verdura",
- "pasta": "Pasta & Riso",
- "pane": "Pane & Forno",
- "surgelati": "Surgelati",
- "bevande": "Bevande",
- "condimenti": "Condimenti",
- "snack": "Snack & Dolci",
- "conserve": "Conserve",
- "cereali": "Cereali & Legumi",
- "igiene": "Igiene",
- "pulizia": "Pulizia Casa",
- "altro": "Altro",
- "select": "-- Seleziona --"
- },
- "units": {
- "pz": "pz",
- "conf": "conf",
- "g": "g",
- "ml": "ml",
- "pieces": "Pezzi",
- "grams": "Grammi",
- "box": "Confezione",
- "boxes": "Confezioni",
- "millilitres": "Millilitri",
- "from": "da"
- },
- "shopping_sections": {
- "frutta_verdura": "Frutta & Verdura",
- "carne_pesce": "Carne & Pesce",
- "latticini": "Latticini & Fresco",
- "pane_dolci": "Pane & Dolci",
- "pasta": "Pasta & Cereali",
- "conserve": "Conserve & Salse",
- "surgelati": "Surgelati",
- "bevande": "Bevande",
- "pulizia_igiene": "Pulizia & Igiene",
- "altro": "Altro"
- },
- "dashboard": {
- "expired_title": "🚫 Scaduti",
- "expiring_title": "⏰ Prossime Scadenze",
- "stats_period": "📊 Ultimi 30 giorni",
- "opened_title": "📦 Prodotti Aperti",
- "review_title": "🔍 Da revisionare",
- "review_hint": "Quantità che sembrano anomale. Conferma se corrette o modifica.",
- "quick_recipe": "Ricetta veloce con prodotti in scadenza",
- "banner_review_title": "Quantità anomala",
- "banner_review_action_ok": "È corretto",
- "banner_review_action_finish": "🗑️ È finito tutto",
- "banner_review_action_edit": "Correggi",
- "banner_review_action_weigh": "Pesa",
- "banner_review_dismiss": "Ignora",
- "banner_prediction_title": "Consumo da verificare",
- "banner_prediction_hint": "La stima di consumo si adatta ai dati recenti: verifica solo se la quantità corrente è corretta.",
- "banner_prediction_action_confirm": "Confermo {qty} {unit}",
- "banner_prediction_action_weigh": "Pesa ora",
- "banner_prediction_action_edit": "Aggiorna quantità",
- "banner_expired_title": "Prodotto scaduto",
- "banner_expired_today": "Scaduto oggi",
- "banner_expired_days": "Scaduto da {days} giorni",
- "banner_expired_action_use": "Usa comunque",
- "banner_expired_action_finished": "L'ho finito!",
- "banner_expired_action_throw": "L'ho buttato",
- "banner_expired_action_edit": "Correggi data",
- "banner_expired_action_modify": "Modifica",
- "banner_expired_action_vacuum": "Metti sottovuoto",
- "banner_anomaly_action_edit": "Correggi inventario",
- "banner_anomaly_action_dismiss": "La quantità è giusta",
- "banner_no_expiry_title": "Scadenza mancante: {name}",
- "banner_no_expiry_detail": "Questo prodotto non ha una data di scadenza. Vuoi aggiungerla o confermare che non scade?",
- "banner_no_expiry_action_set": "Imposta scadenza",
- "banner_no_expiry_action_dismiss": "Non scade ✓",
- "banner_no_expiry_toast_dismissed": "Segnato come 'non scade'",
- "banner_expiring_title": "In scadenza",
- "banner_expiring_today": "Scade oggi!",
- "banner_expiring_tomorrow": "Scade domani",
- "banner_expiring_days": "Scade tra {days} giorni",
- "banner_expiring_action_use": "Usa ora",
- "banner_finished_title": "è finito?",
- "banner_finished_detail": "Ho registrato che {name} ha toccato quota zero. È davvero finito o hai ancora delle scorte?",
- "banner_finished_action_yes": "Sì, è finito",
- "banner_finished_action_no": "No, ne ho ancora",
- "banner_review_unusual_pkg_title": "Confezione insolita",
- "banner_review_unusual_pkg_detail": "Hai impostato una confezione da {qty} {unit} — la dimensione sembra molto alta. Controlla se è corretta o modifica.",
- "banner_review_low_qty_title": "Quantità molto bassa",
- "banner_review_low_qty_detail": "Hai solo {qty} in inventario — sembra poco, potrebbe essere un errore. Conferma se è corretto.",
- "banner_review_high_qty_title": "Quantità insolitamente alta",
- "banner_review_high_qty_detail": "Hai {qty} in inventario — la cifra sembra molto alta. Conferma se è corretto o correggi.",
- "banner_prediction_rate_day": "Media ~{n} {unit}/giorno",
- "banner_prediction_rate_week": "Media ~{n} {unit}/settimana",
- "banner_prediction_days_ago": "{n} giorni fa hai rifornito",
- "banner_prediction_more": "stima precedente: {expected} {unit}{time}; quantità attuale: {actual} {unit}.",
- "banner_prediction_less": "stima: {expected} {unit}{time}; quantità attuale: {actual} {unit}. Se hai cambiato ritmo d'uso, la previsione si aggiorna automaticamente.",
- "banner_finished_zero": "L'inventario segna zero, ma i movimenti registrati dicono che non dovrebbe essere finito.",
- "banner_finished_expected": "Secondo le registrazioni dovresti averne ancora {qty} {unit}.",
- "banner_finished_check": "Puoi controllare?",
- "banner_anomaly_phantom_title": "hai più scorte del previsto",
- "banner_anomaly_phantom_detail": "L'inventario segna {inv_qty} {unit}, ma in base alle registrazioni ne dovresti avere solo {expected_qty} {unit}. Hai aggiunto scorte senza registrarle?",
- "banner_anomaly_untracked_title": "scorte non registrate come entrata",
- "banner_anomaly_untracked_detail": "Hai
{inv_qty} {unit} in inventario, ma le uscite registrate superano le entrate — le scorte iniziali probabilmente non sono mai state aggiunte come entrata. Puoi correggere la quantità o registrare le entrate mancanti.",
- "banner_anomaly_ghost_title": "hai meno scorte del previsto",
- "banner_anomaly_ghost_detail": "In base alle operazioni registrate dovresti avere {expected_qty} {unit} di {name}, ma l'inventario mostra solo {inv_qty} {unit}. Hai prelevato senza registrarlo?",
- "consumed": "Consumati: {n} ({pct}%)",
- "wasted": "Buttati: {n} ({pct}%)",
- "more_opened": "e altri {n} prodotti aperti...",
- "banner_expired_detail": "{when} · hai ancora
{qty}.",
- "banner_opened_detail": "{when} in {location} · hai ancora
{qty}.",
- "banner_explain_title": "Chiedi a Gemini una spiegazione",
- "banner_explain_btn": "Spiega",
- "banner_analyzing": "🤖 Analizzo…"
- },
- "inventory": {
- "title": "Dispensa",
- "filter_all": "Tutti",
- "search_placeholder": "🔍 Cerca prodotto...",
- "recent_title": "🕐 Ultimi usati",
- "popular_title": "⭐ Più usati",
- "empty": "Nessun prodotto qui.\nScansiona un prodotto per aggiungerlo!",
- "no_items_found": "Nessuna voce di inventario trovata",
- "qty_remainder_suffix": "rimasti",
- "vacuum_badge": "🫙 Sotto vuoto",
- "opened_badge": "📭 Aperto",
- "label_expiry": "📅 Scadenza",
- "label_storage": "🫙 Conservazione",
- "label_status": "📭 Stato",
- "opened_since": "Aperto dal {date}",
- "label_position": "📍 Posizione",
- "label_quantity": "📦 Quantità",
- "label_added": "📅 Aggiunto",
- "empty_text": "Nessun prodotto qui.
Scansiona un prodotto per aggiungerlo!",
- "empty_db": "Nessun prodotto nel database.
Scansiona un prodotto per iniziare!",
- "qty_trace": "< 1"
- },
- "scan": {
- "title": "Scansiona",
- "mode_shopping": "🛒 Modalità Spesa",
- "mode_shopping_end": "✅ Fine spesa",
- "spesa_btn": "🛒 Spesa",
- "zoom": "Zoom",
- "tab_barcode": "Barcode",
- "tab_name": "Nome",
- "tab_ai": "AI",
- "recents_label": "Recenti",
- "torch_hint": "Torcia",
- "torch_on": "Torcia accesa",
- "torch_off": "Torcia spenta",
- "torch_unavailable": "Torcia non disponibile su questo dispositivo",
- "flip_hint": "Cambia fotocamera",
- "flip_front": "Fotocamera anteriore",
- "flip_back": "Fotocamera posteriore",
- "num_ocr_btn": "🔢 Leggi numeri con AI",
- "num_ocr_searching": "Cerco il codice con AI...",
- "num_ocr_found": "Codice trovato: {code}",
- "num_ocr_not_found": "Nessun codice trovato nell'immagine",
- "barcode_placeholder": "Inserisci codice a barre...",
- "quick_name_divider": "oppure scrivi il nome",
- "quick_name_placeholder": "Es: Mele, Zucchine, Pane...",
- "manual_entry": "✏️ Inserimento Manuale",
- "ai_identify": "🤖 Identifica con AI",
- "hint": "Scansiona il barcode, scrivi il nome del prodotto, oppure usa l'AI per identificarlo",
- "debug_toggle": "🐛 Debug Log",
- "barcode_acquired": "🔖 Barcode acquisito: {code}",
- "scan_barcode": "🔖 Scansiona Barcode",
- "create_named": "Crea {name}",
- "new_without_barcode": "Nuovo prodotto senza barcode",
- "stock_in_pantry": "Hai gia in dispensa:"
- },
- "action": {
- "title": "Cosa vuoi fare?",
- "add_btn": "📥 AGGIUNGI",
- "add_sub": "in dispensa/frigo",
- "use_btn": "USA",
- "use_sub": "dalla dispensa/frigo",
- "have_title": "📦 Ce l'hai già!",
- "add_more_sub": "altra quantità",
- "use_qty_sub": "quanto ne hai usato",
- "throw_btn": "🗑️ BUTTA",
- "throw_sub": "butta il prodotto",
- "edit_sub": "scadenza, luogo…",
- "create_recipe_btn": "Ricetta",
- "related_stock_title": "Hai anche in casa"
- },
- "add": {
- "title": "Aggiungi alla Dispensa",
- "location_label": "📍 Dove lo metti?",
- "quantity_label": "📦 Quantità",
- "conf_size_label": "📦 Ogni confezione contiene:",
- "conf_size_placeholder": "es. 300",
- "vacuum_label": "🫙 Sotto vuoto",
- "vacuum_hint": "La scadenza verrà estesa automaticamente",
- "submit": "✅ Aggiungi",
- "purchase_type_label": "🛒 Questo prodotto è...",
- "new_btn": "🆕 Appena comprato",
- "existing_btn": "📦 Ce l'avevo già",
- "remaining_label": "📦 Quantità rimasta",
- "remaining_hint": "Quanto è rimasto approssimativamente?",
- "remaining_full": "🟢 Pieno",
- "remaining_half": "🟠 Metà",
- "estimated_expiry": "Scadenza stimata:",
- "suffix_freezer": "(freezer)",
- "suffix_vacuum": "(sotto vuoto)",
- "hint_modify": "📝 Puoi modificare la data o scansionarla con la fotocamera",
- "scan_expiry_title": "📷 Scansiona Data Scadenza",
- "product_added": "✅ {name} aggiunto!{qty}",
- "suffix_freezer_vacuum": "(freezer + sotto vuoto)",
- "history_badge_tip": "Media da {n} inserimenti precedenti",
- "vacuum_question": "Messo sotto vuoto?",
- "vacuum_saved": "🔒 Sotto vuoto registrato"
- },
- "use": {
- "title": "Usa / Consuma",
- "location_label": "📍 Da dove?",
- "quantity_label": "Quanto hai usato?",
- "change": "cambia",
- "partial_hint": "Oppure specifica la quantità usata:",
- "partial_piece_hint": "Hai usato solo una parte?",
- "piece": "pezzo",
- "one_whole": "1 intero",
- "use_all": "🗑️ Usato TUTTO / Finito",
- "submit": "📤 Usa questa quantità",
- "available": "📦 Disponibile:",
- "opened_badge": "APERTO",
- "not_in_inventory": "⚠️ Prodotto non presente nell'inventario.",
- "expiry_warning": "⚠️ Usa prima quella{loc} che scade il {date} — {when}!",
- "expiry_warning_opened": "⚠️ Quella{loc}, aperta da {when} — usala prima!",
- "throw_title": "🗑️ Butta Prodotto",
- "throw_all": "🗑️ Butta TUTTO ({qty})",
- "throw_qty_label": "Quanto butti?",
- "throw_qty_hint": "oppure specifica la quantità:",
- "throw_partial_btn": "🗑️ Butta questa quantità",
- "when_expired": "scaduta da {n} giorni",
- "when_today": "scade
oggi",
- "when_tomorrow": "scade
domani",
- "when_days": "scade tra
{n} giorni",
- "toast_used": "📤 Usato {qty} di {name}",
- "toast_bring": "🛒 Prodotto finito → aggiunto a Bring!",
- "toast_opened_finished": "🔓 Confezione aperta di {name} finita!",
- "disambiguation_hint": "Cosa intendi con \"finito tutto\"?",
- "disambiguation_all": "🗑️ Finito TUTTO ({qty})",
- "error_exceeds_stock": "⚠️ Non puoi usare più di quanto hai disponibile!",
- "use_all_confirm_title": "✅ Finisci tutto",
- "use_all_confirm_msg": "Conferma che hai finito tutto il prodotto:",
- "use_all_confirm_btn": "✅ Sì, finito",
- "throw_all_confirm_title": "🗑️ Butta tutto",
- "throw_all_confirm_msg": "Vuoi davvero buttare via tutto il prodotto?",
- "throw_all_confirm_btn": "🗑️ Sì, butta"
- },
- "product": {
- "title_new": "Nuovo Prodotto",
- "title_edit": "Modifica Prodotto",
- "ai_fill": "📷 Scatta foto e identifica con AI",
- "ai_fill_hint": "L'AI compilerà automaticamente i campi del prodotto",
- "name_label": "🏷️ Nome Prodotto *",
- "name_placeholder": "Es: Latte intero, Pasta penne rigate...",
- "brand_label": "🏢 Marca",
- "brand_placeholder": "Es: Barilla, Granarolo, Mutti...",
- "category_label": "📂 Categoria",
- "unit_label": "📏 Unità di misura",
- "default_qty_label": "🔢 Quantità default",
- "conf_size_label": "📦 Ogni confezione contiene:",
- "conf_size_placeholder": "es. 300",
- "notes_label": "📝 Note",
- "notes_placeholder": "Es: senza lattosio, bio, conservare in frigo dopo apertura...",
- "barcode_label": "🔖 Barcode",
- "barcode_placeholder": "Codice a barre (se disponibile)",
- "barcode_hint": "⚠️ Aggiungi il barcode così al prossimo acquisto basta scansionarlo!",
- "submit": "💾 Salva Prodotto",
- "name_required": "Inserisci il nome del prodotto",
- "conf_size_required": "Specifica il contenuto di ogni confezione",
- "expiry_estimated": "Scadenza stimata:",
- "scan_expiry": "Scansiona data scadenza",
- "expiry_hint": "📝 Puoi modificare la data o scansionarla con la fotocamera",
- "add_batch": "📦 + Lotto con scadenza diversa",
- "package_info": "📦 Confezione: {info}",
- "edit_catalog": "⚙️ Modifica scheda prodotto (nome, marca, categoria…)",
- "not_recognized": "⚠️ Prodotto non riconosciuto",
- "edit_info": "✏️ Modifica informazioni",
- "modify_details": "MODIFICA\nscadenza, luogo…",
- "already_in_pantry": "📋 Già in dispensa",
- "no_barcode": "Senza barcode",
- "unknown_product": "Prodotto non riconosciuto",
- "edit_name_brand": "Modifica nome/marca",
- "weight_label": "Peso",
- "origin_label": "Origine",
- "labels_label": "Etichette",
- "select_variant": "Seleziona la variante esatta o usa i dati AI:"
- },
- "products": {
- "title": "📦 Tutti i Prodotti",
- "search_placeholder": "🔍 Cerca prodotto...",
- "empty": "Nessun prodotto nel database.\nScansiona un prodotto per iniziare!",
- "no_category": "Nessun prodotto in questa categoria"
- },
- "recipes": {
- "title": "🍳 Ricette",
- "generate": "✨ Genera nuova ricetta",
- "archive_empty": "Nessuna ricetta salvata. Genera la tua prima ricetta!",
- "dialog_title": "🍳 Ricetta",
- "dialog_desc": "Genero una ricetta sana con gli ingredienti in dispensa, dando priorità a quelli in scadenza.",
- "meal_label": "🕐 Per quale pasto?",
- "persons_label": "👥 Quante persone?",
- "meal_type_label": "🎯 Tipo di pasto",
- "opt_fast": "⚡ Pasto Veloce",
- "opt_light": "🥗 Poca Fame",
- "opt_expiry": "⏰ Priorità Scadenze",
- "opt_healthy": "💚 Extra Salutare",
- "opt_opened": "📦 Priorità Cose Aperte",
- "opt_zero_waste": "♻️ Zero Sprechi",
- "generate_btn": "✨ Genera Ricetta",
- "loading_msg": "Sto preparando la ricetta...",
- "start_cooking": "👨🍳 Modalità Cucina",
- "regenerate": "🔄 Generane un'altra",
- "close_btn": "✅ Chiudi",
- "ingredients_title": "🧾 Ingredienti",
- "tools_title": "Strumenti necessari",
- "steps_title": "👨🍳 Procedimento",
- "no_steps": "Nessun procedimento disponibile",
- "generate_error": "Errore nella generazione",
- "stream_interrupted": "Generazione interrotta (risposta incompleta dal server). Controlla i log o riprova.",
- "persons_short": "pers.",
- "use_ingredient_title": "Usa ingrediente",
- "recipe_qty_label": "Ricetta",
- "from_where_label": "Da dove?",
- "amount_label": "Quanto",
- "use_amount_btn": "Usa questa quantità",
- "use_all_btn": "Usa TUTTO / Finito",
- "packs_label": "Confezioni",
- "quantity_in_total": "Quantità in {unit} (totale: {total})",
- "packs_of_have": "Confezioni da {size} (hai {count} conf)",
- "scale_wait_stable": "Attendi 10s di stabilità per la compilazione automatica…",
- "ingredient_scaled_toast": "📦 Ingrediente scalato dalla dispensa!",
- "finished_added_bring_toast": "🛒 Prodotto finito → aggiunto a Bring!",
- "load_error": "Errore nel caricamento"
- },
- "shopping": {
- "title": "🛒 Lista della Spesa",
- "bring_loading": "Connessione a Bring!...",
- "bring_not_configured": "Bring! non è configurato. Aggiungi email e password nelle
impostazioni.",
- "tab_to_buy": "🛍️ Da comprare",
- "tab_forecast": "🧠 In previsione",
- "total_label": "💰 Totale stimato",
- "section_to_buy": "🛍️ Da comprare",
- "suggestions_title": "💡 Suggerimenti AI",
- "suggestions_add": "✅ Aggiungi selezionati a Bring!",
- "search_prices": "🔍 Cerca tutti i prezzi",
- "suggest_btn": "Suggerisci cosa comprare",
- "smart_title": "🧠 Previsioni intelligenti",
- "smart_empty": "Nessuna previsione disponibile.
Aggiungi prodotti alla dispensa per ricevere previsioni intelligenti.",
- "smart_filter_all": "Tutti",
- "smart_filter_critical": "🔴 Urgenti",
- "smart_filter_high": "🟠 Presto",
- "smart_filter_medium": "🟡 Pianifica",
- "smart_filter_low": "🟢 Previsione",
- "smart_add": "🛒 Aggiungi selezionati a Bring!",
- "empty": "Lista della spesa vuota!\nUsa il pulsante sotto per generare suggerimenti.",
- "already_in_list": "🛒 \"{name}\" già nella lista della spesa",
- "already_in_list_short": "ℹ️ Già nella lista della spesa",
- "add_prompt": "Vuoi aggiungerlo alla lista della spesa?",
- "smart_already": "📊 La spesa intelligente prevede già {name}",
- "all_searched": "Tutti i prodotti sono già stati cercati. Usa 🔄 per ricercare singoli.",
- "search_complete": "Ricerca completata: {count} prodotti",
- "removed_sufficient": "🧹 {removed} prodotto/i con scorte sufficienti rimosso/i dalla lista",
- "suggest_buy": "🛒 Compra: {qty} {unit}",
- "suggest_buy_approx": "🛒 Almeno: {qty} {unit}",
- "suggest_buy_tip": "Quantità suggerita in base al consumo degli ultimi 14 giorni",
- "suggest_buy_approx_tip": "Stima minima basata sul consumo (compra la confezione più vicina)",
- "bring_badge": "🛒 Già su Bring!",
- "add_urgent_toast": "🔴 {n} prodotto/i urgente/i aggiunto/i automaticamente a Bring!",
- "migration_done": "✅ {migrated} aggiornati, {skipped} già ok",
- "added_to_bring": "🛒 {n} prodotti aggiunti a Bring!",
- "added_to_bring_skip": "{n} già presenti",
- "all_on_bring": "Tutti i prodotti erano già su Bring!",
- "freq_high": "📈 Uso frequente",
- "freq_regular": "📊 Uso regolare",
- "freq_occasional": "📉 Uso occasionale",
- "out_of_stock": "Esaurito",
- "scan_toast": "📷 Scansiona: {name}",
- "empty_category": "Nessun prodotto in questa categoria",
- "session_empty": "🛒 Nessun prodotto ancora",
- "urgency_critical": "Urgente",
- "urgency_high": "Presto",
- "urgency_medium": "Pianifica",
- "urgency_low": "Previsione",
- "urgency_medium_short": "Medio",
- "urgency_low_short": "Ok",
- "tag_urgent": "🔴 Urgente",
- "tag_priority": "⭐ Priorità",
- "tag_check": "✅ Verificare",
- "smart_already_predicted": "📊 La spesa intelligente prevede già
{name}{urgency}.",
- "item_removed": "✅ {name} rimosso dalla lista!",
- "urgency_spec_critical": "⚡ Urgente",
- "urgency_spec_high": "🟠 Presto",
- "bring_add_n": "Aggiungi {n} a Bring!",
- "bring_add_selected": "Aggiungi selezionati a Bring!",
- "bring_adding": "Aggiunta in corso...",
- "bring_added_one": "1 prodotto aggiunto a Bring!",
- "bring_added_many": "{n} prodotti aggiunti a Bring!",
- "bring_skipped": "({n} già in lista)",
- "force_sync": "Forza sincronizzazione Bring!",
- "scan_target_label": "Stai cercando",
- "scan_target_found": "Trovato! Rimuovi dalla lista",
- "bring_add_one": "Aggiungi 1 prodotto a Bring!",
- "bring_add_many": "Aggiungi {n} prodotti a Bring!",
- "syncing": "Sincronizzazione…",
- "sync_done": "Sincronizzazione completata",
- "price_searching": "Cerco...",
- "search_action": "Ricerca",
- "open_action": "Apri",
- "not_found": "Non trovato",
- "search_price": "Cerca prezzo",
- "tap_to_scan": "Tocca per scansionare",
- "tag_title": "Tag",
- "remove_title": "Rimuovi",
- "found_count": "{found}/{total} prodotti trovati",
- "savings_offers": "· 🏷️ Risparmi €{amount} con le offerte",
- "searching_progress": "Cerco {current}/{total}...",
- "remove_error": "Errore nella rimozione",
- "btn_fetch_prices": "Cerca i prezzi",
- "price_total_label": "💰 Spesa stimata:",
- "price_loading": "Ricerca prezzi…",
- "price_not_found": "prezzo n/d",
- "suggest_loading": "Analisi in corso...",
- "suggest_error": "Errore nella generazione",
- "priority_high": "Alta",
- "priority_medium": "Media",
- "priority_low": "Bassa",
- "smart_last_update": "Aggiornato {time}",
- "names_already_updated": "Tutti i nomi sono già aggiornati",
- "pantry_hint": "Hai gia {qty} in dispensa"
- },
- "ai": {
- "title": "🤖 Identificazione AI",
- "capture": "📸 Scatta Foto",
- "retake": "🔄 Riscatta",
- "hint": "Scatta una foto del prodotto e l'AI cercherà di identificarlo",
- "identifying": "🤖 Identifico il prodotto...",
- "no_api_key": "⚠️ Chiave API Gemini non configurata.\n
Aggiungi GEMINI_API_KEY nel file .env sul server.",
- "fields_filled": "✅ Campi compilati dall'AI",
- "use_data": "✅ Usa dati AI",
- "use_data_no_barcode": "✅ Usa dati AI (senza barcode)"
- },
- "log": {
- "title": "📒 Storico",
- "type_added": "Aggiunto",
- "type_waste": "Buttato",
- "type_used": "Usato",
- "type_bring": "Aggiunto a Bring!",
- "undone_badge": "Annullato",
- "undo_title": "Annulla questa operazione",
- "load_error": "Errore nel caricamento log",
- "empty": "Nessuna operazione registrata.",
- "undo_action_remove": "rimozione di",
- "undo_action_restore": "ripristino di",
- "undo_confirm": "Annullare questa operazione?\n→ {action} {name}",
- "undo_success": "↩ Operazione annullata per {name}",
- "already_undone": "Operazione già annullata",
- "too_old": "Non è possibile annullare operazioni più vecchie di 24 ore",
- "undo_error": "Errore durante l'annullamento",
- "recipe_prefix": "Ricetta"
- },
- "chat": {
- "title": "Gemini Chef",
- "welcome": "Ciao! Sono il tuo assistente cucina",
- "welcome_desc": "Chiedimi di prepararti un succo, uno spuntino, un piatto veloce... Conosco la tua dispensa, i tuoi elettrodomestici e le tue preferenze!",
- "suggestion_snack": "🍿 Spuntino veloce",
- "suggestion_juice": "🥤 Succo/Frullato",
- "suggestion_light": "🥗 Qualcosa di leggero",
- "suggestion_expiry": "⏰ Usa le scadenze",
- "clear": "Nuova conversazione",
- "placeholder": "Chiedi qualcosa...",
- "cleared": "Chat cancellata",
- "suggestion_snack_text": "Cosa posso preparare per uno spuntino veloce?",
- "suggestion_juice_text": "Fammi un succo o frullato con quello che ho",
- "suggestion_light_text": "Ho fame ma voglio qualcosa di leggero",
- "suggestion_expiry_text": "Cosa sta per scadere e come posso usarlo?",
- "transfer_to_recipes": "Trasferisci a Ricette",
- "transferring": "Trasferimento in corso...",
- "transferred": "Aggiunta alle Ricette!",
- "open_recipe": "Apri la ricetta",
- "quick_recipe_prompt": "Suggeriscimi una ricetta veloce PER UNA PERSONA usando i prodotti che scadono prima! Ignora i prodotti in freezer, concentrati su frigo e dispensa."
- },
- "cooking": {
- "close": "Chiudi",
- "tts_btn": "Leggi ad alta voce",
- "restart": "↺ Ricomincia",
- "replay": "🔊 Rileggi",
- "timer": "⏱️ {time} · Timer",
- "prev": "◀ Precedente",
- "next": "Successivo ▶",
- "ingredient_used": "✔️ Scalato",
- "ingredient_use_btn": "Usa",
- "ingredient_deduct_title": "Scala dalla dispensa",
- "timer_expired_tts": "Timer {label} scaduto!",
- "timer_warning_tts": "Attenzione! {label}: mancano 10 secondi!",
- "recipe_done_tts": "Ricetta completata! Buon appetito!",
- "expires_chip": "scade {date}",
- "finish": "✅ Fine",
- "step_fallback": "Passo {n}",
- "zerowaste_label": "♻️ Scarto",
- "zerowaste_tip_title": "Consiglio anti-spreco"
- },
- "settings": {
- "title": "⚙️ Configurazione",
- "tab_api": "API Keys",
- "tab_bring": "Bring!",
- "tab_recipe": "Ricette",
- "tab_mealplan": "Piano Settimanale",
- "tab_appliances": "Elettrodomestici",
- "tab_spesa": "Spesa Online",
- "tab_camera": "Fotocamera",
- "tab_security": "Sicurezza",
- "tab_tts": "Voce (TTS)",
- "tab_language": "Lingua",
- "tab_scale": "Bilancia Smart",
- "gemini": {
- "title": "🤖 Google Gemini AI",
- "hint": "Chiave API per identificazione prodotti, scadenze e ricette.",
- "key_label": "API Key Gemini"
+ "app": {
+ "name": "EverShelf",
+ "loading": "Caricamento..."
},
- "bring": {
- "title": "🛒 Bring! Shopping List",
- "hint": "Credenziali per l'integrazione con la lista della spesa Bring!",
- "email_label": "📧 Email Bring!",
- "password_label": "🔒 Password Bring!"
+ "nav": {
+ "title": "EverShelf",
+ "home": "Home",
+ "inventory": "Dispensa",
+ "recipes": "Ricette",
+ "shopping": "Spesa",
+ "log": "Storico",
+ "settings": "Config"
},
- "price": {
- "title": "💰 Stima Prezzi (AI)",
- "hint": "Mostra il costo stimato di ogni prodotto nella lista della spesa usando l'AI.",
- "enabled_label": "Attiva stima prezzi",
- "country_label": "🌍 Paese di riferimento",
- "currency_label": "💱 Valuta",
- "update_label": "🔄 Aggiorna prezzi ogni",
- "update_suffix": "mesi"
+ "btn": {
+ "back": "← Indietro",
+ "save": "💾 Salva",
+ "cancel": "✕ Annulla",
+ "close": "Chiudi",
+ "add": "✅ Aggiungi",
+ "delete": "Elimina",
+ "edit": "✏️ Modifica",
+ "use": "Usa",
+ "edit_item": "Modifica",
+ "search": "🔍 Cerca",
+ "go": "✅ Vai",
+ "toggle_password": "👁️ Mostra/Nascondi",
+ "load_more": "Carica altri...",
+ "save_config": "💾 Salva Configurazione",
+ "save_product": "💾 Salva Prodotto",
+ "restart": "↺ Ricomincia",
+ "reset_default": "↺ Ripristina default",
+ "save_info": "💾 Salva informazioni",
+ "retry": "🔄 Riprova",
+ "yes_short": "Sì",
+ "no_short": "No"
},
- "recipe": {
- "title": "🍳 Preferenze Ricette",
- "hint": "Configura le opzioni predefinite per la generazione delle ricette.",
- "persons_label": "👥 Persone predefinite",
- "options_label": "🎯 Opzioni ricetta predefinite",
- "fast": "⚡ Pasto Veloce",
- "light": "🥗 Poca Fame",
- "expiry": "⏰ Priorità Scadenze",
- "healthy": "💚 Extra Salutare",
- "opened": "📦 Priorità Cose Aperte",
- "zerowaste": "♻️ Zero Sprechi",
- "dietary_label": "🚫 Intolleranze / Restrizioni",
- "dietary_placeholder": "Es: senza glutine, senza lattosio, vegetariano..."
+ "form": {
+ "select_placeholder": "-- Seleziona --"
},
- "mealplan": {
- "title": "📅 Piano Pasti Settimanale",
- "hint": "Imposta la tipologia di pasto per ogni giorno. Sarà usata come guida nella generazione delle ricette.",
- "enabled": "✅ Attiva piano pasti settimanale",
- "legend": "🌤️ = Pranzo · 🌙 = Cena · Tocca un badge per cambiarlo.",
- "types_title": "📋 Tipologie disponibili",
- "reset_btn": "↺ Ripristina default"
+ "locations": {
+ "dispensa": "Dispensa",
+ "frigo": "Frigo",
+ "freezer": "Freezer",
+ "altro": "Altro"
},
- "appliances": {
- "title": "🔌 Elettrodomestici Disponibili",
- "hint": "Indica gli elettrodomestici che hai a disposizione. Saranno considerati nella generazione delle ricette.",
- "new_placeholder": "Es: Macchina del pane, Bimby, Friggitrice ad aria...",
- "quick_title": "Aggiungi velocemente:",
- "oven": "🔥 Forno",
- "microwave": "📡 Microonde",
- "air_fryer": "🍟 Friggitrice ad aria",
- "bread_maker": "🍞 Macchina pane",
- "bimby": "🤖 Bimby/Cookeo",
- "mixer": "🌀 Planetaria",
- "steamer": "♨️ Vaporiera",
- "pressure_cooker": "🫕 Pentola pressione",
- "toaster": "🍞 Tostapane",
- "blender": "🍹 Frullatore",
- "empty": "Nessun elettrodomestico aggiunto"
+ "categories": {
+ "latticini": "Latticini",
+ "carne": "Carne",
+ "pesce": "Pesce",
+ "frutta": "Frutta",
+ "verdura": "Verdura",
+ "pasta": "Pasta & Riso",
+ "pane": "Pane & Forno",
+ "surgelati": "Surgelati",
+ "bevande": "Bevande",
+ "condimenti": "Condimenti",
+ "snack": "Snack & Dolci",
+ "conserve": "Conserve",
+ "cereali": "Cereali & Legumi",
+ "igiene": "Igiene",
+ "pulizia": "Pulizia Casa",
+ "altro": "Altro",
+ "select": "-- Seleziona --"
},
- "spesa": {
- "title": "🛍️ Spesa Online",
- "hint": "Configura il provider per la spesa online.",
- "provider_label": "🏪 Provider",
- "email_label": "📧 Email",
- "password_label": "🔒 Password",
- "login_btn": "🔐 Accedi",
- "ai_prompt_label": "🤖 Prompt AI selezione prodotto",
- "ai_prompt_placeholder": "Istruzioni per l'AI quando deve scegliere tra più prodotti...",
- "ai_prompt_hint": "L'AI usa questo prompt per scegliere il prodotto più appropriato tra i risultati. Lascia vuoto per il comportamento predefinito.",
- "configure_first": "Configura prima la Spesa Online nelle impostazioni",
- "missing_credentials": "Inserisci email e password",
- "login_in_progress": "Accesso in corso...",
- "login_error_prefix": "Errore:",
- "login_network_error_prefix": "Errore di rete:",
- "login_success_default": "Login effettuato!",
- "result_name_label": "Nome",
- "result_card_label": "Tessera",
- "result_pickup_label": "Punto Ritiro",
- "result_points_label": "Punti Fedeltà",
- "connected_relogin": "✅ Connesso — Riaccedi",
- "connected_as": "Connesso come {name}"
+ "units": {
+ "pz": "pz",
+ "conf": "conf",
+ "g": "g",
+ "ml": "ml",
+ "pieces": "Pezzi",
+ "grams": "Grammi",
+ "box": "Confezione",
+ "boxes": "Confezioni",
+ "millilitres": "Millilitri",
+ "from": "da"
},
- "camera": {
- "title": "📷 Fotocamera",
- "hint": "Scegli quale fotocamera utilizzare per la scansione barcode e l'identificazione AI.",
- "device_label": "📸 Fotocamera predefinita",
- "back": "📱 Posteriore (default)",
- "front": "🤳 Anteriore",
- "devices_hint": "Se hai più fotocamere, puoi selezionarne una specifica dall'elenco sopra dopo aver concesso i permessi.",
- "detect_btn": "🔄 Rileva fotocamere"
+ "shopping_sections": {
+ "frutta_verdura": "Frutta & Verdura",
+ "carne_pesce": "Carne & Pesce",
+ "latticini": "Latticini & Fresco",
+ "pane_dolci": "Pane & Dolci",
+ "pasta": "Pasta & Cereali",
+ "conserve": "Conserve & Salse",
+ "surgelati": "Surgelati",
+ "bevande": "Bevande",
+ "pulizia_igiene": "Pulizia & Igiene",
+ "altro": "Altro"
},
- "security": {
- "title": "🔒 Certificato HTTPS",
- "hint": "Se il browser mostra l'errore \"La connessione non è privata\" (ERR_CERT_AUTHORITY_INVALID), devi installare il certificato CA nel dispositivo.",
- "download_btn": "📥 Scarica Certificato CA",
- "token_title": "🔑 Token Impostazioni",
- "token_label": "Token di accesso",
- "token_hint": "Se `SETTINGS_TOKEN` è configurato nel `.env` server, inserisci qui il token prima di salvare le impostazioni. Lascia vuoto se non configurato.",
- "token_placeholder": "(vuoto = nessuna protezione)",
- "token_required_hint": "🔒 Questo server richiede un token per salvare le impostazioni.",
- "cert_instructions": "
Istruzioni per Chrome (Android):1. Scarica il certificato qui sopra
2. Vai in
Impostazioni → Sicurezza e privacy → Altre impostazioni di sicurezza → Installa da archivio dispositivo3. Seleziona il file
EverShelf_CA.crt scaricato
4. Scegli \"CA\" e conferma
5. Riavvia Chrome
Istruzioni per Chrome (PC):1. Scarica il certificato qui sopra
2. Vai in
chrome://settings/certificates (o Impostazioni → Privacy e sicurezza → Sicurezza → Gestisci certificati)
3. Tab \"Autorità\" → Importa → seleziona il file
4. Spunta \"Considera attendibile per identificare siti web\"
5. Riavvia Chrome"
+ "dashboard": {
+ "expired_title": "🚫 Scaduti",
+ "expiring_title": "⏰ Prossime Scadenze",
+ "stats_period": "📊 Ultimi 30 giorni",
+ "opened_title": "📦 Prodotti Aperti",
+ "review_title": "🔍 Da revisionare",
+ "review_hint": "Quantità che sembrano anomale. Conferma se corrette o modifica.",
+ "quick_recipe": "Ricetta veloce con prodotti in scadenza",
+ "banner_review_title": "Quantità anomala",
+ "banner_review_action_ok": "È corretto",
+ "banner_review_action_finish": "🗑️ È finito tutto",
+ "banner_review_action_edit": "Correggi",
+ "banner_review_action_weigh": "Pesa",
+ "banner_review_dismiss": "Ignora",
+ "banner_prediction_title": "Consumo da verificare",
+ "banner_prediction_hint": "La stima di consumo si adatta ai dati recenti: verifica solo se la quantità corrente è corretta.",
+ "banner_prediction_action_confirm": "Confermo {qty} {unit}",
+ "banner_prediction_action_weigh": "Pesa ora",
+ "banner_prediction_action_edit": "Aggiorna quantità",
+ "banner_expired_title": "Prodotto scaduto",
+ "banner_expired_today": "Scaduto oggi",
+ "banner_expired_days": "Scaduto da {days} giorni",
+ "banner_expired_action_use": "Usa comunque",
+ "banner_expired_action_finished": "L'ho finito!",
+ "banner_expired_action_throw": "L'ho buttato",
+ "banner_expired_action_edit": "Correggi data",
+ "banner_expired_action_modify": "Modifica",
+ "banner_expired_action_vacuum": "Metti sottovuoto",
+ "banner_anomaly_action_edit": "Correggi inventario",
+ "banner_anomaly_action_dismiss": "La quantità è giusta",
+ "banner_no_expiry_title": "Scadenza mancante: {name}",
+ "banner_no_expiry_detail": "Questo prodotto non ha una data di scadenza. Vuoi aggiungerla o confermare che non scade?",
+ "banner_no_expiry_action_set": "Imposta scadenza",
+ "banner_no_expiry_action_dismiss": "Non scade ✓",
+ "banner_no_expiry_toast_dismissed": "Segnato come 'non scade'",
+ "banner_expiring_title": "In scadenza",
+ "banner_expiring_today": "Scade oggi!",
+ "banner_expiring_tomorrow": "Scade domani",
+ "banner_expiring_days": "Scade tra {days} giorni",
+ "banner_expiring_action_use": "Usa ora",
+ "banner_finished_title": "è finito?",
+ "banner_finished_detail": "Ho registrato che {name} ha toccato quota zero. È davvero finito o hai ancora delle scorte?",
+ "banner_finished_action_yes": "Sì, è finito",
+ "banner_finished_action_no": "No, ne ho ancora",
+ "banner_review_unusual_pkg_title": "Confezione insolita",
+ "banner_review_unusual_pkg_detail": "Hai impostato una confezione da {qty} {unit} — la dimensione sembra molto alta. Controlla se è corretta o modifica.",
+ "banner_review_low_qty_title": "Quantità molto bassa",
+ "banner_review_low_qty_detail": "Hai solo {qty} in inventario — sembra poco, potrebbe essere un errore. Conferma se è corretto.",
+ "banner_review_high_qty_title": "Quantità insolitamente alta",
+ "banner_review_high_qty_detail": "Hai {qty} in inventario — la cifra sembra molto alta. Conferma se è corretto o correggi.",
+ "banner_prediction_rate_day": "Media ~{n} {unit}/giorno",
+ "banner_prediction_rate_week": "Media ~{n} {unit}/settimana",
+ "banner_prediction_days_ago": "{n} giorni fa hai rifornito",
+ "banner_prediction_more": "stima precedente: {expected} {unit}{time}; quantità attuale: {actual} {unit}.",
+ "banner_prediction_less": "stima: {expected} {unit}{time}; quantità attuale: {actual} {unit}. Se hai cambiato ritmo d'uso, la previsione si aggiorna automaticamente.",
+ "banner_finished_zero": "L'inventario segna zero, ma i movimenti registrati dicono che non dovrebbe essere finito.",
+ "banner_finished_expected": "Secondo le registrazioni dovresti averne ancora {qty} {unit}.",
+ "banner_finished_check": "Puoi controllare?",
+ "banner_anomaly_phantom_title": "hai più scorte del previsto",
+ "banner_anomaly_phantom_detail": "L'inventario segna {inv_qty} {unit}, ma in base alle registrazioni ne dovresti avere solo {expected_qty} {unit}. Hai aggiunto scorte senza registrarle?",
+ "banner_anomaly_untracked_title": "scorte non registrate come entrata",
+ "banner_anomaly_untracked_detail": "Hai
{inv_qty} {unit} in inventario, ma le uscite registrate superano le entrate — le scorte iniziali probabilmente non sono mai state aggiunte come entrata. Puoi correggere la quantità o registrare le entrate mancanti.",
+ "banner_anomaly_ghost_title": "hai meno scorte del previsto",
+ "banner_anomaly_ghost_detail": "In base alle operazioni registrate dovresti avere {expected_qty} {unit} di {name}, ma l'inventario mostra solo {inv_qty} {unit}. Hai prelevato senza registrarlo?",
+ "consumed": "Consumati: {n} ({pct}%)",
+ "wasted": "Buttati: {n} ({pct}%)",
+ "more_opened": "e altri {n} prodotti aperti...",
+ "banner_expired_detail": "{when} · hai ancora
{qty}.",
+ "banner_opened_detail": "{when} in {location} · hai ancora
{qty}.",
+ "banner_explain_title": "Chiedi a Gemini una spiegazione",
+ "banner_explain_btn": "Spiega",
+ "banner_analyzing": "🤖 Analizzo…"
},
- "tts": {
- "title": "🔊 Voce & TTS",
- "hint": "Configura la sintesi vocale tramite qualsiasi API REST esterna. I passi della ricetta e i timer scaduti verranno inviati all'endpoint configurato.",
- "enabled": "✅ Attiva TTS",
- "engine_label": "⚙️ Motore TTS",
- "engine_browser": "🔇 Browser (offline, nessuna configurazione)",
- "engine_server": "🌐 Server esterno (Home Assistant, API REST...)",
- "voice_label": "🗣️ Voce",
- "rate_label": "⚡ Velocità",
- "pitch_label": "🎵 Tono",
- "url_label": "🌐 URL Endpoint",
- "method_label": "📡 Metodo HTTP",
- "auth_label": "🔐 Autenticazione",
- "auth_bearer": "Bearer Token",
- "auth_custom": "Header personalizzato",
- "auth_none": "Nessuna",
- "token_label": "🔑 Bearer Token",
- "custom_header_name": "📋 Nome header",
- "custom_header_value": "📋 Valore header",
- "content_type_label": "📄 Content-Type",
- "payload_key_label": "🗝️ Campo testo nel payload",
- "payload_key_hint": "Nome del campo JSON che conterrà il testo da leggere (es: message, text).",
- "extra_fields_label": "➕ Campi extra (JSON)",
- "extra_fields_placeholder": "{\"entity_id\": \"media_player.living_room\"}",
- "extra_fields_hint": "Campi aggiuntivi da includere nel payload, in formato JSON. Lascia vuoto se non necessario.",
- "test_btn": "🔊 Invia Test Vocale",
- "voices_loading": "Caricamento voci…",
- "voice_not_supported": "Voce non supportata dal browser",
- "voices_none": "Nessuna voce disponibile su questo dispositivo",
- "voices_hint": "Le voci disponibili dipendono dal sistema operativo e dal browser. Su macOS/iOS è disponibile la voce Paola (italiano). Premi ↺ se la lista non si carica.",
- "url_missing": "⚠️ URL endpoint mancante.",
- "test_sending": "⏳ Invio in corso…",
- "test_ok": "✅ Risposta {code} — controlla che l'altoparlante abbia parlato."
+ "inventory": {
+ "title": "Dispensa",
+ "filter_all": "Tutti",
+ "search_placeholder": "🔍 Cerca prodotto...",
+ "recent_title": "🕐 Ultimi usati",
+ "popular_title": "⭐ Più usati",
+ "empty": "Nessun prodotto qui.\nScansiona un prodotto per aggiungerlo!",
+ "no_items_found": "Nessuna voce di inventario trovata",
+ "qty_remainder_suffix": "rimasti",
+ "vacuum_badge": "🫙 Sotto vuoto",
+ "opened_badge": "📭 Aperto",
+ "label_expiry": "📅 Scadenza",
+ "label_storage": "🫙 Conservazione",
+ "label_status": "📭 Stato",
+ "opened_since": "Aperto dal {date}",
+ "label_position": "📍 Posizione",
+ "label_quantity": "📦 Quantità",
+ "label_added": "📅 Aggiunto",
+ "empty_text": "Nessun prodotto qui.
Scansiona un prodotto per aggiungerlo!",
+ "empty_db": "Nessun prodotto nel database.
Scansiona un prodotto per iniziare!",
+ "qty_trace": "< 1"
},
- "language": {
- "title": "🌐 Lingua / Language",
- "hint": "Seleziona la lingua dell'interfaccia. Select the interface language.",
- "label": "🌐 Lingua",
- "restart_notice": "La pagina verrà ricaricata per applicare la nuova lingua."
+ "scan": {
+ "title": "Scansiona",
+ "mode_shopping": "🛒 Modalità Spesa",
+ "mode_shopping_end": "✅ Fine spesa",
+ "spesa_btn": "🛒 Spesa",
+ "zoom": "Zoom",
+ "tab_barcode": "Barcode",
+ "tab_name": "Nome",
+ "tab_ai": "AI",
+ "recents_label": "Recenti",
+ "torch_hint": "Torcia",
+ "torch_on": "Torcia accesa",
+ "torch_off": "Torcia spenta",
+ "torch_unavailable": "Torcia non disponibile su questo dispositivo",
+ "flip_hint": "Cambia fotocamera",
+ "flip_front": "Fotocamera anteriore",
+ "flip_back": "Fotocamera posteriore",
+ "num_ocr_btn": "🔢 Leggi numeri con AI",
+ "num_ocr_searching": "Cerco il codice con AI...",
+ "num_ocr_found": "Codice trovato: {code}",
+ "num_ocr_not_found": "Nessun codice trovato nell'immagine",
+ "barcode_placeholder": "Inserisci codice a barre...",
+ "quick_name_divider": "oppure scrivi il nome",
+ "quick_name_placeholder": "Es: Mele, Zucchine, Pane...",
+ "manual_entry": "✏️ Inserimento Manuale",
+ "ai_identify": "🤖 Identifica con AI",
+ "hint": "Scansiona il barcode, scrivi il nome del prodotto, oppure usa l'AI per identificarlo",
+ "debug_toggle": "🐛 Debug Log",
+ "barcode_acquired": "🔖 Barcode acquisito: {code}",
+ "scan_barcode": "🔖 Scansiona Barcode",
+ "create_named": "Crea {name}",
+ "new_without_barcode": "Nuovo prodotto senza barcode",
+ "stock_in_pantry": "Hai gia in dispensa:"
+ },
+ "action": {
+ "title": "Cosa vuoi fare?",
+ "add_btn": "📥 AGGIUNGI",
+ "add_sub": "in dispensa/frigo",
+ "use_btn": "USA",
+ "use_sub": "dalla dispensa/frigo",
+ "have_title": "📦 Ce l'hai già!",
+ "add_more_sub": "altra quantità",
+ "use_qty_sub": "quanto ne hai usato",
+ "throw_btn": "🗑️ BUTTA",
+ "throw_sub": "butta il prodotto",
+ "edit_sub": "scadenza, luogo…",
+ "create_recipe_btn": "Ricetta",
+ "related_stock_title": "Hai anche in casa"
+ },
+ "add": {
+ "title": "Aggiungi alla Dispensa",
+ "location_label": "📍 Dove lo metti?",
+ "quantity_label": "📦 Quantità",
+ "conf_size_label": "📦 Ogni confezione contiene:",
+ "conf_size_placeholder": "es. 300",
+ "vacuum_label": "🫙 Sotto vuoto",
+ "vacuum_hint": "La scadenza verrà estesa automaticamente",
+ "submit": "✅ Aggiungi",
+ "purchase_type_label": "🛒 Questo prodotto è...",
+ "new_btn": "🆕 Appena comprato",
+ "existing_btn": "📦 Ce l'avevo già",
+ "remaining_label": "📦 Quantità rimasta",
+ "remaining_hint": "Quanto è rimasto approssimativamente?",
+ "remaining_full": "🟢 Pieno",
+ "remaining_half": "🟠 Metà",
+ "estimated_expiry": "Scadenza stimata:",
+ "suffix_freezer": "(freezer)",
+ "suffix_vacuum": "(sotto vuoto)",
+ "hint_modify": "📝 Puoi modificare la data o scansionarla con la fotocamera",
+ "scan_expiry_title": "📷 Scansiona Data Scadenza",
+ "product_added": "✅ {name} aggiunto!{qty}",
+ "suffix_freezer_vacuum": "(freezer + sotto vuoto)",
+ "history_badge_tip": "Media da {n} inserimenti precedenti",
+ "vacuum_question": "Messo sotto vuoto?",
+ "vacuum_saved": "🔒 Sotto vuoto registrato"
+ },
+ "use": {
+ "title": "Usa / Consuma",
+ "location_label": "📍 Da dove?",
+ "quantity_label": "Quanto hai usato?",
+ "change": "cambia",
+ "partial_hint": "Oppure specifica la quantità usata:",
+ "partial_piece_hint": "Hai usato solo una parte?",
+ "piece": "pezzo",
+ "one_whole": "1 intero",
+ "use_all": "🗑️ Usato TUTTO / Finito",
+ "submit": "📤 Usa questa quantità",
+ "available": "📦 Disponibile:",
+ "opened_badge": "APERTO",
+ "not_in_inventory": "⚠️ Prodotto non presente nell'inventario.",
+ "expiry_warning": "⚠️ Usa prima quella{loc} che scade il {date} — {when}!",
+ "expiry_warning_opened": "⚠️ Quella{loc}, aperta da {when} — usala prima!",
+ "throw_title": "🗑️ Butta Prodotto",
+ "throw_all": "🗑️ Butta TUTTO ({qty})",
+ "throw_qty_label": "Quanto butti?",
+ "throw_qty_hint": "oppure specifica la quantità:",
+ "throw_partial_btn": "🗑️ Butta questa quantità",
+ "when_expired": "scaduta da {n} giorni",
+ "when_today": "scade
oggi",
+ "when_tomorrow": "scade
domani",
+ "when_days": "scade tra
{n} giorni",
+ "toast_used": "📤 Usato {qty} di {name}",
+ "toast_bring": "🛒 Prodotto finito → aggiunto a Bring!",
+ "toast_opened_finished": "🔓 Confezione aperta di {name} finita!",
+ "disambiguation_hint": "Cosa intendi con \"finito tutto\"?",
+ "disambiguation_all": "🗑️ Finito TUTTO ({qty})",
+ "error_exceeds_stock": "⚠️ Non puoi usare più di quanto hai disponibile!",
+ "use_all_confirm_title": "✅ Finisci tutto",
+ "use_all_confirm_msg": "Conferma che hai finito tutto il prodotto:",
+ "use_all_confirm_btn": "✅ Sì, finito",
+ "throw_all_confirm_title": "🗑️ Butta tutto",
+ "throw_all_confirm_msg": "Vuoi davvero buttare via tutto il prodotto?",
+ "throw_all_confirm_btn": "🗑️ Sì, butta"
+ },
+ "product": {
+ "title_new": "Nuovo Prodotto",
+ "title_edit": "Modifica Prodotto",
+ "ai_fill": "📷 Scatta foto e identifica con AI",
+ "ai_fill_hint": "L'AI compilerà automaticamente i campi del prodotto",
+ "name_label": "🏷️ Nome Prodotto *",
+ "name_placeholder": "Es: Latte intero, Pasta penne rigate...",
+ "brand_label": "🏢 Marca",
+ "brand_placeholder": "Es: Barilla, Granarolo, Mutti...",
+ "category_label": "📂 Categoria",
+ "unit_label": "📏 Unità di misura",
+ "default_qty_label": "🔢 Quantità default",
+ "conf_size_label": "📦 Ogni confezione contiene:",
+ "conf_size_placeholder": "es. 300",
+ "notes_label": "📝 Note",
+ "notes_placeholder": "Es: senza lattosio, bio, conservare in frigo dopo apertura...",
+ "barcode_label": "🔖 Barcode",
+ "barcode_placeholder": "Codice a barre (se disponibile)",
+ "barcode_hint": "⚠️ Aggiungi il barcode così al prossimo acquisto basta scansionarlo!",
+ "submit": "💾 Salva Prodotto",
+ "name_required": "Inserisci il nome del prodotto",
+ "conf_size_required": "Specifica il contenuto di ogni confezione",
+ "expiry_estimated": "Scadenza stimata:",
+ "scan_expiry": "Scansiona data scadenza",
+ "expiry_hint": "📝 Puoi modificare la data o scansionarla con la fotocamera",
+ "add_batch": "📦 + Lotto con scadenza diversa",
+ "package_info": "📦 Confezione: {info}",
+ "edit_catalog": "⚙️ Modifica scheda prodotto (nome, marca, categoria…)",
+ "not_recognized": "⚠️ Prodotto non riconosciuto",
+ "edit_info": "✏️ Modifica informazioni",
+ "modify_details": "MODIFICA\nscadenza, luogo…",
+ "already_in_pantry": "📋 Già in dispensa",
+ "no_barcode": "Senza barcode",
+ "unknown_product": "Prodotto non riconosciuto",
+ "edit_name_brand": "Modifica nome/marca",
+ "weight_label": "Peso",
+ "origin_label": "Origine",
+ "labels_label": "Etichette",
+ "select_variant": "Seleziona la variante esatta o usa i dati AI:"
+ },
+ "products": {
+ "title": "📦 Tutti i Prodotti",
+ "search_placeholder": "🔍 Cerca prodotto...",
+ "empty": "Nessun prodotto nel database.\nScansiona un prodotto per iniziare!",
+ "no_category": "Nessun prodotto in questa categoria"
+ },
+ "recipes": {
+ "title": "🍳 Ricette",
+ "generate": "✨ Genera nuova ricetta",
+ "archive_empty": "Nessuna ricetta salvata. Genera la tua prima ricetta!",
+ "dialog_title": "🍳 Ricetta",
+ "dialog_desc": "Genero una ricetta sana con gli ingredienti in dispensa, dando priorità a quelli in scadenza.",
+ "meal_label": "🕐 Per quale pasto?",
+ "persons_label": "👥 Quante persone?",
+ "meal_type_label": "🎯 Tipo di pasto",
+ "opt_fast": "⚡ Pasto Veloce",
+ "opt_light": "🥗 Poca Fame",
+ "opt_expiry": "⏰ Priorità Scadenze",
+ "opt_healthy": "💚 Extra Salutare",
+ "opt_opened": "📦 Priorità Cose Aperte",
+ "opt_zero_waste": "♻️ Zero Sprechi",
+ "generate_btn": "✨ Genera Ricetta",
+ "loading_msg": "Sto preparando la ricetta...",
+ "start_cooking": "👨🍳 Modalità Cucina",
+ "regenerate": "🔄 Generane un'altra",
+ "close_btn": "✅ Chiudi",
+ "ingredients_title": "🧾 Ingredienti",
+ "tools_title": "Strumenti necessari",
+ "steps_title": "👨🍳 Procedimento",
+ "no_steps": "Nessun procedimento disponibile",
+ "generate_error": "Errore nella generazione",
+ "stream_interrupted": "Generazione interrotta (risposta incompleta dal server). Controlla i log o riprova.",
+ "persons_short": "pers.",
+ "use_ingredient_title": "Usa ingrediente",
+ "recipe_qty_label": "Ricetta",
+ "from_where_label": "Da dove?",
+ "amount_label": "Quanto",
+ "use_amount_btn": "Usa questa quantità",
+ "use_all_btn": "Usa TUTTO / Finito",
+ "packs_label": "Confezioni",
+ "quantity_in_total": "Quantità in {unit} (totale: {total})",
+ "packs_of_have": "Confezioni da {size} (hai {count} conf)",
+ "scale_wait_stable": "Attendi 10s di stabilità per la compilazione automatica…",
+ "ingredient_scaled_toast": "📦 Ingrediente scalato dalla dispensa!",
+ "finished_added_bring_toast": "🛒 Prodotto finito → aggiunto a Bring!",
+ "load_error": "Errore nel caricamento"
+ },
+ "shopping": {
+ "title": "🛒 Lista della Spesa",
+ "bring_loading": "Connessione a Bring!...",
+ "bring_not_configured": "Bring! non è configurato. Aggiungi email e password nelle
impostazioni.",
+ "tab_to_buy": "🛍️ Da comprare",
+ "tab_forecast": "🧠 In previsione",
+ "total_label": "💰 Totale stimato",
+ "section_to_buy": "🛍️ Da comprare",
+ "suggestions_title": "💡 Suggerimenti AI",
+ "suggestions_add": "✅ Aggiungi selezionati a Bring!",
+ "search_prices": "🔍 Cerca tutti i prezzi",
+ "suggest_btn": "Suggerisci cosa comprare",
+ "smart_title": "🧠 Previsioni intelligenti",
+ "smart_empty": "Nessuna previsione disponibile.
Aggiungi prodotti alla dispensa per ricevere previsioni intelligenti.",
+ "smart_filter_all": "Tutti",
+ "smart_filter_critical": "🔴 Urgenti",
+ "smart_filter_high": "🟠 Presto",
+ "smart_filter_medium": "🟡 Pianifica",
+ "smart_filter_low": "🟢 Previsione",
+ "smart_add": "🛒 Aggiungi selezionati a Bring!",
+ "empty": "Lista della spesa vuota!\nUsa il pulsante sotto per generare suggerimenti.",
+ "already_in_list": "🛒 \"{name}\" già nella lista della spesa",
+ "already_in_list_short": "ℹ️ Già nella lista della spesa",
+ "add_prompt": "Vuoi aggiungerlo alla lista della spesa?",
+ "smart_already": "📊 La spesa intelligente prevede già {name}",
+ "all_searched": "Tutti i prodotti sono già stati cercati. Usa 🔄 per ricercare singoli.",
+ "search_complete": "Ricerca completata: {count} prodotti",
+ "removed_sufficient": "🧹 {removed} prodotto/i con scorte sufficienti rimosso/i dalla lista",
+ "suggest_buy": "🛒 Compra: {qty} {unit}",
+ "suggest_buy_approx": "🛒 Almeno: {qty} {unit}",
+ "suggest_buy_tip": "Quantità suggerita in base al consumo degli ultimi 14 giorni",
+ "suggest_buy_approx_tip": "Stima minima basata sul consumo (compra la confezione più vicina)",
+ "bring_badge": "🛒 Già su Bring!",
+ "add_urgent_toast": "🔴 {n} prodotto/i urgente/i aggiunto/i automaticamente a Bring!",
+ "migration_done": "✅ {migrated} aggiornati, {skipped} già ok",
+ "added_to_bring": "🛒 {n} prodotti aggiunti a Bring!",
+ "added_to_bring_skip": "{n} già presenti",
+ "all_on_bring": "Tutti i prodotti erano già su Bring!",
+ "freq_high": "📈 Uso frequente",
+ "freq_regular": "📊 Uso regolare",
+ "freq_occasional": "📉 Uso occasionale",
+ "out_of_stock": "Esaurito",
+ "scan_toast": "📷 Scansiona: {name}",
+ "empty_category": "Nessun prodotto in questa categoria",
+ "session_empty": "🛒 Nessun prodotto ancora",
+ "urgency_critical": "Urgente",
+ "urgency_high": "Presto",
+ "urgency_medium": "Pianifica",
+ "urgency_low": "Previsione",
+ "urgency_medium_short": "Medio",
+ "urgency_low_short": "Ok",
+ "tag_urgent": "🔴 Urgente",
+ "tag_priority": "⭐ Priorità",
+ "tag_check": "✅ Verificare",
+ "smart_already_predicted": "📊 La spesa intelligente prevede già
{name}{urgency}.",
+ "item_removed": "✅ {name} rimosso dalla lista!",
+ "urgency_spec_critical": "⚡ Urgente",
+ "urgency_spec_high": "🟠 Presto",
+ "bring_add_n": "Aggiungi {n} a Bring!",
+ "bring_add_selected": "Aggiungi selezionati a Bring!",
+ "bring_adding": "Aggiunta in corso...",
+ "bring_added_one": "1 prodotto aggiunto a Bring!",
+ "bring_added_many": "{n} prodotti aggiunti a Bring!",
+ "bring_skipped": "({n} già in lista)",
+ "force_sync": "Forza sincronizzazione Bring!",
+ "scan_target_label": "Stai cercando",
+ "scan_target_found": "Trovato! Rimuovi dalla lista",
+ "bring_add_one": "Aggiungi 1 prodotto a Bring!",
+ "bring_add_many": "Aggiungi {n} prodotti a Bring!",
+ "syncing": "Sincronizzazione…",
+ "sync_done": "Sincronizzazione completata",
+ "price_searching": "Cerco...",
+ "search_action": "Ricerca",
+ "open_action": "Apri",
+ "not_found": "Non trovato",
+ "search_price": "Cerca prezzo",
+ "tap_to_scan": "Tocca per scansionare",
+ "tag_title": "Tag",
+ "remove_title": "Rimuovi",
+ "found_count": "{found}/{total} prodotti trovati",
+ "savings_offers": "· 🏷️ Risparmi €{amount} con le offerte",
+ "searching_progress": "Cerco {current}/{total}...",
+ "remove_error": "Errore nella rimozione",
+ "btn_fetch_prices": "Cerca i prezzi",
+ "price_total_label": "💰 Spesa stimata:",
+ "price_loading": "Ricerca prezzi…",
+ "price_not_found": "prezzo n/d",
+ "suggest_loading": "Analisi in corso...",
+ "suggest_error": "Errore nella generazione",
+ "priority_high": "Alta",
+ "priority_medium": "Media",
+ "priority_low": "Bassa",
+ "smart_last_update": "Aggiornato {time}",
+ "names_already_updated": "Tutti i nomi sono già aggiornati",
+ "pantry_hint": "Hai gia {qty} in dispensa"
+ },
+ "ai": {
+ "title": "🤖 Identificazione AI",
+ "capture": "📸 Scatta Foto",
+ "retake": "🔄 Riscatta",
+ "hint": "Scatta una foto del prodotto e l'AI cercherà di identificarlo",
+ "identifying": "🤖 Identifico il prodotto...",
+ "no_api_key": "⚠️ Chiave API Gemini non configurata.\n
Aggiungi GEMINI_API_KEY nel file .env sul server.",
+ "fields_filled": "✅ Campi compilati dall'AI",
+ "use_data": "✅ Usa dati AI",
+ "use_data_no_barcode": "✅ Usa dati AI (senza barcode)"
+ },
+ "log": {
+ "title": "📒 Storico",
+ "type_added": "Aggiunto",
+ "type_waste": "Buttato",
+ "type_used": "Usato",
+ "type_bring": "Aggiunto a Bring!",
+ "undone_badge": "Annullato",
+ "undo_title": "Annulla questa operazione",
+ "load_error": "Errore nel caricamento log",
+ "empty": "Nessuna operazione registrata.",
+ "undo_action_remove": "rimozione di",
+ "undo_action_restore": "ripristino di",
+ "undo_confirm": "Annullare questa operazione?\n→ {action} {name}",
+ "undo_success": "↩ Operazione annullata per {name}",
+ "already_undone": "Operazione già annullata",
+ "too_old": "Non è possibile annullare operazioni più vecchie di 24 ore",
+ "undo_error": "Errore durante l'annullamento",
+ "recipe_prefix": "Ricetta"
+ },
+ "chat": {
+ "title": "Gemini Chef",
+ "welcome": "Ciao! Sono il tuo assistente cucina",
+ "welcome_desc": "Chiedimi di prepararti un succo, uno spuntino, un piatto veloce... Conosco la tua dispensa, i tuoi elettrodomestici e le tue preferenze!",
+ "suggestion_snack": "🍿 Spuntino veloce",
+ "suggestion_juice": "🥤 Succo/Frullato",
+ "suggestion_light": "🥗 Qualcosa di leggero",
+ "suggestion_expiry": "⏰ Usa le scadenze",
+ "clear": "Nuova conversazione",
+ "placeholder": "Chiedi qualcosa...",
+ "cleared": "Chat cancellata",
+ "suggestion_snack_text": "Cosa posso preparare per uno spuntino veloce?",
+ "suggestion_juice_text": "Fammi un succo o frullato con quello che ho",
+ "suggestion_light_text": "Ho fame ma voglio qualcosa di leggero",
+ "suggestion_expiry_text": "Cosa sta per scadere e come posso usarlo?",
+ "transfer_to_recipes": "Trasferisci a Ricette",
+ "transferring": "Trasferimento in corso...",
+ "transferred": "Aggiunta alle Ricette!",
+ "open_recipe": "Apri la ricetta",
+ "quick_recipe_prompt": "Suggeriscimi una ricetta veloce PER UNA PERSONA usando i prodotti che scadono prima! Ignora i prodotti in freezer, concentrati su frigo e dispensa."
+ },
+ "cooking": {
+ "close": "Chiudi",
+ "tts_btn": "Leggi ad alta voce",
+ "restart": "↺ Ricomincia",
+ "replay": "🔊 Rileggi",
+ "timer": "⏱️ {time} · Timer",
+ "prev": "◀ Precedente",
+ "next": "Successivo ▶",
+ "ingredient_used": "✔️ Scalato",
+ "ingredient_use_btn": "Usa",
+ "ingredient_deduct_title": "Scala dalla dispensa",
+ "timer_expired_tts": "Timer {label} scaduto!",
+ "timer_warning_tts": "Attenzione! {label}: mancano 10 secondi!",
+ "recipe_done_tts": "Ricetta completata! Buon appetito!",
+ "expires_chip": "scade {date}",
+ "finish": "✅ Fine",
+ "step_fallback": "Passo {n}",
+ "zerowaste_label": "♻️ Scarto",
+ "zerowaste_tip_title": "Consiglio anti-spreco"
+ },
+ "settings": {
+ "title": "⚙️ Configurazione",
+ "tab_api": "API Keys",
+ "tab_bring": "Bring!",
+ "tab_recipe": "Ricette",
+ "tab_mealplan": "Piano Settimanale",
+ "tab_appliances": "Elettrodomestici",
+ "tab_spesa": "Spesa Online",
+ "tab_camera": "Fotocamera",
+ "tab_security": "Sicurezza",
+ "tab_tts": "Voce (TTS)",
+ "tab_language": "Lingua",
+ "tab_scale": "Bilancia Smart",
+ "gemini": {
+ "title": "🤖 Google Gemini AI",
+ "hint": "Chiave API per identificazione prodotti, scadenze e ricette.",
+ "key_label": "API Key Gemini"
+ },
+ "bring": {
+ "title": "🛒 Bring! Shopping List",
+ "hint": "Credenziali per l'integrazione con la lista della spesa Bring!",
+ "email_label": "📧 Email Bring!",
+ "password_label": "🔒 Password Bring!"
+ },
+ "price": {
+ "title": "💰 Stima Prezzi (AI)",
+ "hint": "Mostra il costo stimato di ogni prodotto nella lista della spesa usando l'AI.",
+ "enabled_label": "Attiva stima prezzi",
+ "country_label": "🌍 Paese di riferimento",
+ "currency_label": "💱 Valuta",
+ "update_label": "🔄 Aggiorna prezzi ogni",
+ "update_suffix": "mesi"
+ },
+ "recipe": {
+ "title": "🍳 Preferenze Ricette",
+ "hint": "Configura le opzioni predefinite per la generazione delle ricette.",
+ "persons_label": "👥 Persone predefinite",
+ "options_label": "🎯 Opzioni ricetta predefinite",
+ "fast": "⚡ Pasto Veloce",
+ "light": "🥗 Poca Fame",
+ "expiry": "⏰ Priorità Scadenze",
+ "healthy": "💚 Extra Salutare",
+ "opened": "📦 Priorità Cose Aperte",
+ "zerowaste": "♻️ Zero Sprechi",
+ "dietary_label": "🚫 Intolleranze / Restrizioni",
+ "dietary_placeholder": "Es: senza glutine, senza lattosio, vegetariano..."
+ },
+ "mealplan": {
+ "title": "📅 Piano Pasti Settimanale",
+ "hint": "Imposta la tipologia di pasto per ogni giorno. Sarà usata come guida nella generazione delle ricette.",
+ "enabled": "✅ Attiva piano pasti settimanale",
+ "legend": "🌤️ = Pranzo · 🌙 = Cena · Tocca un badge per cambiarlo.",
+ "types_title": "📋 Tipologie disponibili",
+ "reset_btn": "↺ Ripristina default"
+ },
+ "appliances": {
+ "title": "🔌 Elettrodomestici Disponibili",
+ "hint": "Indica gli elettrodomestici che hai a disposizione. Saranno considerati nella generazione delle ricette.",
+ "new_placeholder": "Es: Macchina del pane, Bimby, Friggitrice ad aria...",
+ "quick_title": "Aggiungi velocemente:",
+ "oven": "🔥 Forno",
+ "microwave": "📡 Microonde",
+ "air_fryer": "🍟 Friggitrice ad aria",
+ "bread_maker": "🍞 Macchina pane",
+ "bimby": "🤖 Bimby/Cookeo",
+ "mixer": "🌀 Planetaria",
+ "steamer": "♨️ Vaporiera",
+ "pressure_cooker": "🫕 Pentola pressione",
+ "toaster": "🍞 Tostapane",
+ "blender": "🍹 Frullatore",
+ "empty": "Nessun elettrodomestico aggiunto"
+ },
+ "spesa": {
+ "title": "🛍️ Spesa Online",
+ "hint": "Configura il provider per la spesa online.",
+ "provider_label": "🏪 Provider",
+ "email_label": "📧 Email",
+ "password_label": "🔒 Password",
+ "login_btn": "🔐 Accedi",
+ "ai_prompt_label": "🤖 Prompt AI selezione prodotto",
+ "ai_prompt_placeholder": "Istruzioni per l'AI quando deve scegliere tra più prodotti...",
+ "ai_prompt_hint": "L'AI usa questo prompt per scegliere il prodotto più appropriato tra i risultati. Lascia vuoto per il comportamento predefinito.",
+ "configure_first": "Configura prima la Spesa Online nelle impostazioni",
+ "missing_credentials": "Inserisci email e password",
+ "login_in_progress": "Accesso in corso...",
+ "login_error_prefix": "Errore:",
+ "login_network_error_prefix": "Errore di rete:",
+ "login_success_default": "Login effettuato!",
+ "result_name_label": "Nome",
+ "result_card_label": "Tessera",
+ "result_pickup_label": "Punto Ritiro",
+ "result_points_label": "Punti Fedeltà",
+ "connected_relogin": "✅ Connesso — Riaccedi",
+ "connected_as": "Connesso come {name}"
+ },
+ "camera": {
+ "title": "📷 Fotocamera",
+ "hint": "Scegli quale fotocamera utilizzare per la scansione barcode e l'identificazione AI.",
+ "device_label": "📸 Fotocamera predefinita",
+ "back": "📱 Posteriore (default)",
+ "front": "🤳 Anteriore",
+ "devices_hint": "Se hai più fotocamere, puoi selezionarne una specifica dall'elenco sopra dopo aver concesso i permessi.",
+ "detect_btn": "🔄 Rileva fotocamere"
+ },
+ "security": {
+ "title": "🔒 Certificato HTTPS",
+ "hint": "Se il browser mostra l'errore \"La connessione non è privata\" (ERR_CERT_AUTHORITY_INVALID), devi installare il certificato CA nel dispositivo.",
+ "download_btn": "📥 Scarica Certificato CA",
+ "token_title": "🔑 Token Impostazioni",
+ "token_label": "Token di accesso",
+ "token_hint": "Se `SETTINGS_TOKEN` è configurato nel `.env` server, inserisci qui il token prima di salvare le impostazioni. Lascia vuoto se non configurato.",
+ "token_placeholder": "(vuoto = nessuna protezione)",
+ "token_required_hint": "🔒 Questo server richiede un token per salvare le impostazioni.",
+ "cert_instructions": "
Istruzioni per Chrome (Android):1. Scarica il certificato qui sopra
2. Vai in
Impostazioni → Sicurezza e privacy → Altre impostazioni di sicurezza → Installa da archivio dispositivo3. Seleziona il file
EverShelf_CA.crt scaricato
4. Scegli \"CA\" e conferma
5. Riavvia Chrome
Istruzioni per Chrome (PC):1. Scarica il certificato qui sopra
2. Vai in
chrome://settings/certificates (o Impostazioni → Privacy e sicurezza → Sicurezza → Gestisci certificati)
3. Tab \"Autorità\" → Importa → seleziona il file
4. Spunta \"Considera attendibile per identificare siti web\"
5. Riavvia Chrome"
+ },
+ "tts": {
+ "title": "🔊 Voce & TTS",
+ "hint": "Configura la sintesi vocale tramite qualsiasi API REST esterna. I passi della ricetta e i timer scaduti verranno inviati all'endpoint configurato.",
+ "enabled": "✅ Attiva TTS",
+ "engine_label": "⚙️ Motore TTS",
+ "engine_browser": "🔇 Browser (offline, nessuna configurazione)",
+ "engine_server": "🌐 Server esterno (Home Assistant, API REST...)",
+ "voice_label": "🗣️ Voce",
+ "rate_label": "⚡ Velocità",
+ "pitch_label": "🎵 Tono",
+ "url_label": "🌐 URL Endpoint",
+ "method_label": "📡 Metodo HTTP",
+ "auth_label": "🔐 Autenticazione",
+ "auth_bearer": "Bearer Token",
+ "auth_custom": "Header personalizzato",
+ "auth_none": "Nessuna",
+ "token_label": "🔑 Bearer Token",
+ "custom_header_name": "📋 Nome header",
+ "custom_header_value": "📋 Valore header",
+ "content_type_label": "📄 Content-Type",
+ "payload_key_label": "🗝️ Campo testo nel payload",
+ "payload_key_hint": "Nome del campo JSON che conterrà il testo da leggere (es: message, text).",
+ "extra_fields_label": "➕ Campi extra (JSON)",
+ "extra_fields_placeholder": "{\"entity_id\": \"media_player.living_room\"}",
+ "extra_fields_hint": "Campi aggiuntivi da includere nel payload, in formato JSON. Lascia vuoto se non necessario.",
+ "test_btn": "🔊 Invia Test Vocale",
+ "voices_loading": "Caricamento voci…",
+ "voice_not_supported": "Voce non supportata dal browser",
+ "voices_none": "Nessuna voce disponibile su questo dispositivo",
+ "voices_hint": "Le voci disponibili dipendono dal sistema operativo e dal browser. Su macOS/iOS è disponibile la voce Paola (italiano). Premi ↺ se la lista non si carica.",
+ "url_missing": "⚠️ URL endpoint mancante.",
+ "test_sending": "⏳ Invio in corso…",
+ "test_ok": "✅ Risposta {code} — controlla che l'altoparlante abbia parlato."
+ },
+ "language": {
+ "title": "🌐 Lingua / Language",
+ "hint": "Seleziona la lingua dell'interfaccia. Select the interface language.",
+ "label": "🌐 Lingua",
+ "restart_notice": "La pagina verrà ricaricata per applicare la nuova lingua."
+ },
+ "screensaver": {
+ "label": "Attiva salvaschermo",
+ "card_title": "🌙 Salvaschermo",
+ "card_hint": "Mostra un orologio con fatti utili dopo 5 minuti di inattività. Di default è disattivato.",
+ "timeout_1": "1 minuto",
+ "timeout_2": "2 minuti",
+ "timeout_5": "5 minuti",
+ "timeout_10": "10 minuti",
+ "timeout_15": "15 minuti",
+ "timeout_30": "30 minuti",
+ "timeout_60": "1 ora",
+ "start_after": "⏱️ Avvia dopo"
+ },
+ "scale": {
+ "title": "⚖️ Bilancia Smart",
+ "hint": "Collega una bilancia Bluetooth tramite il gateway Android per leggere il peso automaticamente.",
+ "tab": "Bilancia Smart",
+ "enabled": "✅ Abilita bilancia smart",
+ "url_label": "🌐 URL Gateway WebSocket",
+ "url_placeholder": "ws://192.168.1.x:8765",
+ "url_hint": "URL mostrato dall'app Android (stessa rete Wi-Fi). Es:",
+ "test_btn": "🔗 Testa connessione",
+ "download_btn": "📥 Scarica Gateway Android (APK)",
+ "download_hint": "App Android che fa da ponte tra la bilancia BLE e questo sito.",
+ "download_sub": "Sorgente: evershelf-scale-gateway/ nella root del progetto",
+ "live_weight": "peso in tempo reale",
+ "auto_reconnect": "🔁 Riconnessione: automatica",
+ "kiosk_title": "📡 Bilancia BLE integrata nel Kiosk",
+ "kiosk_hint": "La bilancia è gestita direttamente dal Gateway BLE interno al kiosk. Per abbinare un nuovo dispositivo usa il wizard di configurazione.",
+ "kiosk_reconfigure": "🔄 Riconfigura bilancia BLE",
+ "ble_protocols": "
🔌 Protocolli BLE supportati:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — peso, grasso, BMI
- Xiaomi Mi Body Composition Scale 2
- Generico — heuristica automatica su 100+ modelli
"
+ },
+ "kiosk": {
+ "hint": "Trasforma un tablet Android in un pannello EverShelf sempre acceso, con bilancia BLE integrata.",
+ "download_btn": "📥 Scarica EverShelf Kiosk (APK)",
+ "download_sub": "Modalità kiosk full-screen + gateway bilancia integrato. Sorgente: evershelf-kiosk/",
+ "native_title": "Configurazione Kiosk",
+ "native_hint": "URL server, bilancia BLE, salvaschermo e setup wizard.",
+ "native_btn": "Apri configurazione kiosk",
+ "native_tap_hint": "Tocca la rotella in alto a destra",
+ "native_update_hint": "Aggiorna l'app kiosk per usare questa funzione",
+ "update_title": "Aggiornamento Kiosk",
+ "check_updates_btn": "🔍 Cerca aggiornamenti",
+ "needs_update": "⚠️ Il kiosk installato non supporta questa funzione. Aggiorna l'app kiosk per abilitarla."
+ },
+ "saved": "✅ Configurazione salvata!",
+ "saved_local": "✅ Configurazione salvata localmente",
+ "saved_local_error": "⚠️ Salvato localmente, errore server: {error}",
+ "theme": {
+ "title": "🌙 Tema / Aspetto",
+ "hint": "Scegli il tema dell interfaccia.",
+ "label": "🌙 Tema",
+ "off": "☀️ Chiaro",
+ "on": "🌙 Scuro",
+ "auto": "🔄 Automatico (orario)"
+ },
+ "zerowaste": {
+ "card_title": "♻️ Suggerimenti zero-waste",
+ "card_hint": "Durante la cottura, mostra consigli su come riutilizzare gli scarti prodotti in ogni passo (bucce, acqua di cottura, ecc.). Disattivo per impostazione predefinita.",
+ "label": "Mostra suggerimenti durante la cottura"
+ },
+ "backup": {
+ "tab": "Backup",
+ "local_title": "Backup Locale",
+ "local_hint": "Snapshot giornaliero del database. Configura quanti giorni di backup conservare.",
+ "enabled": "Backup automatico quotidiano",
+ "retention_days": "Giorni di retention",
+ "retention_info": "I backup vengono conservati per",
+ "backup_now": "Backup Ora",
+ "backing_up": "Backup in corso…",
+ "backed_up": "Backup completato",
+ "backup_error": "Errore backup",
+ "last_backup": "Ultimo backup",
+ "no_backup_yet": "Nessun backup ancora eseguito",
+ "list_empty": "Nessun backup disponibile",
+ "restore_btn": "Ripristina",
+ "restore_confirm": "Ripristinare il backup",
+ "delete_btn": "Elimina",
+ "delete_confirm": "Eliminare il backup",
+ "gdrive_title": "Google Drive",
+ "gdrive_hint": "Backup automatici su Google Drive via OAuth 2.0. Nessuna libreria esterna richiesta.",
+ "gdrive_enabled": "Abilita backup Google Drive",
+ "gdrive_folder_id": "ID Cartella Drive",
+ "gdrive_folder_id_hint": "Copia l'ID dalla URL della cartella Drive: …/folders/
ID",
+ "gdrive_retention_days": "Retention Drive (giorni, 0=tutto)",
+ "gdrive_test": "Testa Connessione",
+ "gdrive_ok": "Connessione riuscita!",
+ "gdrive_error": "Connessione fallita",
+ "gdrive_push_now": "Carica Ora su Drive",
+ "gdrive_pushing": "Upload in corso…",
+ "gdrive_pushed": "Caricato su Drive",
+ "gdrive_wizard_hint": "Opzionale: backup giornaliero automatico su Google Drive via OAuth 2.0.",
+ "gdrive_skip": "Salta — configura dopo in Impostazioni",
+ "gdrive_client_id": "Client ID",
+ "gdrive_client_secret": "Client Secret",
+ "gdrive_redirect_uri_label": "Redirect URI (da aggiungere in Google Cloud Console):",
+ "gdrive_redirect_uri_hint": "Aggiungi
http://localhost come URI di reindirizzamento autorizzato in Google Cloud Console. Funziona su qualsiasi server, anche senza dominio pubblico.",
+ "gdrive_oauth_authorize": "Autorizza con Google",
+ "gdrive_oauth_authorized": "Autorizzato",
+ "gdrive_oauth_not_authorized": "Non ancora autorizzato",
+ "gdrive_oauth_window_opened": "Finestra aperta — autorizza e torna qui",
+ "gdrive_oauth_how_to": "Come configurare OAuth 2.0 (passo dopo passo)",
+ "gdrive_oauth_steps": "
Vai su console.cloud.google.com e seleziona il progettoAbilita la Google Drive API: API e servizi → Abilita API → Google Drive APIVai su API e servizi → Credenziali → Crea credenziali → ID client OAuth 2.0Tipo applicazione: Applicazione web; aggiungi http://localhost come URI di reindirizzamento autorizzatoCopia Client ID e Client Secret nei campi qui sopra e salvaClicca Autorizza con Google, accedi e concedi l'accessoIl browser aprirà http://localhost (possibile errore di connessione è normale): copia l'URL dalla barra degli indirizzi e incollalo nel campo che appare qui sotto",
+ "gdrive_code_title": "Incolla l'URL o il codice di autorizzazione",
+ "gdrive_code_hint": "Dopo aver autorizzato, il browser aprirà http://localhost e potrebbe mostrare un errore. Copia l'URL dalla barra degli indirizzi (es.
http://localhost/?code=4%2F0A...) e incollalo qui.",
+ "gdrive_code_submit": "Conferma",
+ "gdrive_code_empty": "Incolla prima l'URL o il codice di autorizzazione"
+ },
+ "info": {
+ "tab": "Info",
+ "ai_title": "Gemini AI — Utilizzo Token",
+ "ai_hint": "Consumo mensile e costo stimato per la chiave API corrente.",
+ "loading": "Caricamento…",
+ "total_tokens": "Token totali",
+ "est_cost": "Costo stimato",
+ "input_tok": "Token input",
+ "output_tok": "Token output",
+ "ai_calls": "Chiamate",
+ "by_action": "Dettaglio per funzione",
+ "by_model": "Dettaglio per modello",
+ "pricing_note": "Prezzi di riferimento Gemini: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.",
+ "system_title": "Sistema",
+ "db_size": "Database",
+ "log_size": "Log",
+ "log_level": "Livello log",
+ "ai_overview": "Prospetto utilizzo AI, inventario e stato del sistema",
+ "calls_unit": "call",
+ "inv_title": "Inventario",
+ "inv_active": "Attivi",
+ "inv_products": "Prodotti totali",
+ "inv_expiring": "In scadenza (7gg)",
+ "inv_expired": "Scaduti",
+ "inv_finished": "Finiti",
+ "act_title": "Attività del mese",
+ "act_tx_month": "Movimenti",
+ "act_restock": "Acquisti",
+ "act_use": "Consumi",
+ "act_new_products": "Nuovi prodotti",
+ "act_tx_year": "Movimenti anno",
+ "price_cache": "Cache prezzi",
+ "cache_entries": "prodotti",
+ "last_backup": "Ultimo backup",
+ "bring_days": "token scade tra {n} giorni",
+ "bring_expired": "token scaduto",
+ "year_label": "Anno {year}",
+ "currency_title": "Valuta",
+ "currency_hint": "La valuta usata per tutti i costi e i prezzi nell'app."
+ },
+ "tab_general": "Generali",
+ "shopping": {
+ "tab": "Lista spesa",
+ "title": "Lista della spesa",
+ "hint": "Configura la lista della spesa integrata o collega Bring!.",
+ "enable_label": "Abilita lista della spesa",
+ "mode_label": "Provider",
+ "mode_internal": "Interno (senza Bring!)",
+ "mode_bring": "Bring! (app esterna)",
+ "bring_section_title": "Configurazione Bring!",
+ "ai_section_title": "Assistenza AI",
+ "smart_suggestions_label": "Suggerimenti AI",
+ "forecast_label": "Previsione prodotti in esaurimento",
+ "auto_add_label": "Aggiungi automaticamente quando",
+ "auto_add_suffix": "rimasto in magazzino (0 = solo quando esaurito)"
+ },
+ "ha": {
+ "tab": "Home Assistant",
+ "title": "Home Assistant",
+ "hint": "Collega EverShelf a Home Assistant per automazioni, notifiche push e sensori REST.",
+ "enabled": "Abilita integrazione Home Assistant",
+ "connection_title": "Connessione",
+ "url_label": "URL Home Assistant",
+ "url_placeholder": "http://192.168.1.50:8123",
+ "url_hint": "URL del tuo server Home Assistant (es. http://homeassistant.local:8123).",
+ "token_label": "Long-Lived Access Token",
+ "token_hint": "Genera da Profilo HA → Sicurezza → Token di accesso a lungo termine.",
+ "token_placeholder": "eyJhbGci...",
+ "token_saved": "Token salvato (non mostrato per sicurezza)",
+ "test_btn": "Testa connessione",
+ "test_ok": "Connesso a {version}",
+ "test_fail": "Connessione fallita: {error}",
+ "test_bad_token": "HA raggiungibile ma token non valido",
+ "testing": "Test in corso…",
+ "error_no_url": "Inserisci prima l'URL di Home Assistant.",
+ "tts_title": "TTS su Speaker Smart",
+ "tts_hint": "Leggi i passi delle ricette su un media player di Home Assistant.",
+ "tts_entity_label": "Entity ID media player",
+ "tts_entity_placeholder": "media_player.living_room",
+ "tts_entity_hint": "Entity ID del media player su cui vuoi la voce. Puoi trovarlo in HA: Strumenti per sviluppatori → Stati.",
+ "tts_platform_label": "Piattaforma TTS",
+ "tts_platform_speak": "tts.speak (raccomandato)",
+ "tts_platform_notify": "notify.* (servizio notifiche)",
+ "tts_apply_btn": "Applica preset HA al tab TTS",
+ "tts_apply_hint": "Pre-compila il tab TTS con l'URL e il token di Home Assistant.",
+ "tts_preset_applied": "Preset HA applicato al tab TTS.",
+ "webhook_title": "Automazioni Webhook",
+ "webhook_hint": "Invia dati a Home Assistant quando avvengono eventi nella dispensa. Crea un'automazione in HA con trigger Webhook e copia l'ID generato.",
+ "webhook_id_label": "Webhook ID",
+ "webhook_id_placeholder": "evershelf_webhook_abc123",
+ "webhook_id_hint": "ID del webhook creato in HA. Copia da: HA → Impostazioni → Automazioni → Crea → Trigger Webhook.",
+ "webhook_events_label": "Notifica per questi eventi",
+ "event_expiry": "Prodotti in scadenza (giornaliero)",
+ "event_shopping": "Aggiunta alla lista della spesa",
+ "event_stock": "Aggiornamento scorte",
+ "expiry_days_label": "Anticipo scadenze (giorni)",
+ "expiry_days_hint": "Invia la notifica di scadenza N giorni prima della data di scadenza.",
+ "webhook_help": "In HA: Impostazioni → Automazioni → Crea automazione → Trigger: Webhook → copia l'ID generato qui sopra.",
+ "notify_title": "Notifiche Push",
+ "notify_hint": "Invia notifiche push al tuo telefono tramite il servizio notify di Home Assistant.",
+ "notify_service_label": "Servizio notify",
+ "notify_service_placeholder": "notify.mobile_app_mio_telefono",
+ "notify_service_hint": "Nome del servizio notify HA (es. notify.mobile_app_phone). Lascia vuoto per disabilitare.",
+ "sensor_title": "Sensori REST",
+ "sensor_hint": "Aggiungi a configuration.yaml per creare sensori EverShelf in Home Assistant.",
+ "sensor_copy_btn": "Copia YAML",
+ "sensor_copied": "YAML copiato negli appunti!",
+ "save_btn": "Salva impostazioni HA",
+ "ha_hint": "Se usi Home Assistant, usa il tab Home Assistant per configurare TTS, webhook e sensori."
+ }
+ },
+ "expiry": {
+ "today": "OGGI",
+ "tomorrow": "Domani",
+ "days": "{days} giorni",
+ "expired_days": "Da {days}g",
+ "expired_yesterday": "Da ieri",
+ "expired_today": "Oggi",
+ "badge_today": "⚠️ Scade oggi!",
+ "badge_tomorrow": "⏰ Domani",
+ "badge_tomorrow_long": "⏰ Scade domani",
+ "badge_days": "⏰ {n} giorni",
+ "badge_expired_ago": "⚠️ Scaduto da {n}g",
+ "badge_expired": "⛔ Scaduto!",
+ "badge_stable": "✅ Stabile",
+ "badge_expiring_short": "⏰ Scade fra {n}gg",
+ "badge_ok_still": "✅ Ancora {n}gg",
+ "badge_expires_red": "🔴 Scade tra {n}g",
+ "badge_expires_yellow": "🟡 Scade tra {n}g",
+ "badge_expired_bare": "⚠️ Scaduto",
+ "badge_expires_warn": "⚠️ Scade tra {n}gg",
+ "badge_days_left": "⏳ ~{n}gg rimasti",
+ "days_approx": "~{n} giorni",
+ "weeks_approx": "~{n} settimane",
+ "months_approx": "~{n} mesi",
+ "years_approx": "~{n} anni",
+ "expired_today_long": "Scaduto oggi",
+ "expired_ago_long": "Scaduto da {n} giorni",
+ "expired_suffix": "— Scaduto!",
+ "expired_suffix_ok": "— Scaduto (ancora ok)",
+ "expired_suffix_warning": "— Scaduto (controlla)",
+ "opened_ago_long": "Aperto da {n} giorni",
+ "opened_today_long": "Aperto oggi",
+ "opened_suffix": "— Aperto da troppo tempo!",
+ "opened_suffix_ok": "— Aperto (ancora ok)",
+ "opened_suffix_warning": "— Aperto (controlla)",
+ "days_compact": "{n}gg",
+ "badge_check_soon": "Controlla presto"
+ },
+ "status": {
+ "ok": "OK",
+ "check": "Controlla",
+ "discard": "Buttare",
+ "tip_freezer_ok": "In freezer: ancora sicuro (~{n}g di margine)",
+ "tip_freezer_check": "In freezer da molto, potrebbe aver perso qualità. Consumare presto",
+ "tip_freezer_danger": "In freezer da troppo tempo, rischio di bruciatura da gelo e degrado",
+ "tip_highRisk_check": "Scaduto da poco, controlla odore e aspetto prima di consumare",
+ "tip_highRisk_danger": "Prodotto deperibile scaduto: da buttare per sicurezza",
+ "tip_medRisk_check1": "Controlla aspetto e odore prima di consumare",
+ "tip_medRisk_check2": "Scaduto da un po', verificare bene prima dell'uso",
+ "tip_medRisk_danger": "Troppo tempo dalla scadenza, meglio buttare",
+ "tip_lowRisk_ok": "Prodotto a lunga conservazione, ancora sicuro da consumare",
+ "tip_lowRisk_check": "Scaduto da oltre un mese, controllare integrità confezione",
+ "tip_lowRisk_danger": "Scaduto da troppo tempo, meglio non rischiare"
+ },
+ "toast": {
+ "product_saved": "Prodotto salvato!",
+ "product_created": "Prodotto creato!",
+ "product_updated": "✅ Prodotto aggiornato!",
+ "product_removed": "Prodotto rimosso",
+ "updated": "Aggiornato!",
+ "quantity_confirmed": "✓ Quantità confermata",
+ "added_to_inventory": "✅ {name} aggiunto!",
+ "removed_from_list": "✅ {name} rimosso dalla lista!",
+ "removed_from_list_short": "Rimosso dalla lista",
+ "added_to_shopping": "🛒 Aggiunto alla lista della spesa!",
+ "removed_from_shopping": "🛒 Rimosso dalla lista della spesa",
+ "finished_to_bring": "🛒 Prodotto finito → aggiunto a Bring!",
+ "thrown_away": "🗑️ {name} buttato!",
+ "thrown_away_partial": "🗑️ Buttato {qty} {unit} di {name}",
+ "finished_all": "📤 {name} terminato!",
+ "vacuum_sealed": "{name} salvato come sottovuoto",
+ "product_finished_confirmed": "✅ Rimosso — riaggiungi quando ne ricompri",
+ "appliance_added": "Elettrodomestico aggiunto",
+ "item_added": "{name} aggiunto"
+ },
+ "antiwaste": {
+ "title": "🌱 Rapporto Anti-Spreco",
+ "grade_label": "Voto",
+ "you": "Tu",
+ "avg_label": "Media",
+ "better": "🎉 Perdi il {diff}% in meno della {country}!",
+ "worse": "⚠️ Perdi più della media {country}. Puoi migliorare!",
+ "on_par": "→ Sei nella media {country}. Prova a fare ancora meglio!",
+ "saved_money": "~{amount}/mese risparmiati",
+ "saved_meals": "~{n} pasti salvati",
+ "saved_co2": "{n} kg CO₂ evitata",
+ "trend_title": "Andamento (ultimi 3 mesi)",
+ "months_ago_2": "-60 gg",
+ "months_ago_1": "-30 gg",
+ "this_month": "Ora",
+ "country_it": "media italiana",
+ "country_de": "media tedesca",
+ "country_en": "media USA",
+ "source": "Fonti: REDUCE, Eurostat, USDA 2021",
+ "live_on": "Dati in tempo reale",
+ "live_off": "Offline",
+ "meals": "pasti",
+ "annual_info": "📅 Tu ~{you} kg/anno · media ~{avg} kg/anno",
+ "badge_rate": "tasso perdita",
+ "badge_saved_money": "risparmio vs media",
+ "badge_wasted": "prod. persi",
+ "badge_better": "in meno vs media"
+ },
+ "error": {
+ "generic": "Errore",
+ "network": "Errore di rete",
+ "no_api_key": "Configura la chiave API nelle impostazioni",
+ "loading": "Errore nel caricamento del prodotto",
+ "not_found": "Prodotto non trovato",
+ "not_found_manual": "Prodotto non trovato. Inseriscilo manualmente.",
+ "search": "Errore nella ricerca. Riprova.",
+ "search_short": "Errore nella ricerca",
+ "save": "Errore nel salvataggio",
+ "connection": "Errore di connessione",
+ "camera": "Impossibile accedere alla fotocamera",
+ "bring_add": "Errore nell'aggiunta a Bring!",
+ "bring_connection": "Errore connessione Bring!",
+ "identification": "Errore nell'identificazione",
+ "ai_quota": "Quota AI esaurita. Riprova tra qualche minuto.",
+ "barcode_empty": "Inserisci un codice a barre",
+ "barcode_format": "Il codice a barre deve contenere solo numeri (4-14 cifre)",
+ "min_chars": "Scrivi almeno 2 caratteri",
+ "not_in_inventory": "Prodotto non nell'inventario",
+ "appliance_exists": "Elettrodomestico già presente",
+ "already_exists": "Già presente",
+ "network_retry": "Errore di connessione. Riprova.",
+ "select_items": "Seleziona almeno un prodotto",
+ "server_offline": "Connessione al server persa",
+ "server_restored": "Connessione al server ripristinata",
+ "server_retry": "Riprova",
+ "unknown": "Errore sconosciuto",
+ "prefix": "Errore",
+ "no_inventory_entry": "Nessuna voce di inventario trovata",
+ "offline_title": "Nessuna connessione",
+ "offline_subtitle": "L'app non riesce a raggiungere il server. Verifica la connessione Wi-Fi.",
+ "offline_checking": "Verifica connessione…",
+ "offline_restored": "Connessione ripristinata!",
+ "offline_continue": "Continua in modalità offline",
+ "offline_reading_cache": "Lettura dalla cache locale",
+ "offline_ops_pending": "{n} operazioni in attesa",
+ "offline_synced": "{n} operazioni sincronizzate",
+ "offline_ai_disabled": "Non disponibile offline",
+ "offline_cache_ready": "Offline — {n} prodotti in cache"
+ },
+ "confirm": {
+ "remove_item": "Vuoi davvero rimuovere questo prodotto dall'inventario?",
+ "kiosk_exit": "Uscire dalla modalità kiosk?",
+ "cancel": "Annulla",
+ "proceed": "Conferma"
+ },
+ "location": {
+ "dispensa": "Dispensa",
+ "frigo": "Frigo",
+ "freezer": "Freezer"
+ },
+ "edit": {
+ "title": "Modifica {name}",
+ "unknown_hint": "Inserisci il nome e le informazioni del prodotto",
+ "label_name": "🏷️ Nome prodotto",
+ "choose_location_title": "Quale modifica?",
+ "choose_location_hint": "Scegli la posizione da modificare:"
},
"screensaver": {
- "label": "Attiva salvaschermo",
- "card_title": "🌙 Salvaschermo",
- "card_hint": "Mostra un orologio con fatti utili dopo 5 minuti di inattività. Di default è disattivato.",
- "timeout_1": "1 minuto",
- "timeout_2": "2 minuti",
- "timeout_5": "5 minuti",
- "timeout_10": "10 minuti",
- "timeout_15": "15 minuti",
- "timeout_30": "30 minuti",
- "timeout_60": "1 ora",
- "start_after": "⏱️ Avvia dopo"
+ "recipe_btn": "Ricette",
+ "scan_btn": "Scansiona prodotto"
+ },
+ "days": {
+ "mon": "Lunedì",
+ "tue": "Martedì",
+ "wed": "Mercoledì",
+ "thu": "Giovedì",
+ "fri": "Venerdì",
+ "sat": "Sabato",
+ "sun": "Domenica",
+ "mon_short": "Lun",
+ "tue_short": "Mar",
+ "wed_short": "Mer",
+ "thu_short": "Gio",
+ "fri_short": "Ven",
+ "sat_short": "Sab",
+ "sun_short": "Dom"
+ },
+ "meal_types": {
+ "lunch": "Pranzo",
+ "dinner": "Cena",
+ "colazione": "Colazione",
+ "merenda": "Merenda",
+ "dolce": "Dolce",
+ "succo": "Succo di Frutta",
+ "pranzo": "Pranzo",
+ "cena": "Cena"
},
"scale": {
- "title": "⚖️ Bilancia Smart",
- "hint": "Collega una bilancia Bluetooth tramite il gateway Android per leggere il peso automaticamente.",
- "tab": "Bilancia Smart",
- "enabled": "✅ Abilita bilancia smart",
- "url_label": "🌐 URL Gateway WebSocket",
- "url_placeholder": "ws://192.168.1.x:8765",
- "url_hint": "URL mostrato dall'app Android (stessa rete Wi-Fi). Es:",
- "test_btn": "🔗 Testa connessione",
- "download_btn": "📥 Scarica Gateway Android (APK)",
- "download_hint": "App Android che fa da ponte tra la bilancia BLE e questo sito.",
- "download_sub": "Sorgente: evershelf-scale-gateway/ nella root del progetto",
- "live_weight": "peso in tempo reale",
- "auto_reconnect": "🔁 Riconnessione: automatica",
- "kiosk_title": "📡 Bilancia BLE integrata nel Kiosk",
- "kiosk_hint": "La bilancia è gestita direttamente dal Gateway BLE interno al kiosk. Per abbinare un nuovo dispositivo usa il wizard di configurazione.",
- "kiosk_reconfigure": "🔄 Riconfigura bilancia BLE",
- "ble_protocols": "
🔌 Protocolli BLE supportati:
- Bluetooth SIG Weight Scale (0x181D)
- Bluetooth SIG Body Composition (0x181B) — peso, grasso, BMI
- Xiaomi Mi Body Composition Scale 2
- Generico — heuristica automatica su 100+ modelli
"
+ "status_connected": "Bilancia connessa",
+ "status_searching": "Connesso al gateway, attesa bilancia…",
+ "status_disconnected": "Gateway bilancia non raggiungibile",
+ "status_error": "Errore connessione gateway",
+ "not_connected": "Gateway bilancia non connesso",
+ "read_btn": "⚖️ Leggi dalla bilancia",
+ "reading_title": "Lettura bilancia",
+ "place_on_scale": "Metti il prodotto sulla bilancia…",
+ "waiting_stable": "Il peso venire rilevato automaticamente quando la lettura sarà stabile.",
+ "no_url": "Inserisci l'URL del gateway",
+ "testing": "⏳ Test connessione…",
+ "connected_ok": "Connessione gateway riuscita!",
+ "timeout": "Timeout: nessuna risposta dal gateway",
+ "error_connect": "Impossibile connettersi al gateway",
+ "tab": "Bilancia Smart",
+ "low_weight": "Peso < 10 g · inserisci manualmente\n(la lettura automatica richiede almeno 10 g)",
+ "density_hint": "(densità {density} g/ml)",
+ "ml_hint": "(verrà convertito in ml)",
+ "weight_detected": "Peso rilevato — attendi 10s di stabilità…",
+ "weight_too_low": "Peso troppo basso — attendi…",
+ "stable": "✓ Stabile",
+ "auto_confirm": "✅ {val} {unit} — conferma automatica tra 5s (tocca per annullare)",
+ "cancelled_replace": "Annullato — rimetti l'ingrediente sulla bilancia per riprendere"
+ },
+ "prediction": {
+ "expected_qty": "Previsto: {expected} {unit}",
+ "actual_qty": "Attuale: {actual} {unit}",
+ "check_suggestion": "Verifica o pesa la quantità residua"
+ },
+ "date": {
+ "today": "📅 Oggi",
+ "yesterday": "📅 Ieri"
+ },
+ "scanner": {
+ "title_barcode": "🔖 Scansiona Barcode",
+ "barcode_hint": "Inquadra il codice a barre del prodotto",
+ "barcode_manual_placeholder": "O inserisci manualmente...",
+ "barcode_use_btn": "✅ Usa questo codice",
+ "ai_identifying": "🤖 Identifico il prodotto...",
+ "ai_analyzing": "🤖 Analisi AI in corso...",
+ "product_label_hint": "Inquadra l'etichetta del prodotto",
+ "expiry_label_hint": "Inquadra la data di scadenza stampata sul prodotto",
+ "capture_btn": "📸 Scatta",
+ "capture_photo_btn": "📸 Scatta Foto",
+ "retake_btn": "🔄 Riscatta",
+ "camera_error_hint": "Assicurati di usare HTTPS e di aver concesso i permessi della fotocamera.
Puoi inserire il barcode manualmente o usare l'identificazione AI.",
+ "no_barcode": "Senza barcode",
+ "save_new_btn": "🆕 Non è nessuno di questi — salva come nuovo",
+ "expiry_found": "Data trovata",
+ "expiry_read_fail": "Non riesco a leggere la data.",
+ "expiry_raw_label": "Letto"
+ },
+ "lowstock": {
+ "title": "⚠️ Sta per finire!",
+ "message": "{name} sta per finire — rimangono solo {qty}.",
+ "question": "Vuoi aggiungerlo alla lista della spesa?",
+ "yes": "🛒 Sì, aggiungi a Bring!",
+ "no": "No, per ora va bene"
+ },
+ "move": {
+ "title": "📦 Spostare il resto?",
+ "question": "Vuoi spostare {thing} di {name} in un'altra posizione?",
+ "question_short": "Vuoi spostare {thing} in un'altra posizione?",
+ "thing_opened": "la confezione aperta",
+ "thing_rest": "il resto",
+ "stay_btn": "No, resta in {location}",
+ "moved_toast": "📦 Confezione aperta spostata in {location}",
+ "vacuum_restore": "Torna sotto vuoto",
+ "vacuum_seal_rest": "Metti sotto vuoto il resto"
+ },
+ "nova": {
+ "1": "Non trasformato",
+ "2": "Ingrediente culinario",
+ "3": "Trasformato",
+ "4": "Ultra-trasformato"
+ },
+ "meal_plan_types": {
+ "pasta": "Pasta",
+ "riso": "Riso",
+ "carne": "Carne",
+ "pesce": "Pesce",
+ "legumi": "Legumi",
+ "uova": "Uova",
+ "formaggio": "Formaggio",
+ "pizza": "Pizza",
+ "affettati": "Affettati",
+ "verdure": "Verdure",
+ "zuppa": "Zuppa",
+ "insalata": "Insalata",
+ "pane": "Pane/Sandwich",
+ "dolce": "Dolce",
+ "libero": "Libero"
+ },
+ "meal_sub": {
+ "dolce_torta": "Torta",
+ "dolce_crema": "Crema / Budino",
+ "dolce_crumble": "Crumble / Crostata",
+ "dolce_biscotti": "Biscotti / Pasticcini",
+ "dolce_frutta": "Dolce alla Frutta",
+ "succo_dolce": "Dolce / Fruttato",
+ "succo_energizzante": "Energizzante",
+ "succo_detox": "Detox / Verde",
+ "succo_rinfrescante": "Rinfrescante",
+ "succo_vitaminico": "Vitaminico / Agrumi"
+ },
+ "meal_plan": {
+ "reset_success": "Piano settimanale ripristinato",
+ "suggested_by": "suggerito dal piano settimanale",
+ "not_available": "non disponibile in dispensa"
+ },
+ "kiosk_session": {
+ "first_item": "Primo prodotto: {name}!",
+ "items_two_four": "{n} prodotti — stai scaldando i motori 🚀",
+ "items_five_nine": "{n} prodotti — ottimo ritmo! 💪",
+ "items_ten_twenty": "{n} prodotti — quasi un recordman 🏆",
+ "items_twenty_plus": "{n} prodotti — spesa epica! 🛒🔥",
+ "duplicates_one": "1 bis (stessa cosa due volte)",
+ "duplicates_many": "{n} bis (roba presa più volte)",
+ "top_category": "Categoria top: {cat} ({count}×)",
+ "items_fallback": "{n} prodott{n} aggiunti"
+ },
+ "nutrition": {
+ "title": "🥗 Analisi Alimentare",
+ "score_excellent": "😄 Ottimo",
+ "score_good": "🙂 Discreto",
+ "score_improve": "😬 Migliorabile",
+ "label_health": "🌿 Salute",
+ "label_variety": "🎨 Varietà",
+ "label_fresh": "❄️ Freschi",
+ "source": "Basato su {n} prodotti in dispensa · EverShelf",
+ "products_count": "prodotti",
+ "today_title": "🥗 La tua dispensa oggi",
+ "products_n": "{n} prodotti"
+ },
+ "facts": {
+ "greeting_morning": "Buongiorno",
+ "greeting_afternoon": "Buon pomeriggio",
+ "greeting_evening": "Buonasera",
+ "pantry_waiting": "{greeting}! La tua Dispensa ti aspetta.",
+ "expired_one": "Hai 1 prodotto scaduto in dispensa. Controlla!",
+ "expired_many": "Hai {n} prodotti scaduti in dispensa. Controlla!",
+ "expired_list": "Prodotti scaduti: {names}",
+ "expired_list_more": "e altri {n}",
+ "freezer_expired_ok": "{name} è scaduto, ma essendo in freezer potrebbe essere ancora buono! Controlla.",
+ "freezer_expired_old": "{name} in freezer è scaduto da troppo tempo. Meglio buttarlo.",
+ "fridge_expired_one": "Hai 1 prodotto scaduto in frigo!",
+ "fridge_expired_many": "Hai {n} prodotti scaduti in frigo!",
+ "expiring_today": "{name} scade oggi! Usalo subito.",
+ "expiring_tomorrow": "{name} scade domani. Pensaci!",
+ "expiring_days": "{name} scade tra {days} giorni.",
+ "expiring_many": "Hai {n} prodotti in scadenza ravvicinata.",
+ "expiring_this_week": "Questa settimana scadono {n} prodotti. Pianifica i pasti di conseguenza!",
+ "expiring_item_loc": "{name} ({loc}) scade tra {days} {dayslabel}.",
+ "expiring_this_month": "In questo mese scadranno {n} prodotti.",
+ "shopping_add": "Metti in lista: {names} 🛒",
+ "shopping_more": "e altri {n}",
+ "shopping_empty": "Lista della spesa vuota. Tutto rifornito! ✅",
+ "in_fridge": "In frigo c'è: {name}.",
+ "in_freezer": "Nel freezer c'è: {name}. Non dimenticartelo!",
+ "top_category": "La categoria più presente è {icon} {cat} con {n} prodotti.",
+ "cat_meat": "Hai {n} prodotti di carne. 🥩",
+ "cat_dairy": "Hai {n} latticini in casa. 🥛",
+ "cat_veggies": "Hai {n} tipi di verdura. Ottimo per la salute! 🥬",
+ "cat_fruit": "Hai {n} tipi di frutta. 🍎",
+ "cat_drinks": "Hai {n} bevande disponibili. 🥤",
+ "cat_frozen": "Hai {n} surgelati nel freezer. ❄️",
+ "cat_pasta": "Hai {n} tipi di pasta. 🍝 Che ne dici di una carbonara?",
+ "cat_canned": "Hai {n} conserve in dispensa. 🥫",
+ "cat_snacks": "Hai {n} snack. Resisti alla tentazione! 🍪",
+ "cat_condiments": "Hai {n} condimenti a disposizione. 🧂",
+ "item_random": "Lo sapevi? Hai {name} in {loc}.",
+ "item_qty": "{name}: ne hai {qty}.",
+ "no_expiry_count": "{n} prodotti non hanno una data di scadenza impostata.",
+ "furthest_expiry": "Il prodotto con scadenza più lontana è {name}: {months} mesi.",
+ "high_qty": "Hai una bella scorta di {name}: {qty}!",
+ "low_qty_item": "{name} sta per finire. Aggiungilo alla spesa?",
+ "low_qty_count": "Ci sono {n} prodotti quasi finiti.",
+ "morning_bread": "Buongiorno! Hai del pane per la colazione. 🍞",
+ "morning_milk": "C'è del latte in frigo per il cappuccino? ☕🥛",
+ "morning_fruit": "Buongiorno! Una bella frutta fresca per iniziare bene. 🍎",
+ "noon_pasta": "Ora di pranzo… Un bel piatto di pasta? 🍝",
+ "noon_salad": "Un'insalata fresca per pranzo? Hai {n} verdure! 🥗",
+ "evening_meat": "Per cena potresti usare la carne che hai. 🥩",
+ "evening_fish": "Che ne dici di pesce per cena? 🐟",
+ "evening_expiring": "Hai {n} prodotti in scadenza questa settimana — usali stasera!",
+ "night_reminder": "Buonanotte! Domani ricordati di usare: {names}.",
+ "weekly_balance": "Bilancio settimana: +{in} aggiunti, −{out} consumati.",
+ "weekly_added": "Questa settimana hai aggiunto {n} prodotti.",
+ "weekly_consumed": "Questa settimana hai consumato {n} prodotti. Ottimo!",
+ "tip_freezer": "💡 I prodotti in freezer durano molto più a lungo della data di scadenza.",
+ "tip_bread": "💡 Il pane congelato mantiene la fragranza per settimane.",
+ "tip_fifo": "💡 Per evitare sprechi, usa prima i prodotti con scadenza più vicina (FIFO).",
+ "tip_meat": "💡 La carne in freezer può durare fino a 6 mesi senza problemi.",
+ "tip_no_refreeze": "💡 Non ricongelare mai un alimento già scongelato. Cucinalo subito!",
+ "tip_fridge": "💡 Un frigo ordinato ti fa risparmiare tempo e denaro.",
+ "tip_canned": "💡 Le conserve aperte vanno in frigo e consumate in pochi giorni.",
+ "top_brand": "Il marchio più presente nella tua dispensa è {brand} con {n} prodotti.",
+ "combo_pasta": "Hai pasta e condimenti: sei pronto per un primo piatto! 🍝",
+ "combo_sandwich": "Pane e carne: un panino veloce è sempre una buona idea! 🥪",
+ "combo_balanced": "Verdura e carne: hai tutto per un piatto equilibrato! 🥗🥩",
+ "pantry_empty": "La dispensa è vuota! Fai una bella spesa. 🛒",
+ "pantry_empty_scan": "Nessun prodotto registrato. Scansiona qualcosa per iniziare!",
+ "location_distribution": "Distribuzione: {parts}",
+ "day": "giorno",
+ "days": "giorni"
},
"kiosk": {
- "hint": "Trasforma un tablet Android in un pannello EverShelf sempre acceso, con bilancia BLE integrata.",
- "download_btn": "📥 Scarica EverShelf Kiosk (APK)",
- "download_sub": "Modalità kiosk full-screen + gateway bilancia integrato. Sorgente: evershelf-kiosk/",
- "native_title": "Configurazione Kiosk",
- "native_hint": "URL server, bilancia BLE, salvaschermo e setup wizard.",
- "native_btn": "Apri configurazione kiosk",
- "native_tap_hint": "Tocca la rotella in alto a destra",
- "native_update_hint": "Aggiorna l'app kiosk per usare questa funzione",
- "update_title": "Aggiornamento Kiosk",
- "check_updates_btn": "🔍 Cerca aggiornamenti",
- "needs_update": "⚠️ Il kiosk installato non supporta questa funzione. Aggiorna l'app kiosk per abilitarla."
+ "check_btn": "🔍 Cerca aggiornamenti",
+ "checking": "⏳ Controllo…",
+ "error_check": "Errore durante il controllo",
+ "error_start_install": "Errore avvio installazione",
+ "version_installed": "Installata: {v}",
+ "update_available": "⬆️ Nuova versione disponibile:
{latest} (installata: {current})",
+ "up_to_date": "✅ Sei già aggiornato — versione
{v}",
+ "too_old": "⚠️ Il kiosk installato è troppo vecchio per il controllo automatico.
Premi il pulsante qui sotto per scaricare e installare la nuova versione direttamente.",
+ "manual_install": "⚠️ Questo kiosk non supporta l'installazione automatica.
Procedura manuale:1. Esci dal kiosk (tasto ✕ in alto a sinistra)
2. Disinstalla l'app EverShelf Kiosk
3. Scarica e installa la nuova APK da GitHub:",
+ "starting_download": "⏳ Avvio download…",
+ "install_btn": "⬇️ Installa aggiornamento",
+ "exit_title": "Esci dal kiosk",
+ "refresh_title": "Aggiorna pagina"
},
- "saved": "✅ Configurazione salvata!",
- "saved_local": "✅ Configurazione salvata localmente",
- "saved_local_error": "⚠️ Salvato localmente, errore server: {error}",
- "theme": {
- "title": "🌙 Tema / Aspetto",
- "hint": "Scegli il tema dell interfaccia.",
- "label": "🌙 Tema",
- "off": "☀️ Chiaro",
- "on": "🌙 Scuro",
- "auto": "🔄 Automatico (orario)"
+ "update": {
+ "new_version": "Nuova versione",
+ "btn": "Aggiorna"
},
- "zerowaste": {
- "card_title": "♻️ Suggerimenti zero-waste",
- "card_hint": "Durante la cottura, mostra consigli su come riutilizzare gli scarti prodotti in ogni passo (bucce, acqua di cottura, ecc.). Disattivo per impostazione predefinita.",
- "label": "Mostra suggerimenti durante la cottura"
+ "gemini": {
+ "chat_title": "Chat con Gemini",
+ "not_configured": "🤖 Gemini non configurato — imposta GEMINI_API_KEY nelle impostazioni"
},
- "backup": {
- "tab": "Backup",
- "local_title": "Backup Locale",
- "local_hint": "Snapshot giornaliero del database. Configura quanti giorni di backup conservare.",
- "enabled": "Backup automatico quotidiano",
- "retention_days": "Giorni di retention",
- "retention_info": "I backup vengono conservati per",
- "backup_now": "Backup Ora",
- "backing_up": "Backup in corso…",
- "backed_up": "Backup completato",
- "backup_error": "Errore backup",
- "last_backup": "Ultimo backup",
- "no_backup_yet": "Nessun backup ancora eseguito",
- "list_empty": "Nessun backup disponibile",
- "restore_btn": "Ripristina",
- "restore_confirm": "Ripristinare il backup",
- "delete_btn": "Elimina",
- "delete_confirm": "Eliminare il backup",
- "gdrive_title": "Google Drive",
- "gdrive_hint": "Backup automatici su Google Drive via OAuth 2.0. Nessuna libreria esterna richiesta.",
- "gdrive_enabled": "Abilita backup Google Drive",
- "gdrive_folder_id": "ID Cartella Drive",
- "gdrive_folder_id_hint": "Copia l'ID dalla URL della cartella Drive: …/folders/
ID",
- "gdrive_retention_days": "Retention Drive (giorni, 0=tutto)",
- "gdrive_test": "Testa Connessione",
- "gdrive_ok": "Connessione riuscita!",
- "gdrive_error": "Connessione fallita",
- "gdrive_push_now": "Carica Ora su Drive",
- "gdrive_pushing": "Upload in corso…",
- "gdrive_pushed": "Caricato su Drive",
- "gdrive_wizard_hint": "Opzionale: backup giornaliero automatico su Google Drive via OAuth 2.0.",
- "gdrive_skip": "Salta — configura dopo in Impostazioni",
- "gdrive_client_id": "Client ID",
- "gdrive_client_secret": "Client Secret",
- "gdrive_redirect_uri_label": "Redirect URI (da aggiungere in Google Cloud Console):",
- "gdrive_redirect_uri_hint": "Aggiungi
http://localhost come URI di reindirizzamento autorizzato in Google Cloud Console. Funziona su qualsiasi server, anche senza dominio pubblico.",
- "gdrive_oauth_authorize": "Autorizza con Google",
- "gdrive_oauth_authorized": "Autorizzato",
- "gdrive_oauth_not_authorized": "Non ancora autorizzato",
- "gdrive_oauth_window_opened": "Finestra aperta — autorizza e torna qui",
- "gdrive_oauth_how_to": "Come configurare OAuth 2.0 (passo dopo passo)",
- "gdrive_oauth_steps": "
Vai su console.cloud.google.com e seleziona il progettoAbilita la Google Drive API: API e servizi → Abilita API → Google Drive APIVai su API e servizi → Credenziali → Crea credenziali → ID client OAuth 2.0Tipo applicazione: Applicazione web; aggiungi http://localhost come URI di reindirizzamento autorizzatoCopia Client ID e Client Secret nei campi qui sopra e salvaClicca Autorizza con Google, accedi e concedi l'accessoIl browser aprirà http://localhost (possibile errore di connessione è normale): copia l'URL dalla barra degli indirizzi e incollalo nel campo che appare qui sotto",
- "gdrive_code_title": "Incolla l'URL o il codice di autorizzazione",
- "gdrive_code_hint": "Dopo aver autorizzato, il browser aprirà http://localhost e potrebbe mostrare un errore. Copia l'URL dalla barra degli indirizzi (es.
http://localhost/?code=4%2F0A...) e incollalo qui.",
- "gdrive_code_submit": "Conferma",
- "gdrive_code_empty": "Incolla prima l'URL o il codice di autorizzazione"
+ "appliances": {
+ "empty": "Nessun elettrodomestico aggiunto"
},
- "info": {
- "tab": "Info",
- "ai_title": "Gemini AI — Utilizzo Token",
- "ai_hint": "Consumo mensile e costo stimato per la chiave API corrente.",
- "loading": "Caricamento…",
- "total_tokens": "Token totali",
- "est_cost": "Costo stimato",
- "input_tok": "Token input",
- "output_tok": "Token output",
- "ai_calls": "Chiamate",
- "by_action": "Dettaglio per funzione",
- "by_model": "Dettaglio per modello",
- "pricing_note": "Prezzi di riferimento Gemini: 2.5-flash $0.15/M in · $0.60/M out — 2.0-flash $0.10/M in · $0.40/M out.",
- "system_title": "Sistema",
- "db_size": "Database",
- "log_size": "Log",
- "log_level": "Livello log",
- "ai_overview": "Prospetto utilizzo AI, inventario e stato del sistema",
- "calls_unit": "call",
- "inv_title": "Inventario",
- "inv_active": "Attivi",
- "inv_products": "Prodotti totali",
- "inv_expiring": "In scadenza (7gg)",
- "inv_expired": "Scaduti",
- "inv_finished": "Finiti",
- "act_title": "Attività del mese",
- "act_tx_month": "Movimenti",
- "act_restock": "Acquisti",
- "act_use": "Consumi",
- "act_new_products": "Nuovi prodotti",
- "act_tx_year": "Movimenti anno",
- "price_cache": "Cache prezzi",
- "cache_entries": "prodotti",
- "last_backup": "Ultimo backup",
- "bring_days": "token scade tra {n} giorni",
- "bring_expired": "token scaduto",
- "year_label": "Anno {year}",
- "currency_title": "Valuta",
- "currency_hint": "La valuta usata per tutti i costi e i prezzi nell'app."
+ "about": {
+ "title": "Informazioni",
+ "version": "Versione",
+ "report_bug": "Segnala un problema",
+ "report_bug_hint": "Qualcosa non funziona? Inviaci una segnalazione direttamente dall'app.",
+ "report_bug_modal_title": "Segnala un problema",
+ "report_type_bug": "Bug",
+ "report_type_feature": "Funzionalità",
+ "report_type_question": "Domanda",
+ "report_field_title": "Titolo",
+ "report_field_title_ph": "Breve descrizione del problema",
+ "report_field_desc": "Descrizione",
+ "report_field_desc_ph": "Descrivi il problema in dettaglio…",
+ "report_field_steps": "Passi per riprodurlo (opzionale)",
+ "report_field_steps_ph": "1. Vai su…\n2. Tocca…\n3. Vedi l'errore…",
+ "report_auto_info": "Saranno allegati automaticamente: versione {version}, lingua {lang}.",
+ "report_send_btn": "Invia segnalazione",
+ "report_bug_sending": "Invio in corso…",
+ "report_bug_sent": "Segnalazione inviata — grazie!",
+ "report_bug_error": "Impossibile inviare la segnalazione. Controlla la connessione.",
+ "changelog": "Changelog",
+ "github": "Repository GitHub"
},
- "tab_general": "Generali",
- "shopping": {
- "tab": "Lista spesa",
- "title": "Lista della spesa",
- "hint": "Configura la lista della spesa integrata o collega Bring!.",
- "enable_label": "Abilita lista della spesa",
- "mode_label": "Provider",
- "mode_internal": "Interno (senza Bring!)",
- "mode_bring": "Bring! (app esterna)",
- "bring_section_title": "Configurazione Bring!",
- "ai_section_title": "Assistenza AI",
- "smart_suggestions_label": "Suggerimenti AI",
- "forecast_label": "Previsione prodotti in esaurimento",
- "auto_add_label": "Aggiungi automaticamente quando",
- "auto_add_suffix": "rimasto in magazzino (0 = solo quando esaurito)"
+ "export": {
+ "title": "Esporta inventario",
+ "hint": "Scarica l inventario corrente in CSV o apri la versione stampabile (PDF).",
+ "btn_csv": "Scarica CSV",
+ "btn_pdf": "PDF / Stampa",
+ "btn_title": "Esporta"
},
- "ha": {
- "tab": "Home Assistant",
- "title": "Home Assistant",
- "hint": "Collega EverShelf a Home Assistant per automazioni, notifiche push e sensori REST.",
- "enabled": "Abilita integrazione Home Assistant",
- "connection_title": "Connessione",
- "url_label": "URL Home Assistant",
- "url_placeholder": "http://192.168.1.50:8123",
- "url_hint": "URL del tuo server Home Assistant (es. http://homeassistant.local:8123).",
- "token_label": "Long-Lived Access Token",
- "token_hint": "Genera da Profilo HA → Sicurezza → Token di accesso a lungo termine.",
- "token_placeholder": "eyJhbGci...",
- "token_saved": "Token salvato (non mostrato per sicurezza)",
- "test_btn": "Testa connessione",
- "test_ok": "Connesso a {version}",
- "test_fail": "Connessione fallita: {error}",
- "test_bad_token": "HA raggiungibile ma token non valido",
- "testing": "Test in corso…",
- "error_no_url": "Inserisci prima l'URL di Home Assistant.",
- "tts_title": "TTS su Speaker Smart",
- "tts_hint": "Leggi i passi delle ricette su un media player di Home Assistant.",
- "tts_entity_label": "Entity ID media player",
- "tts_entity_placeholder": "media_player.living_room",
- "tts_entity_hint": "Entity ID del media player su cui vuoi la voce. Puoi trovarlo in HA: Strumenti per sviluppatori → Stati.",
- "tts_platform_label": "Piattaforma TTS",
- "tts_platform_speak": "tts.speak (raccomandato)",
- "tts_platform_notify": "notify.* (servizio notifiche)",
- "tts_apply_btn": "Applica preset HA al tab TTS",
- "tts_apply_hint": "Pre-compila il tab TTS con l'URL e il token di Home Assistant.",
- "tts_preset_applied": "Preset HA applicato al tab TTS.",
- "webhook_title": "Automazioni Webhook",
- "webhook_hint": "Invia dati a Home Assistant quando avvengono eventi nella dispensa. Crea un'automazione in HA con trigger Webhook e copia l'ID generato.",
- "webhook_id_label": "Webhook ID",
- "webhook_id_placeholder": "evershelf_webhook_abc123",
- "webhook_id_hint": "ID del webhook creato in HA. Copia da: HA → Impostazioni → Automazioni → Crea → Trigger Webhook.",
- "webhook_events_label": "Notifica per questi eventi",
- "event_expiry": "Prodotti in scadenza (giornaliero)",
- "event_shopping": "Aggiunta alla lista della spesa",
- "event_stock": "Aggiornamento scorte",
- "expiry_days_label": "Anticipo scadenze (giorni)",
- "expiry_days_hint": "Invia la notifica di scadenza N giorni prima della data di scadenza.",
- "webhook_help": "In HA: Impostazioni → Automazioni → Crea automazione → Trigger: Webhook → copia l'ID generato qui sopra.",
- "notify_title": "Notifiche Push",
- "notify_hint": "Invia notifiche push al tuo telefono tramite il servizio notify di Home Assistant.",
- "notify_service_label": "Servizio notify",
- "notify_service_placeholder": "notify.mobile_app_mio_telefono",
- "notify_service_hint": "Nome del servizio notify HA (es. notify.mobile_app_phone). Lascia vuoto per disabilitare.",
- "sensor_title": "Sensori REST",
- "sensor_hint": "Aggiungi a configuration.yaml per creare sensori EverShelf in Home Assistant.",
- "sensor_copy_btn": "Copia YAML",
- "sensor_copied": "YAML copiato negli appunti!",
- "save_btn": "Salva impostazioni HA",
- "ha_hint": "Se usi Home Assistant, usa il tab Home Assistant per configurare TTS, webhook e sensori."
+ "startup": {
+ "connecting": "Connessione al server...",
+ "check_php_memory": "Memoria PHP",
+ "check_php_timeout": "Timeout PHP",
+ "check_php_upload": "Upload PHP",
+ "check_data_dir": "Cartella dati",
+ "check_rate_limits": "Dir rate limits",
+ "check_backups": "Dir backup",
+ "check_write_test": "Test scrittura disco",
+ "check_disk_space": "Spazio disco",
+ "check_db_legacy": "DB legacy (dispensa.db)",
+ "check_db_connect": "Connessione database",
+ "check_db_tables": "Tabelle database",
+ "check_db_integrity": "Integrità database",
+ "check_db_wal": "WAL mode",
+ "check_db_size": "Dimensione database",
+ "check_db_rows": "Dati inventario",
+ "check_env": "File .env",
+ "check_gemini": "Chiave Gemini AI",
+ "check_bring_creds": "Credenziali Bring!",
+ "check_bring_token": "Token Bring!",
+ "check_tts": "URL Text-to-Speech",
+ "check_scale": "Gateway bilancia",
+ "check_curl_ssl": "cURL SSL",
+ "check_internet": "Connessione internet",
+ "fresh_install": "nuovo impianto",
+ "warnings_found": "avvisi rilevati",
+ "all_ok": "Sistema OK",
+ "critical_error_short": "Errore critico",
+ "critical_error": "Errore critico: l'app non può avviarsi. Controlla i log del server.",
+ "critical_error_intro": "L'app non può avviarsi a causa dei seguenti problemi:",
+ "error_network": "Impossibile contattare il server.",
+ "error_network_detail": "Il browser non riesce a raggiungere il server PHP.\n\nPossibili cause:\n• Il server Apache/PHP non è in esecuzione\n• Problema di rete o firewall\n• URL dell'app non corretta\n\nControlla che il server sia avviato e riprova.",
+ "retry": "Riprova",
+ "syncing_local": "Sincronizzazione dati locali...",
+ "sync_done": "Dati locali aggiornati"
}
- },
- "expiry": {
- "today": "OGGI",
- "tomorrow": "Domani",
- "days": "{days} giorni",
- "expired_days": "Da {days}g",
- "expired_yesterday": "Da ieri",
- "expired_today": "Oggi",
- "badge_today": "⚠️ Scade oggi!",
- "badge_tomorrow": "⏰ Domani",
- "badge_tomorrow_long": "⏰ Scade domani",
- "badge_days": "⏰ {n} giorni",
- "badge_expired_ago": "⚠️ Scaduto da {n}g",
- "badge_expired": "⛔ Scaduto!",
- "badge_stable": "✅ Stabile",
- "badge_expiring_short": "⏰ Scade fra {n}gg",
- "badge_ok_still": "✅ Ancora {n}gg",
- "badge_expires_red": "🔴 Scade tra {n}g",
- "badge_expires_yellow": "🟡 Scade tra {n}g",
- "badge_expired_bare": "⚠️ Scaduto",
- "badge_expires_warn": "⚠️ Scade tra {n}gg",
- "badge_days_left": "⏳ ~{n}gg rimasti",
- "days_approx": "~{n} giorni",
- "weeks_approx": "~{n} settimane",
- "months_approx": "~{n} mesi",
- "years_approx": "~{n} anni",
- "expired_today_long": "Scaduto oggi",
- "expired_ago_long": "Scaduto da {n} giorni",
- "expired_suffix": "— Scaduto!",
- "expired_suffix_ok": "— Scaduto (ancora ok)",
- "expired_suffix_warning": "— Scaduto (controlla)",
- "opened_ago_long": "Aperto da {n} giorni",
- "opened_today_long": "Aperto oggi",
- "opened_suffix": "— Aperto da troppo tempo!",
- "opened_suffix_ok": "— Aperto (ancora ok)",
- "opened_suffix_warning": "— Aperto (controlla)",
- "days_compact": "{n}gg",
- "badge_check_soon": "Controlla presto"
- },
- "status": {
- "ok": "OK",
- "check": "Controlla",
- "discard": "Buttare",
- "tip_freezer_ok": "In freezer: ancora sicuro (~{n}g di margine)",
- "tip_freezer_check": "In freezer da molto, potrebbe aver perso qualità. Consumare presto",
- "tip_freezer_danger": "In freezer da troppo tempo, rischio di bruciatura da gelo e degrado",
- "tip_highRisk_check": "Scaduto da poco, controlla odore e aspetto prima di consumare",
- "tip_highRisk_danger": "Prodotto deperibile scaduto: da buttare per sicurezza",
- "tip_medRisk_check1": "Controlla aspetto e odore prima di consumare",
- "tip_medRisk_check2": "Scaduto da un po', verificare bene prima dell'uso",
- "tip_medRisk_danger": "Troppo tempo dalla scadenza, meglio buttare",
- "tip_lowRisk_ok": "Prodotto a lunga conservazione, ancora sicuro da consumare",
- "tip_lowRisk_check": "Scaduto da oltre un mese, controllare integrità confezione",
- "tip_lowRisk_danger": "Scaduto da troppo tempo, meglio non rischiare"
- },
- "toast": {
- "product_saved": "Prodotto salvato!",
- "product_created": "Prodotto creato!",
- "product_updated": "✅ Prodotto aggiornato!",
- "product_removed": "Prodotto rimosso",
- "updated": "Aggiornato!",
- "quantity_confirmed": "✓ Quantità confermata",
- "added_to_inventory": "✅ {name} aggiunto!",
- "removed_from_list": "✅ {name} rimosso dalla lista!",
- "removed_from_list_short": "Rimosso dalla lista",
- "added_to_shopping": "🛒 Aggiunto alla lista della spesa!",
- "removed_from_shopping": "🛒 Rimosso dalla lista della spesa",
- "finished_to_bring": "🛒 Prodotto finito → aggiunto a Bring!",
- "thrown_away": "🗑️ {name} buttato!",
- "thrown_away_partial": "🗑️ Buttato {qty} {unit} di {name}",
- "finished_all": "📤 {name} terminato!",
- "vacuum_sealed": "{name} salvato come sottovuoto",
- "product_finished_confirmed": "✅ Rimosso — riaggiungi quando ne ricompri",
- "appliance_added": "Elettrodomestico aggiunto",
- "item_added": "{name} aggiunto"
- },
- "antiwaste": {
- "title": "🌱 Rapporto Anti-Spreco",
- "grade_label": "Voto",
- "you": "Tu",
- "avg_label": "Media",
- "better": "🎉 Perdi il {diff}% in meno della {country}!",
- "worse": "⚠️ Perdi più della media {country}. Puoi migliorare!",
- "on_par": "→ Sei nella media {country}. Prova a fare ancora meglio!",
- "saved_money": "~{amount}/mese risparmiati",
- "saved_meals": "~{n} pasti salvati",
- "saved_co2": "{n} kg CO₂ evitata",
- "trend_title": "Andamento (ultimi 3 mesi)",
- "months_ago_2": "-60 gg",
- "months_ago_1": "-30 gg",
- "this_month": "Ora",
- "country_it": "media italiana",
- "country_de": "media tedesca",
- "country_en": "media USA",
- "source": "Fonti: REDUCE, Eurostat, USDA 2021",
- "live_on": "Dati in tempo reale",
- "live_off": "Offline",
- "meals": "pasti",
- "annual_info": "📅 Tu ~{you} kg/anno · media ~{avg} kg/anno",
- "badge_rate": "tasso perdita",
- "badge_saved_money": "risparmio vs media",
- "badge_wasted": "prod. persi",
- "badge_better": "in meno vs media"
- },
- "error": {
- "generic": "Errore",
- "network": "Errore di rete",
- "no_api_key": "Configura la chiave API nelle impostazioni",
- "loading": "Errore nel caricamento del prodotto",
- "not_found": "Prodotto non trovato",
- "not_found_manual": "Prodotto non trovato. Inseriscilo manualmente.",
- "search": "Errore nella ricerca. Riprova.",
- "search_short": "Errore nella ricerca",
- "save": "Errore nel salvataggio",
- "connection": "Errore di connessione",
- "camera": "Impossibile accedere alla fotocamera",
- "bring_add": "Errore nell'aggiunta a Bring!",
- "bring_connection": "Errore connessione Bring!",
- "identification": "Errore nell'identificazione",
- "ai_quota": "Quota AI esaurita. Riprova tra qualche minuto.",
- "barcode_empty": "Inserisci un codice a barre",
- "barcode_format": "Il codice a barre deve contenere solo numeri (4-14 cifre)",
- "min_chars": "Scrivi almeno 2 caratteri",
- "not_in_inventory": "Prodotto non nell'inventario",
- "appliance_exists": "Elettrodomestico già presente",
- "already_exists": "Già presente",
- "network_retry": "Errore di connessione. Riprova.",
- "select_items": "Seleziona almeno un prodotto",
- "server_offline": "Connessione al server persa",
- "server_restored": "Connessione al server ripristinata",
- "server_retry": "Riprova",
- "unknown": "Errore sconosciuto",
- "prefix": "Errore",
- "no_inventory_entry": "Nessuna voce di inventario trovata"
- },
- "confirm": {
- "remove_item": "Vuoi davvero rimuovere questo prodotto dall'inventario?",
- "kiosk_exit": "Uscire dalla modalità kiosk?",
- "cancel": "Annulla",
- "proceed": "Conferma"
- },
- "location": {
- "dispensa": "Dispensa",
- "frigo": "Frigo",
- "freezer": "Freezer"
- },
- "edit": {
- "title": "Modifica {name}",
- "unknown_hint": "Inserisci il nome e le informazioni del prodotto",
- "label_name": "🏷️ Nome prodotto",
- "choose_location_title": "Quale modifica?",
- "choose_location_hint": "Scegli la posizione da modificare:"
- },
- "screensaver": {
- "recipe_btn": "Ricette",
- "scan_btn": "Scansiona prodotto"
- },
- "days": {
- "mon": "Lunedì",
- "tue": "Martedì",
- "wed": "Mercoledì",
- "thu": "Giovedì",
- "fri": "Venerdì",
- "sat": "Sabato",
- "sun": "Domenica",
- "mon_short": "Lun",
- "tue_short": "Mar",
- "wed_short": "Mer",
- "thu_short": "Gio",
- "fri_short": "Ven",
- "sat_short": "Sab",
- "sun_short": "Dom"
- },
- "meal_types": {
- "lunch": "Pranzo",
- "dinner": "Cena",
- "colazione": "Colazione",
- "merenda": "Merenda",
- "dolce": "Dolce",
- "succo": "Succo di Frutta",
- "pranzo": "Pranzo",
- "cena": "Cena"
- },
- "scale": {
- "status_connected": "Bilancia connessa",
- "status_searching": "Connesso al gateway, attesa bilancia…",
- "status_disconnected": "Gateway bilancia non raggiungibile",
- "status_error": "Errore connessione gateway",
- "not_connected": "Gateway bilancia non connesso",
- "read_btn": "⚖️ Leggi dalla bilancia",
- "reading_title": "Lettura bilancia",
- "place_on_scale": "Metti il prodotto sulla bilancia…",
- "waiting_stable": "Il peso venire rilevato automaticamente quando la lettura sarà stabile.",
- "no_url": "Inserisci l'URL del gateway",
- "testing": "⏳ Test connessione…",
- "connected_ok": "Connessione gateway riuscita!",
- "timeout": "Timeout: nessuna risposta dal gateway",
- "error_connect": "Impossibile connettersi al gateway",
- "tab": "Bilancia Smart",
- "low_weight": "Peso < 10 g · inserisci manualmente\n(la lettura automatica richiede almeno 10 g)",
- "density_hint": "(densità {density} g/ml)",
- "ml_hint": "(verrà convertito in ml)",
- "weight_detected": "Peso rilevato — attendi 10s di stabilità…",
- "weight_too_low": "Peso troppo basso — attendi…",
- "stable": "✓ Stabile",
- "auto_confirm": "✅ {val} {unit} — conferma automatica tra 5s (tocca per annullare)",
- "cancelled_replace": "Annullato — rimetti l'ingrediente sulla bilancia per riprendere"
- },
- "prediction": {
- "expected_qty": "Previsto: {expected} {unit}",
- "actual_qty": "Attuale: {actual} {unit}",
- "check_suggestion": "Verifica o pesa la quantità residua"
- },
- "date": {
- "today": "📅 Oggi",
- "yesterday": "📅 Ieri"
- },
- "scanner": {
- "title_barcode": "🔖 Scansiona Barcode",
- "barcode_hint": "Inquadra il codice a barre del prodotto",
- "barcode_manual_placeholder": "O inserisci manualmente...",
- "barcode_use_btn": "✅ Usa questo codice",
- "ai_identifying": "🤖 Identifico il prodotto...",
- "ai_analyzing": "🤖 Analisi AI in corso...",
- "product_label_hint": "Inquadra l'etichetta del prodotto",
- "expiry_label_hint": "Inquadra la data di scadenza stampata sul prodotto",
- "capture_btn": "📸 Scatta",
- "capture_photo_btn": "📸 Scatta Foto",
- "retake_btn": "🔄 Riscatta",
- "camera_error_hint": "Assicurati di usare HTTPS e di aver concesso i permessi della fotocamera.
Puoi inserire il barcode manualmente o usare l'identificazione AI.",
- "no_barcode": "Senza barcode",
- "save_new_btn": "🆕 Non è nessuno di questi — salva come nuovo",
- "expiry_found": "Data trovata",
- "expiry_read_fail": "Non riesco a leggere la data.",
- "expiry_raw_label": "Letto"
- },
- "lowstock": {
- "title": "⚠️ Sta per finire!",
- "message": "{name} sta per finire — rimangono solo {qty}.",
- "question": "Vuoi aggiungerlo alla lista della spesa?",
- "yes": "🛒 Sì, aggiungi a Bring!",
- "no": "No, per ora va bene"
- },
- "move": {
- "title": "📦 Spostare il resto?",
- "question": "Vuoi spostare {thing} di {name} in un'altra posizione?",
- "question_short": "Vuoi spostare {thing} in un'altra posizione?",
- "thing_opened": "la confezione aperta",
- "thing_rest": "il resto",
- "stay_btn": "No, resta in {location}",
- "moved_toast": "📦 Confezione aperta spostata in {location}",
- "vacuum_restore": "Torna sotto vuoto",
- "vacuum_seal_rest": "Metti sotto vuoto il resto"
- },
- "nova": {
- "1": "Non trasformato",
- "2": "Ingrediente culinario",
- "3": "Trasformato",
- "4": "Ultra-trasformato"
- },
- "meal_plan_types": {
- "pasta": "Pasta",
- "riso": "Riso",
- "carne": "Carne",
- "pesce": "Pesce",
- "legumi": "Legumi",
- "uova": "Uova",
- "formaggio": "Formaggio",
- "pizza": "Pizza",
- "affettati": "Affettati",
- "verdure": "Verdure",
- "zuppa": "Zuppa",
- "insalata": "Insalata",
- "pane": "Pane/Sandwich",
- "dolce": "Dolce",
- "libero": "Libero"
- },
- "meal_sub": {
- "dolce_torta": "Torta",
- "dolce_crema": "Crema / Budino",
- "dolce_crumble": "Crumble / Crostata",
- "dolce_biscotti": "Biscotti / Pasticcini",
- "dolce_frutta": "Dolce alla Frutta",
- "succo_dolce": "Dolce / Fruttato",
- "succo_energizzante": "Energizzante",
- "succo_detox": "Detox / Verde",
- "succo_rinfrescante": "Rinfrescante",
- "succo_vitaminico": "Vitaminico / Agrumi"
- },
- "meal_plan": {
- "reset_success": "Piano settimanale ripristinato",
- "suggested_by": "suggerito dal piano settimanale",
- "not_available": "non disponibile in dispensa"
- },
- "kiosk_session": {
- "first_item": "Primo prodotto: {name}!",
- "items_two_four": "{n} prodotti — stai scaldando i motori 🚀",
- "items_five_nine": "{n} prodotti — ottimo ritmo! 💪",
- "items_ten_twenty": "{n} prodotti — quasi un recordman 🏆",
- "items_twenty_plus": "{n} prodotti — spesa epica! 🛒🔥",
- "duplicates_one": "1 bis (stessa cosa due volte)",
- "duplicates_many": "{n} bis (roba presa più volte)",
- "top_category": "Categoria top: {cat} ({count}×)",
- "items_fallback": "{n} prodott{n} aggiunti"
- },
- "nutrition": {
- "title": "🥗 Analisi Alimentare",
- "score_excellent": "😄 Ottimo",
- "score_good": "🙂 Discreto",
- "score_improve": "😬 Migliorabile",
- "label_health": "🌿 Salute",
- "label_variety": "🎨 Varietà",
- "label_fresh": "❄️ Freschi",
- "source": "Basato su {n} prodotti in dispensa · EverShelf",
- "products_count": "prodotti",
- "today_title": "🥗 La tua dispensa oggi",
- "products_n": "{n} prodotti"
- },
- "facts": {
- "greeting_morning": "Buongiorno",
- "greeting_afternoon": "Buon pomeriggio",
- "greeting_evening": "Buonasera",
- "pantry_waiting": "{greeting}! La tua Dispensa ti aspetta.",
- "expired_one": "Hai 1 prodotto scaduto in dispensa. Controlla!",
- "expired_many": "Hai {n} prodotti scaduti in dispensa. Controlla!",
- "expired_list": "Prodotti scaduti: {names}",
- "expired_list_more": "e altri {n}",
- "freezer_expired_ok": "{name} è scaduto, ma essendo in freezer potrebbe essere ancora buono! Controlla.",
- "freezer_expired_old": "{name} in freezer è scaduto da troppo tempo. Meglio buttarlo.",
- "fridge_expired_one": "Hai 1 prodotto scaduto in frigo!",
- "fridge_expired_many": "Hai {n} prodotti scaduti in frigo!",
- "expiring_today": "{name} scade oggi! Usalo subito.",
- "expiring_tomorrow": "{name} scade domani. Pensaci!",
- "expiring_days": "{name} scade tra {days} giorni.",
- "expiring_many": "Hai {n} prodotti in scadenza ravvicinata.",
- "expiring_this_week": "Questa settimana scadono {n} prodotti. Pianifica i pasti di conseguenza!",
- "expiring_item_loc": "{name} ({loc}) scade tra {days} {dayslabel}.",
- "expiring_this_month": "In questo mese scadranno {n} prodotti.",
- "shopping_add": "Metti in lista: {names} 🛒",
- "shopping_more": "e altri {n}",
- "shopping_empty": "Lista della spesa vuota. Tutto rifornito! ✅",
- "in_fridge": "In frigo c'è: {name}.",
- "in_freezer": "Nel freezer c'è: {name}. Non dimenticartelo!",
- "top_category": "La categoria più presente è {icon} {cat} con {n} prodotti.",
- "cat_meat": "Hai {n} prodotti di carne. 🥩",
- "cat_dairy": "Hai {n} latticini in casa. 🥛",
- "cat_veggies": "Hai {n} tipi di verdura. Ottimo per la salute! 🥬",
- "cat_fruit": "Hai {n} tipi di frutta. 🍎",
- "cat_drinks": "Hai {n} bevande disponibili. 🥤",
- "cat_frozen": "Hai {n} surgelati nel freezer. ❄️",
- "cat_pasta": "Hai {n} tipi di pasta. 🍝 Che ne dici di una carbonara?",
- "cat_canned": "Hai {n} conserve in dispensa. 🥫",
- "cat_snacks": "Hai {n} snack. Resisti alla tentazione! 🍪",
- "cat_condiments": "Hai {n} condimenti a disposizione. 🧂",
- "item_random": "Lo sapevi? Hai {name} in {loc}.",
- "item_qty": "{name}: ne hai {qty}.",
- "no_expiry_count": "{n} prodotti non hanno una data di scadenza impostata.",
- "furthest_expiry": "Il prodotto con scadenza più lontana è {name}: {months} mesi.",
- "high_qty": "Hai una bella scorta di {name}: {qty}!",
- "low_qty_item": "{name} sta per finire. Aggiungilo alla spesa?",
- "low_qty_count": "Ci sono {n} prodotti quasi finiti.",
- "morning_bread": "Buongiorno! Hai del pane per la colazione. 🍞",
- "morning_milk": "C'è del latte in frigo per il cappuccino? ☕🥛",
- "morning_fruit": "Buongiorno! Una bella frutta fresca per iniziare bene. 🍎",
- "noon_pasta": "Ora di pranzo… Un bel piatto di pasta? 🍝",
- "noon_salad": "Un'insalata fresca per pranzo? Hai {n} verdure! 🥗",
- "evening_meat": "Per cena potresti usare la carne che hai. 🥩",
- "evening_fish": "Che ne dici di pesce per cena? 🐟",
- "evening_expiring": "Hai {n} prodotti in scadenza questa settimana — usali stasera!",
- "night_reminder": "Buonanotte! Domani ricordati di usare: {names}.",
- "weekly_balance": "Bilancio settimana: +{in} aggiunti, −{out} consumati.",
- "weekly_added": "Questa settimana hai aggiunto {n} prodotti.",
- "weekly_consumed": "Questa settimana hai consumato {n} prodotti. Ottimo!",
- "tip_freezer": "💡 I prodotti in freezer durano molto più a lungo della data di scadenza.",
- "tip_bread": "💡 Il pane congelato mantiene la fragranza per settimane.",
- "tip_fifo": "💡 Per evitare sprechi, usa prima i prodotti con scadenza più vicina (FIFO).",
- "tip_meat": "💡 La carne in freezer può durare fino a 6 mesi senza problemi.",
- "tip_no_refreeze": "💡 Non ricongelare mai un alimento già scongelato. Cucinalo subito!",
- "tip_fridge": "💡 Un frigo ordinato ti fa risparmiare tempo e denaro.",
- "tip_canned": "💡 Le conserve aperte vanno in frigo e consumate in pochi giorni.",
- "top_brand": "Il marchio più presente nella tua dispensa è {brand} con {n} prodotti.",
- "combo_pasta": "Hai pasta e condimenti: sei pronto per un primo piatto! 🍝",
- "combo_sandwich": "Pane e carne: un panino veloce è sempre una buona idea! 🥪",
- "combo_balanced": "Verdura e carne: hai tutto per un piatto equilibrato! 🥗🥩",
- "pantry_empty": "La dispensa è vuota! Fai una bella spesa. 🛒",
- "pantry_empty_scan": "Nessun prodotto registrato. Scansiona qualcosa per iniziare!",
- "location_distribution": "Distribuzione: {parts}",
- "day": "giorno",
- "days": "giorni"
- },
- "kiosk": {
- "check_btn": "🔍 Cerca aggiornamenti",
- "checking": "⏳ Controllo…",
- "error_check": "Errore durante il controllo",
- "error_start_install": "Errore avvio installazione",
- "version_installed": "Installata: {v}",
- "update_available": "⬆️ Nuova versione disponibile:
{latest} (installata: {current})",
- "up_to_date": "✅ Sei già aggiornato — versione
{v}",
- "too_old": "⚠️ Il kiosk installato è troppo vecchio per il controllo automatico.
Premi il pulsante qui sotto per scaricare e installare la nuova versione direttamente.",
- "manual_install": "⚠️ Questo kiosk non supporta l'installazione automatica.
Procedura manuale:1. Esci dal kiosk (tasto ✕ in alto a sinistra)
2. Disinstalla l'app EverShelf Kiosk
3. Scarica e installa la nuova APK da GitHub:",
- "starting_download": "⏳ Avvio download…",
- "install_btn": "⬇️ Installa aggiornamento",
- "exit_title": "Esci dal kiosk",
- "refresh_title": "Aggiorna pagina"
- },
- "update": {
- "new_version": "Nuova versione",
- "btn": "Aggiorna"
- },
- "gemini": {
- "chat_title": "Chat con Gemini",
- "not_configured": "🤖 Gemini non configurato — imposta GEMINI_API_KEY nelle impostazioni"
- },
- "appliances": {
- "empty": "Nessun elettrodomestico aggiunto"
- },
- "about": {
- "title": "Informazioni",
- "version": "Versione",
- "report_bug": "Segnala un problema",
- "report_bug_hint": "Qualcosa non funziona? Inviaci una segnalazione direttamente dall'app.",
- "report_bug_modal_title": "Segnala un problema",
- "report_type_bug": "Bug",
- "report_type_feature": "Funzionalità",
- "report_type_question": "Domanda",
- "report_field_title": "Titolo",
- "report_field_title_ph": "Breve descrizione del problema",
- "report_field_desc": "Descrizione",
- "report_field_desc_ph": "Descrivi il problema in dettaglio…",
- "report_field_steps": "Passi per riprodurlo (opzionale)",
- "report_field_steps_ph": "1. Vai su…\n2. Tocca…\n3. Vedi l'errore…",
- "report_auto_info": "Saranno allegati automaticamente: versione {version}, lingua {lang}.",
- "report_send_btn": "Invia segnalazione",
- "report_bug_sending": "Invio in corso…",
- "report_bug_sent": "Segnalazione inviata — grazie!",
- "report_bug_error": "Impossibile inviare la segnalazione. Controlla la connessione.",
- "changelog": "Changelog",
- "github": "Repository GitHub"
- },
- "export": {
- "title": "Esporta inventario",
- "hint": "Scarica l inventario corrente in CSV o apri la versione stampabile (PDF).",
- "btn_csv": "Scarica CSV",
- "btn_pdf": "PDF / Stampa",
- "btn_title": "Esporta"
- },
- "startup": {
- "connecting": "Connessione al server...",
- "check_php_memory": "Memoria PHP",
- "check_php_timeout": "Timeout PHP",
- "check_php_upload": "Upload PHP",
- "check_data_dir": "Cartella dati",
- "check_rate_limits": "Dir rate limits",
- "check_backups": "Dir backup",
- "check_write_test": "Test scrittura disco",
- "check_disk_space": "Spazio disco",
- "check_db_legacy": "DB legacy (dispensa.db)",
- "check_db_connect": "Connessione database",
- "check_db_tables": "Tabelle database",
- "check_db_integrity": "Integrità database",
- "check_db_wal": "WAL mode",
- "check_db_size": "Dimensione database",
- "check_db_rows": "Dati inventario",
- "check_env": "File .env",
- "check_gemini": "Chiave Gemini AI",
- "check_bring_creds": "Credenziali Bring!",
- "check_bring_token": "Token Bring!",
- "check_tts": "URL Text-to-Speech",
- "check_scale": "Gateway bilancia",
- "check_curl_ssl": "cURL SSL",
- "check_internet": "Connessione internet",
- "fresh_install": "nuovo impianto",
- "warnings_found": "avvisi rilevati",
- "all_ok": "Sistema OK",
- "critical_error_short": "Errore critico",
- "critical_error": "Errore critico: l'app non può avviarsi. Controlla i log del server.",
- "critical_error_intro": "L'app non può avviarsi a causa dei seguenti problemi:",
- "error_network": "Impossibile contattare il server.",
- "error_network_detail": "Il browser non riesce a raggiungere il server PHP.\n\nPossibili cause:\n• Il server Apache/PHP non è in esecuzione\n• Problema di rete o firewall\n• URL dell'app non corretta\n\nControlla che il server sia avviato e riprova.",
- "retry": "Riprova"
- }
}
\ No newline at end of file