i18n(recept): steg + täcknings-etiketter spanska, översatta saknade-namn, cache-nyckel

- cooking-skärmen renderade instructionSv (alltid svenska) -> nu instruction ?? instructionSv
- recept-skärmens täckningskort hårdkodade svenska (Du har X% hemma, Saknar N,
  Du saknar, Lägg till i inköpslistan, Tillagt) -> t()-nycklar recipe.coverage.*
- coverage.missing fick översatt displayName i API:t (annars svenska ingrediensnamn)
- 7 nya recipe.coverage.*-nycklar i alla 12 språk
- persister-cache-nyckel -> -v2 (slänger gammal helsvensk cache en gang)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 00:06:33 +00:00
parent 02d5bc4f73
commit 4e86b1b724
16 changed files with 106 additions and 10 deletions
+7 -1
View File
@@ -261,7 +261,13 @@ export async function recipeRoutes(app: FastifyInstance) {
}),
safety,
variants,
coverage,
coverage: coverage && {
...coverage,
missing: coverage.missing.map((m) => ({
...m,
displayName: ingredientNames.get(m.canonicalIngredientId) ?? m.displayNameSv,
})),
},
myRating: myRating ?? null,
isFavorite: Boolean(favorite),
};
+1 -1
View File
@@ -29,7 +29,7 @@ const queryClient = new QueryClient({
const persister = createAsyncStoragePersister({
storage: AsyncStorage,
key: `${BRAND.slug}-query-cache`,
key: `${BRAND.slug}-query-cache-v2`,
});
export default function RootLayout() {
+2 -1
View File
@@ -34,6 +34,7 @@ interface RecipeForCooking {
id: string;
stepNumber: number;
instructionSv: string;
instruction?: string;
timerSeconds: number | null;
temperatureC: number | null;
tip: string | null;
@@ -246,7 +247,7 @@ export default function CookingScreen() {
{t("cooking.step", { current: stepIndex + 1, total: recipe.steps.length })}
</Heading>
<Text style={{ fontSize: 24, lineHeight: 34, color: colors.text }}>
{step?.instructionSv}
{step?.instruction ?? step?.instructionSv}
{step?.temperatureC ? ` (${step.temperatureC} °C)` : ""}
</Text>
{step?.tip && <Small>💡 {step.tip}</Small>}
+12 -7
View File
@@ -61,6 +61,7 @@ interface RecipeDetail {
missing: Array<{
canonicalIngredientId: string;
displayNameSv: string;
displayName?: string;
quantity: number;
unit: string;
}>;
@@ -181,8 +182,8 @@ export default function RecipeScreen() {
onSuccess: (count) => {
void queryClient.invalidateQueries({ queryKey: ["shopping-lists"] });
Alert.alert(
"Tillagt i inköpslistan",
`${count} ${count === 1 ? "vara" : "varor"} lades till.`,
t("recipe.coverage.added"),
t("recipe.coverage.addedBody", { count }),
);
},
onError: (err) => {
@@ -263,21 +264,25 @@ export default function RecipeScreen() {
{recipe.coverage && (
<Card>
<Row style={{ justifyContent: "space-between", alignItems: "center" }}>
<Heading>Du har {recipe.coverage.percent}% hemma</Heading>
<Heading>{t("recipe.coverage.have", { percent: recipe.coverage.percent })}</Heading>
{recipe.coverage.missing.length > 0 && (
<Tag label={`Saknar ${recipe.coverage.missing.length}`} tone="warning" />
<Tag label={t("recipe.coverage.missingCount", { count: recipe.coverage.missing.length })} tone="warning" />
)}
</Row>
{recipe.coverage.missing.length === 0 ? (
<Small>Du har allt som behövs.</Small>
<Small>{t("recipe.coverage.haveAll")}</Small>
) : (
<>
<Small>
Du saknar: {recipe.coverage.missing.map((m) => m.displayNameSv).join(", ")}
{t("recipe.coverage.youAreMissing", {
names: recipe.coverage.missing
.map((m) => m.displayName ?? m.displayNameSv)
.join(", "),
})}
</Small>
<Spacer size={spacing.xs} />
<Button
label={`Lägg till ${recipe.coverage.missing.length} i inköpslistan`}
label={t("recipe.coverage.addToList", { count: recipe.coverage.missing.length })}
variant="primary"
loading={addMissing.isPending}
onPress={() => addMissing.mutate(recipe.coverage!.missing)}
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Gennemsnit {avg} af {count} bedømmelser",
"recipe.cook": "Lav mad nu",
"recipe.cost": "ca. {amount}/portion",
"recipe.coverage.have": "Du har {percent}% hjemme",
"recipe.coverage.missingCount": "Mangler {count}",
"recipe.coverage.haveAll": "Du har alt, hvad du skal bruge.",
"recipe.coverage.youAreMissing": "Du mangler: {names}",
"recipe.coverage.addToList": "Tilføj {count} til indkøbslisten",
"recipe.coverage.added": "Tilføjet til indkøbslisten",
"recipe.coverage.addedBody": "Tilføjet: {count}",
"recipe.iCookedThis": "Jeg har lavet denne",
"recipe.ingredients": "Ingredienser",
"recipe.notSafe": "Passer ikke til dine kostindstillinger",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Schnitt {avg} bei {count} Bewertungen",
"recipe.cook": "Jetzt kochen",
"recipe.cost": "ca. {amount}/Portion",
"recipe.coverage.have": "Du hast {percent}% zu Hause",
"recipe.coverage.missingCount": "Fehlend: {count}",
"recipe.coverage.haveAll": "Du hast alles, was du brauchst.",
"recipe.coverage.youAreMissing": "Dir fehlt: {names}",
"recipe.coverage.addToList": "{count} zur Einkaufsliste hinzufügen",
"recipe.coverage.added": "Zur Einkaufsliste hinzugefügt",
"recipe.coverage.addedBody": "Hinzugefügt: {count}",
"recipe.iCookedThis": "Habe ich gekocht",
"recipe.ingredients": "Zutaten",
"recipe.notSafe": "Passt nicht zu deinen Ernährungseinstellungen",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Average {avg} from {count} ratings",
"recipe.cook": "Cook now",
"recipe.cost": "approx. {amount}/serving",
"recipe.coverage.have": "You have {percent}% at home",
"recipe.coverage.missingCount": "Missing {count}",
"recipe.coverage.haveAll": "You have everything you need.",
"recipe.coverage.youAreMissing": "You're missing: {names}",
"recipe.coverage.addToList": "Add {count} to shopping list",
"recipe.coverage.added": "Added to shopping list",
"recipe.coverage.addedBody": "Added: {count}",
"recipe.iCookedThis": "I cooked this",
"recipe.ingredients": "Ingredients",
"recipe.notSafe": "Doesn't match your dietary settings",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Media de {avg} con {count} valoraciones",
"recipe.cook": "Cocinar ahora",
"recipe.cost": "aprox. {amount}/ración",
"recipe.coverage.have": "Tienes {percent}% en casa",
"recipe.coverage.missingCount": "Faltan {count}",
"recipe.coverage.haveAll": "Tienes todo lo necesario.",
"recipe.coverage.youAreMissing": "Te falta: {names}",
"recipe.coverage.addToList": "Añadir {count} a la lista de la compra",
"recipe.coverage.added": "Añadido a la lista de la compra",
"recipe.coverage.addedBody": "Añadidos: {count}",
"recipe.iCookedThis": "Lo he cocinado",
"recipe.ingredients": "Ingredientes",
"recipe.notSafe": "No encaja con tu configuración dietética",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Keskiarvo {avg}, {count} arviota",
"recipe.cook": "Kokkaa nyt",
"recipe.cost": "n. {amount}/annos",
"recipe.coverage.have": "Sinulla on {percent}% kotona",
"recipe.coverage.missingCount": "Puuttuu {count}",
"recipe.coverage.haveAll": "Sinulla on kaikki tarvittava.",
"recipe.coverage.youAreMissing": "Sinulta puuttuu: {names}",
"recipe.coverage.addToList": "Lisää {count} ostoslistalle",
"recipe.coverage.added": "Lisätty ostoslistalle",
"recipe.coverage.addedBody": "Lisätty: {count}",
"recipe.iCookedThis": "Tein tämän",
"recipe.ingredients": "Ainekset",
"recipe.notSafe": "Ei sovi ruokavalioasetuksiisi",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Moyenne {avg} sur {count} avis",
"recipe.cook": "Cuisiner maintenant",
"recipe.cost": "env. {amount}/portion",
"recipe.coverage.have": "Vous avez {percent}% chez vous",
"recipe.coverage.missingCount": "Manquant : {count}",
"recipe.coverage.haveAll": "Vous avez tout ce qu'il faut.",
"recipe.coverage.youAreMissing": "Il vous manque : {names}",
"recipe.coverage.addToList": "Ajouter {count} à la liste de courses",
"recipe.coverage.added": "Ajouté à la liste de courses",
"recipe.coverage.addedBody": "Ajoutés : {count}",
"recipe.iCookedThis": "Je l'ai cuisiné",
"recipe.ingredients": "Ingrédients",
"recipe.notSafe": "Incompatible avec vos réglages alimentaires",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Media {avg} su {count} valutazioni",
"recipe.cook": "Cucina ora",
"recipe.cost": "circa {amount}/porzione",
"recipe.coverage.have": "Hai il {percent}% in casa",
"recipe.coverage.missingCount": "Mancanti: {count}",
"recipe.coverage.haveAll": "Hai tutto il necessario.",
"recipe.coverage.youAreMissing": "Ti manca: {names}",
"recipe.coverage.addToList": "Aggiungi {count} alla lista della spesa",
"recipe.coverage.added": "Aggiunto alla lista della spesa",
"recipe.coverage.addedBody": "Aggiunti: {count}",
"recipe.iCookedThis": "L'ho cucinato",
"recipe.ingredients": "Ingredienti",
"recipe.notSafe": "Non adatto alle tue impostazioni alimentari",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Snitt {avg} av {count} vurderinger",
"recipe.cook": "Lag mat nå",
"recipe.cost": "ca. {amount}/porsjon",
"recipe.coverage.have": "Du har {percent}% hjemme",
"recipe.coverage.missingCount": "Mangler {count}",
"recipe.coverage.haveAll": "Du har alt du trenger.",
"recipe.coverage.youAreMissing": "Du mangler: {names}",
"recipe.coverage.addToList": "Legg til {count} i handlelisten",
"recipe.coverage.added": "Lagt til i handlelisten",
"recipe.coverage.addedBody": "Lagt til: {count}",
"recipe.iCookedThis": "Jeg har laget denne",
"recipe.ingredients": "Ingredienser",
"recipe.notSafe": "Passer ikke kostinnstillingene dine",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Gemiddeld {avg} uit {count} beoordelingen",
"recipe.cook": "Nu koken",
"recipe.cost": "ca. {amount}/portie",
"recipe.coverage.have": "Je hebt {percent}% in huis",
"recipe.coverage.missingCount": "Ontbreekt: {count}",
"recipe.coverage.haveAll": "Je hebt alles wat je nodig hebt.",
"recipe.coverage.youAreMissing": "Je mist: {names}",
"recipe.coverage.addToList": "{count} toevoegen aan boodschappenlijst",
"recipe.coverage.added": "Toegevoegd aan boodschappenlijst",
"recipe.coverage.addedBody": "Toegevoegd: {count}",
"recipe.iCookedThis": "Dit heb ik gekookt",
"recipe.ingredients": "Ingrediënten",
"recipe.notSafe": "Past niet bij je voedingsinstellingen",
+7
View File
@@ -285,6 +285,13 @@
"recipe.avgRating": "Średnia {avg} z {count} ocen",
"recipe.cook": "Gotuj teraz",
"recipe.cost": "ok. {amount}/porcja",
"recipe.coverage.have": "Masz {percent}% w domu",
"recipe.coverage.missingCount": "Brakuje: {count}",
"recipe.coverage.haveAll": "Masz wszystko, czego potrzebujesz.",
"recipe.coverage.youAreMissing": "Brakuje Ci: {names}",
"recipe.coverage.addToList": "Dodaj {count} do listy zakupów",
"recipe.coverage.added": "Dodano do listy zakupów",
"recipe.coverage.addedBody": "Dodano: {count}",
"recipe.iCookedThis": "Ugotowałem/-am to",
"recipe.ingredients": "Składniki",
"recipe.notSafe": "Nie pasuje do Twoich ustawień żywieniowych",
+7
View File
@@ -277,6 +277,13 @@
"recipe.avgRating": "Média {avg} em {count} avaliações",
"recipe.cook": "Cozinhar agora",
"recipe.cost": "aprox. {amount}/dose",
"recipe.coverage.have": "Tens {percent}% em casa",
"recipe.coverage.missingCount": "Faltam {count}",
"recipe.coverage.haveAll": "Tens tudo o que precisas.",
"recipe.coverage.youAreMissing": "Falta-te: {names}",
"recipe.coverage.addToList": "Adicionar {count} à lista de compras",
"recipe.coverage.added": "Adicionado à lista de compras",
"recipe.coverage.addedBody": "Adicionados: {count}",
"recipe.iCookedThis": "Cozinhei isto",
"recipe.ingredients": "Ingredientes",
"recipe.notSafe": "Não se adequa às suas definições alimentares",
+7
View File
@@ -285,6 +285,13 @@
"recipe.avgRating": "Snitt {avg} av {count} betyg",
"recipe.cook": "Laga nu",
"recipe.cost": "ca {amount}/portion",
"recipe.coverage.have": "Du har {percent}% hemma",
"recipe.coverage.missingCount": "Saknar {count}",
"recipe.coverage.haveAll": "Du har allt som behövs.",
"recipe.coverage.youAreMissing": "Du saknar: {names}",
"recipe.coverage.addToList": "Lägg till {count} i inköpslistan",
"recipe.coverage.added": "Tillagt i inköpslistan",
"recipe.coverage.addedBody": "Tillagda: {count}",
"recipe.iCookedThis": "Jag lagade detta",
"recipe.ingredients": "Ingredienser",
"recipe.notSafe": "Passar inte dina kostinställningar",