fix(veckoplan+streckkod): byt ratt, planera om, variation, man-son + fota fram/bak

Veckoplan/kalender:
- Byt ratt: PATCH satter nu titeln (och portioner/status) efter det nya
  receptet, inte bara recipeId - annars visade kalendern kvar gamla ratten.
- Planera om veckan: ersatter nu veckans plan (raderar tidigare + entries via
  kaskad) sa man aldrig far dubbletter och alltid ser den nya planen. Nytt
  plan-id ger ocksa ny variation.
- Variation: liten deterministisk jitter per recept, seedad pa plan-id +
  veckostart, sa olika veckor OCH omgenereringar blir olika. Tackning/utgang
  dominerar fortfarande. Skalas av varietyLevel.
- Veckoplaneraren visar alltid mandag-sondag (mondayOf), oavsett dagens dag.

Streckkod (fota okand produkt):
- Fotar nu fram- OCH baksida (multi-foto) sa AI:n far bade namn (fram) och
  naring/innehall (bak). Prompt uppdaterad for flera bilder.
- Worker uppdaterar en befintlig delad produkt aven utan nytt namn (behaller
  namnet, fyller pa naring/innehall/allergener) - och sparar ny produkt aven
  fran baksidesfoto (fallback-namn) sa data inte tappas.
- Produktpanelen visar 'Fota baksidan (naring & innehall)' nar info saknas, sa
  man kan komplettera en halvfardig produkt. Fel visas som dialog utan att
  tappa vyn.
- 5 nya i18n-nycklar x 12 sprak.

