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
|
|
@ -44,24 +44,24 @@
|
|||
"aboutOpen": "About Tanemaki"
|
||||
},
|
||||
"backup": {
|
||||
"title": "Backup",
|
||||
"exportCsv": "Export as CSV",
|
||||
"exportCsvSubtitle": "For spreadsheets — no photos",
|
||||
"exportJson": "Export as JSON",
|
||||
"exportJsonSubtitle": "Complete copy, can be imported back",
|
||||
"importJson": "Import from JSON",
|
||||
"importJsonSubtitle": "Merge a saved copy into this inventory",
|
||||
"importCsv": "Import from CSV",
|
||||
"importCsvSubtitle": "Add a list from a spreadsheet",
|
||||
"importConfirmTitle": "Import a saved copy?",
|
||||
"title": "Backup & restore",
|
||||
"exportJson": "Save a backup",
|
||||
"exportJsonSubtitle": "A complete copy to keep safe, restore later or move to another device",
|
||||
"importJson": "Restore a backup",
|
||||
"importJsonSubtitle": "Bring a saved copy back — nothing gets duplicated",
|
||||
"exportCsv": "Export to a spreadsheet",
|
||||
"exportCsvSubtitle": "A simple list for Excel or LibreOffice — no photos",
|
||||
"importCsv": "Import a list",
|
||||
"importCsvSubtitle": "Add entries from a spreadsheet",
|
||||
"importConfirmTitle": "Restore a backup?",
|
||||
"importConfirmBody": "Entries merge with your inventory; when the same entry exists on both sides, the newest version wins. Nothing gets duplicated.",
|
||||
"importCsvConfirmTitle": "Import a CSV list?",
|
||||
"importCsvConfirmTitle": "Import a list?",
|
||||
"importCsvConfirmBody": "Every row is added as a new entry. This does not merge or replace, so importing the same file twice adds it twice.",
|
||||
"importAction": "Import",
|
||||
"exportSaved": "Copy saved",
|
||||
"cancelled": "Cancelled",
|
||||
"importDone": "Imported: {added} new, {updated} updated",
|
||||
"importCsvDone": "Imported {count} from CSV",
|
||||
"importCsvDone": "Added {count} entries",
|
||||
"importFailed": "This file could not be read as a Tanemaki copy",
|
||||
"failed": "Something went wrong"
|
||||
},
|
||||
|
|
@ -112,7 +112,19 @@
|
|||
"empty": "No seeds yet. Tap + to add your first.",
|
||||
"noMatches": "No seeds match your filters.",
|
||||
"clearFilters": "Clear filters",
|
||||
"uncategorized": "Uncategorized"
|
||||
"uncategorized": "Uncategorized",
|
||||
"needsReproductionFilter": "To regrow"
|
||||
},
|
||||
"draft": {
|
||||
"capture": "Capture photos",
|
||||
"captured": "{n} captured to catalogue",
|
||||
"triageTitle": "To catalogue",
|
||||
"triageCount": "{n} to catalogue",
|
||||
"untitled": "Unnamed",
|
||||
"nameField": "Name this seed",
|
||||
"nameHint": "What is it?",
|
||||
"suggestFromPhoto": "Suggest name from photo",
|
||||
"discard": "Discard"
|
||||
},
|
||||
"quickAdd": {
|
||||
"title": "Add a seed",
|
||||
|
|
@ -151,13 +163,21 @@
|
|||
"none": "No germination tests yet.",
|
||||
"result": "{percent}%"
|
||||
},
|
||||
"viability": {
|
||||
"expiringSoon": "Use or reproduce this season",
|
||||
"expiringSoonYears": "Use or reproduce this season · keeps ~{years} yr",
|
||||
"expired": "Past typical viability — reproduce",
|
||||
"expiredYears": "Past typical viability (~{years} yr) — reproduce"
|
||||
},
|
||||
"editVariety": {
|
||||
"title": "Edit seed",
|
||||
"name": "Name",
|
||||
"category": "Category",
|
||||
"notes": "Notes",
|
||||
"species": "Species (from catalog)",
|
||||
"speciesHint": "Search a species…"
|
||||
"speciesHint": "Search a species…",
|
||||
"organic": "Organic",
|
||||
"organicHint": "Grown organically (eco)"
|
||||
},
|
||||
"addLot": {
|
||||
"title": "Add lot",
|
||||
|
|
@ -191,6 +211,65 @@
|
|||
"bareRoot": "Bare-root",
|
||||
"rootBall": "Root-ball"
|
||||
},
|
||||
"provenance": {
|
||||
"section": "Where it's from",
|
||||
"seedsFrom": "Seeds from",
|
||||
"seedsFromHint": "Who grew or gave them",
|
||||
"place": "Place",
|
||||
"placeHint": "Where they come from (with region)",
|
||||
"addSeedsFrom": "Seeds from",
|
||||
"addPlace": "Place"
|
||||
},
|
||||
"abundance": {
|
||||
"add": "How much I have",
|
||||
"title": "How much I have",
|
||||
"none": "Not set",
|
||||
"plentyToShare": "Plenty to share",
|
||||
"enoughToShare": "Enough to share a little",
|
||||
"enoughForMe": "Enough for me",
|
||||
"runningLow": "Running low"
|
||||
},
|
||||
"cropCalendar": {
|
||||
"add": "Crop calendar",
|
||||
"title": "Crop calendar",
|
||||
"sow": "Sow",
|
||||
"transplant": "Transplant",
|
||||
"flowering": "Flowering",
|
||||
"fruiting": "Fruiting",
|
||||
"seedHarvest": "Seed harvest",
|
||||
"unset": "—"
|
||||
},
|
||||
"needsReproduction": {
|
||||
"label": "To regrow this season",
|
||||
"hint": "Grow it out before the seed runs out",
|
||||
"badge": "To regrow"
|
||||
},
|
||||
"preservation": {
|
||||
"add": "How it's kept",
|
||||
"title": "How it's kept",
|
||||
"none": "Unspecified",
|
||||
"jarWithDesiccant": "Jar with drying agent",
|
||||
"glassJar": "Glass jar",
|
||||
"paperEnvelope": "Paper envelope",
|
||||
"paperBag": "Paper bag",
|
||||
"plasticBag": "Plastic bag"
|
||||
},
|
||||
"conditionCheck": {
|
||||
"advanced": "Storage & seed-bank details",
|
||||
"title": "Storage checks",
|
||||
"add": "Add check",
|
||||
"containers": "Jars / containers",
|
||||
"desiccant": "Drying agent",
|
||||
"none": "No storage checks yet.",
|
||||
"summary": "{count} jar(s) · {state}"
|
||||
},
|
||||
"desiccant": {
|
||||
"none": "None",
|
||||
"add": "Add some",
|
||||
"replace": "Replace it",
|
||||
"dry": "Blue — dry",
|
||||
"fresh": "Just renewed"
|
||||
},
|
||||
"unit": {
|
||||
"aFew": "a few",
|
||||
"some": "some",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue