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
+312 -308
View File
@@ -1,339 +1,343 @@
{
"common.loading": "Loading …",
"auth.backToLogin": "Back to login",
"auth.displayName": "What should we call you?",
"auth.email": "Email",
"auth.forgotBody": "Enter your email and we'll send a reset link if the account exists.",
"auth.forgotLink": "Forgot your password?",
"auth.forgotSentBody": "If the address has an account, we've sent a link valid for 30 minutes. Open it on this device.",
"auth.forgotSentTitle": "Check your inbox",
"auth.forgotSubmit": "Send link",
"auth.forgotTitle": "Reset password",
"auth.hasAccount": "Already have an account? Log in",
"auth.login": "Log in",
"auth.newPassword": "New password (at least 10 characters)",
"auth.noAccount": "New here? Create an account",
"auth.password": "Password",
"auth.register": "Create account",
"auth.resetBody": "The link is valid for 30 minutes and can only be used once. All devices are signed out when the password changes.",
"auth.resetDoneBody": "Your password has been changed. Log in with your new password.",
"auth.resetDoneTitle": "Done!",
"auth.resetSubmit": "Change password",
"auth.resetTitle": "Choose a new password",
"auth.resetTokenPlaceholder": "Paste the code from the email",
"auth.trialNote": "7 days of full access no card required.",
"auth.verifyDoneBody": "Thanks! Your account is now verified.",
"auth.verifyDoneTitle": "Email confirmed",
"auth.verifyFailedBody": "The link is invalid or has expired. Request a new one from your profile.",
"auth.verifyFailedTitle": "The link didn't work",
"barcode.addPrompt": "Add to your inventory?",
"barcode.aim": "Point the camera at the barcode",
"barcode.allowCamera": "Allow camera",
"barcode.cameraBody": "{brand} needs the camera to scan barcodes.",
"barcode.cameraTitle": "Camera needed",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "You need a household first.",
"barcode.noLocation": "No storage location found.",
"barcode.noNutrition": "No nutrition data",
"barcode.photoPackage": "Photograph the package",
"barcode.unknownBody": "This product isn't in the database yet. Photograph the front and the nutrition label and we'll add it.",
"barcode.unknownTitle": "Unknown product",
"common.add": "Add",
"common.back": "Back",
"common.cancel": "Cancel",
"common.done": "Done",
"common.error": "Something went wrong. Please try again.",
"common.estimate": "Estimate",
"common.loading": "Loading …",
"common.next": "Next",
"common.off": "Off",
"common.offline": "Offline showing last fetched data",
"common.on": "On",
"common.oops": "Oops",
"common.remove": "Remove",
"common.retry": "Try again",
"common.save": "Save",
"common.cancel": "Cancel",
"common.next": "Next",
"common.back": "Back",
"common.done": "Done",
"common.skip": "Skip",
"common.offline": "Offline showing last fetched data",
"common.estimate": "Estimate",
"tabs.whatToEat": "What's for dinner?",
"tabs.scan": "Scan",
"tabs.myDay": "My day",
"tabs.home": "At home",
"auth.login": "Log in",
"auth.register": "Create account",
"auth.email": "Email",
"auth.password": "Password",
"auth.displayName": "What should we call you?",
"auth.noAccount": "New here? Create an account",
"auth.hasAccount": "Already have an account? Log in",
"auth.trialNote": "7 days of full access no card required.",
"onboarding.title": "Tell us a little about yourself",
"onboarding.subtitle": "Everything is optional and can be changed at any time. The more you fill in, the better the suggestions.",
"onboarding.goal": "What is your main goal?",
"onboarding.diet": "How do you eat?",
"common.undo": "Undo",
"cooked.deductPantry": "Deduct ingredients from inventory",
"cooked.deductPantryNote": "Ingredients are deducted from your inventory, first-expiring first.",
"cooked.mealBoxes": "Servings to meal boxes",
"cooked.portionsCooked": "Servings cooked",
"cooked.rate": "Rate it",
"cooked.title": "How did it go?",
"cooked.whoAte": "Who ate?",
"cooking.finish": "Done log the meal",
"cooking.keepAwake": "The screen stays awake while you cook",
"cooking.scale": "Servings",
"cooking.step": "Step {current} of {total}",
"cooking.timer": "Start timer",
"cooking.timerDone": "Done!",
"cooking.timerRunning": "{time} left",
"home.budget": "Food budget",
"home.emptyInventory": "Your inventory is empty. Scan the fridge or add items manually.",
"home.expired": "Expired",
"home.expiresIn_one": "1 day left",
"home.expiresIn_other": "{days} days left",
"home.expiresToday": "Expires today",
"home.household": "Household",
"home.inventory": "Food inventory",
"home.mealBoxes": "Meal boxes",
"home.moreItems_one": "+ 1 more …",
"home.moreItems_other": "+ {count} more …",
"home.pastBestBefore": "Past best before smell and taste first",
"home.shopping": "Shopping list",
"home.thisWeek": "This week",
"home.title": "At home",
"home.useSoon": "Use soon",
"home.useSoonHint": "Best before is about quality smell, look and taste before you throw anything away. Use-by dates, however, should be respected.",
"home.waste": "Food waste",
"home.wasteWeek": "Food waste this week",
"household.empty": "You don't have a household yet. Create one in onboarding or join with a code.",
"household.invite": "Invite with code: {code}",
"household.locations": "Storage locations",
"household.members": "Members",
"household.portionFactor": "×{factor} portion",
"household.private": "Private per person: goals, allergies, health data and meal history.",
"household.role.adult": "Adult",
"household.role.child": "Child",
"household.role.member": "Member",
"household.role.owner": "Owner",
"household.shareCode": "Share the code with your family to share inventory, list and plan.",
"household.shared": "Shared in the household: food inventory, shopping list, week plan, meal boxes and budget.",
"logmeal.manualTitle": "Manual",
"logmeal.mealTypeTitle": "Meal type",
"logmeal.photoTip": "Tip: photograph your plate under Scan and the app estimates for you you always confirm.",
"logmeal.proteinPlaceholder": "protein (g)",
"logmeal.quickSubtitle": "Logs the same nutrition values as last time.",
"logmeal.quickTitle": "Quick re-log",
"logmeal.validationBody": "Name and calories are required for manual logging.",
"logmeal.validationTitle": "Missing info",
"logmeal.whatPlaceholder": "What did you eat?",
"mealbox.eat": "Eat now",
"mealbox.eatBy": "Eat by {date}",
"mealbox.empty": "No meal boxes right now. When you cook, you can save servings here.",
"mealbox.enjoyBody": "The portion is logged in My day.",
"mealbox.enjoyTitle": "Enjoy your meal!",
"mealbox.guidanceNote": "The recommended use-by is guidance trust smell and taste.",
"mealbox.portionsLeft_one": "1 serving left",
"mealbox.portionsLeft_other": "{count} servings left",
"mealbox.title": "Meal boxes",
"memory.delete": "Delete",
"memory.deleteAll": "Delete all memory",
"memory.deleteAllTitle": "Delete all memory?",
"memory.empty": "{brand} hasn't learned anything about you yet.",
"memory.irreversible": "This cannot be undone.",
"memory.origin.ai_inferred": "AI assumption",
"memory.origin.observed": "Observed pattern",
"memory.origin.user_stated": "You told us",
"memory.pause": "Pause",
"memory.paused": "Paused",
"memory.resume": "Resume",
"memory.subtitle": "Full transparency. Correct what's wrong, pause or delete you own your memory.",
"memory.title": "What {brand} knows about me",
"memory.verified": "Verified",
"memory.verify": "Correct",
"myday.calories": "Calories",
"myday.carbs": "Carbohydrates",
"myday.estimateNote": "Values marked with ~ are estimates you can adjust.",
"myday.estimateRange": "{min}{max} kcal, likely {kcal}",
"myday.fat": "Fat",
"myday.fiber": "Fiber",
"myday.kcalProtein": "{kcal} kcal · {protein} g protein",
"myday.logMeal": "Log meal",
"myday.mealType.breakfast": "Breakfast",
"myday.mealType.dessert": "Dessert",
"myday.mealType.dinner": "Dinner",
"myday.mealType.lunch": "Lunch",
"myday.mealType.snack": "Snack",
"myday.noMeals": "No meals logged today.",
"myday.over": "{kcal} kcal over target",
"myday.overRecommended": "Above recommended",
"myday.protein": "Protein",
"myday.remaining": "{kcal} kcal remaining",
"myday.salt": "Salt",
"myday.targetsNote": "Targets are guidance, not medical advice.",
"myday.title": "My day",
"myday.todaysMeals": "Today's meals",
"onboarding.allergen.crustaceans": "Crustaceans",
"onboarding.allergen.eggs": "Eggs",
"onboarding.allergen.fish": "Fish",
"onboarding.allergen.gluten": "Gluten",
"onboarding.allergen.milk": "Milk/lactose",
"onboarding.allergen.peanuts": "Peanuts",
"onboarding.allergen.sesame": "Sesame",
"onboarding.allergen.soy": "Soy",
"onboarding.allergen.tree_nuts": "Tree nuts",
"onboarding.allergies": "Allergies and intolerances",
"onboarding.allergyNote": "Allergy filtering is always strict dishes with your allergens are never shown.",
"onboarding.birthYearPlaceholder": "Birth year",
"onboarding.bodyTitle": "For personal calorie targets (optional)",
"onboarding.diet": "How do you eat?",
"onboarding.diet.flexitarian": "Flexitarian",
"onboarding.diet.omnivore": "Omnivore",
"onboarding.diet.pescatarian": "Pescatarian",
"onboarding.diet.vegan": "Vegan",
"onboarding.diet.vegetarian": "Vegetarian",
"onboarding.goal": "What is your main goal?",
"onboarding.goal.build_muscle": "Build muscle",
"onboarding.goal.cook_more": "Cook more at home",
"onboarding.goal.less_waste": "Reduce food waste",
"onboarding.goal.lose_weight": "Lose weight",
"onboarding.goal.lower_cost": "Spend less on food",
"onboarding.goal.maintain_weight": "Maintain weight",
"onboarding.goal.more_protein": "Eat more protein",
"onboarding.heightPlaceholder": "Height (cm)",
"onboarding.household": "Your household",
"onboarding.householdCreate": "Create household",
"onboarding.householdDefaultName": "Home",
"onboarding.householdJoin": "Join with a code",
"onboarding.householdName": "Household name",
"onboarding.inviteCode": "Invite code",
"onboarding.mode": "How precise do you want to be?",
"onboarding.modeSimple": "Simple mode",
"onboarding.modeSimpleDesc": "Snap photos of your food, accept estimates, minimal fuss.",
"onboarding.modeExact": "Exact mode",
"onboarding.modeExactDesc": "Weigh food, enter grams, full control of the numbers.",
"onboarding.modesCombine": "You can mix modes simple day to day, exact when you want.",
"onboarding.modeSimple": "Simple mode",
"onboarding.modeSimpleDesc": "Snap photos of your food, accept estimates, minimal fuss.",
"onboarding.notMedical": "{brand} provides guidance not medical advice.",
"wte.title": "What's for dinner?",
"wte.subtitle": "Based on what you have at home, what should be used up, and what you like.",
"wte.cravingPlaceholder": "I'm craving … (e.g. creamy, Asian, under 500 kcal)",
"wte.mealBoxFirst": "Ready-made food at home",
"wte.whyTitle": "Why this suggestion?",
"wte.coverage": "{pct}% at home",
"wte.missing": "Missing: {items}",
"wte.empty": "No suggestions yet add some food to your inventory or loosen the filters.",
"wte.refresh": "New suggestions",
"scan.title": "Scan",
"scan.subtitle": "Stock your food inventory or log a meal with the camera.",
"scan.fridge": "Fridge",
"scan.freezer": "Freezer",
"scan.pantry": "Pantry",
"scan.ingredients": "Ingredients",
"scan.plate": "Plate",
"scan.receipt": "Receipt",
"scan.barcode": "Barcode",
"scan.expiry": "Best-before date",
"scan.nutrition": "Nutrition label",
"scan.takePhoto": "Take photo",
"scan.tips.title": "How to get the best analysis",
"scan.tips.overview": "Start with an overview photo",
"scan.tips.shelf": "Photograph shelf by shelf",
"scan.tips.light": "Avoid darkness and shadows",
"scan.tips.move": "Move items that block each other",
"scan.analyzing": "Analyzing …",
"scan.quotaLeft_one": "1 AI scan left this month",
"scan.quotaLeft_other": "{count} AI scans left this month",
"scan.review.title": "Review the result",
"scan.review.subtitle": "AI is sometimes uncertain you decide. Edit, remove or add before saving.",
"scan.review.uncertain": "Uncertain please check",
"scan.review.approveAll": "Save to inventory",
"scan.review.rejected": "Removed",
"scan.failed": "The analysis failed. Try again or add items manually.",
"myday.title": "My day",
"myday.calories": "Calories",
"myday.protein": "Protein",
"myday.carbs": "Carbohydrates",
"myday.fat": "Fat",
"myday.fiber": "Fiber",
"myday.salt": "Salt",
"myday.remaining": "{kcal} kcal remaining",
"myday.over": "{kcal} kcal over target",
"myday.logMeal": "Log meal",
"myday.noMeals": "No meals logged today.",
"myday.estimateNote": "Values marked with ~ are estimates you can adjust.",
"myday.targetsNote": "Targets are guidance, not medical advice.",
"home.title": "At home",
"home.inventory": "Food inventory",
"home.useSoon": "Use soon",
"home.mealBoxes": "Meal boxes",
"home.shopping": "Shopping list",
"home.budget": "Food budget",
"home.household": "Household",
"home.waste": "Food waste",
"home.emptyInventory": "Your inventory is empty. Scan the fridge or add items manually.",
"home.expiresIn_one": "1 day left",
"home.expiresIn_other": "{days} days left",
"home.expiresToday": "Expires today",
"home.expired": "Expired",
"home.pastBestBefore": "Past best before smell and taste first",
"onboarding.stepBSubtitle": "Customize your profile for better suggestions.",
"onboarding.stepBTitle": "Continue setup",
"onboarding.stepCSubtitle": "Optional used for personal calorie targets.",
"onboarding.stepCTitle": "Your health profile",
"onboarding.subtitle": "Everything is optional and can be changed at any time. The more you fill in, the better the suggestions.",
"onboarding.title": "Tell us a little about yourself",
"onboarding.weightPlaceholder": "Weight (kg)",
"paywall.choose": "Choose",
"paywall.fairUse": "Fair-use quota for AI scans included.",
"paywall.family": "Family · up to 6 people",
"paywall.freeNote": "Free: 10 AI scans/month, manual inventory, saved recipes and simple logging.",
"paywall.household": "Household · up to 3 people",
"paywall.large": "Large Household · up to 12 people",
"paywall.perMonth": "{price}/month",
"paywall.popular": "Most popular",
"paywall.restore": "Restore purchases",
"paywall.soonBody": "Purchases activate via App Store/Google Play once store integration is switched on (phase 7). The backend flow is already done.",
"paywall.soonTitle": "Coming soon!",
"paywall.subtitle": "The whole household's food OS: unlimited inventory, week planning and sharing.",
"paywall.title": "{brand} Premium",
"paywall.trialActive_one": "1 day left of your trial",
"paywall.trialActive_other": "{days} days left of your trial",
"profile.aiScansUsed": "AI scans: {used} / {total} this month",
"profile.consent.anonymized_improvement": "Anonymised AI improvement",
"profile.consent.health_integration": "Health data (Apple Health / Health Connect)",
"profile.consent.image_training": "My photos may be used for training",
"profile.consent.location_weather": "Location for weather-based suggestions",
"profile.consent.personalization": "Personal features (memory & taste profile)",
"profile.consent.product_analytics": "Product analytics",
"profile.consent.push_notifications": "Push notifications",
"profile.consents": "Consents & data",
"profile.consentsNote": "Consents are separate personal features never require training consent.",
"profile.deleteAccount": "Delete account",
"profile.deleteBody": "All your data is permanently deleted in line with GDPR.",
"profile.deleteTitle": "Delete your account?",
"profile.emailUnverified": "Email address not confirmed",
"profile.export": "Export my data",
"profile.goals": "Goals & diet",
"profile.language": "Language",
"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": "Log out",
"profile.memory": "What {brand} knows about me",
"profile.memorySubtitle": "See, correct, pause or delete what the platform has learned.",
"profile.modeLabel": "Mode:",
"profile.resendVerification": "Resend confirmation email",
"profile.subscription": "Subscription",
"profile.title": "Profile",
"profile.verificationSent": "Sent! Check your inbox.",
"recipe.avgRating": "Average {avg} from {count} ratings",
"recipe.cook": "Cook now",
"recipe.cost": "approx. {amount}/serving",
"recipe.iCookedThis": "I cooked this",
"recipe.ingredients": "Ingredients",
"recipe.notSafe": "Doesn't match your dietary settings",
"recipe.perPortion": "per serving",
"recipe.portions_one": "1 serving",
"recipe.portions_other": "{count} servings",
"recipe.time": "{min} min",
"recipe.perPortion": "per serving",
"recipe.ingredients": "Ingredients",
"recipe.save": "Save",
"recipe.saved": "Saved",
"recipe.steps": "Instructions",
"recipe.cook": "Cook now",
"recipe.notSafe": "Doesn't match your dietary settings",
"recipe.substitutions": "Substitute",
"recipe.iCookedThis": "I cooked this",
"recipe.cost": "approx. {amount}/serving",
"cooking.step": "Step {current} of {total}",
"cooking.timer": "Start timer",
"cooking.timerRunning": "{time} left",
"cooking.finish": "Done log the meal",
"cooking.scale": "Servings",
"cooking.keepAwake": "The screen stays awake while you cook",
"cooked.title": "How did it go?",
"cooked.portionsCooked": "Servings cooked",
"cooked.whoAte": "Who ate?",
"cooked.mealBoxes": "Servings to meal boxes",
"cooked.deductPantry": "Deduct ingredients from inventory",
"cooked.rate": "Rate it",
"shopping.title": "Shopping list",
"recipe.time": "{min} min",
"scan.analyzing": "Analyzing",
"scan.barcode": "Barcode",
"scan.expiry": "Best-before date",
"scan.failed": "The analysis failed. Try again or add items manually.",
"scan.freezer": "Freezer",
"scan.fridge": "Fridge",
"scan.ingredients": "Ingredients",
"scan.nutrition": "Nutrition label",
"scan.pantry": "Pantry",
"scan.plate": "Plate",
"scan.quotaLeft_one": "1 AI scan left this month",
"scan.quotaLeft_other": "{count} AI scans left this month",
"scan.receipt": "Receipt",
"scan.review.addItem": "+ Add item",
"scan.review.approveAll": "Save to inventory",
"scan.review.bestBeforeNote": "Quality date the food can be fine well beyond it. Smell and taste before discarding.",
"scan.review.dateKindBestBefore": "Best before",
"scan.review.dateKindUseBy": "Use by",
"scan.review.datePlaceholder": "YYYY-MM-DD (optional)",
"scan.review.itemPlaceholder": "Item",
"scan.review.quantityPlaceholder": "Amount",
"scan.review.rejected": "Removed",
"scan.review.subtitle": "AI is sometimes uncertain you decide. Edit, remove or add before saving.",
"scan.review.title": "Review the result",
"scan.review.uncertain": "Uncertain please check",
"scan.review.unitPlaceholder": "Unit (g, l, pcs …)",
"scan.review.unknownItem": "Unknown item",
"scan.review.useByNote": "Safety date do not eat after this date.",
"scan.subtitle": "Stock your food inventory or log a meal with the camera.",
"scan.takePhoto": "Take photo",
"scan.tips.light": "Avoid darkness and shadows",
"scan.tips.move": "Move items that block each other",
"scan.tips.overview": "Start with an overview photo",
"scan.tips.shelf": "Photograph shelf by shelf",
"scan.tips.title": "How to get the best analysis",
"scan.title": "Scan",
"shopping.addPlaceholder": "Add item …",
"shopping.complete": "Finish shopping trip",
"shopping.completedBody_one": "1 item was added to your inventory.",
"shopping.completedBody_other": "{count} items were added to your inventory.",
"shopping.completedTitle": "Done!",
"shopping.completeNote": "Checked items are added to your food inventory.",
"shopping.empty": "The list is empty.",
"shopping.estimated": "approx. {amount}",
"shopping.estimatedTotal": "Estimated total: approx. {amount}",
"mealbox.title": "Meal boxes",
"mealbox.eatBy": "Eat by {date}",
"mealbox.portionsLeft_one": "1 serving left",
"mealbox.portionsLeft_other": "{count} servings left",
"mealbox.eat": "Eat now",
"mealbox.empty": "No meal boxes right now. When you cook, you can save servings here.",
"household.members": "Members",
"household.invite": "Invite with code: {code}",
"household.shared": "Shared in the household: food inventory, shopping list, week plan, meal boxes and budget.",
"household.private": "Private per person: goals, allergies, health data and meal history.",
"memory.title": "What {brand} knows about me",
"memory.subtitle": "Full transparency. Correct what's wrong, pause or delete you own your memory.",
"memory.paused": "Paused",
"memory.verify": "Correct",
"memory.pause": "Pause",
"memory.delete": "Delete",
"memory.deleteAll": "Delete all memory",
"memory.empty": "{brand} hasn't learned anything about you yet.",
"memory.origin.user_stated": "You told us",
"memory.origin.observed": "Observed pattern",
"memory.origin.ai_inferred": "AI assumption",
"paywall.title": "{brand} Premium",
"paywall.subtitle": "The whole household's food OS: unlimited inventory, week planning and sharing.",
"paywall.trialActive_one": "1 day left of your trial",
"paywall.trialActive_other": "{days} days left of your trial",
"paywall.household": "Household · up to 3 people",
"paywall.family": "Family · up to 6 people",
"paywall.large": "Large Household · up to 12 people",
"paywall.perMonth": "{price}/month",
"paywall.fairUse": "Fair-use quota for AI scans included.",
"paywall.restore": "Restore purchases",
"paywall.freeNote": "Free: 10 AI scans/month, manual inventory, saved recipes and simple logging.",
"profile.title": "Profile",
"profile.goals": "Goals & diet",
"profile.consents": "Consents & data",
"profile.memory": "What {brand} knows about me",
"profile.subscription": "Subscription",
"profile.logout": "Log out",
"profile.export": "Export my data",
"profile.deleteAccount": "Delete account",
"profile.language": "Language",
"profile.language.sv": "Svenska",
"profile.language.en": "English",
"auth.forgotLink": "Forgot your password?",
"auth.forgotTitle": "Reset password",
"auth.forgotBody": "Enter your email and we'll send a reset link if the account exists.",
"auth.forgotSubmit": "Send link",
"auth.forgotSentTitle": "Check your inbox",
"auth.forgotSentBody": "If the address has an account, we've sent a link valid for 30 minutes. Open it on this device.",
"auth.backToLogin": "Back to login",
"auth.resetTitle": "Choose a new password",
"auth.resetBody": "The link is valid for 30 minutes and can only be used once. All devices are signed out when the password changes.",
"auth.resetTokenPlaceholder": "Paste the code from the email",
"auth.newPassword": "New password (at least 10 characters)",
"auth.resetSubmit": "Change password",
"auth.resetDoneTitle": "Done!",
"auth.resetDoneBody": "Your password has been changed. Log in with your new password.",
"auth.verifyDoneTitle": "Email confirmed",
"auth.verifyDoneBody": "Thanks! Your account is now verified.",
"auth.verifyFailedTitle": "The link didn't work",
"auth.verifyFailedBody": "The link is invalid or has expired. Request a new one from your profile.",
"profile.emailUnverified": "Email address not confirmed",
"profile.resendVerification": "Resend confirmation email",
"profile.verificationSent": "Sent! Check your inbox.",
"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": "Oops",
"common.undo": "Undo",
"common.remove": "Remove",
"common.add": "Add",
"common.on": "On",
"common.off": "Off",
"home.thisWeek": "This week",
"home.wasteWeek": "Food waste this week",
"home.useSoonHint": "Best before is about quality smell, look and taste before you throw anything away. Use-by dates, however, should be respected.",
"scan.review.itemPlaceholder": "Item",
"scan.review.quantityPlaceholder": "Amount",
"scan.review.unitPlaceholder": "Unit (g, l, pcs …)",
"scan.review.datePlaceholder": "YYYY-MM-DD (optional)",
"scan.review.dateKindBestBefore": "Best before",
"scan.review.dateKindUseBy": "Use by",
"scan.review.bestBeforeNote": "Quality date the food can be fine well beyond it. Smell and taste before discarding.",
"scan.review.useByNote": "Safety date do not eat after this date.",
"scan.review.addItem": "+ Add item",
"scan.review.unknownItem": "Unknown item",
"barcode.aim": "Point the camera at the barcode",
"barcode.addPrompt": "Add to your inventory?",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.noNutrition": "No nutrition data",
"barcode.unknownTitle": "Unknown product",
"barcode.unknownBody": "This product isn't in the database yet. Photograph the front and the nutrition label and we'll add it.",
"barcode.photoPackage": "Photograph the package",
"barcode.needHousehold": "You need a household first.",
"barcode.noLocation": "No storage location found.",
"barcode.cameraTitle": "Camera needed",
"barcode.cameraBody": "{brand} needs the camera to scan barcodes.",
"barcode.allowCamera": "Allow camera",
"onboarding.goal.lose_weight": "Lose weight",
"onboarding.goal.build_muscle": "Build muscle",
"onboarding.goal.maintain_weight": "Maintain weight",
"onboarding.goal.more_protein": "Eat more protein",
"onboarding.goal.less_waste": "Reduce food waste",
"onboarding.goal.lower_cost": "Spend less on food",
"onboarding.goal.cook_more": "Cook more at home",
"onboarding.diet.omnivore": "Omnivore",
"onboarding.diet.flexitarian": "Flexitarian",
"onboarding.diet.pescatarian": "Pescatarian",
"onboarding.diet.vegetarian": "Vegetarian",
"onboarding.diet.vegan": "Vegan",
"onboarding.allergen.gluten": "Gluten",
"onboarding.allergen.milk": "Milk/lactose",
"onboarding.allergen.eggs": "Eggs",
"onboarding.allergen.tree_nuts": "Tree nuts",
"onboarding.allergen.peanuts": "Peanuts",
"onboarding.allergen.fish": "Fish",
"onboarding.allergen.crustaceans": "Crustaceans",
"onboarding.allergen.soy": "Soy",
"onboarding.allergen.sesame": "Sesame",
"onboarding.bodyTitle": "For personal calorie targets (optional)",
"onboarding.weightPlaceholder": "Weight (kg)",
"onboarding.heightPlaceholder": "Height (cm)",
"onboarding.birthYearPlaceholder": "Birth year",
"onboarding.householdDefaultName": "Home",
"onboarding.modesCombine": "You can mix modes simple day to day, exact when you want.",
"myday.mealType.breakfast": "Breakfast",
"myday.mealType.lunch": "Lunch",
"myday.mealType.dinner": "Dinner",
"myday.mealType.snack": "Snack",
"myday.mealType.dessert": "Dessert",
"myday.overRecommended": "Above recommended",
"myday.todaysMeals": "Today's meals",
"myday.estimateRange": "{min}{max} kcal, likely {kcal}",
"myday.kcalProtein": "{kcal} kcal · {protein} g protein",
"logmeal.mealTypeTitle": "Meal type",
"logmeal.quickTitle": "Quick re-log",
"logmeal.quickSubtitle": "Logs the same nutrition values as last time.",
"logmeal.manualTitle": "Manual",
"logmeal.whatPlaceholder": "What did you eat?",
"logmeal.proteinPlaceholder": "protein (g)",
"logmeal.validationTitle": "Missing info",
"logmeal.validationBody": "Name and calories are required for manual logging.",
"logmeal.photoTip": "Tip: photograph your plate under Scan and the app estimates for you you always confirm.",
"shopping.section.frukt_gront": "Fruit & veg",
"shopping.section.brod": "Bread",
"shopping.section.mejeri": "Dairy",
"shopping.section.kott_fagel": "Meat & poultry",
"shopping.section.fisk": "Fish",
"shopping.section.chark": "Deli & cold cuts",
"shopping.section.frys": "Frozen",
"shopping.section.skafferi": "Pantry",
"shopping.section.konserver": "Canned goods",
"shopping.section.kryddor_bak": "Spices & baking",
"shopping.section.dryck": "Drinks",
"shopping.section.snacks": "Snacks",
"shopping.section.fisk": "Fish",
"shopping.section.frukt_gront": "Fruit & veg",
"shopping.section.frys": "Frozen",
"shopping.section.hygien_ovrigt": "Other",
"shopping.completedTitle": "Done!",
"household.role.owner": "Owner",
"household.role.adult": "Adult",
"household.role.member": "Member",
"household.role.child": "Child",
"household.empty": "You don't have a household yet. Create one in onboarding or join with a code.",
"household.shareCode": "Share the code with your family to share inventory, list and plan.",
"household.locations": "Storage locations",
"household.portionFactor": "×{factor} portion",
"mealbox.enjoyTitle": "Enjoy your meal!",
"mealbox.enjoyBody": "The portion is logged in My day.",
"mealbox.guidanceNote": "The recommended use-by is guidance trust smell and taste.",
"paywall.soonTitle": "Coming soon!",
"paywall.soonBody": "Purchases activate via App Store/Google Play once store integration is switched on (phase 7). The backend flow is already done.",
"paywall.popular": "Most popular",
"paywall.choose": "Choose",
"recipe.saved": "Saved",
"recipe.save": "Save",
"recipe.avgRating": "Average {avg} from {count} ratings",
"memory.verified": "Verified",
"memory.resume": "Resume",
"memory.deleteAllTitle": "Delete all memory?",
"memory.irreversible": "This cannot be undone.",
"profile.consent.personalization": "Personal features (memory & taste profile)",
"profile.consent.anonymized_improvement": "Anonymised AI improvement",
"profile.consent.image_training": "My photos may be used for training",
"profile.consent.health_integration": "Health data (Apple Health / Health Connect)",
"profile.consent.location_weather": "Location for weather-based suggestions",
"profile.consent.push_notifications": "Push notifications",
"profile.modeLabel": "Mode:",
"profile.memorySubtitle": "See, correct, pause or delete what the platform has learned.",
"profile.consentsNote": "Consents are separate personal features never require training consent.",
"profile.aiScansUsed": "AI scans: {used} / {total} this month",
"profile.deleteTitle": "Delete your account?",
"profile.deleteBody": "All your data is permanently deleted in line with GDPR.",
"cooking.timerDone": "Done!",
"cooked.deductPantryNote": "Ingredients are deducted from your inventory, first-expiring first.",
"home.moreItems_one": "+ 1 more …",
"home.moreItems_other": "+ {count} more …",
"shopping.completedBody_one": "1 item was added to your inventory.",
"shopping.completedBody_other": "{count} items were added to your inventory.",
"profile.consent.product_analytics": "Product analytics"
"shopping.section.konserver": "Canned goods",
"shopping.section.kott_fagel": "Meat & poultry",
"shopping.section.kryddor_bak": "Spices & baking",
"shopping.section.mejeri": "Dairy",
"shopping.section.skafferi": "Pantry",
"shopping.section.snacks": "Snacks",
"shopping.title": "Shopping list",
"tabs.home": "At home",
"tabs.myDay": "My day",
"tabs.scan": "Scan",
"tabs.whatToEat": "What's for dinner?",
"wte.coverage": "{pct}% at home",
"wte.cravingPlaceholder": "I'm craving … (e.g. creamy, Asian, under 500 kcal)",
"wte.empty": "No suggestions yet add some food to your inventory or loosen the filters.",
"wte.mealBoxFirst": "Ready-made food at home",
"wte.missing": "Missing: {items}",
"wte.refresh": "New suggestions",
"wte.subtitle": "Based on what you have at home, what should be used up, and what you like.",
"wte.title": "What's for dinner?",
"wte.whyTitle": "Why this suggestion?"
}