Verifierat: typecheck (ai-contracts/worker/api/mobil), worker 23/23,
ai-contracts 24/24, api 104/104, samt repro mot testdatabas (byt ratt satter
titel; planera om ger 1 plan; 3 veckor + omgenerering varierar; delvis produkt
kompletteras och behaller namnet).
This commit is contained in:
Claude
2026-08-20 13:45:35 +00:00
parent 2f596060b4
commit cac98b8dc7
18 changed files with 204 additions and 34 deletions
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Aflæser emballagen…",
"barcode.contributing.body": "Vi finder næring, allergener og indhold og gemmer produktet i det delte katalog, så den næste slipper for at fotografere det.",
"barcode.contributing.failed": "Kunne ikke aflæse emballagen. Prøv igen med et skarpt billede og godt lys.",
"barcode.contributing.frontDone": "Forside taget",
"barcode.contributing.backPrompt": "Tag nu et billede af bagsiden med næringsdeklaration og indhold så bliver produktet komplet.",
"barcode.contributing.skipBack": "Færdig, analysér",
"barcode.contributing.shootBack": "Tag billede af bagsiden",
"barcode.complete": "Tag billede af bagsiden (næring & indhold)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Du skal først have en husstand.",
"barcode.noLocation": "Ingen opbevaringssteder fundet.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Verpackung wird gelesen…",
"barcode.contributing.body": "Wir ermitteln Nährwerte, Allergene und Zutaten und speichern das Produkt im gemeinsamen Katalog, damit es der Nächste nicht fotografieren muss.",
"barcode.contributing.failed": "Die Verpackung konnte nicht gelesen werden. Versuche es mit einem scharfen Foto und gutem Licht erneut.",
"barcode.contributing.frontDone": "Vorderseite aufgenommen",
"barcode.contributing.backPrompt": "Fotografiere jetzt die Rückseite mit Nährwerttabelle und Zutaten so wird das Produkt vollständig.",
"barcode.contributing.skipBack": "Fertig, analysieren",
"barcode.contributing.shootBack": "Rückseite fotografieren",
"barcode.complete": "Rückseite fotografieren (Nährwerte & Zutaten)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Du brauchst zuerst einen Haushalt.",
"barcode.noLocation": "Kein Lagerort gefunden.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Reading the package…",
"barcode.contributing.body": "We're extracting nutrition, allergens and ingredients and saving the product to the shared catalogue so the next person doesn't have to photograph it.",
"barcode.contributing.failed": "Couldn't read the package. Try again with a sharp photo and good lighting.",
"barcode.contributing.frontDone": "Front photo taken",
"barcode.contributing.backPrompt": "Now photograph the back with the nutrition label and ingredients that completes the product.",
"barcode.contributing.skipBack": "Done, analyse",
"barcode.contributing.shootBack": "Photograph the back",
"barcode.complete": "Photograph the back (nutrition & ingredients)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "You need a household first.",
"barcode.noLocation": "No storage location found.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Leyendo el envase…",
"barcode.contributing.body": "Estamos extrayendo la información nutricional, los alérgenos y los ingredientes, y guardando el producto en el catálogo compartido para que la próxima persona no tenga que fotografiarlo.",
"barcode.contributing.failed": "No se pudo leer el envase. Inténtalo de nuevo con una foto nítida y buena luz.",
"barcode.contributing.frontDone": "Foto del frente tomada",
"barcode.contributing.backPrompt": "Ahora fotografía la parte de atrás con la información nutricional y los ingredientes: así el producto queda completo.",
"barcode.contributing.skipBack": "Listo, analizar",
"barcode.contributing.shootBack": "Fotografiar la parte de atrás",
"barcode.complete": "Fotografiar la parte de atrás (nutrición e ingredientes)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Primero necesitas un hogar.",
"barcode.noLocation": "No se encontró ningún lugar de almacenamiento.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Luetaan pakkausta…",
"barcode.contributing.body": "Poimimme ravintoarvot, allergeenit ja ainesosat ja tallennamme tuotteen jaettuun luetteloon, jottei seuraavan tarvitse kuvata sitä.",
"barcode.contributing.failed": "Pakkausta ei voitu lukea. Yritä uudelleen terävällä kuvalla ja hyvässä valossa.",
"barcode.contributing.frontDone": "Etupuoli kuvattu",
"barcode.contributing.backPrompt": "Kuvaa nyt takapuoli, jossa on ravintosisältö ja ainesosat näin tuote täydentyy.",
"barcode.contributing.skipBack": "Valmis, analysoi",
"barcode.contributing.shootBack": "Kuvaa takapuoli",
"barcode.complete": "Kuvaa takapuoli (ravinto & ainesosat)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Tarvitset ensin kotitalouden.",
"barcode.noLocation": "Säilytyspaikkaa ei löytynyt.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Lecture de l'emballage…",
"barcode.contributing.body": "Nous extrayons les valeurs nutritionnelles, les allergènes et les ingrédients, et enregistrons le produit dans le catalogue partagé pour que la prochaine personne n'ait pas à le photographier.",
"barcode.contributing.failed": "Impossible de lire l'emballage. Réessayez avec une photo nette et un bon éclairage.",
"barcode.contributing.frontDone": "Photo de l'avant prise",
"barcode.contributing.backPrompt": "Photographiez maintenant l'arrière avec la déclaration nutritionnelle et les ingrédients le produit sera ainsi complet.",
"barcode.contributing.skipBack": "Terminé, analyser",
"barcode.contributing.shootBack": "Photographier l'arrière",
"barcode.complete": "Photographier l'arrière (nutrition et ingrédients)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Il vous faut d'abord un foyer.",
"barcode.noLocation": "Aucun lieu de stockage trouvé.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Lettura della confezione…",
"barcode.contributing.body": "Stiamo estraendo valori nutrizionali, allergeni e ingredienti e salvando il prodotto nel catalogo condiviso, così il prossimo non dovrà fotografarlo.",
"barcode.contributing.failed": "Impossibile leggere la confezione. Riprova con una foto nitida e una buona illuminazione.",
"barcode.contributing.frontDone": "Foto del fronte scattata",
"barcode.contributing.backPrompt": "Ora fotografa il retro con la tabella nutrizionale e gli ingredienti: così il prodotto è completo.",
"barcode.contributing.skipBack": "Fatto, analizza",
"barcode.contributing.shootBack": "Fotografa il retro",
"barcode.complete": "Fotografa il retro (valori e ingredienti)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Prima ti serve una famiglia.",
"barcode.noLocation": "Nessun luogo di conservazione trovato.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Leser av emballasjen…",
"barcode.contributing.body": "Vi henter næring, allergener og innhold og lagrer produktet i den delte katalogen, så den neste slipper å fotografere det.",
"barcode.contributing.failed": "Kunne ikke lese av emballasjen. Prøv igjen med et skarpt bilde og godt lys.",
"barcode.contributing.frontDone": "Forside tatt",
"barcode.contributing.backPrompt": "Ta nå bilde av baksiden med næringsinnhold og ingredienser da blir produktet komplett.",
"barcode.contributing.skipBack": "Ferdig, analyser",
"barcode.contributing.shootBack": "Ta bilde av baksiden",
"barcode.complete": "Ta bilde av baksiden (næring og innhold)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Du trenger en husstand først.",
"barcode.noLocation": "Fant ingen oppbevaringssteder.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Verpakking wordt gelezen…",
"barcode.contributing.body": "We halen voedingswaarde, allergenen en ingrediënten op en slaan het product op in de gedeelde catalogus, zodat de volgende het niet hoeft te fotograferen.",
"barcode.contributing.failed": "Kon de verpakking niet lezen. Probeer het opnieuw met een scherpe foto en goed licht.",
"barcode.contributing.frontDone": "Voorkant gefotografeerd",
"barcode.contributing.backPrompt": "Fotografeer nu de achterkant met de voedingswaarde en ingrediënten dan is het product compleet.",
"barcode.contributing.skipBack": "Klaar, analyseren",
"barcode.contributing.shootBack": "Achterkant fotograferen",
"barcode.complete": "Achterkant fotograferen (voeding & ingrediënten)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Je hebt eerst een huishouden nodig.",
"barcode.noLocation": "Geen bewaarplek gevonden.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Odczytywanie opakowania…",
"barcode.contributing.body": "Odczytujemy wartości odżywcze, alergeny i składniki i zapisujemy produkt we wspólnym katalogu, aby następna osoba nie musiała go fotografować.",
"barcode.contributing.failed": "Nie udało się odczytać opakowania. Spróbuj ponownie z ostrym zdjęciem i dobrym oświetleniem.",
"barcode.contributing.frontDone": "Zrobiono zdjęcie przodu",
"barcode.contributing.backPrompt": "Teraz sfotografuj tył z tabelą wartości odżywczych i składnikami dzięki temu produkt będzie kompletny.",
"barcode.contributing.skipBack": "Gotowe, analizuj",
"barcode.contributing.shootBack": "Sfotografuj tył",
"barcode.complete": "Sfotografuj tył (wartości i składniki)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Najpierw potrzebujesz gospodarstwa.",
"barcode.noLocation": "Nie znaleziono miejsca przechowywania.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "A ler a embalagem…",
"barcode.contributing.body": "Estamos a extrair informação nutricional, alergénios e ingredientes e a guardar o produto no catálogo partilhado, para que a próxima pessoa não precise de o fotografar.",
"barcode.contributing.failed": "Não foi possível ler a embalagem. Tente novamente com uma foto nítida e boa iluminação.",
"barcode.contributing.frontDone": "Foto da frente tirada",
"barcode.contributing.backPrompt": "Agora fotografe a parte de trás com a declaração nutricional e os ingredientes assim o produto fica completo.",
"barcode.contributing.skipBack": "Pronto, analisar",
"barcode.contributing.shootBack": "Fotografar a parte de trás",
"barcode.complete": "Fotografar a parte de trás (nutrição e ingredientes)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Primeiro precisa de um agregado.",
"barcode.noLocation": "Nenhum local de armazenamento encontrado.",
+5
View File
@@ -33,6 +33,11 @@
"barcode.contributing.title": "Läser av förpackningen…",
"barcode.contributing.body": "Vi tar fram näring, allergener och innehåll och sparar produkten i den delade katalogen så nästa person slipper fota den.",
"barcode.contributing.failed": "Kunde inte läsa av förpackningen. Försök igen med skarp bild och bra ljus.",
"barcode.contributing.frontDone": "Framsida tagen",
"barcode.contributing.backPrompt": "Fota nu baksidan med näringsdeklaration och innehåll då blir produkten komplett.",
"barcode.contributing.skipBack": "Klar, analysera",
"barcode.contributing.shootBack": "Fota baksidan",
"barcode.complete": "Fota baksidan (näring & innehåll)",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Du behöver ett hushåll först.",
"barcode.noLocation": "Ingen förvaringsplats hittades.",