feat: retry-failed-translations + kok-läget på rätt språk (titel, ingredienser, valfritt)

- retry-failed-translations.ts: kör om de receptöversättningar som föll på
  verifieringen (skriver ut vilka + varför, köar TRANSLATE_RECIPE på nytt).
  Idempotent; kör tills listan är tom, publicera sedan.
- Kok-läget (cooking/[id].tsx) visade svensk titel + svenska ingrediensnamn fast
  detalj-API:t redan levererar översatta fält. Nu title/displayName med svensk
  fallback (stegen var redan översatta).
- "(valfritt)" var hårdkodat i kok-läget + recept-detaljen -> ny nyckel
  recipe.optional i alla 12 språk. Vakt + typecheck gröna.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 18:49:34 +00:00
parent fbb251ea72
commit 52bd6e74bd
15 changed files with 117 additions and 6 deletions
+7 -5
View File
@@ -29,6 +29,7 @@ import { formatQuantity } from "@/lib/units";
interface RecipeForCooking {
id: string;
titleSv: string;
title?: string;
portions: number;
steps: Array<{
id: string;
@@ -42,6 +43,7 @@ interface RecipeForCooking {
ingredients: Array<{
id: string;
displayNameSv: string;
displayName?: string;
quantity: number;
unit: string;
optional: boolean;
@@ -110,7 +112,7 @@ export default function CookingScreen() {
<Screen>
<Card>
<Heading> {t("cooked.title")}</Heading>
<Body>{recipe.titleSv}</Body>
<Body>{recipe.title ?? recipe.titleSv}</Body>
<Small>
{t("cooked.portionsCooked")}: {portionsCooked}
</Small>
@@ -140,7 +142,7 @@ export default function CookingScreen() {
<Screen>
<Heading>{t("cooked.title")}</Heading>
<Card>
<Body>{recipe.titleSv}</Body>
<Body>{recipe.title ?? recipe.titleSv}</Body>
<Small>
{t("cooked.portionsCooked")}: {portionsCooked}
</Small>
@@ -242,7 +244,7 @@ export default function CookingScreen() {
<Screen scroll={false} style={{ justifyContent: "space-between" }}>
<View style={{ gap: spacing.md }}>
<Button label={t("common.cancel")} variant="ghost" onPress={() => router.dismissAll()} />
<Small>{recipe.titleSv}</Small>
<Small>{recipe.title ?? recipe.titleSv}</Small>
<Heading>
{t("cooking.step", { current: stepIndex + 1, total: recipe.steps.length })}
</Heading>
@@ -275,8 +277,8 @@ export default function CookingScreen() {
return (
<Row key={ing.id} style={{ justifyContent: "space-between" }}>
<Body>
{ing.displayNameSv}
{ing.optional ? " (valfritt)" : ""}
{ing.displayName ?? ing.displayNameSv}
{ing.optional ? ` ${t("recipe.optional")}` : ""}
</Body>
<Small>{formatQuantity(scaledQty, ing.unit)}</Small>
</Row>
+1 -1
View File
@@ -316,7 +316,7 @@ export default function RecipeScreen() {
>
<Body>
{ing.displayName ?? ing.displayNameSv}
{ing.optional ? " (valfritt)" : ""}
{ing.optional ? ` ${t("recipe.optional")}` : ""}
</Body>
<Small>{formatQuantity(ing.quantity, ing.unit)}</Small>
</Row>
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Tilføjet: {count}",
"recipe.iCookedThis": "Jeg har lavet denne",
"recipe.ingredients": "Ingredienser",
"recipe.optional": "(valgfri)",
"recipe.notSafe": "Passer ikke til dine kostindstillinger",
"recipe.perPortion": "pr. portion",
"recipe.portions_one": "1 portion",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Hinzugefügt: {count}",
"recipe.iCookedThis": "Habe ich gekocht",
"recipe.ingredients": "Zutaten",
"recipe.optional": "(optional)",
"recipe.notSafe": "Passt nicht zu deinen Ernährungseinstellungen",
"recipe.perPortion": "pro Portion",
"recipe.portions_one": "1 Portion",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Added: {count}",
"recipe.iCookedThis": "I cooked this",
"recipe.ingredients": "Ingredients",
"recipe.optional": "(optional)",
"recipe.notSafe": "Doesn't match your dietary settings",
"recipe.perPortion": "per serving",
"recipe.portions_one": "1 serving",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Añadidos: {count}",
"recipe.iCookedThis": "Lo he cocinado",
"recipe.ingredients": "Ingredientes",
"recipe.optional": "(opcional)",
"recipe.notSafe": "No encaja con tu configuración dietética",
"recipe.perPortion": "por ración",
"recipe.portions_one": "1 ración",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Lisätty: {count}",
"recipe.iCookedThis": "Tein tämän",
"recipe.ingredients": "Ainekset",
"recipe.optional": "(valinnainen)",
"recipe.notSafe": "Ei sovi ruokavalioasetuksiisi",
"recipe.perPortion": "per annos",
"recipe.portions_one": "1 annos",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Ajoutés : {count}",
"recipe.iCookedThis": "Je l'ai cuisiné",
"recipe.ingredients": "Ingrédients",
"recipe.optional": "(facultatif)",
"recipe.notSafe": "Incompatible avec vos réglages alimentaires",
"recipe.perPortion": "par portion",
"recipe.portions_one": "1 portion",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Aggiunti: {count}",
"recipe.iCookedThis": "L'ho cucinato",
"recipe.ingredients": "Ingredienti",
"recipe.optional": "(facoltativo)",
"recipe.notSafe": "Non adatto alle tue impostazioni alimentari",
"recipe.perPortion": "a porzione",
"recipe.portions_one": "1 porzione",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Lagt til: {count}",
"recipe.iCookedThis": "Jeg har laget denne",
"recipe.ingredients": "Ingredienser",
"recipe.optional": "(valgfritt)",
"recipe.notSafe": "Passer ikke kostinnstillingene dine",
"recipe.perPortion": "per porsjon",
"recipe.portions_one": "1 porsjon",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Toegevoegd: {count}",
"recipe.iCookedThis": "Dit heb ik gekookt",
"recipe.ingredients": "Ingrediënten",
"recipe.optional": "(optioneel)",
"recipe.notSafe": "Past niet bij je voedingsinstellingen",
"recipe.perPortion": "per portie",
"recipe.portions_one": "1 portie",
+1
View File
@@ -353,6 +353,7 @@
"recipe.coverage.addedBody": "Dodano: {count}",
"recipe.iCookedThis": "Ugotowałem/-am to",
"recipe.ingredients": "Składniki",
"recipe.optional": "(opcjonalne)",
"recipe.notSafe": "Nie pasuje do Twoich ustawień żywieniowych",
"recipe.perPortion": "na porcję",
"recipe.portions_few": "{count} porcje",
+1
View File
@@ -345,6 +345,7 @@
"recipe.coverage.addedBody": "Adicionados: {count}",
"recipe.iCookedThis": "Cozinhei isto",
"recipe.ingredients": "Ingredientes",
"recipe.optional": "(opcional)",
"recipe.notSafe": "Não se adequa às suas definições alimentares",
"recipe.perPortion": "por dose",
"recipe.portions_one": "1 dose",
+1
View File
@@ -353,6 +353,7 @@
"recipe.coverage.addedBody": "Tillagda: {count}",
"recipe.iCookedThis": "Jag lagade detta",
"recipe.ingredients": "Ingredienser",
"recipe.optional": "(valfritt)",
"recipe.notSafe": "Passar inte dina kostinställningar",
"recipe.perPortion": "per portion",
"recipe.portions_one": "1 portion",