FAS 1b: Progressive onboarding (3-layer flow)

- Add PROGRESSIVE_ONBOARDING feature flag to KNOWN_FLAGS
- Add onboarding_step column to users table (a/b/c)
- New backend endpoints:
  - GET /v1/onboarding/status – check current step + feature flag
  - POST /v1/onboarding/quick-start – complete Step A (goal + precision)
  - POST /v1/onboarding/complete-b – complete Step B (diet, allergens, household)
  - POST /v1/onboarding/complete-c – complete Step C (health profile)
  - POST /v1/onboarding/skip – GDPR-friendly skip
- Refactor mobile onboarding screen into 3 progressive layers
- Update auth store with onboardingStep state
- Update tab layout to only block on Step A
- Update registration to set onboardingStep='a'
- Add i18n keys for Step B/C titles across all 12 locales
- Add Zod validation schemas (quickStartInputSchema, onboardingStatusSchema)
- Add tests for validation and feature flag
- Preserve existing /v1/me/onboarding for backward compatibility
- Migration: 0004_progressive_onboarding.sql
This commit is contained in:
Sven (AAMOS AI)
2026-08-06 00:39:44 +07:00
parent b2b6601f54
commit 2f3cdf8016
24 changed files with 4338 additions and 3837 deletions
+310 -306
View File
@@ -1,339 +1,343 @@
{
"common.loading": "Laddar …",
"auth.backToLogin": "Tillbaka till inloggning",
"auth.displayName": "Vad ska vi kalla dig?",
"auth.email": "E-post",
"auth.forgotBody": "Ange din e-postadress så skickar vi en återställningslänk om kontot finns.",
"auth.forgotLink": "Glömt lösenordet?",
"auth.forgotSentBody": "Om adressen har ett konto har vi skickat en länk som gäller i 30 minuter. Öppna den på den här enheten.",
"auth.forgotSentTitle": "Kolla din inkorg",
"auth.forgotSubmit": "Skicka länk",
"auth.forgotTitle": "Återställ lösenord",
"auth.hasAccount": "Har du redan ett konto? Logga in",
"auth.login": "Logga in",
"auth.newPassword": "Nytt lösenord (minst 10 tecken)",
"auth.noAccount": "Ny här? Skapa konto",
"auth.password": "Lösenord",
"auth.register": "Skapa konto",
"auth.resetBody": "Länken gäller i 30 minuter och kan bara användas en gång. Alla enheter loggas ut när lösenordet byts.",
"auth.resetDoneBody": "Ditt lösenord är bytt. Logga in med det nya lösenordet.",
"auth.resetDoneTitle": "Klart!",
"auth.resetSubmit": "Byt lösenord",
"auth.resetTitle": "Välj nytt lösenord",
"auth.resetTokenPlaceholder": "Klistra in koden från mejlet",
"auth.trialNote": "7 dagars full tillgång inget kort behövs.",
"auth.verifyDoneBody": "Tack! Ditt konto är nu verifierat.",
"auth.verifyDoneTitle": "E-postadressen är bekräftad",
"auth.verifyFailedBody": "Länken är ogiltig eller har gått ut. Begär en ny från profilen.",
"auth.verifyFailedTitle": "Länken fungerade inte",
"barcode.addPrompt": "Lägga till i lagret?",
"barcode.aim": "Rikta kameran mot streckkoden",
"barcode.allowCamera": "Tillåt kamera",
"barcode.cameraBody": "För att skanna streckkoder behöver {brand} kameran.",
"barcode.cameraTitle": "Kameran behövs",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Du behöver ett hushåll först.",
"barcode.noLocation": "Ingen förvaringsplats hittades.",
"barcode.noNutrition": "Näringsdata saknas",
"barcode.photoPackage": "Fota förpackningen",
"barcode.unknownBody": "Produkten finns inte i databasen ännu. Fota framsidan och näringsdeklarationen så lägger vi till den.",
"barcode.unknownTitle": "Okänd produkt",
"common.add": "Lägg till",
"common.back": "Tillbaka",
"common.cancel": "Avbryt",
"common.done": "Klar",
"common.error": "Något gick fel. Försök igen.",
"common.estimate": "Uppskattning",
"common.loading": "Laddar …",
"common.next": "Nästa",
"common.off": "Av",
"common.offline": "Offline visar senast hämtade data",
"common.on": "På",
"common.oops": "Hoppsan",
"common.remove": "Ta bort",
"common.retry": "Försök igen",
"common.save": "Spara",
"common.cancel": "Avbryt",
"common.next": "Nästa",
"common.back": "Tillbaka",
"common.done": "Klar",
"common.skip": "Hoppa över",
"common.offline": "Offline visar senast hämtade data",
"common.estimate": "Uppskattning",
"tabs.whatToEat": "Vad ska vi äta?",
"tabs.scan": "Skanna",
"tabs.myDay": "Min dag",
"tabs.home": "Hemma",
"auth.login": "Logga in",
"auth.register": "Skapa konto",
"auth.email": "E-post",
"auth.password": "Lösenord",
"auth.displayName": "Vad ska vi kalla dig?",
"auth.noAccount": "Ny här? Skapa konto",
"auth.hasAccount": "Har du redan ett konto? Logga in",
"auth.trialNote": "7 dagars full tillgång inget kort behövs.",
"onboarding.title": "Berätta lite om dig",
"onboarding.subtitle": "Allt är frivilligt och kan ändras när som helst. Ju mer du fyller i, desto bättre förslag.",
"onboarding.goal": "Vad är ditt främsta mål?",
"onboarding.diet": "Hur äter du?",
"common.undo": "Ångra",
"cooked.deductPantry": "Dra ingredienser från lagret",
"cooked.deductPantryNote": "Ingredienserna dras från lagret enligt först-utgången-först.",
"cooked.mealBoxes": "Portioner till matlådor",
"cooked.portionsCooked": "Portioner lagade",
"cooked.rate": "Betygsätt",
"cooked.title": "Hur gick det?",
"cooked.whoAte": "Vilka åt?",
"cooking.finish": "Klart logga måltiden",
"cooking.keepAwake": "Skärmen hålls vaken medan du lagar",
"cooking.scale": "Portioner",
"cooking.step": "Steg {current} av {total}",
"cooking.timer": "Starta timer",
"cooking.timerDone": "Klart!",
"cooking.timerRunning": "{time} kvar",
"home.budget": "Matbudget",
"home.emptyInventory": "Lagret är tomt. Skanna kylen eller lägg till varor manuellt.",
"home.expired": "Utgången",
"home.expiresIn_one": "1 dag kvar",
"home.expiresIn_other": "{days} dagar kvar",
"home.expiresToday": "Går ut i dag",
"home.household": "Hushåll",
"home.inventory": "Matlager",
"home.mealBoxes": "Matlådor",
"home.moreItems_one": "+ 1 till …",
"home.moreItems_other": "+ {count} till …",
"home.pastBestBefore": "Passerat bäst före lukta och smaka",
"home.shopping": "Inköpslista",
"home.thisWeek": "Denna vecka",
"home.title": "Hemma",
"home.useSoon": "Använd snart",
"home.useSoonHint": "Bäst före handlar om kvalitet lukta, titta och smaka innan du slänger. Sista förbrukningsdag ska däremot respekteras.",
"home.waste": "Matsvinn",
"home.wasteWeek": "Matsvinn denna vecka",
"household.empty": "Du har inget hushåll ännu. Skapa ett i onboardingen eller gå med via kod.",
"household.invite": "Bjud in med kod: {code}",
"household.locations": "Förvaringsplatser",
"household.members": "Medlemmar",
"household.portionFactor": "×{factor} portion",
"household.private": "Privat per person: mål, allergier, hälsodata och måltidshistorik.",
"household.role.adult": "Vuxen",
"household.role.child": "Barn",
"household.role.member": "Medlem",
"household.role.owner": "Ägare",
"household.shareCode": "Dela koden med familjen så delar ni lager, lista och plan.",
"household.shared": "Delas i hushållet: matlager, inköpslista, veckoplan, matlådor och budget.",
"logmeal.manualTitle": "Manuellt",
"logmeal.mealTypeTitle": "Måltidstyp",
"logmeal.photoTip": "Tips: fota tallriken under Skanna så uppskattar appen åt dig du bekräftar alltid.",
"logmeal.proteinPlaceholder": "protein (g)",
"logmeal.quickSubtitle": "Loggar samma näringsvärden som förra gången.",
"logmeal.quickTitle": "Snabb återloggning",
"logmeal.validationBody": "Namn och kalorier behövs för manuell loggning.",
"logmeal.validationTitle": "Fyll i",
"logmeal.whatPlaceholder": "Vad åt du?",
"mealbox.eat": "Ät nu",
"mealbox.eatBy": "Ät senast {date}",
"mealbox.empty": "Inga matlådor just nu. När du lagar mat kan du spara portioner här.",
"mealbox.enjoyBody": "Portionen är loggad i Min dag.",
"mealbox.enjoyTitle": "Smaklig måltid!",
"mealbox.guidanceNote": "Rekommenderad användningstid är vägledning lita på lukt och smak.",
"mealbox.portionsLeft_one": "1 portion kvar",
"mealbox.portionsLeft_other": "{count} portioner kvar",
"mealbox.title": "Matlådor",
"memory.delete": "Radera",
"memory.deleteAll": "Radera allt minne",
"memory.deleteAllTitle": "Radera allt minne?",
"memory.empty": "{brand} har inte lärt sig något om dig ännu.",
"memory.irreversible": "Detta går inte att ångra.",
"memory.origin.ai_inferred": "AI-antagande",
"memory.origin.observed": "Observerat mönster",
"memory.origin.user_stated": "Du har berättat",
"memory.pause": "Pausa",
"memory.paused": "Pausad",
"memory.resume": "Återuppta",
"memory.subtitle": "Full insyn. Rätta det som är fel, pausa eller radera du äger ditt minne.",
"memory.title": "Vad {brand} vet om mig",
"memory.verified": "Verifierad",
"memory.verify": "Stämmer",
"myday.calories": "Kalorier",
"myday.carbs": "Kolhydrater",
"myday.estimateNote": "Värden med ~ är uppskattningar som du kan justera.",
"myday.estimateRange": "{min}{max} kcal, troligen {kcal}",
"myday.fat": "Fett",
"myday.fiber": "Fibrer",
"myday.kcalProtein": "{kcal} kcal · {protein} g protein",
"myday.logMeal": "Logga måltid",
"myday.mealType.breakfast": "Frukost",
"myday.mealType.dessert": "Efterrätt",
"myday.mealType.dinner": "Middag",
"myday.mealType.lunch": "Lunch",
"myday.mealType.snack": "Mellanmål",
"myday.noMeals": "Inga måltider loggade i dag.",
"myday.over": "{kcal} kcal över målet",
"myday.overRecommended": "Över rekommenderat",
"myday.protein": "Protein",
"myday.remaining": "{kcal} kcal kvar",
"myday.salt": "Salt",
"myday.targetsNote": "Målen är vägledning, inte medicinsk rådgivning.",
"myday.title": "Min dag",
"myday.todaysMeals": "Dagens måltider",
"onboarding.allergen.crustaceans": "Skaldjur",
"onboarding.allergen.eggs": "Ägg",
"onboarding.allergen.fish": "Fisk",
"onboarding.allergen.gluten": "Gluten",
"onboarding.allergen.milk": "Mjölk/laktos",
"onboarding.allergen.peanuts": "Jordnötter",
"onboarding.allergen.sesame": "Sesam",
"onboarding.allergen.soy": "Soja",
"onboarding.allergen.tree_nuts": "Nötter",
"onboarding.allergies": "Allergier och intoleranser",
"onboarding.allergyNote": "Allergifiltrering är alltid strikt rätter med dina allergener visas aldrig.",
"onboarding.birthYearPlaceholder": "Födelseår",
"onboarding.bodyTitle": "För personliga kalorimål (frivilligt)",
"onboarding.diet": "Hur äter du?",
"onboarding.diet.flexitarian": "Flexitarian",
"onboarding.diet.omnivore": "Allätare",
"onboarding.diet.pescatarian": "Pescetarian",
"onboarding.diet.vegan": "Vegan",
"onboarding.diet.vegetarian": "Vegetarian",
"onboarding.goal": "Vad är ditt främsta mål?",
"onboarding.goal.build_muscle": "Bygga muskler",
"onboarding.goal.cook_more": "Laga mer hemma",
"onboarding.goal.less_waste": "Minska matsvinn",
"onboarding.goal.lose_weight": "Gå ner i vikt",
"onboarding.goal.lower_cost": "Sänka matkostnaden",
"onboarding.goal.maintain_weight": "Behålla vikten",
"onboarding.goal.more_protein": "Äta mer protein",
"onboarding.heightPlaceholder": "Längd (cm)",
"onboarding.household": "Ditt hushåll",
"onboarding.householdCreate": "Skapa hushåll",
"onboarding.householdDefaultName": "Hemma",
"onboarding.householdJoin": "Gå med via kod",
"onboarding.householdName": "Hushållets namn",
"onboarding.inviteCode": "Inbjudningskod",
"onboarding.mode": "Hur noggrann vill du vara?",
"onboarding.modeSimple": "Enkelt läge",
"onboarding.modeSimpleDesc": "Fota maten, acceptera uppskattningar, minimalt pyssel.",
"onboarding.modeExact": "Exakt läge",
"onboarding.modeExactDesc": "Väg mat, ange gram, full kontroll på siffrorna.",
"onboarding.modesCombine": "Lägena kan kombineras enkelt till vardags, exakt när du vill.",
"onboarding.modeSimple": "Enkelt läge",
"onboarding.modeSimpleDesc": "Fota maten, acceptera uppskattningar, minimalt pyssel.",
"onboarding.notMedical": "{brand} ger vägledning inte medicinsk rådgivning.",
"wte.title": "Vad ska vi äta?",
"wte.subtitle": "Utifrån vad ni har hemma, vad som bör användas och vad ni gillar.",
"wte.cravingPlaceholder": "Jag är sugen på … (t.ex. krämigt, asiatiskt, under 500 kcal)",
"wte.mealBoxFirst": "Färdig mat hemma",
"wte.whyTitle": "Varför detta förslag?",
"wte.coverage": "{pct} % hemma",
"wte.missing": "Saknas: {items}",
"wte.empty": "Inga förslag ännu lägg in lite mat i lagret eller lätta på filtren.",
"wte.refresh": "Nya förslag",
"scan.title": "Skanna",
"scan.subtitle": "Fyll på matlagret eller logga en måltid med kameran.",
"scan.fridge": "Kyl",
"scan.freezer": "Frys",
"scan.pantry": "Skafferi",
"scan.ingredients": "Ingredienser",
"scan.plate": "Tallrik",
"scan.receipt": "Kvitto",
"onboarding.stepBSubtitle": "Anpassa din profil för bättre förslag.",
"onboarding.stepBTitle": "Fortsätt konfiguration",
"onboarding.stepCSubtitle": "Valfritt används för personliga kalorimål.",
"onboarding.stepCTitle": "Din hälsoprofil",
"onboarding.subtitle": "Allt är frivilligt och kan ändras när som helst. Ju mer du fyller i, desto bättre förslag.",
"onboarding.title": "Berätta lite om dig",
"onboarding.weightPlaceholder": "Vikt (kg)",
"paywall.choose": "Välj",
"paywall.fairUse": "Fair use-kvot för AI-skanningar ingår.",
"paywall.family": "Family · upp till 6 personer",
"paywall.freeNote": "Gratis: 10 AI-skanningar/mån, manuellt lager, sparade recept och enkel loggning.",
"paywall.household": "Household · upp till 3 personer",
"paywall.large": "Large Household · upp till 12 personer",
"paywall.perMonth": "{price}/mån",
"paywall.popular": "Populärast",
"paywall.restore": "Återställ köp",
"paywall.soonBody": "Köp aktiveras via App Store/Google Play när butiksintegrationen slås på (fas 7). Backend-flödet är redan klart.",
"paywall.soonTitle": "Snart!",
"paywall.subtitle": "Hela hushållets mat-OS: obegränsat lager, veckoplan och delning.",
"paywall.title": "{brand} Premium",
"paywall.trialActive_one": "1 dag kvar av din provperiod",
"paywall.trialActive_other": "{days} dagar kvar av din provperiod",
"profile.aiScansUsed": "AI-skanningar: {used} / {total} denna månad",
"profile.consent.anonymized_improvement": "Anonymiserad förbättring av AI:n",
"profile.consent.health_integration": "Hälsodata (Apple Health / Health Connect)",
"profile.consent.image_training": "Mina bilder får användas för träning",
"profile.consent.location_weather": "Plats för väderbaserade förslag",
"profile.consent.personalization": "Personliga funktioner (minne & smakprofil)",
"profile.consent.product_analytics": "Produktanalys",
"profile.consent.push_notifications": "Push-notiser",
"profile.consents": "Samtycken & data",
"profile.consentsNote": "Samtyckena är separata personlig funktion kräver aldrig träningssamtycke.",
"profile.deleteAccount": "Radera konto",
"profile.deleteBody": "All din data raderas permanent enligt GDPR.",
"profile.deleteTitle": "Radera kontot?",
"profile.emailUnverified": "E-postadressen är inte bekräftad",
"profile.export": "Exportera min data",
"profile.goals": "Mål & kost",
"profile.language": "Språk",
"profile.language.da": "Dansk",
"profile.language.de": "Deutsch",
"profile.language.en": "English",
"profile.language.es": "Español",
"profile.language.fi": "Suomi",
"profile.language.fr": "Français",
"profile.language.it": "Italiano",
"profile.language.nb": "Norsk",
"profile.language.nl": "Nederlands",
"profile.language.pl": "Polski",
"profile.language.pt": "Português",
"profile.language.sv": "Svenska",
"profile.logout": "Logga ut",
"profile.memory": "Vad {brand} vet om mig",
"profile.memorySubtitle": "Se, rätta, pausa eller radera det plattformen lärt sig.",
"profile.modeLabel": "Läge:",
"profile.resendVerification": "Skicka bekräftelsemejl igen",
"profile.subscription": "Prenumeration",
"profile.title": "Profil",
"profile.verificationSent": "Skickat! Kolla din inkorg.",
"recipe.avgRating": "Snitt {avg} av {count} betyg",
"recipe.cook": "Laga nu",
"recipe.cost": "ca {amount}/portion",
"recipe.iCookedThis": "Jag lagade detta",
"recipe.ingredients": "Ingredienser",
"recipe.notSafe": "Passar inte dina kostinställningar",
"recipe.perPortion": "per portion",
"recipe.portions_one": "1 portion",
"recipe.portions_other": "{count} portioner",
"recipe.save": "Spara",
"recipe.saved": "Sparad",
"recipe.steps": "Gör så här",
"recipe.substitutions": "Byt ut",
"recipe.time": "{min} min",
"scan.analyzing": "Analyserar …",
"scan.barcode": "Streckkod",
"scan.expiry": "Bäst före-datum",
"scan.failed": "Analysen misslyckades. Försök igen eller registrera manuellt.",
"scan.freezer": "Frys",
"scan.fridge": "Kyl",
"scan.ingredients": "Ingredienser",
"scan.nutrition": "Näringsdeklaration",
"scan.pantry": "Skafferi",
"scan.plate": "Tallrik",
"scan.quotaLeft_one": "1 AI-skanning kvar denna månad",
"scan.quotaLeft_other": "{count} AI-skanningar kvar denna månad",
"scan.receipt": "Kvitto",
"scan.review.addItem": "+ Lägg till vara",
"scan.review.approveAll": "Spara till lagret",
"scan.review.bestBeforeNote": "Kvalitetsgräns varan kan vara god längre. Lukta och smaka innan du slänger.",
"scan.review.dateKindBestBefore": "Bäst före",
"scan.review.dateKindUseBy": "Sista förbrukningsdag",
"scan.review.datePlaceholder": "ÅÅÅÅ-MM-DD (valfritt)",
"scan.review.itemPlaceholder": "Vara",
"scan.review.quantityPlaceholder": "Mängd",
"scan.review.rejected": "Borttagen",
"scan.review.subtitle": "AI:n är osäker ibland du bestämmer. Ändra, ta bort eller lägg till innan du sparar.",
"scan.review.title": "Granska resultatet",
"scan.review.uncertain": "Osäker kontrollera",
"scan.review.unitPlaceholder": "Enhet (g, l, st …)",
"scan.review.unknownItem": "Okänd vara",
"scan.review.useByNote": "Säkerhetsgräns ät inte efter detta datum.",
"scan.subtitle": "Fyll på matlagret eller logga en måltid med kameran.",
"scan.takePhoto": "Ta bild",
"scan.tips.title": "Så blir analysen bäst",
"scan.tips.overview": "Ta en översiktsbild först",
"scan.tips.shelf": "Fota gärna hylla för hylla",
"scan.tips.light": "Undvik mörker och skugga",
"scan.tips.move": "Flytta varor som skymmer varandra",
"scan.analyzing": "Analyserar …",
"scan.review.title": "Granska resultatet",
"scan.review.subtitle": "AI:n är osäker ibland du bestämmer. Ändra, ta bort eller lägg till innan du sparar.",
"scan.review.uncertain": "Osäker kontrollera",
"scan.review.approveAll": "Spara till lagret",
"scan.review.rejected": "Borttagen",
"scan.failed": "Analysen misslyckades. Försök igen eller registrera manuellt.",
"myday.title": "Min dag",
"myday.calories": "Kalorier",
"myday.protein": "Protein",
"myday.carbs": "Kolhydrater",
"myday.fat": "Fett",
"myday.fiber": "Fibrer",
"myday.salt": "Salt",
"myday.remaining": "{kcal} kcal kvar",
"myday.over": "{kcal} kcal över målet",
"myday.logMeal": "Logga måltid",
"myday.noMeals": "Inga måltider loggade i dag.",
"myday.estimateNote": "Värden med ~ är uppskattningar som du kan justera.",
"myday.targetsNote": "Målen är vägledning, inte medicinsk rådgivning.",
"home.title": "Hemma",
"home.inventory": "Matlager",
"home.useSoon": "Använd snart",
"home.mealBoxes": "Matlådor",
"home.shopping": "Inköpslista",
"home.budget": "Matbudget",
"home.household": "Hushåll",
"home.waste": "Matsvinn",
"home.emptyInventory": "Lagret är tomt. Skanna kylen eller lägg till varor manuellt.",
"home.expiresToday": "Går ut i dag",
"home.expired": "Utgången",
"home.pastBestBefore": "Passerat bäst före lukta och smaka",
"recipe.time": "{min} min",
"recipe.perPortion": "per portion",
"recipe.ingredients": "Ingredienser",
"recipe.steps": "Gör så här",
"recipe.cook": "Laga nu",
"recipe.notSafe": "Passar inte dina kostinställningar",
"recipe.substitutions": "Byt ut",
"recipe.iCookedThis": "Jag lagade detta",
"recipe.cost": "ca {amount}/portion",
"cooking.step": "Steg {current} av {total}",
"cooking.timer": "Starta timer",
"cooking.timerRunning": "{time} kvar",
"cooking.finish": "Klart logga måltiden",
"cooking.scale": "Portioner",
"cooking.keepAwake": "Skärmen hålls vaken medan du lagar",
"cooked.title": "Hur gick det?",
"cooked.portionsCooked": "Portioner lagade",
"cooked.whoAte": "Vilka åt?",
"cooked.mealBoxes": "Portioner till matlådor",
"cooked.deductPantry": "Dra ingredienser från lagret",
"cooked.rate": "Betygsätt",
"shopping.title": "Inköpslista",
"scan.tips.overview": "Ta en översiktsbild först",
"scan.tips.shelf": "Fota gärna hylla för hylla",
"scan.tips.title": "Så blir analysen bäst",
"scan.title": "Skanna",
"shopping.addPlaceholder": "Lägg till vara …",
"shopping.complete": "Avsluta köprundan",
"shopping.completedBody_one": "1 vara lades in i matlagret.",
"shopping.completedBody_other": "{count} varor lades in i matlagret.",
"shopping.completedTitle": "Klart!",
"shopping.completeNote": "Bockade varor läggs in i matlagret.",
"shopping.empty": "Listan är tom.",
"shopping.estimated": "ca {amount}",
"shopping.estimatedTotal": "Uppskattad totalsumma: ca {amount}",
"mealbox.title": "Matlådor",
"mealbox.eatBy": "Ät senast {date}",
"mealbox.eat": "Ät nu",
"mealbox.empty": "Inga matlådor just nu. När du lagar mat kan du spara portioner här.",
"household.members": "Medlemmar",
"household.invite": "Bjud in med kod: {code}",
"household.shared": "Delas i hushållet: matlager, inköpslista, veckoplan, matlådor och budget.",
"household.private": "Privat per person: mål, allergier, hälsodata och måltidshistorik.",
"memory.title": "Vad {brand} vet om mig",
"memory.subtitle": "Full insyn. Rätta det som är fel, pausa eller radera du äger ditt minne.",
"memory.paused": "Pausad",
"memory.verify": "Stämmer",
"memory.pause": "Pausa",
"memory.delete": "Radera",
"memory.deleteAll": "Radera allt minne",
"memory.empty": "{brand} har inte lärt sig något om dig ännu.",
"memory.origin.user_stated": "Du har berättat",
"memory.origin.observed": "Observerat mönster",
"memory.origin.ai_inferred": "AI-antagande",
"paywall.title": "{brand} Premium",
"paywall.subtitle": "Hela hushållets mat-OS: obegränsat lager, veckoplan och delning.",
"paywall.household": "Household · upp till 3 personer",
"paywall.family": "Family · upp till 6 personer",
"paywall.large": "Large Household · upp till 12 personer",
"paywall.perMonth": "{price}/mån",
"paywall.fairUse": "Fair use-kvot för AI-skanningar ingår.",
"paywall.restore": "Återställ köp",
"paywall.freeNote": "Gratis: 10 AI-skanningar/mån, manuellt lager, sparade recept och enkel loggning.",
"profile.title": "Profil",
"profile.goals": "Mål & kost",
"profile.consents": "Samtycken & data",
"profile.memory": "Vad {brand} vet om mig",
"profile.subscription": "Prenumeration",
"profile.logout": "Logga ut",
"profile.export": "Exportera min data",
"profile.deleteAccount": "Radera konto",
"home.expiresIn_one": "1 dag kvar",
"home.expiresIn_other": "{days} dagar kvar",
"scan.quotaLeft_one": "1 AI-skanning kvar denna månad",
"scan.quotaLeft_other": "{count} AI-skanningar kvar denna månad",
"mealbox.portionsLeft_one": "1 portion kvar",
"mealbox.portionsLeft_other": "{count} portioner kvar",
"recipe.portions_one": "1 portion",
"recipe.portions_other": "{count} portioner",
"paywall.trialActive_one": "1 dag kvar av din provperiod",
"paywall.trialActive_other": "{days} dagar kvar av din provperiod",
"profile.language": "Språk",
"profile.language.sv": "Svenska",
"profile.language.en": "English",
"auth.forgotLink": "Glömt lösenordet?",
"auth.forgotTitle": "Återställ lösenord",
"auth.forgotBody": "Ange din e-postadress så skickar vi en återställningslänk om kontot finns.",
"auth.forgotSubmit": "Skicka länk",
"auth.forgotSentTitle": "Kolla din inkorg",
"auth.forgotSentBody": "Om adressen har ett konto har vi skickat en länk som gäller i 30 minuter. Öppna den på den här enheten.",
"auth.backToLogin": "Tillbaka till inloggning",
"auth.resetTitle": "Välj nytt lösenord",
"auth.resetBody": "Länken gäller i 30 minuter och kan bara användas en gång. Alla enheter loggas ut när lösenordet byts.",
"auth.resetTokenPlaceholder": "Klistra in koden från mejlet",
"auth.newPassword": "Nytt lösenord (minst 10 tecken)",
"auth.resetSubmit": "Byt lösenord",
"auth.resetDoneTitle": "Klart!",
"auth.resetDoneBody": "Ditt lösenord är bytt. Logga in med det nya lösenordet.",
"auth.verifyDoneTitle": "E-postadressen är bekräftad",
"auth.verifyDoneBody": "Tack! Ditt konto är nu verifierat.",
"auth.verifyFailedTitle": "Länken fungerade inte",
"auth.verifyFailedBody": "Länken är ogiltig eller har gått ut. Begär en ny från profilen.",
"profile.emailUnverified": "E-postadressen är inte bekräftad",
"profile.resendVerification": "Skicka bekräftelsemejl igen",
"profile.verificationSent": "Skickat! Kolla din inkorg.",
"profile.language.es": "Español",
"profile.language.it": "Italiano",
"profile.language.de": "Deutsch",
"profile.language.fr": "Français",
"profile.language.da": "Dansk",
"profile.language.nb": "Norsk",
"profile.language.fi": "Suomi",
"profile.language.nl": "Nederlands",
"profile.language.pl": "Polski",
"profile.language.pt": "Português",
"common.oops": "Hoppsan",
"common.undo": "Ångra",
"common.remove": "Ta bort",
"common.add": "Lägg till",
"common.on": "På",
"common.off": "Av",
"home.thisWeek": "Denna vecka",
"home.wasteWeek": "Matsvinn denna vecka",
"home.useSoonHint": "Bäst före handlar om kvalitet lukta, titta och smaka innan du slänger. Sista förbrukningsdag ska däremot respekteras.",
"scan.review.itemPlaceholder": "Vara",
"scan.review.quantityPlaceholder": "Mängd",
"scan.review.unitPlaceholder": "Enhet (g, l, st …)",
"scan.review.datePlaceholder": "ÅÅÅÅ-MM-DD (valfritt)",
"scan.review.dateKindBestBefore": "Bäst före",
"scan.review.dateKindUseBy": "Sista förbrukningsdag",
"scan.review.bestBeforeNote": "Kvalitetsgräns varan kan vara god längre. Lukta och smaka innan du slänger.",
"scan.review.useByNote": "Säkerhetsgräns ät inte efter detta datum.",
"scan.review.addItem": "+ Lägg till vara",
"scan.review.unknownItem": "Okänd vara",
"barcode.aim": "Rikta kameran mot streckkoden",
"barcode.addPrompt": "Lägga till i lagret?",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.noNutrition": "Näringsdata saknas",
"barcode.unknownTitle": "Okänd produkt",
"barcode.unknownBody": "Produkten finns inte i databasen ännu. Fota framsidan och näringsdeklarationen så lägger vi till den.",
"barcode.photoPackage": "Fota förpackningen",
"barcode.needHousehold": "Du behöver ett hushåll först.",
"barcode.noLocation": "Ingen förvaringsplats hittades.",
"barcode.cameraTitle": "Kameran behövs",
"barcode.cameraBody": "För att skanna streckkoder behöver {brand} kameran.",
"barcode.allowCamera": "Tillåt kamera",
"onboarding.goal.lose_weight": "Gå ner i vikt",
"onboarding.goal.build_muscle": "Bygga muskler",
"onboarding.goal.maintain_weight": "Behålla vikten",
"onboarding.goal.more_protein": "Äta mer protein",
"onboarding.goal.less_waste": "Minska matsvinn",
"onboarding.goal.lower_cost": "Sänka matkostnaden",
"onboarding.goal.cook_more": "Laga mer hemma",
"onboarding.diet.omnivore": "Allätare",
"onboarding.diet.flexitarian": "Flexitarian",
"onboarding.diet.pescatarian": "Pescetarian",
"onboarding.diet.vegetarian": "Vegetarian",
"onboarding.diet.vegan": "Vegan",
"onboarding.allergen.gluten": "Gluten",
"onboarding.allergen.milk": "Mjölk/laktos",
"onboarding.allergen.eggs": "Ägg",
"onboarding.allergen.tree_nuts": "Nötter",
"onboarding.allergen.peanuts": "Jordnötter",
"onboarding.allergen.fish": "Fisk",
"onboarding.allergen.crustaceans": "Skaldjur",
"onboarding.allergen.soy": "Soja",
"onboarding.allergen.sesame": "Sesam",
"onboarding.bodyTitle": "För personliga kalorimål (frivilligt)",
"onboarding.weightPlaceholder": "Vikt (kg)",
"onboarding.heightPlaceholder": "Längd (cm)",
"onboarding.birthYearPlaceholder": "Födelseår",
"onboarding.householdDefaultName": "Hemma",
"onboarding.modesCombine": "Lägena kan kombineras enkelt till vardags, exakt när du vill.",
"myday.mealType.breakfast": "Frukost",
"myday.mealType.lunch": "Lunch",
"myday.mealType.dinner": "Middag",
"myday.mealType.snack": "Mellanmål",
"myday.mealType.dessert": "Efterrätt",
"myday.overRecommended": "Över rekommenderat",
"myday.todaysMeals": "Dagens måltider",
"myday.estimateRange": "{min}{max} kcal, troligen {kcal}",
"myday.kcalProtein": "{kcal} kcal · {protein} g protein",
"logmeal.mealTypeTitle": "Måltidstyp",
"logmeal.quickTitle": "Snabb återloggning",
"logmeal.quickSubtitle": "Loggar samma näringsvärden som förra gången.",
"logmeal.manualTitle": "Manuellt",
"logmeal.whatPlaceholder": "Vad åt du?",
"logmeal.proteinPlaceholder": "protein (g)",
"logmeal.validationTitle": "Fyll i",
"logmeal.validationBody": "Namn och kalorier behövs för manuell loggning.",
"logmeal.photoTip": "Tips: fota tallriken under Skanna så uppskattar appen åt dig du bekräftar alltid.",
"shopping.section.frukt_gront": "Frukt & grönt",
"shopping.section.brod": "Bröd",
"shopping.section.mejeri": "Mejeri",
"shopping.section.kott_fagel": "Kött & fågel",
"shopping.section.fisk": "Fisk",
"shopping.section.chark": "Chark",
"shopping.section.frys": "Frys",
"shopping.section.skafferi": "Skafferi",
"shopping.section.konserver": "Konserver",
"shopping.section.kryddor_bak": "Kryddor & bakning",
"shopping.section.dryck": "Dryck",
"shopping.section.snacks": "Snacks",
"shopping.section.fisk": "Fisk",
"shopping.section.frukt_gront": "Frukt & grönt",
"shopping.section.frys": "Frys",
"shopping.section.hygien_ovrigt": "Övrigt",
"shopping.completedTitle": "Klart!",
"household.role.owner": "Ägare",
"household.role.adult": "Vuxen",
"household.role.member": "Medlem",
"household.role.child": "Barn",
"household.empty": "Du har inget hushåll ännu. Skapa ett i onboardingen eller gå med via kod.",
"household.shareCode": "Dela koden med familjen så delar ni lager, lista och plan.",
"household.locations": "Förvaringsplatser",
"household.portionFactor": "×{factor} portion",
"mealbox.enjoyTitle": "Smaklig måltid!",
"mealbox.enjoyBody": "Portionen är loggad i Min dag.",
"mealbox.guidanceNote": "Rekommenderad användningstid är vägledning lita på lukt och smak.",
"paywall.soonTitle": "Snart!",
"paywall.soonBody": "Köp aktiveras via App Store/Google Play när butiksintegrationen slås på (fas 7). Backend-flödet är redan klart.",
"paywall.popular": "Populärast",
"paywall.choose": "Välj",
"recipe.saved": "Sparad",
"recipe.save": "Spara",
"recipe.avgRating": "Snitt {avg} av {count} betyg",
"memory.verified": "Verifierad",
"memory.resume": "Återuppta",
"memory.deleteAllTitle": "Radera allt minne?",
"memory.irreversible": "Detta går inte att ångra.",
"profile.consent.personalization": "Personliga funktioner (minne & smakprofil)",
"profile.consent.anonymized_improvement": "Anonymiserad förbättring av AI:n",
"profile.consent.image_training": "Mina bilder får användas för träning",
"profile.consent.health_integration": "Hälsodata (Apple Health / Health Connect)",
"profile.consent.location_weather": "Plats för väderbaserade förslag",
"profile.consent.push_notifications": "Push-notiser",
"profile.modeLabel": "Läge:",
"profile.memorySubtitle": "Se, rätta, pausa eller radera det plattformen lärt sig.",
"profile.consentsNote": "Samtyckena är separata personlig funktion kräver aldrig träningssamtycke.",
"profile.aiScansUsed": "AI-skanningar: {used} / {total} denna månad",
"profile.deleteTitle": "Radera kontot?",
"profile.deleteBody": "All din data raderas permanent enligt GDPR.",
"cooking.timerDone": "Klart!",
"cooked.deductPantryNote": "Ingredienserna dras från lagret enligt först-utgången-först.",
"home.moreItems_one": "+ 1 till …",
"home.moreItems_other": "+ {count} till …",
"shopping.completedBody_one": "1 vara lades in i matlagret.",
"shopping.completedBody_other": "{count} varor lades in i matlagret.",
"profile.consent.product_analytics": "Produktanalys"
"shopping.section.konserver": "Konserver",
"shopping.section.kott_fagel": "Kött & fågel",
"shopping.section.kryddor_bak": "Kryddor & bakning",
"shopping.section.mejeri": "Mejeri",
"shopping.section.skafferi": "Skafferi",
"shopping.section.snacks": "Snacks",
"shopping.title": "Inköpslista",
"tabs.home": "Hemma",
"tabs.myDay": "Min dag",
"tabs.scan": "Skanna",
"tabs.whatToEat": "Vad ska vi äta?",
"wte.coverage": "{pct} % hemma",
"wte.cravingPlaceholder": "Jag är sugen på … (t.ex. krämigt, asiatiskt, under 500 kcal)",
"wte.empty": "Inga förslag ännu lägg in lite mat i lagret eller lätta på filtren.",
"wte.mealBoxFirst": "Färdig mat hemma",
"wte.missing": "Saknas: {items}",
"wte.refresh": "Nya förslag",
"wte.subtitle": "Utifrån vad ni har hemma, vad som bör användas och vad ni gillar.",
"wte.title": "Vad ska vi äta?",
"wte.whyTitle": "Varför detta förslag?"
}