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": "Ladataan …",
"auth.backToLogin": "Takaisin kirjautumiseen",
"auth.displayName": "Miksi kutsumme sinua?",
"auth.email": "Sähköposti",
"auth.forgotBody": "Anna sähköpostiosoitteesi, niin lähetämme linkin, jos tili on olemassa.",
"auth.forgotLink": "Unohtuiko salasana?",
"auth.forgotSentBody": "Jos osoitteella on tili, lähetimme linkin, joka on voimassa 30 minuuttia. Avaa se tällä laitteella.",
"auth.forgotSentTitle": "Tarkista sähköpostisi",
"auth.forgotSubmit": "Lähetä linkki",
"auth.forgotTitle": "Nollaa salasana",
"auth.hasAccount": "Onko sinulla jo tili? Kirjaudu",
"auth.login": "Kirjaudu",
"auth.newPassword": "Uusi salasana (vähintään 10 merkkiä)",
"auth.noAccount": "Uusi täällä? Luo tili",
"auth.password": "Salasana",
"auth.register": "Luo tili",
"auth.resetBody": "Linkki on voimassa 30 minuuttia ja sitä voi käyttää vain kerran. Kaikki laitteet kirjataan ulos.",
"auth.resetDoneBody": "Salasanasi on vaihdettu. Kirjaudu uudella salasanalla.",
"auth.resetDoneTitle": "Valmis!",
"auth.resetSubmit": "Vaihda salasana",
"auth.resetTitle": "Valitse uusi salasana",
"auth.resetTokenPlaceholder": "Liitä koodi sähköpostista",
"auth.trialNote": "7 päivää täyttä käyttöä ilman korttia.",
"auth.verifyDoneBody": "Kiitos! Tilisi on nyt vahvistettu.",
"auth.verifyDoneTitle": "Sähköposti vahvistettu",
"auth.verifyFailedBody": "Linkki on virheellinen tai vanhentunut. Pyydä uusi profiilista.",
"auth.verifyFailedTitle": "Linkki ei toiminut",
"barcode.addPrompt": "Lisätäänkö ruokavarastoon?",
"barcode.aim": "Suuntaa kamera viivakoodiin",
"barcode.allowCamera": "Salli kamera",
"barcode.cameraBody": "{brand} tarvitsee kameraa viivakoodien skannaamiseen.",
"barcode.cameraTitle": "Kamera tarvitaan",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Tarvitset ensin kotitalouden.",
"barcode.noLocation": "Säilytyspaikkaa ei löytynyt.",
"barcode.noNutrition": "Ei ravintotietoja",
"barcode.photoPackage": "Kuvaa pakkaus",
"barcode.unknownBody": "Tuotetta ei ole vielä tietokannassa. Kuvaa etupuoli ja ravintosisältö, niin lisäämme sen.",
"barcode.unknownTitle": "Tuntematon tuote",
"common.add": "Lisää",
"common.back": "Takaisin",
"common.cancel": "Peruuta",
"common.done": "Valmis",
"common.error": "Jokin meni pieleen. Yritä uudelleen.",
"common.estimate": "Arvio",
"common.loading": "Ladataan …",
"common.next": "Seuraava",
"common.off": "Pois",
"common.offline": "Offline näytetään viimeksi haetut tiedot",
"common.on": "Päällä",
"common.oops": "Hups",
"common.remove": "Poista",
"common.retry": "Yritä uudelleen",
"common.save": "Tallenna",
"common.cancel": "Peruuta",
"common.next": "Seuraava",
"common.back": "Takaisin",
"common.done": "Valmis",
"common.skip": "Ohita",
"common.offline": "Offline näytetään viimeksi haetut tiedot",
"common.estimate": "Arvio",
"tabs.whatToEat": "Mitä syödään?",
"tabs.scan": "Skannaa",
"tabs.myDay": "Minun päiväni",
"tabs.home": "Kotona",
"auth.login": "Kirjaudu",
"auth.register": "Luo tili",
"auth.email": "Sähköposti",
"auth.password": "Salasana",
"auth.displayName": "Miksi kutsumme sinua?",
"auth.noAccount": "Uusi täällä? Luo tili",
"auth.hasAccount": "Onko sinulla jo tili? Kirjaudu",
"auth.trialNote": "7 päivää täyttä käyttöä ilman korttia.",
"auth.forgotLink": "Unohtuiko salasana?",
"auth.forgotTitle": "Nollaa salasana",
"auth.forgotBody": "Anna sähköpostiosoitteesi, niin lähetämme linkin, jos tili on olemassa.",
"auth.forgotSubmit": "Lähetä linkki",
"auth.forgotSentTitle": "Tarkista sähköpostisi",
"auth.forgotSentBody": "Jos osoitteella on tili, lähetimme linkin, joka on voimassa 30 minuuttia. Avaa se tällä laitteella.",
"auth.backToLogin": "Takaisin kirjautumiseen",
"auth.resetTitle": "Valitse uusi salasana",
"auth.resetBody": "Linkki on voimassa 30 minuuttia ja sitä voi käyttää vain kerran. Kaikki laitteet kirjataan ulos.",
"auth.resetTokenPlaceholder": "Liitä koodi sähköpostista",
"auth.newPassword": "Uusi salasana (vähintään 10 merkkiä)",
"auth.resetSubmit": "Vaihda salasana",
"auth.resetDoneTitle": "Valmis!",
"auth.resetDoneBody": "Salasanasi on vaihdettu. Kirjaudu uudella salasanalla.",
"auth.verifyDoneTitle": "Sähköposti vahvistettu",
"auth.verifyDoneBody": "Kiitos! Tilisi on nyt vahvistettu.",
"auth.verifyFailedTitle": "Linkki ei toiminut",
"auth.verifyFailedBody": "Linkki on virheellinen tai vanhentunut. Pyydä uusi profiilista.",
"onboarding.title": "Kerro hieman itsestäsi",
"onboarding.subtitle": "Kaikki on vapaaehtoista ja muutettavissa milloin tahansa. Mitä enemmän täytät, sitä paremmat ehdotukset.",
"onboarding.goal": "Mikä on tärkein tavoitteesi?",
"onboarding.diet": "Miten syöt?",
"common.undo": "Kumoa",
"cooked.deductPantry": "Vähennä ainekset varastosta",
"cooked.deductPantryNote": "Ainekset vähennetään ruokavarastosta ensin erääntyvät ensin.",
"cooked.mealBoxes": "Annoksia eväsrasioihin",
"cooked.portionsCooked": "Valmistetut annokset",
"cooked.rate": "Arvioi",
"cooked.title": "Miten meni?",
"cooked.whoAte": "Ketkä söivät?",
"cooking.finish": "Valmis kirjaa ateria",
"cooking.keepAwake": "Näyttö pysyy päällä, kun kokkaat",
"cooking.scale": "Annokset",
"cooking.step": "Vaihe {current}/{total}",
"cooking.timer": "Käynnistä ajastin",
"cooking.timerDone": "Valmis!",
"cooking.timerRunning": "{time} jäljellä",
"home.budget": "Ruokabudjetti",
"home.emptyInventory": "Varasto on tyhjä. Skannaa jääkaappi tai lisää tuotteita käsin.",
"home.expired": "Vanhentunut",
"home.expiresIn_one": "1 päivä jäljellä",
"home.expiresIn_other": "{days} päivää jäljellä",
"home.expiresToday": "Vanhenee tänään",
"home.household": "Kotitalous",
"home.inventory": "Ruokavarasto",
"home.mealBoxes": "Eväsrasiat",
"home.moreItems_one": "+ 1 lisää …",
"home.moreItems_other": "+ {count} lisää …",
"home.pastBestBefore": "Parasta ennen ylitetty haista ja maista ensin",
"home.shopping": "Ostoslista",
"home.thisWeek": "Tällä viikolla",
"home.title": "Kotona",
"home.useSoon": "Käytä pian",
"home.useSoonHint": "Parasta ennen koskee laatua haista, katso ja maista ennen kuin heität pois. Viimeistä käyttöpäivää sen sijaan on noudatettava.",
"home.waste": "Ruokahävikki",
"home.wasteWeek": "Hävikki tällä viikolla",
"household.empty": "Sinulla ei ole vielä kotitaloutta. Luo sellainen käyttöönotossa tai liity koodilla.",
"household.invite": "Kutsu koodilla: {code}",
"household.locations": "Säilytyspaikat",
"household.members": "Jäsenet",
"household.portionFactor": "×{factor} annos",
"household.private": "Yksityistä per henkilö: tavoitteet, allergiat, terveystiedot ja ateriahistoria.",
"household.role.adult": "Aikuinen",
"household.role.child": "Lapsi",
"household.role.member": "Jäsen",
"household.role.owner": "Omistaja",
"household.shareCode": "Jaa koodi perheelle, niin jaatte ruokavaraston, listan ja suunnitelman.",
"household.shared": "Jaettua kotitaloudessa: ruokavarasto, ostoslista, viikkosuunnitelma, eväsrasiat ja budjetti.",
"logmeal.manualTitle": "Manuaalisesti",
"logmeal.mealTypeTitle": "Ateriatyyppi",
"logmeal.photoTip": "Vinkki: kuvaa lautanen Skannaa-välilehdellä, niin sovellus arvioi puolestasi sinä vahvistat aina.",
"logmeal.proteinPlaceholder": "proteiini (g)",
"logmeal.quickSubtitle": "Kirjaa samat ravintoarvot kuin viimeksi.",
"logmeal.quickTitle": "Nopea uudelleenkirjaus",
"logmeal.validationBody": "Manuaalinen kirjaus vaatii nimen ja kalorit.",
"logmeal.validationTitle": "Täydennä",
"logmeal.whatPlaceholder": "Mitä söit?",
"mealbox.eat": "Syö nyt",
"mealbox.eatBy": "Syö viimeistään {date}",
"mealbox.empty": "Ei eväsrasioita juuri nyt. Kokatessasi voit tallentaa annoksia tänne.",
"mealbox.enjoyBody": "Annos on kirjattu Minun päiväni -näkymään.",
"mealbox.enjoyTitle": "Hyvää ruokahalua!",
"mealbox.guidanceNote": "Suositeltu käyttöaika on ohjeellinen luota hajuun ja makuun.",
"mealbox.portionsLeft_one": "1 annos jäljellä",
"mealbox.portionsLeft_other": "{count} annosta jäljellä",
"mealbox.title": "Eväsrasiat",
"memory.delete": "Poista",
"memory.deleteAll": "Poista koko muisti",
"memory.deleteAllTitle": "Poistetaanko koko muisti?",
"memory.empty": "{brand} ei ole vielä oppinut sinusta mitään.",
"memory.irreversible": "Tätä ei voi kumota.",
"memory.origin.ai_inferred": "AI:n oletus",
"memory.origin.observed": "Havaittu tapa",
"memory.origin.user_stated": "Kerroit itse",
"memory.pause": "Keskeytä",
"memory.paused": "Keskeytetty",
"memory.resume": "Jatka",
"memory.subtitle": "Täysi läpinäkyvyys. Korjaa virheet, keskeytä tai poista omistat muistisi.",
"memory.title": "Mitä {brand} tietää minusta",
"memory.verified": "Vahvistettu",
"memory.verify": "Oikein",
"myday.calories": "Kalorit",
"myday.carbs": "Hiilihydraatit",
"myday.estimateNote": "Arvot, joissa on ~, ovat arvioita, joita voit säätää.",
"myday.estimateRange": "{min}{max} kcal, todennäköisesti {kcal}",
"myday.fat": "Rasva",
"myday.fiber": "Kuitu",
"myday.kcalProtein": "{kcal} kcal · {protein} g proteiinia",
"myday.logMeal": "Kirjaa ateria",
"myday.mealType.breakfast": "Aamiainen",
"myday.mealType.dessert": "Jälkiruoka",
"myday.mealType.dinner": "Päivällinen",
"myday.mealType.lunch": "Lounas",
"myday.mealType.snack": "Välipala",
"myday.noMeals": "Ei kirjattuja aterioita tänään.",
"myday.over": "{kcal} kcal yli tavoitteen",
"myday.overRecommended": "Yli suosituksen",
"myday.protein": "Proteiini",
"myday.remaining": "{kcal} kcal jäljellä",
"myday.salt": "Suola",
"myday.targetsNote": "Tavoitteet ovat ohjeellisia, eivät lääketieteellisiä neuvoja.",
"myday.title": "Minun päiväni",
"myday.todaysMeals": "Päivän ateriat",
"onboarding.allergen.crustaceans": "Äyriäiset",
"onboarding.allergen.eggs": "Kananmuna",
"onboarding.allergen.fish": "Kala",
"onboarding.allergen.gluten": "Gluteeni",
"onboarding.allergen.milk": "Maito/laktoosi",
"onboarding.allergen.peanuts": "Maapähkinät",
"onboarding.allergen.sesame": "Seesami",
"onboarding.allergen.soy": "Soija",
"onboarding.allergen.tree_nuts": "Pähkinät",
"onboarding.allergies": "Allergiat ja intoleranssit",
"onboarding.allergyNote": "Allergiasuodatus on aina tiukka ruokia, joissa on allergeenejasi, ei koskaan näytetä.",
"onboarding.birthYearPlaceholder": "Syntymävuosi",
"onboarding.bodyTitle": "Henkilökohtaisia kaloritavoitteita varten (valinnainen)",
"onboarding.diet": "Miten syöt?",
"onboarding.diet.flexitarian": "Fleksitaristi",
"onboarding.diet.omnivore": "Sekasyöjä",
"onboarding.diet.pescatarian": "Pescetaristi",
"onboarding.diet.vegan": "Vegaani",
"onboarding.diet.vegetarian": "Kasvissyöjä",
"onboarding.goal": "Mikä on tärkein tavoitteesi?",
"onboarding.goal.build_muscle": "Kasvattaa lihasta",
"onboarding.goal.cook_more": "Kokata enemmän kotona",
"onboarding.goal.less_waste": "Vähentää hävikkiä",
"onboarding.goal.lose_weight": "Laihduttaa",
"onboarding.goal.lower_cost": "Säästää ruokakuluissa",
"onboarding.goal.maintain_weight": "Pitää painon ennallaan",
"onboarding.goal.more_protein": "Syödä enemmän proteiinia",
"onboarding.heightPlaceholder": "Pituus (cm)",
"onboarding.household": "Kotitaloutesi",
"onboarding.householdCreate": "Luo kotitalous",
"onboarding.householdDefaultName": "Koti",
"onboarding.householdJoin": "Liity koodilla",
"onboarding.householdName": "Kotitalouden nimi",
"onboarding.inviteCode": "Kutsukoodi",
"onboarding.mode": "Kuinka tarkka haluat olla?",
"onboarding.modeSimple": "Helppo tila",
"onboarding.modeSimpleDesc": "Kuvaa ruokasi, hyväksy arviot, mahdollisimman vaivatonta.",
"onboarding.modeExact": "Tarkka tila",
"onboarding.modeExactDesc": "Punnitse ruoka, syötä grammat, täysi hallinta lukuihin.",
"onboarding.modesCombine": "Tiloja voi yhdistellä arjessa helposti, tarkasti kun haluat.",
"onboarding.modeSimple": "Helppo tila",
"onboarding.modeSimpleDesc": "Kuvaa ruokasi, hyväksy arviot, mahdollisimman vaivatonta.",
"onboarding.notMedical": "{brand} antaa ohjeita ei lääketieteellisiä neuvoja.",
"wte.title": "Mitä syödään?",
"wte.subtitle": "Sen mukaan mitä kotona on, mikä pitäisi käyttää pian ja mistä pidätte.",
"wte.cravingPlaceholder": "Tekisi mieli … (esim. kermaista, aasialaista, alle 500 kcal)",
"wte.mealBoxFirst": "Valmista ruokaa kotona",
"wte.whyTitle": "Miksi tämä ehdotus?",
"wte.coverage": "{pct} % kotona",
"wte.missing": "Puuttuu: {items}",
"wte.empty": "Ei vielä ehdotuksia lisää ruokaa varastoon tai löysää suodattimia.",
"wte.refresh": "Uudet ehdotukset",
"scan.title": "Skannaa",
"scan.subtitle": "Täytä ruokavarasto tai kirjaa ateria kameralla.",
"scan.fridge": "Jääkaappi",
"scan.freezer": "Pakastin",
"scan.pantry": "Ruokakomero",
"scan.ingredients": "Ainekset",
"scan.plate": "Lautanen",
"scan.receipt": "Kuitti",
"scan.barcode": "Viivakoodi",
"scan.expiry": "Parasta ennen -päiväys",
"scan.nutrition": "Ravintosisältö",
"scan.takePhoto": "Ota kuva",
"scan.tips.title": "Näin analyysi onnistuu parhaiten",
"scan.tips.overview": "Aloita yleiskuvalla",
"scan.tips.shelf": "Kuvaa hylly kerrallaan",
"scan.tips.light": "Vältä pimeyttä ja varjoja",
"scan.tips.move": "Siirrä toisiaan peittävät tuotteet",
"scan.analyzing": "Analysoidaan …",
"scan.quotaLeft_one": "1 AI-skannaus jäljellä tässä kuussa",
"scan.quotaLeft_other": "{count} AI-skannausta jäljellä tässä kuussa",
"scan.review.title": "Tarkista tulos",
"scan.review.subtitle": "AI on joskus epävarma sinä päätät. Muokkaa, poista tai lisää ennen tallennusta.",
"scan.review.uncertain": "Epävarma tarkista",
"scan.review.approveAll": "Tallenna varastoon",
"scan.review.rejected": "Poistettu",
"scan.failed": "Analyysi epäonnistui. Yritä uudelleen tai lisää käsin.",
"myday.title": "Minun päiväni",
"myday.calories": "Kalorit",
"myday.protein": "Proteiini",
"myday.carbs": "Hiilihydraatit",
"myday.fat": "Rasva",
"myday.fiber": "Kuitu",
"myday.salt": "Suola",
"myday.remaining": "{kcal} kcal jäljellä",
"myday.over": "{kcal} kcal yli tavoitteen",
"myday.logMeal": "Kirjaa ateria",
"myday.noMeals": "Ei kirjattuja aterioita tänään.",
"myday.estimateNote": "Arvot, joissa on ~, ovat arvioita, joita voit säätää.",
"myday.targetsNote": "Tavoitteet ovat ohjeellisia, eivät lääketieteellisiä neuvoja.",
"home.title": "Kotona",
"home.inventory": "Ruokavarasto",
"home.useSoon": "Käytä pian",
"home.mealBoxes": "Eväsrasiat",
"home.shopping": "Ostoslista",
"home.budget": "Ruokabudjetti",
"home.household": "Kotitalous",
"home.waste": "Ruokahävikki",
"home.emptyInventory": "Varasto on tyhjä. Skannaa jääkaappi tai lisää tuotteita käsin.",
"home.expiresIn_one": "1 päivä jäljellä",
"home.expiresIn_other": "{days} päivää jäljellä",
"home.expiresToday": "Vanhenee tänään",
"home.expired": "Vanhentunut",
"home.pastBestBefore": "Parasta ennen ylitetty haista ja maista ensin",
"onboarding.stepBSubtitle": "Mukauta profiiliasi parempia ehdotuksia varten.",
"onboarding.stepBTitle": "Jatka asetuksia",
"onboarding.stepCSubtitle": "Valinnainen käytetään henkilökohtaisiin kaloritavoitteisiin.",
"onboarding.stepCTitle": "Terveysprofiilisi",
"onboarding.subtitle": "Kaikki on vapaaehtoista ja muutettavissa milloin tahansa. Mitä enemmän täytät, sitä paremmat ehdotukset.",
"onboarding.title": "Kerro hieman itsestäsi",
"onboarding.weightPlaceholder": "Paino (kg)",
"paywall.choose": "Valitse",
"paywall.fairUse": "Kohtuukäytön AI-skannauskiintiö sisältyy.",
"paywall.family": "Family · enintään 6 henkilöä",
"paywall.freeNote": "Ilmainen: 10 AI-skannausta/kk, manuaalinen varasto, tallennetut reseptit ja helppo kirjaus.",
"paywall.household": "Household · enintään 3 henkilöä",
"paywall.large": "Large Household · enintään 12 henkilöä",
"paywall.perMonth": "{price}/kk",
"paywall.popular": "Suosituin",
"paywall.restore": "Palauta ostot",
"paywall.soonBody": "Ostot aktivoituvat App Storen/Google Playn kautta, kun kauppaintegraatio otetaan käyttöön (vaihe 7). Taustajärjestelmä on jo valmis.",
"paywall.soonTitle": "Pian!",
"paywall.subtitle": "Koko kotitalouden ruoka-OS: rajaton varasto, viikkosuunnitelma ja jakaminen.",
"paywall.title": "{brand} Premium",
"paywall.trialActive_one": "1 päivä kokeilua jäljellä",
"paywall.trialActive_other": "{days} päivää kokeilua jäljellä",
"profile.aiScansUsed": "AI-skannaukset: {used} / {total} tässä kuussa",
"profile.consent.anonymized_improvement": "Tekoälyn anonymisoitu parantaminen",
"profile.consent.health_integration": "Terveystiedot (Apple Health / Health Connect)",
"profile.consent.image_training": "Kuviani saa käyttää koulutukseen",
"profile.consent.location_weather": "Sijainti sääpohjaisia ehdotuksia varten",
"profile.consent.personalization": "Henkilökohtaiset toiminnot (muisti & makuprofiili)",
"profile.consent.product_analytics": "Tuoteanalyysi",
"profile.consent.push_notifications": "Push-ilmoitukset",
"profile.consents": "Suostumukset & tiedot",
"profile.consentsNote": "Suostumukset ovat erillisiä henkilökohtaiset toiminnot eivät koskaan vaadi koulutussuostumusta.",
"profile.deleteAccount": "Poista tili",
"profile.deleteBody": "Kaikki tietosi poistetaan pysyvästi GDPR:n mukaisesti.",
"profile.deleteTitle": "Poistetaanko tili?",
"profile.emailUnverified": "Sähköpostia ei ole vahvistettu",
"profile.export": "Vie tietoni",
"profile.goals": "Tavoitteet & ruokavalio",
"profile.language": "Kieli",
"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": "Kirjaudu ulos",
"profile.memory": "Mitä {brand} tietää minusta",
"profile.memorySubtitle": "Katso, korjaa, keskeytä tai poista se, mitä alusta on oppinut.",
"profile.modeLabel": "Tila:",
"profile.resendVerification": "Lähetä vahvistusviesti uudelleen",
"profile.subscription": "Tilaus",
"profile.title": "Profiili",
"profile.verificationSent": "Lähetetty! Tarkista sähköpostisi.",
"recipe.avgRating": "Keskiarvo {avg}, {count} arviota",
"recipe.cook": "Kokkaa nyt",
"recipe.cost": "n. {amount}/annos",
"recipe.iCookedThis": "Tein tämän",
"recipe.ingredients": "Ainekset",
"recipe.notSafe": "Ei sovi ruokavalioasetuksiisi",
"recipe.perPortion": "per annos",
"recipe.portions_one": "1 annos",
"recipe.portions_other": "{count} annosta",
"recipe.time": "{min} min",
"recipe.perPortion": "per annos",
"recipe.ingredients": "Ainekset",
"recipe.save": "Tallenna",
"recipe.saved": "Tallennettu",
"recipe.steps": "Valmistus",
"recipe.cook": "Kokkaa nyt",
"recipe.notSafe": "Ei sovi ruokavalioasetuksiisi",
"recipe.substitutions": "Korvaa",
"recipe.iCookedThis": "Tein tämän",
"recipe.cost": "n. {amount}/annos",
"cooking.step": "Vaihe {current}/{total}",
"cooking.timer": "Käynnistä ajastin",
"cooking.timerRunning": "{time} jäljellä",
"cooking.finish": "Valmis kirjaa ateria",
"cooking.scale": "Annokset",
"cooking.keepAwake": "Näyttö pysyy päällä, kun kokkaat",
"cooked.title": "Miten meni?",
"cooked.portionsCooked": "Valmistetut annokset",
"cooked.whoAte": "Ketkä söivät?",
"cooked.mealBoxes": "Annoksia eväsrasioihin",
"cooked.deductPantry": "Vähennä ainekset varastosta",
"cooked.rate": "Arvioi",
"shopping.title": "Ostoslista",
"recipe.time": "{min} min",
"scan.analyzing": "Analysoidaan …",
"scan.barcode": "Viivakoodi",
"scan.expiry": "Parasta ennen -päiväys",
"scan.failed": "Analyysi epäonnistui. Yritä uudelleen tai lisää käsin.",
"scan.freezer": "Pakastin",
"scan.fridge": "Jääkaappi",
"scan.ingredients": "Ainekset",
"scan.nutrition": "Ravintosisältö",
"scan.pantry": "Ruokakomero",
"scan.plate": "Lautanen",
"scan.quotaLeft_one": "1 AI-skannaus jäljellä tässä kuussa",
"scan.quotaLeft_other": "{count} AI-skannausta jäljellä tässä kuussa",
"scan.receipt": "Kuitti",
"scan.review.addItem": "+ Lisää tuote",
"scan.review.approveAll": "Tallenna varastoon",
"scan.review.bestBeforeNote": "Laatupäivä tuote voi olla hyvää senkin jälkeen. Haista ja maista ennen poisheittoa.",
"scan.review.dateKindBestBefore": "Parasta ennen",
"scan.review.dateKindUseBy": "Viimeinen käyttöpäivä",
"scan.review.datePlaceholder": "VVVV-KK-PP (valinnainen)",
"scan.review.itemPlaceholder": "Tuote",
"scan.review.quantityPlaceholder": "Määrä",
"scan.review.rejected": "Poistettu",
"scan.review.subtitle": "AI on joskus epävarma sinä päätät. Muokkaa, poista tai lisää ennen tallennusta.",
"scan.review.title": "Tarkista tulos",
"scan.review.uncertain": "Epävarma tarkista",
"scan.review.unitPlaceholder": "Yksikkö (g, l, kpl …)",
"scan.review.unknownItem": "Tuntematon tuote",
"scan.review.useByNote": "Turvallisuusraja älä syö tämän päivän jälkeen.",
"scan.subtitle": "Täytä ruokavarasto tai kirjaa ateria kameralla.",
"scan.takePhoto": "Ota kuva",
"scan.tips.light": "Vältä pimeyttä ja varjoja",
"scan.tips.move": "Siirrä toisiaan peittävät tuotteet",
"scan.tips.overview": "Aloita yleiskuvalla",
"scan.tips.shelf": "Kuvaa hylly kerrallaan",
"scan.tips.title": "Näin analyysi onnistuu parhaiten",
"scan.title": "Skannaa",
"shopping.addPlaceholder": "Lisää tuote …",
"shopping.complete": "Päätä ostosreissu",
"shopping.completedBody_one": "1 tuote lisättiin ruokavarastoon.",
"shopping.completedBody_other": "{count} tuotetta lisättiin ruokavarastoon.",
"shopping.completedTitle": "Valmis!",
"shopping.completeNote": "Merkityt tuotteet lisätään ruokavarastoon.",
"shopping.empty": "Lista on tyhjä.",
"shopping.estimated": "n. {amount}",
"shopping.estimatedTotal": "Arvioitu yhteensä: n. {amount}",
"mealbox.title": "Eväsrasiat",
"mealbox.eatBy": "Syö viimeistään {date}",
"mealbox.portionsLeft_one": "1 annos jäljellä",
"mealbox.portionsLeft_other": "{count} annosta jäljellä",
"mealbox.eat": "Syö nyt",
"mealbox.empty": "Ei eväsrasioita juuri nyt. Kokatessasi voit tallentaa annoksia tänne.",
"household.members": "Jäsenet",
"household.invite": "Kutsu koodilla: {code}",
"household.shared": "Jaettua kotitaloudessa: ruokavarasto, ostoslista, viikkosuunnitelma, eväsrasiat ja budjetti.",
"household.private": "Yksityistä per henkilö: tavoitteet, allergiat, terveystiedot ja ateriahistoria.",
"memory.title": "Mitä {brand} tietää minusta",
"memory.subtitle": "Täysi läpinäkyvyys. Korjaa virheet, keskeytä tai poista omistat muistisi.",
"memory.paused": "Keskeytetty",
"memory.verify": "Oikein",
"memory.pause": "Keskeytä",
"memory.delete": "Poista",
"memory.deleteAll": "Poista koko muisti",
"memory.empty": "{brand} ei ole vielä oppinut sinusta mitään.",
"memory.origin.user_stated": "Kerroit itse",
"memory.origin.observed": "Havaittu tapa",
"memory.origin.ai_inferred": "AI:n oletus",
"paywall.title": "{brand} Premium",
"paywall.subtitle": "Koko kotitalouden ruoka-OS: rajaton varasto, viikkosuunnitelma ja jakaminen.",
"paywall.trialActive_one": "1 päivä kokeilua jäljellä",
"paywall.trialActive_other": "{days} päivää kokeilua jäljellä",
"paywall.household": "Household · enintään 3 henkilöä",
"paywall.family": "Family · enintään 6 henkilöä",
"paywall.large": "Large Household · enintään 12 henkilöä",
"paywall.perMonth": "{price}/kk",
"paywall.fairUse": "Kohtuukäytön AI-skannauskiintiö sisältyy.",
"paywall.restore": "Palauta ostot",
"paywall.freeNote": "Ilmainen: 10 AI-skannausta/kk, manuaalinen varasto, tallennetut reseptit ja helppo kirjaus.",
"profile.title": "Profiili",
"profile.goals": "Tavoitteet & ruokavalio",
"profile.consents": "Suostumukset & tiedot",
"profile.memory": "Mitä {brand} tietää minusta",
"profile.subscription": "Tilaus",
"profile.logout": "Kirjaudu ulos",
"profile.export": "Vie tietoni",
"profile.deleteAccount": "Poista tili",
"profile.language": "Kieli",
"profile.emailUnverified": "Sähköpostia ei ole vahvistettu",
"profile.resendVerification": "Lähetä vahvistusviesti uudelleen",
"profile.verificationSent": "Lähetetty! Tarkista sähköpostisi.",
"profile.language.sv": "Svenska",
"profile.language.en": "English",
"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": "Hups",
"common.undo": "Kumoa",
"common.remove": "Poista",
"common.add": "Lisää",
"common.on": "Päällä",
"common.off": "Pois",
"home.thisWeek": "Tällä viikolla",
"home.wasteWeek": "Hävikki tällä viikolla",
"home.useSoonHint": "Parasta ennen koskee laatua haista, katso ja maista ennen kuin heität pois. Viimeistä käyttöpäivää sen sijaan on noudatettava.",
"scan.review.itemPlaceholder": "Tuote",
"scan.review.quantityPlaceholder": "Määrä",
"scan.review.unitPlaceholder": "Yksikkö (g, l, kpl …)",
"scan.review.datePlaceholder": "VVVV-KK-PP (valinnainen)",
"scan.review.dateKindBestBefore": "Parasta ennen",
"scan.review.dateKindUseBy": "Viimeinen käyttöpäivä",
"scan.review.bestBeforeNote": "Laatupäivä tuote voi olla hyvää senkin jälkeen. Haista ja maista ennen poisheittoa.",
"scan.review.useByNote": "Turvallisuusraja älä syö tämän päivän jälkeen.",
"scan.review.addItem": "+ Lisää tuote",
"scan.review.unknownItem": "Tuntematon tuote",
"barcode.aim": "Suuntaa kamera viivakoodiin",
"barcode.addPrompt": "Lisätäänkö ruokavarastoon?",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.noNutrition": "Ei ravintotietoja",
"barcode.unknownTitle": "Tuntematon tuote",
"barcode.unknownBody": "Tuotetta ei ole vielä tietokannassa. Kuvaa etupuoli ja ravintosisältö, niin lisäämme sen.",
"barcode.photoPackage": "Kuvaa pakkaus",
"barcode.needHousehold": "Tarvitset ensin kotitalouden.",
"barcode.noLocation": "Säilytyspaikkaa ei löytynyt.",
"barcode.cameraTitle": "Kamera tarvitaan",
"barcode.cameraBody": "{brand} tarvitsee kameraa viivakoodien skannaamiseen.",
"barcode.allowCamera": "Salli kamera",
"onboarding.goal.lose_weight": "Laihduttaa",
"onboarding.goal.build_muscle": "Kasvattaa lihasta",
"onboarding.goal.maintain_weight": "Pitää painon ennallaan",
"onboarding.goal.more_protein": "Syödä enemmän proteiinia",
"onboarding.goal.less_waste": "Vähentää hävikkiä",
"onboarding.goal.lower_cost": "Säästää ruokakuluissa",
"onboarding.goal.cook_more": "Kokata enemmän kotona",
"onboarding.diet.omnivore": "Sekasyöjä",
"onboarding.diet.flexitarian": "Fleksitaristi",
"onboarding.diet.pescatarian": "Pescetaristi",
"onboarding.diet.vegetarian": "Kasvissyöjä",
"onboarding.diet.vegan": "Vegaani",
"onboarding.allergen.gluten": "Gluteeni",
"onboarding.allergen.milk": "Maito/laktoosi",
"onboarding.allergen.eggs": "Kananmuna",
"onboarding.allergen.tree_nuts": "Pähkinät",
"onboarding.allergen.peanuts": "Maapähkinät",
"onboarding.allergen.fish": "Kala",
"onboarding.allergen.crustaceans": "Äyriäiset",
"onboarding.allergen.soy": "Soija",
"onboarding.allergen.sesame": "Seesami",
"onboarding.bodyTitle": "Henkilökohtaisia kaloritavoitteita varten (valinnainen)",
"onboarding.weightPlaceholder": "Paino (kg)",
"onboarding.heightPlaceholder": "Pituus (cm)",
"onboarding.birthYearPlaceholder": "Syntymävuosi",
"onboarding.householdDefaultName": "Koti",
"onboarding.modesCombine": "Tiloja voi yhdistellä arjessa helposti, tarkasti kun haluat.",
"myday.mealType.breakfast": "Aamiainen",
"myday.mealType.lunch": "Lounas",
"myday.mealType.dinner": "Päivällinen",
"myday.mealType.snack": "Välipala",
"myday.mealType.dessert": "Jälkiruoka",
"myday.overRecommended": "Yli suosituksen",
"myday.todaysMeals": "Päivän ateriat",
"myday.estimateRange": "{min}{max} kcal, todennäköisesti {kcal}",
"myday.kcalProtein": "{kcal} kcal · {protein} g proteiinia",
"logmeal.mealTypeTitle": "Ateriatyyppi",
"logmeal.quickTitle": "Nopea uudelleenkirjaus",
"logmeal.quickSubtitle": "Kirjaa samat ravintoarvot kuin viimeksi.",
"logmeal.manualTitle": "Manuaalisesti",
"logmeal.whatPlaceholder": "Mitä söit?",
"logmeal.proteinPlaceholder": "proteiini (g)",
"logmeal.validationTitle": "Täydennä",
"logmeal.validationBody": "Manuaalinen kirjaus vaatii nimen ja kalorit.",
"logmeal.photoTip": "Vinkki: kuvaa lautanen Skannaa-välilehdellä, niin sovellus arvioi puolestasi sinä vahvistat aina.",
"shopping.section.frukt_gront": "Hedelmät & vihannekset",
"shopping.section.brod": "Leipä",
"shopping.section.mejeri": "Maitotuotteet",
"shopping.section.kott_fagel": "Liha & siipikarja",
"shopping.section.fisk": "Kala",
"shopping.section.chark": "Leikkeleet",
"shopping.section.frys": "Pakasteet",
"shopping.section.skafferi": "Kuivatuotteet",
"shopping.section.konserver": "Säilykkeet",
"shopping.section.kryddor_bak": "Mausteet & leivonta",
"shopping.section.dryck": "Juomat",
"shopping.section.snacks": "Naposteltavat",
"shopping.section.fisk": "Kala",
"shopping.section.frukt_gront": "Hedelmät & vihannekset",
"shopping.section.frys": "Pakasteet",
"shopping.section.hygien_ovrigt": "Muut",
"shopping.completedTitle": "Valmis!",
"household.role.owner": "Omistaja",
"household.role.adult": "Aikuinen",
"household.role.member": "Jäsen",
"household.role.child": "Lapsi",
"household.empty": "Sinulla ei ole vielä kotitaloutta. Luo sellainen käyttöönotossa tai liity koodilla.",
"household.shareCode": "Jaa koodi perheelle, niin jaatte ruokavaraston, listan ja suunnitelman.",
"household.locations": "Säilytyspaikat",
"household.portionFactor": "×{factor} annos",
"mealbox.enjoyTitle": "Hyvää ruokahalua!",
"mealbox.enjoyBody": "Annos on kirjattu Minun päiväni -näkymään.",
"mealbox.guidanceNote": "Suositeltu käyttöaika on ohjeellinen luota hajuun ja makuun.",
"paywall.soonTitle": "Pian!",
"paywall.soonBody": "Ostot aktivoituvat App Storen/Google Playn kautta, kun kauppaintegraatio otetaan käyttöön (vaihe 7). Taustajärjestelmä on jo valmis.",
"paywall.popular": "Suosituin",
"paywall.choose": "Valitse",
"recipe.saved": "Tallennettu",
"recipe.save": "Tallenna",
"recipe.avgRating": "Keskiarvo {avg}, {count} arviota",
"memory.verified": "Vahvistettu",
"memory.resume": "Jatka",
"memory.deleteAllTitle": "Poistetaanko koko muisti?",
"memory.irreversible": "Tätä ei voi kumota.",
"profile.consent.personalization": "Henkilökohtaiset toiminnot (muisti & makuprofiili)",
"profile.consent.anonymized_improvement": "Tekoälyn anonymisoitu parantaminen",
"profile.consent.image_training": "Kuviani saa käyttää koulutukseen",
"profile.consent.health_integration": "Terveystiedot (Apple Health / Health Connect)",
"profile.consent.location_weather": "Sijainti sääpohjaisia ehdotuksia varten",
"profile.consent.push_notifications": "Push-ilmoitukset",
"profile.modeLabel": "Tila:",
"profile.memorySubtitle": "Katso, korjaa, keskeytä tai poista se, mitä alusta on oppinut.",
"profile.consentsNote": "Suostumukset ovat erillisiä henkilökohtaiset toiminnot eivät koskaan vaadi koulutussuostumusta.",
"profile.aiScansUsed": "AI-skannaukset: {used} / {total} tässä kuussa",
"profile.deleteTitle": "Poistetaanko tili?",
"profile.deleteBody": "Kaikki tietosi poistetaan pysyvästi GDPR:n mukaisesti.",
"cooking.timerDone": "Valmis!",
"cooked.deductPantryNote": "Ainekset vähennetään ruokavarastosta ensin erääntyvät ensin.",
"home.moreItems_one": "+ 1 lisää …",
"home.moreItems_other": "+ {count} lisää …",
"shopping.completedBody_one": "1 tuote lisättiin ruokavarastoon.",
"shopping.completedBody_other": "{count} tuotetta lisättiin ruokavarastoon.",
"profile.consent.product_analytics": "Tuoteanalyysi"
"shopping.section.konserver": "Säilykkeet",
"shopping.section.kott_fagel": "Liha & siipikarja",
"shopping.section.kryddor_bak": "Mausteet & leivonta",
"shopping.section.mejeri": "Maitotuotteet",
"shopping.section.skafferi": "Kuivatuotteet",
"shopping.section.snacks": "Naposteltavat",
"shopping.title": "Ostoslista",
"tabs.home": "Kotona",
"tabs.myDay": "Minun päiväni",
"tabs.scan": "Skannaa",
"tabs.whatToEat": "Mitä syödään?",
"wte.coverage": "{pct} % kotona",
"wte.cravingPlaceholder": "Tekisi mieli … (esim. kermaista, aasialaista, alle 500 kcal)",
"wte.empty": "Ei vielä ehdotuksia lisää ruokaa varastoon tai löysää suodattimia.",
"wte.mealBoxFirst": "Valmista ruokaa kotona",
"wte.missing": "Puuttuu: {items}",
"wte.refresh": "Uudet ehdotukset",
"wte.subtitle": "Sen mukaan mitä kotona on, mikä pitäisi käyttää pian ja mistä pidätte.",
"wte.title": "Mitä syödään?",
"wte.whyTitle": "Miksi tämä ehdotus?"
}