feat(inventory): photo-first drafts + on-device OCR (digitization R2+R4)
Lower the bulk-digitization cliff with two more routes on top of the already-landed CSV import and "save and add another": - Photo-first drafts (capture now, catalogue later): burst-capture photos (camera or multi-gallery) into unnamed draft varieties, shown in a "to catalogue" tray, hidden from the main list until named. Adds Variety.isDraft (schema), addDraftVariety/watchDrafts/nameDraft, the triage sheet and the inventory banner. - On-device OCR label suggestion (Tesseract, offline, no Google): a "Suggest name from photo" button in the naming dialog behind a LabelTextExtractor interface (Tesseract on Android/iOS, no-op elsewhere). Reads the largest print via hOCR bounding boxes, drops boilerplate/low-confidence noise, preprocesses (grayscale, contrast, upscale) and sweeps rotations (0-315 deg) so tilted packets still read. Bundles tessdata_fast eng+spa; validated on-device against real packets. The photo is written to a temp file deleted immediately in a finally block (the plugin needs a path) - a bounded, documented exception to no-plaintext-at-rest. This commit also carries the co-developed schema evolution v5 to v8 that shares these files (organic flag, species viability years, crop calendar, lot provenance/abundance/preservation format, condition checks) plus their exports/migrations and i18n. Tests: CSV/draft/OCR unit + widget + migration green in isolation. Note: the full widget suite currently hangs (>10 min) - under investigation.
This commit is contained in:
parent
12a2ee2d64
commit
6809dc6143
89 changed files with 17141 additions and 228 deletions
|
|
@ -45,23 +45,23 @@
|
|||
},
|
||||
"backup": {
|
||||
"title": "Copia de seguridad",
|
||||
"exportCsv": "Exportar a CSV",
|
||||
"exportCsvSubtitle": "Para hojas de cálculo — sin fotos",
|
||||
"exportJson": "Exportar a JSON",
|
||||
"exportJsonSubtitle": "Copia completa, se puede volver a importar",
|
||||
"importJson": "Importar desde JSON",
|
||||
"importJsonSubtitle": "Fusiona una copia guardada con este inventario",
|
||||
"importCsv": "Importar desde CSV",
|
||||
"importCsvSubtitle": "Añade una lista desde una hoja de cálculo",
|
||||
"importConfirmTitle": "¿Importar una copia guardada?",
|
||||
"exportJson": "Guardar una copia de seguridad",
|
||||
"exportJsonSubtitle": "Una copia completa para guardar a salvo, restaurar luego o pasar a otro dispositivo",
|
||||
"importJson": "Restaurar una copia",
|
||||
"importJsonSubtitle": "Recupera una copia guardada — no se duplica nada",
|
||||
"exportCsv": "Exportar a una hoja de cálculo",
|
||||
"exportCsvSubtitle": "Una lista sencilla para Excel o LibreOffice — sin fotos",
|
||||
"importCsv": "Importar una lista",
|
||||
"importCsvSubtitle": "Añade entradas desde una hoja de cálculo",
|
||||
"importConfirmTitle": "¿Restaurar una copia?",
|
||||
"importConfirmBody": "Las entradas se fusionan con tu inventario; si una entrada existe en ambos lados, gana la versión más reciente. No se duplica nada.",
|
||||
"importCsvConfirmTitle": "¿Importar una lista CSV?",
|
||||
"importCsvConfirmTitle": "¿Importar una lista?",
|
||||
"importCsvConfirmBody": "Cada fila se añade como una entrada nueva. No fusiona ni reemplaza, así que importar el mismo fichero dos veces lo añade dos veces.",
|
||||
"importAction": "Importar",
|
||||
"exportSaved": "Copia guardada",
|
||||
"cancelled": "Cancelado",
|
||||
"importDone": "Importado: {added} nuevas, {updated} actualizadas",
|
||||
"importCsvDone": "Importadas {count} desde CSV",
|
||||
"importCsvDone": "Añadidas {count} entradas",
|
||||
"importFailed": "Este fichero no se pudo leer como una copia de Tanemaki",
|
||||
"failed": "Algo ha salido mal"
|
||||
},
|
||||
|
|
@ -112,7 +112,19 @@
|
|||
"empty": "Aún no hay semillas. Toca + para añadir la primera.",
|
||||
"noMatches": "Ninguna semilla coincide con los filtros.",
|
||||
"clearFilters": "Quitar filtros",
|
||||
"uncategorized": "Sin categoría"
|
||||
"uncategorized": "Sin categoría",
|
||||
"needsReproductionFilter": "Por reproducir"
|
||||
},
|
||||
"draft": {
|
||||
"capture": "Capturar fotos",
|
||||
"captured": "{n} capturadas por catalogar",
|
||||
"triageTitle": "Por catalogar",
|
||||
"triageCount": "{n} por catalogar",
|
||||
"untitled": "Sin nombre",
|
||||
"nameField": "Nombra esta semilla",
|
||||
"nameHint": "¿Qué es?",
|
||||
"suggestFromPhoto": "Sugerir nombre de la foto",
|
||||
"discard": "Descartar"
|
||||
},
|
||||
"quickAdd": {
|
||||
"title": "Añadir una semilla",
|
||||
|
|
@ -151,13 +163,21 @@
|
|||
"none": "Aún no hay pruebas de germinación.",
|
||||
"result": "{percent}%"
|
||||
},
|
||||
"viability": {
|
||||
"expiringSoon": "Úsala o multiplícala esta temporada",
|
||||
"expiringSoonYears": "Úsala o multiplícala esta temporada · dura ~{years} años",
|
||||
"expired": "Supera su viabilidad típica — a multiplicar",
|
||||
"expiredYears": "Supera su viabilidad típica (~{years} años) — a multiplicar"
|
||||
},
|
||||
"editVariety": {
|
||||
"title": "Editar semilla",
|
||||
"name": "Nombre",
|
||||
"category": "Categoría",
|
||||
"notes": "Notas",
|
||||
"species": "Especie (del catálogo)",
|
||||
"speciesHint": "Buscar una especie…"
|
||||
"speciesHint": "Buscar una especie…",
|
||||
"organic": "Ecológica",
|
||||
"organicHint": "Cultivada de forma ecológica (eco)"
|
||||
},
|
||||
"addLot": {
|
||||
"title": "Añadir lote",
|
||||
|
|
@ -191,6 +211,65 @@
|
|||
"bareRoot": "Raíz desnuda",
|
||||
"rootBall": "Cepellón"
|
||||
},
|
||||
"provenance": {
|
||||
"section": "De dónde viene",
|
||||
"seedsFrom": "Semillas de",
|
||||
"seedsFromHint": "Quién las cultivó o las dio",
|
||||
"place": "Lugar",
|
||||
"placeHint": "De dónde vienen (con provincia)",
|
||||
"addSeedsFrom": "Semillas de",
|
||||
"addPlace": "Lugar"
|
||||
},
|
||||
"abundance": {
|
||||
"add": "Cuánta tengo",
|
||||
"title": "Cuánta tengo",
|
||||
"none": "Sin indicar",
|
||||
"plentyToShare": "De sobra para compartir",
|
||||
"enoughToShare": "Bastante, para compartir con moderación",
|
||||
"enoughForMe": "Suficiente para mí",
|
||||
"runningLow": "Queda poca"
|
||||
},
|
||||
"cropCalendar": {
|
||||
"add": "Calendario de cultivo",
|
||||
"title": "Calendario de cultivo",
|
||||
"sow": "Siembra",
|
||||
"transplant": "Trasplante",
|
||||
"flowering": "Floración",
|
||||
"fruiting": "Fructificación",
|
||||
"seedHarvest": "Cosecha de semilla",
|
||||
"unset": "—"
|
||||
},
|
||||
"needsReproduction": {
|
||||
"label": "Reproducir esta temporada",
|
||||
"hint": "Cultívala antes de que se acabe la semilla",
|
||||
"badge": "Por reproducir"
|
||||
},
|
||||
"preservation": {
|
||||
"add": "Cómo se conserva",
|
||||
"title": "Cómo se conserva",
|
||||
"none": "Sin especificar",
|
||||
"jarWithDesiccant": "Bote con desecante",
|
||||
"glassJar": "Bote de cristal",
|
||||
"paperEnvelope": "Sobre de papel",
|
||||
"paperBag": "Bolsa de papel",
|
||||
"plasticBag": "Bolsa de plástico"
|
||||
},
|
||||
"conditionCheck": {
|
||||
"advanced": "Conservación y detalles del banco",
|
||||
"title": "Revisiones de conservación",
|
||||
"add": "Añadir revisión",
|
||||
"containers": "Botes / recipientes",
|
||||
"desiccant": "Desecante",
|
||||
"none": "Aún no hay revisiones.",
|
||||
"summary": "{count} bote(s) · {state}"
|
||||
},
|
||||
"desiccant": {
|
||||
"none": "No tiene",
|
||||
"add": "Se pone",
|
||||
"replace": "Se cambia",
|
||||
"dry": "Azul — seco",
|
||||
"fresh": "Recién puesto"
|
||||
},
|
||||
"unit": {
|
||||
"aFew": "unas pocas",
|
||||
"some": "algunas",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue