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": "A carregar …",
"auth.backToLogin": "Voltar ao início de sessão",
"auth.displayName": "Como o devemos tratar?",
"auth.email": "E-mail",
"auth.forgotBody": "Introduza o seu e-mail e enviaremos um link se a conta existir.",
"auth.forgotLink": "Esqueceu-se da palavra-passe?",
"auth.forgotSentBody": "Se o endereço tiver uma conta, enviámos um link válido por 30 minutos. Abra-o neste dispositivo.",
"auth.forgotSentTitle": "Verifique a sua caixa de entrada",
"auth.forgotSubmit": "Enviar link",
"auth.forgotTitle": "Repor palavra-passe",
"auth.hasAccount": "Já tem conta? Inicie sessão",
"auth.login": "Iniciar sessão",
"auth.newPassword": "Nova palavra-passe (mín. 10 caracteres)",
"auth.noAccount": "Novo por aqui? Crie uma conta",
"auth.password": "Palavra-passe",
"auth.register": "Criar conta",
"auth.resetBody": "O link é válido por 30 minutos e de uso único. Todos os dispositivos serão desligados.",
"auth.resetDoneBody": "A palavra-passe foi alterada. Inicie sessão com a nova.",
"auth.resetDoneTitle": "Concluído!",
"auth.resetSubmit": "Alterar palavra-passe",
"auth.resetTitle": "Escolha uma nova palavra-passe",
"auth.resetTokenPlaceholder": "Cole o código do e-mail",
"auth.trialNote": "7 dias de acesso completo sem cartão.",
"auth.verifyDoneBody": "Obrigado! A sua conta está verificada.",
"auth.verifyDoneTitle": "E-mail confirmado",
"auth.verifyFailedBody": "O link é inválido ou expirou. Peça um novo no perfil.",
"auth.verifyFailedTitle": "O link não funcionou",
"barcode.addPrompt": "Adicionar à despensa?",
"barcode.aim": "Aponte a câmara para o código de barras",
"barcode.allowCamera": "Permitir câmara",
"barcode.cameraBody": "O {brand} precisa da câmara para ler códigos de barras.",
"barcode.cameraTitle": "É precisa a câmara",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.needHousehold": "Primeiro precisa de um agregado.",
"barcode.noLocation": "Nenhum local de armazenamento encontrado.",
"barcode.noNutrition": "Sem dados nutricionais",
"barcode.photoPackage": "Fotografar a embalagem",
"barcode.unknownBody": "Este produto ainda não está na base de dados. Fotografe a frente e o rótulo nutricional e nós adicionamo-lo.",
"barcode.unknownTitle": "Produto desconhecido",
"common.add": "Adicionar",
"common.back": "Voltar",
"common.cancel": "Cancelar",
"common.done": "Concluído",
"common.error": "Algo correu mal. Tente novamente.",
"common.estimate": "Estimativa",
"common.loading": "A carregar …",
"common.next": "Seguinte",
"common.off": "Desativado",
"common.offline": "Offline a mostrar os últimos dados",
"common.on": "Ativado",
"common.oops": "Ops",
"common.remove": "Remover",
"common.retry": "Tentar novamente",
"common.save": "Guardar",
"common.cancel": "Cancelar",
"common.next": "Seguinte",
"common.back": "Voltar",
"common.done": "Concluído",
"common.skip": "Ignorar",
"common.offline": "Offline a mostrar os últimos dados",
"common.estimate": "Estimativa",
"tabs.whatToEat": "O que vamos comer?",
"tabs.scan": "Digitalizar",
"tabs.myDay": "O meu dia",
"tabs.home": "Em casa",
"auth.login": "Iniciar sessão",
"auth.register": "Criar conta",
"auth.email": "E-mail",
"auth.password": "Palavra-passe",
"auth.displayName": "Como o devemos tratar?",
"auth.noAccount": "Novo por aqui? Crie uma conta",
"auth.hasAccount": "Já tem conta? Inicie sessão",
"auth.trialNote": "7 dias de acesso completo sem cartão.",
"auth.forgotLink": "Esqueceu-se da palavra-passe?",
"auth.forgotTitle": "Repor palavra-passe",
"auth.forgotBody": "Introduza o seu e-mail e enviaremos um link se a conta existir.",
"auth.forgotSubmit": "Enviar link",
"auth.forgotSentTitle": "Verifique a sua caixa de entrada",
"auth.forgotSentBody": "Se o endereço tiver uma conta, enviámos um link válido por 30 minutos. Abra-o neste dispositivo.",
"auth.backToLogin": "Voltar ao início de sessão",
"auth.resetTitle": "Escolha uma nova palavra-passe",
"auth.resetBody": "O link é válido por 30 minutos e de uso único. Todos os dispositivos serão desligados.",
"auth.resetTokenPlaceholder": "Cole o código do e-mail",
"auth.newPassword": "Nova palavra-passe (mín. 10 caracteres)",
"auth.resetSubmit": "Alterar palavra-passe",
"auth.resetDoneTitle": "Concluído!",
"auth.resetDoneBody": "A palavra-passe foi alterada. Inicie sessão com a nova.",
"auth.verifyDoneTitle": "E-mail confirmado",
"auth.verifyDoneBody": "Obrigado! A sua conta está verificada.",
"auth.verifyFailedTitle": "O link não funcionou",
"auth.verifyFailedBody": "O link é inválido ou expirou. Peça um novo no perfil.",
"onboarding.title": "Fale-nos um pouco de si",
"onboarding.subtitle": "Tudo é opcional e pode ser alterado a qualquer momento. Quanto mais preencher, melhores as sugestões.",
"onboarding.goal": "Qual é o seu objetivo principal?",
"onboarding.diet": "Como come?",
"common.undo": "Desfazer",
"cooked.deductPantry": "Descontar ingredientes da despensa",
"cooked.deductPantryNote": "Os ingredientes são descontados da despensa: primeiro os que expiram antes.",
"cooked.mealBoxes": "Doses para marmitas",
"cooked.portionsCooked": "Doses cozinhadas",
"cooked.rate": "Avaliar",
"cooked.title": "Como correu?",
"cooked.whoAte": "Quem comeu?",
"cooking.finish": "Concluído registar a refeição",
"cooking.keepAwake": "O ecrã fica ligado enquanto cozinha",
"cooking.scale": "Doses",
"cooking.step": "Passo {current} de {total}",
"cooking.timer": "Iniciar temporizador",
"cooking.timerDone": "Pronto!",
"cooking.timerRunning": "Faltam {time}",
"home.budget": "Orçamento alimentar",
"home.emptyInventory": "A despensa está vazia. Digitalize o frigorífico ou adicione manualmente.",
"home.expired": "Expirado",
"home.expiresIn_one": "Falta 1 dia",
"home.expiresIn_other": "Faltam {days} dias",
"home.expiresToday": "Expira hoje",
"home.household": "Agregado",
"home.inventory": "Despensa",
"home.mealBoxes": "Marmitas",
"home.moreItems_one": "+ mais 1 …",
"home.moreItems_other": "+ mais {count} …",
"home.pastBestBefore": "Validade ultrapassada cheire e prove primeiro",
"home.shopping": "Lista de compras",
"home.thisWeek": "Esta semana",
"home.title": "Em casa",
"home.useSoon": "Usar em breve",
"home.useSoonHint": "„A consumir de preferência antes de” é sobre qualidade: cheire, observe e prove antes de deitar fora. Já a data-limite de consumo deve ser respeitada.",
"home.waste": "Desperdício",
"home.wasteWeek": "Desperdício esta semana",
"household.empty": "Ainda não tem um agregado. Crie um na configuração inicial ou junte-se com um código.",
"household.invite": "Convide com o código: {code}",
"household.locations": "Locais de armazenamento",
"household.members": "Membros",
"household.portionFactor": "×{factor} dose",
"household.private": "Privado por pessoa: objetivos, alergias, dados de saúde e histórico de refeições.",
"household.role.adult": "Adulto",
"household.role.child": "Criança",
"household.role.member": "Membro",
"household.role.owner": "Proprietário",
"household.shareCode": "Partilhe o código com a família para partilhar despensa, lista e plano.",
"household.shared": "Partilhado no agregado: despensa, lista de compras, plano semanal, marmitas e orçamento.",
"logmeal.manualTitle": "Manual",
"logmeal.mealTypeTitle": "Tipo de refeição",
"logmeal.photoTip": "Dica: fotografe o prato em Digitalizar e a app estima por si você confirma sempre.",
"logmeal.proteinPlaceholder": "proteína (g)",
"logmeal.quickSubtitle": "Regista os mesmos valores nutricionais da última vez.",
"logmeal.quickTitle": "Registo rápido",
"logmeal.validationBody": "Nome e calorias são necessários para o registo manual.",
"logmeal.validationTitle": "Preencha",
"logmeal.whatPlaceholder": "O que comeu?",
"mealbox.eat": "Comer agora",
"mealbox.eatBy": "Comer até {date}",
"mealbox.empty": "Sem marmitas de momento. Ao cozinhar, pode guardar doses aqui.",
"mealbox.enjoyBody": "A dose foi registada em O meu dia.",
"mealbox.enjoyTitle": "Bom apetite!",
"mealbox.guidanceNote": "O prazo recomendado é indicativo: confie no cheiro e no sabor.",
"mealbox.portionsLeft_one": "Falta 1 dose",
"mealbox.portionsLeft_other": "Faltam {count} doses",
"mealbox.title": "Marmitas",
"memory.delete": "Apagar",
"memory.deleteAll": "Apagar toda a memória",
"memory.deleteAllTitle": "Apagar toda a memória?",
"memory.empty": "O {brand} ainda não aprendeu nada sobre si.",
"memory.irreversible": "Isto não pode ser anulado.",
"memory.origin.ai_inferred": "Suposição da IA",
"memory.origin.observed": "Padrão observado",
"memory.origin.user_stated": "Disse-nos você",
"memory.pause": "Pausar",
"memory.paused": "Em pausa",
"memory.resume": "Retomar",
"memory.subtitle": "Transparência total. Corrija, pause ou apague a memória é sua.",
"memory.title": "O que o {brand} sabe sobre mim",
"memory.verified": "Verificado",
"memory.verify": "Correto",
"myday.calories": "Calorias",
"myday.carbs": "Hidratos",
"myday.estimateNote": "Valores com ~ são estimativas que pode ajustar.",
"myday.estimateRange": "{min}{max} kcal, provavelmente {kcal}",
"myday.fat": "Gorduras",
"myday.fiber": "Fibras",
"myday.kcalProtein": "{kcal} kcal · {protein} g de proteína",
"myday.logMeal": "Registar refeição",
"myday.mealType.breakfast": "Pequeno-almoço",
"myday.mealType.dessert": "Sobremesa",
"myday.mealType.dinner": "Jantar",
"myday.mealType.lunch": "Almoço",
"myday.mealType.snack": "Lanche",
"myday.noMeals": "Sem refeições registadas hoje.",
"myday.over": "{kcal} kcal acima do objetivo",
"myday.overRecommended": "Acima do recomendado",
"myday.protein": "Proteínas",
"myday.remaining": "Faltam {kcal} kcal",
"myday.salt": "Sal",
"myday.targetsNote": "Os objetivos são orientações, não aconselhamento médico.",
"myday.title": "O meu dia",
"myday.todaysMeals": "Refeições de hoje",
"onboarding.allergen.crustaceans": "Crustáceos",
"onboarding.allergen.eggs": "Ovos",
"onboarding.allergen.fish": "Peixe",
"onboarding.allergen.gluten": "Glúten",
"onboarding.allergen.milk": "Leite/lactose",
"onboarding.allergen.peanuts": "Amendoins",
"onboarding.allergen.sesame": "Sésamo",
"onboarding.allergen.soy": "Soja",
"onboarding.allergen.tree_nuts": "Frutos de casca rija",
"onboarding.allergies": "Alergias e intolerâncias",
"onboarding.allergyNote": "O filtro de alergias é sempre rigoroso pratos com os seus alergénios nunca são mostrados.",
"onboarding.birthYearPlaceholder": "Ano de nascimento",
"onboarding.bodyTitle": "Para objetivos calóricos pessoais (opcional)",
"onboarding.diet": "Como come?",
"onboarding.diet.flexitarian": "Flexitariano",
"onboarding.diet.omnivore": "Omnívoro",
"onboarding.diet.pescatarian": "Pescetariano",
"onboarding.diet.vegan": "Vegano",
"onboarding.diet.vegetarian": "Vegetariano",
"onboarding.goal": "Qual é o seu objetivo principal?",
"onboarding.goal.build_muscle": "Ganhar músculo",
"onboarding.goal.cook_more": "Cozinhar mais em casa",
"onboarding.goal.less_waste": "Reduzir o desperdício",
"onboarding.goal.lose_weight": "Perder peso",
"onboarding.goal.lower_cost": "Gastar menos em comida",
"onboarding.goal.maintain_weight": "Manter o peso",
"onboarding.goal.more_protein": "Comer mais proteína",
"onboarding.heightPlaceholder": "Altura (cm)",
"onboarding.household": "O seu agregado",
"onboarding.householdCreate": "Criar agregado",
"onboarding.householdDefaultName": "Casa",
"onboarding.householdJoin": "Aderir com código",
"onboarding.householdName": "Nome do agregado",
"onboarding.inviteCode": "Código de convite",
"onboarding.mode": "Que nível de precisão quer?",
"onboarding.modeSimple": "Modo simples",
"onboarding.modeSimpleDesc": "Fotografe a comida, aceite estimativas, zero complicações.",
"onboarding.modeExact": "Modo exato",
"onboarding.modeExactDesc": "Pese a comida, introduza gramas, controlo total dos números.",
"onboarding.modesCombine": "Os modos podem combinar-se: simples no dia a dia, exato quando quiser.",
"onboarding.modeSimple": "Modo simples",
"onboarding.modeSimpleDesc": "Fotografe a comida, aceite estimativas, zero complicações.",
"onboarding.notMedical": "{brand} dá orientações não aconselhamento médico.",
"wte.title": "O que vamos comer?",
"wte.subtitle": "Com base no que têm em casa, no que deve ser usado em breve e no que gostam.",
"wte.cravingPlaceholder": "Apetece-me … (ex.: cremoso, asiático, menos de 500 kcal)",
"wte.mealBoxFirst": "Comida pronta em casa",
"wte.whyTitle": "Porquê esta sugestão?",
"wte.coverage": "{pct}% em casa",
"wte.missing": "Falta: {items}",
"wte.empty": "Ainda sem sugestões adicione comida à despensa ou alivie os filtros.",
"wte.refresh": "Novas sugestões",
"scan.title": "Digitalizar",
"scan.subtitle": "Abasteça a despensa ou registe uma refeição com a câmara.",
"scan.fridge": "Frigorífico",
"scan.freezer": "Congelador",
"scan.pantry": "Despensa",
"scan.ingredients": "Ingredientes",
"scan.plate": "Prato",
"scan.receipt": "Talão",
"scan.barcode": "Código de barras",
"scan.expiry": "Data de validade",
"scan.nutrition": "Rótulo nutricional",
"scan.takePhoto": "Tirar foto",
"scan.tips.title": "Como obter a melhor análise",
"scan.tips.overview": "Comece com uma foto geral",
"scan.tips.shelf": "Fotografe prateleira a prateleira",
"scan.tips.light": "Evite escuridão e sombras",
"scan.tips.move": "Afaste produtos que se tapam",
"scan.analyzing": "A analisar …",
"scan.quotaLeft_one": "1 análise de IA restante este mês",
"scan.quotaLeft_other": "{count} análises de IA restantes este mês",
"scan.review.title": "Reveja o resultado",
"scan.review.subtitle": "A IA às vezes hesita você decide. Edite, remova ou acrescente antes de guardar.",
"scan.review.uncertain": "Incerto verifique",
"scan.review.approveAll": "Guardar na despensa",
"scan.review.rejected": "Removido",
"scan.failed": "A análise falhou. Tente novamente ou adicione manualmente.",
"myday.title": "O meu dia",
"myday.calories": "Calorias",
"myday.protein": "Proteínas",
"myday.carbs": "Hidratos",
"myday.fat": "Gorduras",
"myday.fiber": "Fibras",
"myday.salt": "Sal",
"myday.remaining": "Faltam {kcal} kcal",
"myday.over": "{kcal} kcal acima do objetivo",
"myday.logMeal": "Registar refeição",
"myday.noMeals": "Sem refeições registadas hoje.",
"myday.estimateNote": "Valores com ~ são estimativas que pode ajustar.",
"myday.targetsNote": "Os objetivos são orientações, não aconselhamento médico.",
"home.title": "Em casa",
"home.inventory": "Despensa",
"home.useSoon": "Usar em breve",
"home.mealBoxes": "Marmitas",
"home.shopping": "Lista de compras",
"home.budget": "Orçamento alimentar",
"home.household": "Agregado",
"home.waste": "Desperdício",
"home.emptyInventory": "A despensa está vazia. Digitalize o frigorífico ou adicione manualmente.",
"home.expiresIn_one": "Falta 1 dia",
"home.expiresIn_other": "Faltam {days} dias",
"home.expiresToday": "Expira hoje",
"home.expired": "Expirado",
"home.pastBestBefore": "Validade ultrapassada cheire e prove primeiro",
"onboarding.stepBSubtitle": "Personaliza o teu perfil para melhores sugestões.",
"onboarding.stepBTitle": "Continuar configuração",
"onboarding.stepCSubtitle": "Opcional usado para objetivos calóricos pessoais.",
"onboarding.stepCTitle": "O teu perfil de saúde",
"onboarding.subtitle": "Tudo é opcional e pode ser alterado a qualquer momento. Quanto mais preencher, melhores as sugestões.",
"onboarding.title": "Fale-nos um pouco de si",
"onboarding.weightPlaceholder": "Peso (kg)",
"paywall.choose": "Escolher",
"paywall.fairUse": "Quota de utilização justa de análises de IA incluída.",
"paywall.family": "Family · até 6 pessoas",
"paywall.freeNote": "Grátis: 10 análises de IA/mês, despensa manual, receitas guardadas e registo simples.",
"paywall.household": "Household · até 3 pessoas",
"paywall.large": "Large Household · até 12 pessoas",
"paywall.perMonth": "{price}/mês",
"paywall.popular": "Mais popular",
"paywall.restore": "Restaurar compras",
"paywall.soonBody": "As compras ativam-se via App Store/Google Play quando a integração com as lojas for ligada (fase 7). O fluxo de backend já está pronto.",
"paywall.soonTitle": "Em breve!",
"paywall.subtitle": "O sistema operativo alimentar de todo o agregado: despensa ilimitada, plano semanal e partilha.",
"paywall.title": "{brand} Premium",
"paywall.trialActive_one": "Falta 1 dia de período experimental",
"paywall.trialActive_other": "Faltam {days} dias de período experimental",
"profile.aiScansUsed": "Digitalizações de IA: {used} / {total} este mês",
"profile.consent.anonymized_improvement": "Melhoria anonimizada da IA",
"profile.consent.health_integration": "Dados de saúde (Apple Health / Health Connect)",
"profile.consent.image_training": "As minhas fotos podem ser usadas para treino",
"profile.consent.location_weather": "Localização para sugestões conforme o tempo",
"profile.consent.personalization": "Funções pessoais (memória e perfil de gosto)",
"profile.consent.product_analytics": "Análise de produto",
"profile.consent.push_notifications": "Notificações push",
"profile.consents": "Consentimentos e dados",
"profile.consentsNote": "Os consentimentos são separados: as funções pessoais nunca exigem consentimento de treino.",
"profile.deleteAccount": "Eliminar conta",
"profile.deleteBody": "Todos os seus dados são apagados permanentemente conforme o RGPD.",
"profile.deleteTitle": "Apagar a conta?",
"profile.emailUnverified": "Endereço de e-mail não confirmado",
"profile.export": "Exportar os meus dados",
"profile.goals": "Objetivos e alimentação",
"profile.language": "Idioma",
"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": "Terminar sessão",
"profile.memory": "O que o {brand} sabe sobre mim",
"profile.memorySubtitle": "Veja, corrija, pause ou apague o que a plataforma aprendeu.",
"profile.modeLabel": "Modo:",
"profile.resendVerification": "Reenviar e-mail de confirmação",
"profile.subscription": "Subscrição",
"profile.title": "Perfil",
"profile.verificationSent": "Enviado! Verifique a caixa de entrada.",
"recipe.avgRating": "Média {avg} em {count} avaliações",
"recipe.cook": "Cozinhar agora",
"recipe.cost": "aprox. {amount}/dose",
"recipe.iCookedThis": "Cozinhei isto",
"recipe.ingredients": "Ingredientes",
"recipe.notSafe": "Não se adequa às suas definições alimentares",
"recipe.perPortion": "por dose",
"recipe.portions_one": "1 dose",
"recipe.portions_other": "{count} doses",
"recipe.time": "{min} min",
"recipe.perPortion": "por dose",
"recipe.ingredients": "Ingredientes",
"recipe.save": "Guardar",
"recipe.saved": "Guardada",
"recipe.steps": "Preparação",
"recipe.cook": "Cozinhar agora",
"recipe.notSafe": "Não se adequa às suas definições alimentares",
"recipe.substitutions": "Substituir",
"recipe.iCookedThis": "Cozinhei isto",
"recipe.cost": "aprox. {amount}/dose",
"cooking.step": "Passo {current} de {total}",
"cooking.timer": "Iniciar temporizador",
"cooking.timerRunning": "Faltam {time}",
"cooking.finish": "Concluído registar a refeição",
"cooking.scale": "Doses",
"cooking.keepAwake": "O ecrã fica ligado enquanto cozinha",
"cooked.title": "Como correu?",
"cooked.portionsCooked": "Doses cozinhadas",
"cooked.whoAte": "Quem comeu?",
"cooked.mealBoxes": "Doses para marmitas",
"cooked.deductPantry": "Descontar ingredientes da despensa",
"cooked.rate": "Avaliar",
"shopping.title": "Lista de compras",
"recipe.time": "{min} min",
"scan.analyzing": "A analisar …",
"scan.barcode": "Código de barras",
"scan.expiry": "Data de validade",
"scan.failed": "A análise falhou. Tente novamente ou adicione manualmente.",
"scan.freezer": "Congelador",
"scan.fridge": "Frigorífico",
"scan.ingredients": "Ingredientes",
"scan.nutrition": "Rótulo nutricional",
"scan.pantry": "Despensa",
"scan.plate": "Prato",
"scan.quotaLeft_one": "1 análise de IA restante este mês",
"scan.quotaLeft_other": "{count} análises de IA restantes este mês",
"scan.receipt": "Talão",
"scan.review.addItem": "+ Adicionar produto",
"scan.review.approveAll": "Guardar na despensa",
"scan.review.bestBeforeNote": "Data de qualidade: o alimento pode estar bom depois dela. Cheire e prove antes de deitar fora.",
"scan.review.dateKindBestBefore": "Preferencialmente antes de",
"scan.review.dateKindUseBy": "Data-limite de consumo",
"scan.review.datePlaceholder": "AAAA-MM-DD (opcional)",
"scan.review.itemPlaceholder": "Produto",
"scan.review.quantityPlaceholder": "Quantidade",
"scan.review.rejected": "Removido",
"scan.review.subtitle": "A IA às vezes hesita você decide. Edite, remova ou acrescente antes de guardar.",
"scan.review.title": "Reveja o resultado",
"scan.review.uncertain": "Incerto verifique",
"scan.review.unitPlaceholder": "Unidade (g, l, un. …)",
"scan.review.unknownItem": "Produto desconhecido",
"scan.review.useByNote": "Data de segurança: não coma depois desta data.",
"scan.subtitle": "Abasteça a despensa ou registe uma refeição com a câmara.",
"scan.takePhoto": "Tirar foto",
"scan.tips.light": "Evite escuridão e sombras",
"scan.tips.move": "Afaste produtos que se tapam",
"scan.tips.overview": "Comece com uma foto geral",
"scan.tips.shelf": "Fotografe prateleira a prateleira",
"scan.tips.title": "Como obter a melhor análise",
"scan.title": "Digitalizar",
"shopping.addPlaceholder": "Adicionar artigo …",
"shopping.complete": "Terminar as compras",
"shopping.completedBody_one": "1 produto foi adicionado à despensa.",
"shopping.completedBody_other": "{count} produtos foram adicionados à despensa.",
"shopping.completedTitle": "Feito!",
"shopping.completeNote": "Os artigos marcados vão para a despensa.",
"shopping.empty": "A lista está vazia.",
"shopping.estimated": "aprox. {amount}",
"shopping.estimatedTotal": "Total estimado: aprox. {amount}",
"mealbox.title": "Marmitas",
"mealbox.eatBy": "Comer até {date}",
"mealbox.portionsLeft_one": "Falta 1 dose",
"mealbox.portionsLeft_other": "Faltam {count} doses",
"mealbox.eat": "Comer agora",
"mealbox.empty": "Sem marmitas de momento. Ao cozinhar, pode guardar doses aqui.",
"household.members": "Membros",
"household.invite": "Convide com o código: {code}",
"household.shared": "Partilhado no agregado: despensa, lista de compras, plano semanal, marmitas e orçamento.",
"household.private": "Privado por pessoa: objetivos, alergias, dados de saúde e histórico de refeições.",
"memory.title": "O que o {brand} sabe sobre mim",
"memory.subtitle": "Transparência total. Corrija, pause ou apague a memória é sua.",
"memory.paused": "Em pausa",
"memory.verify": "Correto",
"memory.pause": "Pausar",
"memory.delete": "Apagar",
"memory.deleteAll": "Apagar toda a memória",
"memory.empty": "O {brand} ainda não aprendeu nada sobre si.",
"memory.origin.user_stated": "Disse-nos você",
"memory.origin.observed": "Padrão observado",
"memory.origin.ai_inferred": "Suposição da IA",
"paywall.title": "{brand} Premium",
"paywall.subtitle": "O sistema operativo alimentar de todo o agregado: despensa ilimitada, plano semanal e partilha.",
"paywall.trialActive_one": "Falta 1 dia de período experimental",
"paywall.trialActive_other": "Faltam {days} dias de período experimental",
"paywall.household": "Household · até 3 pessoas",
"paywall.family": "Family · até 6 pessoas",
"paywall.large": "Large Household · até 12 pessoas",
"paywall.perMonth": "{price}/mês",
"paywall.fairUse": "Quota de utilização justa de análises de IA incluída.",
"paywall.restore": "Restaurar compras",
"paywall.freeNote": "Grátis: 10 análises de IA/mês, despensa manual, receitas guardadas e registo simples.",
"profile.title": "Perfil",
"profile.goals": "Objetivos e alimentação",
"profile.consents": "Consentimentos e dados",
"profile.memory": "O que o {brand} sabe sobre mim",
"profile.subscription": "Subscrição",
"profile.logout": "Terminar sessão",
"profile.export": "Exportar os meus dados",
"profile.deleteAccount": "Eliminar conta",
"profile.language": "Idioma",
"profile.emailUnverified": "Endereço de e-mail não confirmado",
"profile.resendVerification": "Reenviar e-mail de confirmação",
"profile.verificationSent": "Enviado! Verifique a caixa de entrada.",
"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": "Ops",
"common.undo": "Desfazer",
"common.remove": "Remover",
"common.add": "Adicionar",
"common.on": "Ativado",
"common.off": "Desativado",
"home.thisWeek": "Esta semana",
"home.wasteWeek": "Desperdício esta semana",
"home.useSoonHint": "„A consumir de preferência antes de” é sobre qualidade: cheire, observe e prove antes de deitar fora. Já a data-limite de consumo deve ser respeitada.",
"scan.review.itemPlaceholder": "Produto",
"scan.review.quantityPlaceholder": "Quantidade",
"scan.review.unitPlaceholder": "Unidade (g, l, un. …)",
"scan.review.datePlaceholder": "AAAA-MM-DD (opcional)",
"scan.review.dateKindBestBefore": "Preferencialmente antes de",
"scan.review.dateKindUseBy": "Data-limite de consumo",
"scan.review.bestBeforeNote": "Data de qualidade: o alimento pode estar bom depois dela. Cheire e prove antes de deitar fora.",
"scan.review.useByNote": "Data de segurança: não coma depois desta data.",
"scan.review.addItem": "+ Adicionar produto",
"scan.review.unknownItem": "Produto desconhecido",
"barcode.aim": "Aponte a câmara para o código de barras",
"barcode.addPrompt": "Adicionar à despensa?",
"barcode.kcalPer100": "{kcal} kcal/100 g",
"barcode.noNutrition": "Sem dados nutricionais",
"barcode.unknownTitle": "Produto desconhecido",
"barcode.unknownBody": "Este produto ainda não está na base de dados. Fotografe a frente e o rótulo nutricional e nós adicionamo-lo.",
"barcode.photoPackage": "Fotografar a embalagem",
"barcode.needHousehold": "Primeiro precisa de um agregado.",
"barcode.noLocation": "Nenhum local de armazenamento encontrado.",
"barcode.cameraTitle": "É precisa a câmara",
"barcode.cameraBody": "O {brand} precisa da câmara para ler códigos de barras.",
"barcode.allowCamera": "Permitir câmara",
"onboarding.goal.lose_weight": "Perder peso",
"onboarding.goal.build_muscle": "Ganhar músculo",
"onboarding.goal.maintain_weight": "Manter o peso",
"onboarding.goal.more_protein": "Comer mais proteína",
"onboarding.goal.less_waste": "Reduzir o desperdício",
"onboarding.goal.lower_cost": "Gastar menos em comida",
"onboarding.goal.cook_more": "Cozinhar mais em casa",
"onboarding.diet.omnivore": "Omnívoro",
"onboarding.diet.flexitarian": "Flexitariano",
"onboarding.diet.pescatarian": "Pescetariano",
"onboarding.diet.vegetarian": "Vegetariano",
"onboarding.diet.vegan": "Vegano",
"onboarding.allergen.gluten": "Glúten",
"onboarding.allergen.milk": "Leite/lactose",
"onboarding.allergen.eggs": "Ovos",
"onboarding.allergen.tree_nuts": "Frutos de casca rija",
"onboarding.allergen.peanuts": "Amendoins",
"onboarding.allergen.fish": "Peixe",
"onboarding.allergen.crustaceans": "Crustáceos",
"onboarding.allergen.soy": "Soja",
"onboarding.allergen.sesame": "Sésamo",
"onboarding.bodyTitle": "Para objetivos calóricos pessoais (opcional)",
"onboarding.weightPlaceholder": "Peso (kg)",
"onboarding.heightPlaceholder": "Altura (cm)",
"onboarding.birthYearPlaceholder": "Ano de nascimento",
"onboarding.householdDefaultName": "Casa",
"onboarding.modesCombine": "Os modos podem combinar-se: simples no dia a dia, exato quando quiser.",
"myday.mealType.breakfast": "Pequeno-almoço",
"myday.mealType.lunch": "Almoço",
"myday.mealType.dinner": "Jantar",
"myday.mealType.snack": "Lanche",
"myday.mealType.dessert": "Sobremesa",
"myday.overRecommended": "Acima do recomendado",
"myday.todaysMeals": "Refeições de hoje",
"myday.estimateRange": "{min}{max} kcal, provavelmente {kcal}",
"myday.kcalProtein": "{kcal} kcal · {protein} g de proteína",
"logmeal.mealTypeTitle": "Tipo de refeição",
"logmeal.quickTitle": "Registo rápido",
"logmeal.quickSubtitle": "Regista os mesmos valores nutricionais da última vez.",
"logmeal.manualTitle": "Manual",
"logmeal.whatPlaceholder": "O que comeu?",
"logmeal.proteinPlaceholder": "proteína (g)",
"logmeal.validationTitle": "Preencha",
"logmeal.validationBody": "Nome e calorias são necessários para o registo manual.",
"logmeal.photoTip": "Dica: fotografe o prato em Digitalizar e a app estima por si você confirma sempre.",
"shopping.section.frukt_gront": "Fruta e legumes",
"shopping.section.brod": "Pão",
"shopping.section.mejeri": "Laticínios",
"shopping.section.kott_fagel": "Carne e aves",
"shopping.section.fisk": "Peixe",
"shopping.section.chark": "Charcutaria",
"shopping.section.frys": "Congelados",
"shopping.section.skafferi": "Despensa",
"shopping.section.konserver": "Conservas",
"shopping.section.kryddor_bak": "Especiarias e pastelaria",
"shopping.section.dryck": "Bebidas",
"shopping.section.snacks": "Snacks",
"shopping.section.fisk": "Peixe",
"shopping.section.frukt_gront": "Fruta e legumes",
"shopping.section.frys": "Congelados",
"shopping.section.hygien_ovrigt": "Outros",
"shopping.completedTitle": "Feito!",
"household.role.owner": "Proprietário",
"household.role.adult": "Adulto",
"household.role.member": "Membro",
"household.role.child": "Criança",
"household.empty": "Ainda não tem um agregado. Crie um na configuração inicial ou junte-se com um código.",
"household.shareCode": "Partilhe o código com a família para partilhar despensa, lista e plano.",
"household.locations": "Locais de armazenamento",
"household.portionFactor": "×{factor} dose",
"mealbox.enjoyTitle": "Bom apetite!",
"mealbox.enjoyBody": "A dose foi registada em O meu dia.",
"mealbox.guidanceNote": "O prazo recomendado é indicativo: confie no cheiro e no sabor.",
"paywall.soonTitle": "Em breve!",
"paywall.soonBody": "As compras ativam-se via App Store/Google Play quando a integração com as lojas for ligada (fase 7). O fluxo de backend já está pronto.",
"paywall.popular": "Mais popular",
"paywall.choose": "Escolher",
"recipe.saved": "Guardada",
"recipe.save": "Guardar",
"recipe.avgRating": "Média {avg} em {count} avaliações",
"memory.verified": "Verificado",
"memory.resume": "Retomar",
"memory.deleteAllTitle": "Apagar toda a memória?",
"memory.irreversible": "Isto não pode ser anulado.",
"profile.consent.personalization": "Funções pessoais (memória e perfil de gosto)",
"profile.consent.anonymized_improvement": "Melhoria anonimizada da IA",
"profile.consent.image_training": "As minhas fotos podem ser usadas para treino",
"profile.consent.health_integration": "Dados de saúde (Apple Health / Health Connect)",
"profile.consent.location_weather": "Localização para sugestões conforme o tempo",
"profile.consent.push_notifications": "Notificações push",
"profile.modeLabel": "Modo:",
"profile.memorySubtitle": "Veja, corrija, pause ou apague o que a plataforma aprendeu.",
"profile.consentsNote": "Os consentimentos são separados: as funções pessoais nunca exigem consentimento de treino.",
"profile.aiScansUsed": "Digitalizações de IA: {used} / {total} este mês",
"profile.deleteTitle": "Apagar a conta?",
"profile.deleteBody": "Todos os seus dados são apagados permanentemente conforme o RGPD.",
"cooking.timerDone": "Pronto!",
"cooked.deductPantryNote": "Os ingredientes são descontados da despensa: primeiro os que expiram antes.",
"home.moreItems_one": "+ mais 1 …",
"home.moreItems_other": "+ mais {count} …",
"shopping.completedBody_one": "1 produto foi adicionado à despensa.",
"shopping.completedBody_other": "{count} produtos foram adicionados à despensa.",
"profile.consent.product_analytics": "Análise de produto"
"shopping.section.konserver": "Conservas",
"shopping.section.kott_fagel": "Carne e aves",
"shopping.section.kryddor_bak": "Especiarias e pastelaria",
"shopping.section.mejeri": "Laticínios",
"shopping.section.skafferi": "Despensa",
"shopping.section.snacks": "Snacks",
"shopping.title": "Lista de compras",
"tabs.home": "Em casa",
"tabs.myDay": "O meu dia",
"tabs.scan": "Digitalizar",
"tabs.whatToEat": "O que vamos comer?",
"wte.coverage": "{pct}% em casa",
"wte.cravingPlaceholder": "Apetece-me … (ex.: cremoso, asiático, menos de 500 kcal)",
"wte.empty": "Ainda sem sugestões adicione comida à despensa ou alivie os filtros.",
"wte.mealBoxFirst": "Comida pronta em casa",
"wte.missing": "Falta: {items}",
"wte.refresh": "Novas sugestões",
"wte.subtitle": "Com base no que têm em casa, no que deve ser usado em breve e no que gostam.",
"wte.title": "O que vamos comer?",
"wte.whyTitle": "Porquê esta sugestão?"
}