From 3ba4f7eaad69905c4a5afd9c9305e4e28fc005a5 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sun, 17 May 2026 08:36:46 +0000 Subject: [PATCH] feat: add French and Spanish translations (#77) - Complete fr.json (1049 keys, 52 sections) - Complete es.json (1049 keys, 52 sections) - Language selector updated with Francais and Espanol - Setup wizard localized for fr/es - Default fallback language changed from 'it' to 'en' - Version bump to 1.7.17 --- CHANGELOG.md | 9 + README.md | 6 +- assets/js/app.js | 14 +- index.html | 4 +- manifest.json | 2 +- translations/es.json | 1183 ++++++++++++++++++++++++++++++++++++++++++ translations/fr.json | 1183 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 2390 insertions(+), 11 deletions(-) create mode 100644 translations/es.json create mode 100644 translations/fr.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 90414da..fbab979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ 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.17] - 2026-05-19 + +### Added +- **French translation (🇫🇷 Français)** — Complete `translations/fr.json` with all 1049 translation keys. Resolves [#77](https://github.com/dadaloop82/EverShelf/issues/77). +- **Spanish translation (🇪🇸 Español)** — Complete `translations/es.json` with all 1049 translation keys. Resolves [#77](https://github.com/dadaloop82/EverShelf/issues/77). +- Language selector in Settings now shows all 5 languages: 🇮🇹 Italiano, 🇬🇧 English, 🇩🇪 Deutsch, 🇫🇷 Français, 🇪🇸 Español. +- Default fallback language changed from Italian to English (for users with unsupported browser locale). +- Setup wizard "Done" screen and navigation buttons localised for French and Spanish. + ## [1.7.16] - 2026-05-17 ### Added diff --git a/README.md b/README.md index 53e3dbb..7fea76e 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ [![PHP](https://img.shields.io/badge/PHP-8.0+-blue.svg)](https://www.php.net/) [![SQLite](https://img.shields.io/badge/SQLite-3-blue.svg)](https://www.sqlite.org/) [![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](Dockerfile) -[![i18n](https://img.shields.io/badge/i18n-IT%20%7C%20EN%20%7C%20DE-orange.svg)](translations/) -[![Version](https://img.shields.io/badge/version-1.7.15-brightgreen.svg)](CHANGELOG.md) +[![i18n](https://img.shields.io/badge/i18n-IT%20%7C%20EN%20%7C%20DE%20%7C%20FR%20%7C%20ES-orange.svg)](translations/) +[![Version](https://img.shields.io/badge/version-1.7.17-brightgreen.svg)](CHANGELOG.md) [![GitHub stars](https://img.shields.io/github/stars/dadaloop82/EverShelf?style=social)](https://github.com/dadaloop82/EverShelf/stargazers) [![Last commit](https://img.shields.io/github/last-commit/dadaloop82/EverShelf/main)](https://github.com/dadaloop82/EverShelf/commits/main) [![Contributors](https://img.shields.io/github/contributors/dadaloop82/EverShelf)](https://github.com/dadaloop82/EverShelf/graphs/contributors) @@ -365,6 +365,8 @@ The app supports multiple languages via JSON translation files in the `translati | 🇮🇹 Italian (it) | ✅ Complete (base) | | 🇬🇧 English (en) | ✅ Complete | | 🇩🇪 German (de) | ✅ Complete | +| 🇫🇷 French (fr) | ✅ Complete | +| 🇪🇸 Spanish (es) | ✅ Complete | **Want to add your language?** See the [Translation Guide](CONTRIBUTING.md#-adding-translations) — just copy `translations/it.json`, translate the values, and submit a PR! diff --git a/assets/js/app.js b/assets/js/app.js index d4be1ac..564ab90 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1043,9 +1043,9 @@ async function discoverScaleGateway() { // ===== i18n TRANSLATION SYSTEM ===== let _i18nStrings = null; // current language translations (flat) let _i18nFallback = null; // Italian fallback (flat) -let _currentLang = localStorage.getItem('evershelf_lang') || navigator.language?.slice(0, 2) || 'it'; -const _SUPPORTED_LANGS = { it: 'Italiano', en: 'English', de: 'Deutsch' }; -if (!_SUPPORTED_LANGS[_currentLang]) _currentLang = 'it'; +let _currentLang = localStorage.getItem('evershelf_lang') || navigator.language?.slice(0, 2) || 'en'; +const _SUPPORTED_LANGS = { it: 'Italiano', en: 'English', de: 'Deutsch', fr: 'Français', es: 'Español' }; +if (!_SUPPORTED_LANGS[_currentLang]) _currentLang = 'en'; // Flatten nested JSON: { a: { b: "x" } } → { "a.b": "x" } function _flattenI18n(obj, prefix = '') { @@ -14566,9 +14566,11 @@ function _setupSteps() { ` }, { - title: '✅ ' + (_currentLang === 'it' ? 'Tutto pronto!' : _currentLang === 'de' ? 'Alles bereit!' : 'All set!'), + title: '✅ ' + (_currentLang === 'it' ? 'Tutto pronto!' : _currentLang === 'de' ? 'Alles bereit!' : _currentLang === 'fr' ? 'Tout est prêt !' : _currentLang === 'es' ? '¡Todo listo!' : 'All set!'), desc: _currentLang === 'it' ? 'La configurazione è completata. Puoi sempre modificare queste impostazioni dalla pagina Configurazione.' : _currentLang === 'de' ? 'Die Konfiguration ist abgeschlossen. Du kannst diese Einstellungen jederzeit ändern.' + : _currentLang === 'fr' ? 'La configuration est terminée. Vous pouvez toujours modifier ces paramètres depuis la page Paramètres.' + : _currentLang === 'es' ? 'La configuración está completa. Puedes cambiar estos ajustes desde la página Ajustes.' : 'Setup is complete. You can always change these settings from the Settings page.', render: () => { let summary = '
🎉
'; @@ -14618,9 +14620,9 @@ function _renderSetupStep() { prevBtn.textContent = t('btn.back'); if (_setupStep === totalPending - 1) { - nextBtn.textContent = _currentLang === 'it' ? '🚀 Inizia!' : _currentLang === 'de' ? '🚀 Los geht\'s!' : '🚀 Start!'; + nextBtn.textContent = _currentLang === 'it' ? '🚀 Inizia!' : _currentLang === 'de' ? '🚀 Los geht\'s!' : _currentLang === 'fr' ? '🚀 Allons-y !' : _currentLang === 'es' ? '🚀 ¡Empezar!' : '🚀 Start!'; } else { - nextBtn.textContent = _currentLang === 'it' ? 'Avanti →' : _currentLang === 'de' ? 'Weiter →' : 'Next →'; + nextBtn.textContent = _currentLang === 'it' ? 'Avanti →' : _currentLang === 'de' ? 'Weiter →' : _currentLang === 'fr' ? 'Suivant →' : _currentLang === 'es' ? 'Siguiente →' : 'Next →'; } } diff --git a/index.html b/index.html index 233adfa..c53152c 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ EverShelf - + @@ -1560,6 +1560,6 @@ - + diff --git a/manifest.json b/manifest.json index e41d7a9..d801b48 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.15", + "version": "1.7.17", "start_url": "/evershelf/", "display": "standalone", "background_color": "#f0f4e8", diff --git a/translations/es.json b/translations/es.json new file mode 100644 index 0000000..be1b924 --- /dev/null +++ b/translations/es.json @@ -0,0 +1,1183 @@ +{ + "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.\nAñ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}" + }, + "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 almacenamiento
3. 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/certificates
3. 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:

" + }, + "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}" + }, + "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" + } +} diff --git a/translations/fr.json b/translations/fr.json new file mode 100644 index 0000000..ef7de32 --- /dev/null +++ b/translations/fr.json @@ -0,0 +1,1183 @@ +{ + "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.\nAjoutez 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}" + }, + "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 stockage
3. 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/certificates
3. 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 :

" + }, + "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}" + }, + "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" + } +}