mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-05-28 04:30:25 -04:00
Compare commits
1 Commits
v3.18.0
...
fix/mealpl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ff7f27fea |
@@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
|
||||
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
|
||||
|
||||
1. Take a backup just in case!
|
||||
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.18.0`
|
||||
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.17.0`
|
||||
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
|
||||
4. Restart the container
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
|
||||
```yaml
|
||||
services:
|
||||
mealie:
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.18.0 # (3)
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.17.0 # (3)
|
||||
container_name: mealie
|
||||
restart: always
|
||||
ports:
|
||||
|
||||
@@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
|
||||
```yaml
|
||||
services:
|
||||
mealie:
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.18.0 # (3)
|
||||
image: ghcr.io/mealie-recipes/mealie:v3.17.0 # (3)
|
||||
container_name: mealie
|
||||
restart: always
|
||||
ports:
|
||||
|
||||
@@ -38,7 +38,7 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Svenska (Swedish)",
|
||||
value: "sv-SE",
|
||||
progress: 75,
|
||||
progress: 74,
|
||||
dir: "ltr",
|
||||
pluralFoodHandling: "always",
|
||||
},
|
||||
@@ -101,14 +101,14 @@ export const LOCALES = [
|
||||
{
|
||||
name: "Norsk (Norwegian)",
|
||||
value: "no-NO",
|
||||
progress: 60,
|
||||
progress: 59,
|
||||
dir: "ltr",
|
||||
pluralFoodHandling: "always",
|
||||
},
|
||||
{
|
||||
name: "Nederlands (Dutch)",
|
||||
value: "nl-NL",
|
||||
progress: 98,
|
||||
progress: 97,
|
||||
dir: "ltr",
|
||||
pluralFoodHandling: "always",
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"category": "Kategoria"
|
||||
},
|
||||
"events": {
|
||||
"apprise-url": "Apprise-url",
|
||||
"apprise-url": "Apprise URL",
|
||||
"database": "Tietokanta",
|
||||
"delete-event": "Poista tapahtuma",
|
||||
"event-delete-confirmation": "Haluatko varmasti poistaa tämän tapahtuman?",
|
||||
@@ -98,7 +98,7 @@
|
||||
"dashboard": "Hallintanäkymä",
|
||||
"delete": "Poista",
|
||||
"disabled": "Poistettu käytöstä",
|
||||
"done": "Valmis",
|
||||
"done": "Done",
|
||||
"download": "Lataa",
|
||||
"duplicate": "Monista",
|
||||
"edit": "Muokkaa",
|
||||
@@ -169,7 +169,7 @@
|
||||
"token": "Tunniste",
|
||||
"tuesday": "Tiistai",
|
||||
"type": "Tyyppi",
|
||||
"undo": "Peru",
|
||||
"undo": "Undo",
|
||||
"update": "Päivitä",
|
||||
"updated": "Päivitetty",
|
||||
"upload": "Lähetä",
|
||||
@@ -333,8 +333,8 @@
|
||||
"any-household": "Mikä tahansa kotitalous",
|
||||
"no-meal-plan-defined-yet": "Ateriasuunnitelmaa ei ole vielä määritelty",
|
||||
"no-meal-planned-for-today": "Ei ateriasuunnitelmaa tälle päivälle",
|
||||
"numberOfDaysPast-hint": "Menneisyydestä ladattujen päivien määrä",
|
||||
"numberOfDaysPast-label": "Oletusarvo menneiden päivien lataukselle",
|
||||
"numberOfDaysPast-hint": "Number of days in the past on page load",
|
||||
"numberOfDaysPast-label": "Default Days in the Past",
|
||||
"numberOfDays-hint": "Sivun latauspäivien lukumäärä",
|
||||
"numberOfDays-label": "Oletuspäivät",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Vain näiden luokkien reseptejä käytetään ateriasuunnitelmissa",
|
||||
@@ -392,7 +392,7 @@
|
||||
"nextcloud": {
|
||||
"description": "Tuo tiedot Nextcloudin Cookbookista",
|
||||
"description-long": "Nextcloud reseptejä voidaan tuoda zip-tiedostosta, joka sisältää Nextcloudin tallennetut tiedot. Katso esimerkkikansiorakenne alla varmistaaksesi, että reseptisi voidaan tuoda.",
|
||||
"title": "Nextcloud-keittokirja"
|
||||
"title": "Nextcloud Cookbook"
|
||||
},
|
||||
"copymethat": {
|
||||
"description-long": "Mealie voi tuoda reseptejä Copy Me That -sovelluksesta. Vie reseptisi HTML-muodossa ja lataa sitten zip-tiedosto.",
|
||||
@@ -702,7 +702,7 @@
|
||||
"confidence-score": "Varmuuspisteet",
|
||||
"ingredient-parser-description": "Ainesosat on haettu onnistuneesti. Ole hyvä ja tarkista ainesosat joista emme ole varmoja.",
|
||||
"ingredient-parser-final-review-description": "Kun kaikki ainesosat on tarkistettu, sinulla on vielä yksi mahdollisuus tarkistaa kaikki ainesosat ennen kuin muokkaat reseptiäsi.",
|
||||
"add-text-as-alias-for-item": "Lisää \"{text}\" kohteen {item} aliakseksi",
|
||||
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
|
||||
"delete-item": "Poista kohde"
|
||||
},
|
||||
"reset-servings-count": "Palauta Annoksien Määrä",
|
||||
@@ -893,17 +893,17 @@
|
||||
"server-side-base-url-error-text": "`BASE_URL` on API-palvelimen oletusarvo. Tämä aiheuttaa ongelmia ilmoitusten linkkien kanssa, jotka on luotu palvelimella sähköposteja varten jne.",
|
||||
"server-side-base-url-success-text": "Palvelimen nettiosoite ei vastaa oletusta",
|
||||
"ldap-ready": "LDAP Valmis",
|
||||
"ldap-not-ready": "LDAP ei valmis",
|
||||
"ldap-not-ready": "LDAP Not Ready",
|
||||
"ldap-ready-error-text": "Kaikkia LDAP-arvoja ei ole määritetty. Tämä voidaan ohittaa, jos et käytä LDAP-todennusta.",
|
||||
"ldap-ready-success-text": "Kaikki vaaditut LDAP-muuttujat on asetettu.",
|
||||
"build": "Koonti",
|
||||
"recipe-scraper-version": "Reseptikaappaimen versio",
|
||||
"oidc-ready": "OIDC valmis",
|
||||
"oidc-not-ready": "OIDC ei ole valmis",
|
||||
"oidc-not-ready": "OIDC Not Ready",
|
||||
"oidc-ready-error-text": "Kaikkia OIDC-arvoja ei ole määritelty. Jos et käytä OIDC-todennusta, voidaan asia jättää huomiotta.",
|
||||
"oidc-ready-success-text": "Kaikki vaaditut OIDC-muuttujat asetettu.",
|
||||
"openai-ready": "OpenAI valmis",
|
||||
"openai-not-ready": "OpenAI ei ole valmis",
|
||||
"openai-not-ready": "OpenAI Not Ready",
|
||||
"openai-ready-error-text": "Kaikkia OpenAI:n arvoja ei ole määritelty. Tämä voidaan sivuuttaa, mikäli et käytä OpenAI:n toimintoja.",
|
||||
"openai-ready-success-text": "Vaadittavat OpenAI-muuttujat ovat asetetut."
|
||||
},
|
||||
@@ -917,7 +917,7 @@
|
||||
"quantity": "Määrä: {0}",
|
||||
"shopping-list": "Ostoslista",
|
||||
"shopping-lists": "Ostoslistat",
|
||||
"add-item": "Lisää kohde",
|
||||
"add-item": "Add item",
|
||||
"food": "Elintarvikkeet",
|
||||
"note": "Muistiinpano",
|
||||
"label": "Tunnus",
|
||||
@@ -962,7 +962,7 @@
|
||||
"language": "Kieli",
|
||||
"maintenance": "Ylläpito",
|
||||
"background-tasks": "Taustatehtävät",
|
||||
"parser": "Jäsentäjä",
|
||||
"parser": "Parser",
|
||||
"developer": "Kehittäjä",
|
||||
"cookbook": "Keittokirja",
|
||||
"create-cookbook": "Luo uusi keittokirja"
|
||||
@@ -1351,7 +1351,7 @@
|
||||
"ingredient-text": "Ainesosan Teksti",
|
||||
"average-confident": "{0} Luottamus",
|
||||
"try-an-example": "Kokeile esimerkkiä",
|
||||
"parser": "Jäsentäjä",
|
||||
"parser": "Parser",
|
||||
"background-tasks": "Taustatehtävät",
|
||||
"background-tasks-description": "Täältä voit tarkastella kaikkia käynnissä olevia taustatehtäviä ja niiden tilaa",
|
||||
"no-logs-found": "Lokeja Ei Löytynyt",
|
||||
@@ -1481,7 +1481,7 @@
|
||||
"announcements": "Announcements",
|
||||
"all-announcements": "All announcements",
|
||||
"mark-all-as-read": "Mark All as Read",
|
||||
"show-announcements-from-mealie": "Näytä Mealien ilmoitukset",
|
||||
"show-announcements-from-mealie": "Show announcements from Mealie",
|
||||
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Biztos, hogy minden elem kijelölését visszavonja?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Biztosan törölni akarja az összes bejelölt elemet?",
|
||||
"no-shopping-lists-found": "Nem találhatók bevásárlólisták",
|
||||
"item-checked-off": "{item} leellenőrzve"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Minden recept",
|
||||
|
||||
@@ -333,8 +333,8 @@
|
||||
"any-household": "Öll heimili",
|
||||
"no-meal-plan-defined-yet": "Ekkert matarplan hefur verið skilgreint",
|
||||
"no-meal-planned-for-today": "Ekkert matarplan skipulagt í dag",
|
||||
"numberOfDaysPast-hint": "Fjöldi liðina daga við síðuhleðslu",
|
||||
"numberOfDaysPast-label": "Sjálfgefnir liðnir dagar",
|
||||
"numberOfDaysPast-hint": "Number of days in the past on page load",
|
||||
"numberOfDaysPast-label": "Default Days in the Past",
|
||||
"numberOfDays-hint": "Fjöldi daga við síðuhleðslu",
|
||||
"numberOfDays-label": "Sjálfgefnir dagar",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Aðeins uppskriftir í þessum flokkum verða notaðir í matarplan",
|
||||
@@ -640,8 +640,8 @@
|
||||
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Stofnaðu uppskrift með því að gefa henni nafn, allar uppskriftir þurfa að hafa einstakt nafn.",
|
||||
"new-recipe-names-must-be-unique": "Nöfn uppskrifta þurfa að vera einstök",
|
||||
"scrape-recipe": "Vinna uppskrift",
|
||||
"scrape-recipe-description": "Sækja uppskrift af vefslóð. Settu inn vefslóð fyrir síðuna þar sem þú vilt sækja uppskrift og Mealie mun reyna að vinna uppskriftina þaðan og bæta henni við safnið þitt.",
|
||||
"scrape-recipe-description-transcription": "Þú getur einnig sett inn slóð á video og Mealie mun reyna að umrita það yfir í uppskrift.",
|
||||
"scrape-recipe-description": "Scrape a recipe by url. Provide the url for the site you want to scrape, and Mealie will attempt to scrape the recipe from that site and add it to your collection.",
|
||||
"scrape-recipe-description-transcription": "You can also provide the url to a video and Mealie will attempt to transcribe it into a recipe.",
|
||||
"scrape-recipe-have-a-lot-of-recipes": "Ertu með margar uppskriftir sem þú villt setja inn í einu?",
|
||||
"scrape-recipe-suggest-bulk-importer": "Prófaðu að setja inn margar uppskriftir í einu",
|
||||
"scrape-recipe-have-raw-html-or-json-data": "Ertu með hrá HTML eða JSON gögn?",
|
||||
@@ -893,17 +893,17 @@
|
||||
"server-side-base-url-error-text": "'BASE_URL' er enn sjálfgefið gildi á API netþjóns. Þetta getur valdið vandræðum með tilkynninga tengla sem netþjónninn býr til fyrir tölvupósta og annað.",
|
||||
"server-side-base-url-success-text": "Slóð netþjóns samsvarar ekki sjálfgefnu gildi",
|
||||
"ldap-ready": "LDAP klár",
|
||||
"ldap-not-ready": "LDAP er ekki tilbúið",
|
||||
"ldap-not-ready": "LDAP Not Ready",
|
||||
"ldap-ready-error-text": "Ekki öll LDAP-gildi eru stillt. Þetta má hunsa ef þú notar ekki LDAP-auðkenningu.",
|
||||
"ldap-ready-success-text": "Öll nauðsynleg LDAP-gildi eru stillt.",
|
||||
"build": "Build",
|
||||
"recipe-scraper-version": "Recipe Scraper útgáfa",
|
||||
"oidc-ready": "OIDC klár",
|
||||
"oidc-not-ready": "OIDC er ekki tilbúið",
|
||||
"oidc-not-ready": "OIDC Not Ready",
|
||||
"oidc-ready-error-text": "Ekki öll OIDC gildi eru stillt. Þetta má hunsa ef þú notar ekki OIDC-auðkenningu.",
|
||||
"oidc-ready-success-text": "Öll nauðsynleg OIDC-gildi eru stillt.",
|
||||
"openai-ready": "OpenAI klár",
|
||||
"openai-not-ready": "OpenAI er ekki tilbúið",
|
||||
"openai-not-ready": "OpenAI Not Ready",
|
||||
"openai-ready-error-text": "Ekki öll OpenAI gildi eru stillt. Þetta má hunsa ef þú notar ekki OpenAI.",
|
||||
"openai-ready-success-text": "Öll nauðsynleg OpenAI-gildi eru stillt."
|
||||
},
|
||||
@@ -917,7 +917,7 @@
|
||||
"quantity": "Fjöldi: {0}",
|
||||
"shopping-list": "Innkaupalisti",
|
||||
"shopping-lists": "Innkaupalistar",
|
||||
"add-item": "Bæta við vöru",
|
||||
"add-item": "Add item",
|
||||
"food": "Matvara",
|
||||
"note": "Minnispunktur",
|
||||
"label": "Merkimiði",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"category": "Categorie"
|
||||
},
|
||||
"events": {
|
||||
"apprise-url": "Kennisgevings-url",
|
||||
"apprise-url": "Apprise URL",
|
||||
"database": "Database",
|
||||
"delete-event": "Gebeurtenis verwijderen",
|
||||
"event-delete-confirmation": "Weet je zeker dat je deze gebeurtenis wilt verwijderen?",
|
||||
@@ -98,7 +98,7 @@
|
||||
"dashboard": "Dashboard",
|
||||
"delete": "Verwijderen",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"done": "Gereed",
|
||||
"done": "Done",
|
||||
"download": "Downloaden",
|
||||
"duplicate": "Dupliceren",
|
||||
"edit": "Bewerken",
|
||||
@@ -169,7 +169,7 @@
|
||||
"token": "Token",
|
||||
"tuesday": "dinsdag",
|
||||
"type": "Soort",
|
||||
"undo": "Ongedaan maken",
|
||||
"undo": "Undo",
|
||||
"update": "Bijwerken",
|
||||
"updated": "Bijgewerkt",
|
||||
"upload": "Uploaden",
|
||||
@@ -333,8 +333,8 @@
|
||||
"any-household": "Elk huishouden",
|
||||
"no-meal-plan-defined-yet": "Nog geen maaltijdplan opgesteld",
|
||||
"no-meal-planned-for-today": "Geen maaltijd gepland voor vandaag",
|
||||
"numberOfDaysPast-hint": "Aantal dagen in het verleden bij laden pagina",
|
||||
"numberOfDaysPast-label": "Standaard dagen in het verleden",
|
||||
"numberOfDaysPast-hint": "Number of days in the past on page load",
|
||||
"numberOfDaysPast-label": "Default Days in the Past",
|
||||
"numberOfDays-hint": "Aantal dagen bij laden van de pagina",
|
||||
"numberOfDays-label": "Standaard aantal dagen",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Alleen recepten met deze categorieën zullen worden gebruikt in maaltijdplannen",
|
||||
@@ -443,7 +443,7 @@
|
||||
"error-details": "Alleen websites met ld+json of microdata kunnen worden geïmporteerd door Mealie. De meeste grote receptenwebsites ondersteunen deze gegevensstructuur. Als je site niet kan worden geïmporteerd, maar er zijn json-gegevens in de log, maak dan een github issue aan met de URL en gegevens.",
|
||||
"error-title": "Het lijkt erop dat we niets konden vinden",
|
||||
"from-url": "Recept importeren",
|
||||
"github-issues": "GitHubproblemen",
|
||||
"github-issues": "GitHub Issues",
|
||||
"google-ld-json-info": "Google ld+json Info",
|
||||
"must-be-a-valid-url": "Moet een geldige URL zijn",
|
||||
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Plak je receptgegevens. Elke regel wordt behandeld als een item in een lijst",
|
||||
@@ -893,17 +893,17 @@
|
||||
"server-side-base-url-error-text": "`BASE_URL` is nog steeds de standaard waarde op de API Server. Dit geeft problemen met notificatielinks in e-mails etc.",
|
||||
"server-side-base-url-success-text": "Server-side URL komt niet overeen met de standaard",
|
||||
"ldap-ready": "LDAP klaar",
|
||||
"ldap-not-ready": "LDAP niet gereed",
|
||||
"ldap-not-ready": "LDAP Not Ready",
|
||||
"ldap-ready-error-text": "Niet alle LDAP-waarden zijn geconfigureerd. Dit kan worden genegeerd als je geen LDAP-authenticatie gebruikt.",
|
||||
"ldap-ready-success-text": "Vereiste LDAP variabelen zijn helemaal ingesteld.",
|
||||
"build": "Build",
|
||||
"recipe-scraper-version": "Versie van de receptenscraper",
|
||||
"oidc-ready": "OIDC klaar",
|
||||
"oidc-not-ready": "OIDC niet gereed",
|
||||
"oidc-not-ready": "OIDC Not Ready",
|
||||
"oidc-ready-error-text": "Niet alle OIDC-waarden zijn geconfigureerd. Dit kan worden genegeerd als je geen OIDC-authenticatie gebruikt.",
|
||||
"oidc-ready-success-text": "Vereiste OIDC-variabelen zijn allemaal ingesteld.",
|
||||
"openai-ready": "OpenAI staat klaar",
|
||||
"openai-not-ready": "OpenAI niet gereed",
|
||||
"openai-not-ready": "OpenAI Not Ready",
|
||||
"openai-ready-error-text": "Niet alle tekstvakken voor OpenAI zijn ingevuld. Als je geen OpenAI gebruikt kun je dit leeg laten.",
|
||||
"openai-ready-success-text": "Verplichte tekstvakken voor OpenAI zijn ingevuld."
|
||||
},
|
||||
@@ -917,7 +917,7 @@
|
||||
"quantity": "Hoeveelheid: {0}",
|
||||
"shopping-list": "Boodschappenlijst",
|
||||
"shopping-lists": "Boodschappenlijsten",
|
||||
"add-item": "Item toevoegen",
|
||||
"add-item": "Add item",
|
||||
"food": "Levensmiddelen",
|
||||
"note": "Notitie",
|
||||
"label": "Label",
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Weet je zeker dat je alle items wilt deselecteren?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Weet je zeker dat je de geselecteerde items wilt verwijderen?",
|
||||
"no-shopping-lists-found": "Geen boodschappenlijsten gevonden",
|
||||
"item-checked-off": "Uitgevinkt {item}"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Alle Recepten",
|
||||
@@ -1283,7 +1283,7 @@
|
||||
"split-by-block": "Splits per tekstblok",
|
||||
"flatten": "Plat maken ongeacht originele opmaak",
|
||||
"help": {
|
||||
"help": "Hulp",
|
||||
"help": "Help",
|
||||
"mouse-modes": "Muismodus",
|
||||
"selection-mode": "Selectiemodus (standaard)",
|
||||
"selection-mode-desc": "De selectiemodus is de hoofdmodus die gebruikt kan worden om gegevens in te voeren:",
|
||||
@@ -1478,10 +1478,10 @@
|
||||
"max-length": "Moet maximaal {max} tekens bevatten|Moet maximaal {max} tekens bevatten"
|
||||
},
|
||||
"announcements": {
|
||||
"announcements": "Aankondigingen",
|
||||
"all-announcements": "Alle aankondigingen",
|
||||
"mark-all-as-read": "Alles markeren als gelezen",
|
||||
"show-announcements-from-mealie": "Aankondigingen van Mealie weergeven",
|
||||
"show-announcements-setting-description": "Of je gebruikers wel of niet meldingen van Mealie wilt laten zien. Wanneer ingeschakeld kunnen gebruikers nog steeds afzien van het bekijken van hen in hun gebruikersinstellingen"
|
||||
"announcements": "Announcements",
|
||||
"all-announcements": "All announcements",
|
||||
"mark-all-as-read": "Mark All as Read",
|
||||
"show-announcements-from-mealie": "Show announcements from Mealie",
|
||||
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
"dashboard": "Kontrollpanel",
|
||||
"delete": "Slett",
|
||||
"disabled": "Deaktivert",
|
||||
"done": "Ferdig",
|
||||
"done": "Done",
|
||||
"download": "Last ned",
|
||||
"duplicate": "Dupliser",
|
||||
"edit": "Rediger",
|
||||
@@ -169,7 +169,7 @@
|
||||
"token": "Token",
|
||||
"tuesday": "Tirsdag",
|
||||
"type": "Type",
|
||||
"undo": "Angre",
|
||||
"undo": "Undo",
|
||||
"update": "Oppdater",
|
||||
"updated": "Oppdatert",
|
||||
"upload": "Last opp",
|
||||
@@ -334,7 +334,7 @@
|
||||
"no-meal-plan-defined-yet": "Ingen måltidsplan er definert ennå",
|
||||
"no-meal-planned-for-today": "Ingen måltid planlagt i dag",
|
||||
"numberOfDaysPast-hint": "Number of days in the past on page load",
|
||||
"numberOfDaysPast-label": "Standard antall dager tilbake",
|
||||
"numberOfDaysPast-label": "Default Days in the Past",
|
||||
"numberOfDays-hint": "Antall dager på sideinnlasting",
|
||||
"numberOfDays-label": "Standard antall dager",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Kun oppskrifter med disse kategoriene vil bli brukt i måltidsplaner",
|
||||
@@ -392,7 +392,7 @@
|
||||
"nextcloud": {
|
||||
"description": "Overfør data fra en Nextcloud Cookbook-instans",
|
||||
"description-long": "Oppskrifter fra Nextcloud kan importeres fra en zip-fil som inneholder dataene lagret i Nextcloud. Se eksempelet på mappestrukture nedenfor for å sikre at oppskriftene kan importeres.",
|
||||
"title": "Nextcloud kokebok"
|
||||
"title": "Nextcloud Cookbook"
|
||||
},
|
||||
"copymethat": {
|
||||
"description-long": "Mealie kan importere oppskrifter fra Copy Me That. Eksporter oppskrifter i HTML-format, last deretter opp .zip-filen under.",
|
||||
@@ -917,7 +917,7 @@
|
||||
"quantity": "Antall: {0}",
|
||||
"shopping-list": "Handleliste",
|
||||
"shopping-lists": "Handlelister",
|
||||
"add-item": "Legg til produkt",
|
||||
"add-item": "Add item",
|
||||
"food": "Matvare",
|
||||
"note": "Notat",
|
||||
"label": "Etikett",
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Er du sikker på at du vil fjerne valg av alle elementer?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Er du sikker på at du vil slette alle valgte elementer?",
|
||||
"no-shopping-lists-found": "Ingen handlelister funnet",
|
||||
"item-checked-off": "Avkrysset av {item}"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Alle oppskrifter",
|
||||
@@ -1478,10 +1478,10 @@
|
||||
"max-length": "Må være minst minst {max} tegn må bestå av maks {max} tegn"
|
||||
},
|
||||
"announcements": {
|
||||
"announcements": "Kunngjøringer",
|
||||
"all-announcements": "Alle kunngjøringer",
|
||||
"mark-all-as-read": "Marker alle som lest",
|
||||
"show-announcements-from-mealie": "Vis kunngjøringer fra Mealie",
|
||||
"announcements": "Announcements",
|
||||
"all-announcements": "All announcements",
|
||||
"mark-all-as-read": "Mark All as Read",
|
||||
"show-announcements-from-mealie": "Show announcements from Mealie",
|
||||
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"category": "Kategoria"
|
||||
},
|
||||
"events": {
|
||||
"apprise-url": "URL Apprise",
|
||||
"apprise-url": "Apprise URL",
|
||||
"database": "Baza danych",
|
||||
"delete-event": "Usuń wydarzenie",
|
||||
"event-delete-confirmation": "Czy na pewno chcesz usunąć to zdarzenie?",
|
||||
@@ -98,7 +98,7 @@
|
||||
"dashboard": "Panel główny",
|
||||
"delete": "Usuń",
|
||||
"disabled": "Wyłączone",
|
||||
"done": "Gotowe",
|
||||
"done": "Done",
|
||||
"download": "Pobierz",
|
||||
"duplicate": "Duplikuj",
|
||||
"edit": "Edytuj",
|
||||
@@ -169,7 +169,7 @@
|
||||
"token": "Token",
|
||||
"tuesday": "Wtorek",
|
||||
"type": "Typ",
|
||||
"undo": "Cofnij",
|
||||
"undo": "Undo",
|
||||
"update": "Zaktualizuj",
|
||||
"updated": "Zaktualizowano",
|
||||
"upload": "Prześlij",
|
||||
@@ -917,7 +917,7 @@
|
||||
"quantity": "Ilość: {0}",
|
||||
"shopping-list": "Lista zakupów",
|
||||
"shopping-lists": "Listy zakupów",
|
||||
"add-item": "Dodaj element",
|
||||
"add-item": "Add item",
|
||||
"food": "Jedzenie",
|
||||
"note": "Notatka",
|
||||
"label": "Etykieta",
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Czy na pewno chcesz odznaczyć wszystkie elementy?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Czy jesteś pewien, że chcesz usunąć wszystkie zaznaczone elementy?",
|
||||
"no-shopping-lists-found": "Nie znaleziono list zakupów",
|
||||
"item-checked-off": "Zaznaczono {item}"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Wszystkie",
|
||||
@@ -1478,10 +1478,10 @@
|
||||
"max-length": "Może zawierać co najwyżej {max} znak|Może zawierać co najwyżej {max} znaki|Może zawierać co najwyżej {max} znaków"
|
||||
},
|
||||
"announcements": {
|
||||
"announcements": "Ogłoszenia",
|
||||
"all-announcements": "Wszystkie ogłoszenia",
|
||||
"mark-all-as-read": "Oznacz wszystkie jako przeczytane",
|
||||
"show-announcements-from-mealie": "Pokazuj ogłoszenia z Mealie",
|
||||
"show-announcements-setting-description": "Czy chcesz by użytkownicy widzieli ogłoszenia z Mealie? Użytkownicy będą w dalszym ciągu mogli wyłączyć ogłoszenia w swoich ustawieniach użytkownika"
|
||||
"announcements": "Announcements",
|
||||
"all-announcements": "All announcements",
|
||||
"mark-all-as-read": "Mark All as Read",
|
||||
"show-announcements-from-mealie": "Show announcements from Mealie",
|
||||
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Ali res ne želite izbrati vseh elementov?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Ali ste prepričani, da želite izbrisati vse izbrane elemente?",
|
||||
"no-shopping-lists-found": "Ni nakupovalnih seznamov",
|
||||
"item-checked-off": "Odkljukano {item}"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Vsi recepti",
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
"token": "Token",
|
||||
"tuesday": "Tisdag",
|
||||
"type": "Typ",
|
||||
"undo": "Ångra",
|
||||
"undo": "Undo",
|
||||
"update": "Uppdatera",
|
||||
"updated": "Uppdaterad",
|
||||
"upload": "Ladda upp",
|
||||
@@ -333,8 +333,8 @@
|
||||
"any-household": "Valfritt hushåll",
|
||||
"no-meal-plan-defined-yet": "Ingen måltidsplan definierad ännu",
|
||||
"no-meal-planned-for-today": "Ingen måltidsplan för idag",
|
||||
"numberOfDaysPast-hint": "Antal förflutna dagar vid sidhämtning",
|
||||
"numberOfDaysPast-label": "Förvalda förflutna dagar",
|
||||
"numberOfDaysPast-hint": "Number of days in the past on page load",
|
||||
"numberOfDaysPast-label": "Default Days in the Past",
|
||||
"numberOfDays-hint": "Antal dagar vid sidhämtning",
|
||||
"numberOfDays-label": "Förvalda dagar",
|
||||
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Endast recept med dessa kategorier kommer att användas i måltidsplaner",
|
||||
@@ -812,7 +812,7 @@
|
||||
"settings-updated": "Inställningar uppdaterade",
|
||||
"site-settings": "Systeminställningar",
|
||||
"theme": {
|
||||
"accent": "Accentfärg",
|
||||
"accent": "Accent",
|
||||
"dark": "Mörkt",
|
||||
"default-to-system": "Standard",
|
||||
"error": "Fel",
|
||||
@@ -893,17 +893,17 @@
|
||||
"server-side-base-url-error-text": "`BASE_URL` är fortfarande standardvärdet på API-servern. Detta kommer att orsaka problem med meddelanden som genereras på servern för e-postmeddelanden, etc.",
|
||||
"server-side-base-url-success-text": "Serversidans URL matchar inte standard",
|
||||
"ldap-ready": "LDAP Redo",
|
||||
"ldap-not-ready": "LDAP ej tillgängligt",
|
||||
"ldap-not-ready": "LDAP Not Ready",
|
||||
"ldap-ready-error-text": "Alla LDAP-värden är inte konfigurerade. Detta kan ignoreras om du inte använder LDAP-autentisering.",
|
||||
"ldap-ready-success-text": "Alla obligatoriska LDAP-variabler är satta.",
|
||||
"build": "Bygge",
|
||||
"recipe-scraper-version": "Version av Recept-scraper",
|
||||
"oidc-ready": "OIDC Klar",
|
||||
"oidc-not-ready": "OIDC ej tillgängligt",
|
||||
"oidc-not-ready": "OIDC Not Ready",
|
||||
"oidc-ready-error-text": "Alla OIDC-värden är inte konfigurerade. Detta kan ignoreras om du inte använder OIDC-autentisering.",
|
||||
"oidc-ready-success-text": "Alla obligatoriska OIDC-variabler är satta.",
|
||||
"openai-ready": "OpenAI redo",
|
||||
"openai-not-ready": "OpenAI ej tillgängligt",
|
||||
"openai-not-ready": "OpenAI Not Ready",
|
||||
"openai-ready-error-text": "Alla OpenAI-värden är inte konfigurerade. Detta kan ignoreras om du inte använder OpenAI-funktioner.",
|
||||
"openai-ready-success-text": "Alla obligatoriska OpenAI-variabler är satta."
|
||||
},
|
||||
@@ -917,7 +917,7 @@
|
||||
"quantity": "Antal {0}",
|
||||
"shopping-list": "Inköpslista",
|
||||
"shopping-lists": "Inköpslistor",
|
||||
"add-item": "Lägg till vara",
|
||||
"add-item": "Add item",
|
||||
"food": "Mat",
|
||||
"note": "Anteckning",
|
||||
"label": "Etikett",
|
||||
@@ -943,7 +943,7 @@
|
||||
"are-you-sure-you-want-to-uncheck-all-items": "Är du säker på att du vill avmarkera alla objekt?",
|
||||
"are-you-sure-you-want-to-delete-checked-items": "Är du säker på att du vill ta bort alla markerade objekt?",
|
||||
"no-shopping-lists-found": "Inga inköpslistor hittades",
|
||||
"item-checked-off": "Kryssat av {item}"
|
||||
"item-checked-off": "Checked off {item}"
|
||||
},
|
||||
"sidebar": {
|
||||
"all-recipes": "Recept",
|
||||
@@ -1478,10 +1478,10 @@
|
||||
"max-length": "Måste Vara Som Mest {max} Tecken|Måste Vara Som Mest {max} Tecken"
|
||||
},
|
||||
"announcements": {
|
||||
"announcements": "Meddelanden",
|
||||
"all-announcements": "Alla meddelanden",
|
||||
"mark-all-as-read": "Markera alla som lästa",
|
||||
"show-announcements-from-mealie": "Visa meddelanden från Mealie",
|
||||
"show-announcements-setting-description": "Om du vill tillåta användare att se meddelanden från Mealie eller inte. När funktionen är aktiverad kan användarna fortfarande välja att inte se dem i sina användarinställningar"
|
||||
"announcements": "Announcements",
|
||||
"all-announcements": "All announcements",
|
||||
"mark-all-as-read": "Mark All as Read",
|
||||
"show-announcements-from-mealie": "Show announcements from Mealie",
|
||||
"show-announcements-setting-description": "Whether or not you want to allow users to see announcements from Mealie. When enabled users can still opt-out from seeing them in their user settings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,7 +911,7 @@
|
||||
"all-lists": "Всі списки",
|
||||
"create-shopping-list": "Сторити список покупок",
|
||||
"from-recipe": "З рецепту",
|
||||
"ingredient-of-recipe": "Інгредієнт з {recipe}",
|
||||
"ingredient-of-recipe": "Ingredient of {recipe}",
|
||||
"list-name": "Назва списку",
|
||||
"new-list": "Новий список",
|
||||
"quantity": "Кількість: {0}",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mealie",
|
||||
"version": "3.18.0",
|
||||
"version": "3.17.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxt dev",
|
||||
|
||||
@@ -35,7 +35,7 @@ class OpenIDProvider(AuthProvider[UserInfo]):
|
||||
self._logger.debug("[OIDC] %s: %s", key, value)
|
||||
|
||||
if not self.required_claims.issubset(claims.keys()):
|
||||
self._logger.debug(
|
||||
self._logger.error(
|
||||
"[OIDC] Required claims not present. Expected: %s Actual: %s",
|
||||
self.required_claims,
|
||||
claims.keys(),
|
||||
@@ -45,7 +45,7 @@ class OpenIDProvider(AuthProvider[UserInfo]):
|
||||
# Check for empty required claims
|
||||
for claim in self.required_claims:
|
||||
if not claims.get(claim):
|
||||
self._logger.debug("[OIDC] Required claim '%s' is empty", claim)
|
||||
self._logger.error("[OIDC] Required claim '%s' is empty", claim)
|
||||
raise MissingClaimException()
|
||||
|
||||
repos = get_repositories(self.session, group_id=None, household_id=None)
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
"recipe": {
|
||||
"unique-name-error": "Recipe names must be unique",
|
||||
"recipe-created": "Recipe Created",
|
||||
"made-this-as-side": "{name} made this as a side",
|
||||
"made-this-for-breakfast": "{name} made this for breakfast",
|
||||
"made-this-for-lunch": "{name} made this for lunch",
|
||||
"made-this-for-dinner": "{name} made this for dinner",
|
||||
"made-this-for-snack": "{name} made this for a snack",
|
||||
"made-this-for-drink": "{name} made this for a drink",
|
||||
"made-this-for-dessert": "{name} made this for dessert",
|
||||
"recipe-image-deleted": "Recipe image deleted",
|
||||
"recipe-defaults": {
|
||||
"ingredient-note": "1 Cup Flour",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"create-progress": {
|
||||
"creating-recipe-with-ai": "Recept maken met AI...",
|
||||
"creating-recipe-from-transcript-with-ai": "Maak recept van een transcript met AI...",
|
||||
"creating-recipe-from-webpage-data": "Creëren recept van webpaginagegevens...",
|
||||
"creating-recipe-from-webpage-data": "Creëren recept van webpagina-gegevens...",
|
||||
"downloading-image": "Afbeeldingen downloaden...",
|
||||
"downloading-video": "Video downloaden...",
|
||||
"extracting-recipe-data": "Receptgegevens ophalen...",
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "acelga",
|
||||
"plural_name": "acelga"
|
||||
"plural_name": "chard"
|
||||
},
|
||||
"pimiento": {
|
||||
"aliases": [],
|
||||
@@ -979,8 +979,8 @@
|
||||
"chestnut purée": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "puré de castañas",
|
||||
"plural_name": "puré de castañas"
|
||||
"name": "chestnut purée",
|
||||
"plural_name": "chestnut purée"
|
||||
},
|
||||
"prickly pear": {
|
||||
"aliases": [],
|
||||
@@ -1295,7 +1295,7 @@
|
||||
"black fungu": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "hongos negros",
|
||||
"name": "black fungus",
|
||||
"plural_name": "hongos negros"
|
||||
},
|
||||
"black truffle": {
|
||||
@@ -1361,7 +1361,7 @@
|
||||
"white fungu": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "hongos blancos",
|
||||
"name": "white fungus",
|
||||
"plural_name": "hongos blancos"
|
||||
},
|
||||
"pioppini": {
|
||||
@@ -1373,7 +1373,7 @@
|
||||
"snow fungu": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "hongos de nieve",
|
||||
"name": "snow fungus",
|
||||
"plural_name": "hongos de nieve"
|
||||
},
|
||||
"white beech mushroom": {
|
||||
|
||||
@@ -571,8 +571,8 @@
|
||||
"delicata squash": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "delikat squash",
|
||||
"plural_name": "delikate squasher"
|
||||
"name": "delicata squash",
|
||||
"plural_name": "delicata squashes"
|
||||
},
|
||||
"Frisée": {
|
||||
"aliases": [
|
||||
@@ -980,7 +980,7 @@
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "kastanjepuré",
|
||||
"plural_name": "kastanjepuré"
|
||||
"plural_name": "chestnut purée"
|
||||
},
|
||||
"prickly pear": {
|
||||
"aliases": [],
|
||||
@@ -1045,7 +1045,7 @@
|
||||
"sweet lime": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "søt lime",
|
||||
"name": "sweet lime",
|
||||
"plural_name": "sweet limes"
|
||||
},
|
||||
"custard-apple": {
|
||||
@@ -1873,8 +1873,8 @@
|
||||
"melon seed": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "melonfrø",
|
||||
"plural_name": "melonfrø"
|
||||
"name": "melon seed",
|
||||
"plural_name": "melon seeds"
|
||||
},
|
||||
"lotus seed": {
|
||||
"aliases": [],
|
||||
@@ -2003,48 +2003,48 @@
|
||||
"parmesan cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "parmesanost",
|
||||
"plural_name": "parmesanost"
|
||||
"name": "parmesan cheese",
|
||||
"plural_name": "parmesan cheese"
|
||||
},
|
||||
"cheddar cheese": {
|
||||
"aliases": [
|
||||
"cheddarost"
|
||||
"cheddar cheese"
|
||||
],
|
||||
"description": "",
|
||||
"name": "cheddarost",
|
||||
"plural_name": "cheddarost"
|
||||
"name": "cheddar cheese",
|
||||
"plural_name": "cheddar cheese"
|
||||
},
|
||||
"cream cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "kremost",
|
||||
"plural_name": "kremost"
|
||||
"name": "cream cheese",
|
||||
"plural_name": "cream cheese"
|
||||
},
|
||||
"sharp cheddar cheese": {
|
||||
"aliases": [
|
||||
"skarp cheddarost"
|
||||
"sharp cheddar"
|
||||
],
|
||||
"description": "",
|
||||
"name": "skarp cheddarost",
|
||||
"plural_name": "skarp cheddarost"
|
||||
"name": "sharp cheddar cheese",
|
||||
"plural_name": "sharp cheddar cheese"
|
||||
},
|
||||
"cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "ost",
|
||||
"plural_name": "ost"
|
||||
"name": "cheese",
|
||||
"plural_name": "cheese"
|
||||
},
|
||||
"mozzarella cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "mozzarellaost",
|
||||
"plural_name": "mozzarellaost"
|
||||
"name": "mozzarella cheese",
|
||||
"plural_name": "mozzarella cheese"
|
||||
},
|
||||
"feta cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "fetaost",
|
||||
"plural_name": "fetaost"
|
||||
"name": "feta cheese",
|
||||
"plural_name": "feta cheese"
|
||||
},
|
||||
"ricotta cheese": {
|
||||
"aliases": [],
|
||||
@@ -2073,14 +2073,14 @@
|
||||
"goat cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "geitost",
|
||||
"plural_name": "geitost"
|
||||
"name": "goat cheese",
|
||||
"plural_name": "goat cheese"
|
||||
},
|
||||
"fresh mozzarella cheese": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "fersk mozzarellaost",
|
||||
"plural_name": "fersk mozzarellaost"
|
||||
"name": "fresh mozzarella cheese",
|
||||
"plural_name": "fresh mozzarella cheese"
|
||||
},
|
||||
"swis cheese": {
|
||||
"aliases": [],
|
||||
|
||||
@@ -919,7 +919,7 @@
|
||||
"jackfruit": {
|
||||
"aliases": [],
|
||||
"description": "",
|
||||
"name": "owoc chlebowca",
|
||||
"name": "jackfruit",
|
||||
"plural_name": "jackfruity"
|
||||
},
|
||||
"dragon fruit": {
|
||||
|
||||
@@ -134,7 +134,6 @@ async def oauth_callback(request: Request, session: Session = Depends(generate_s
|
||||
auth_provider = OpenIDProvider(session, userinfo, use_default_groups=True)
|
||||
auth = auth_provider.authenticate()
|
||||
except MissingClaimException:
|
||||
logger.error("[OIDC] Required claims not present in ID token or userinfo endpoint")
|
||||
auth = None
|
||||
|
||||
if not auth:
|
||||
|
||||
@@ -54,11 +54,7 @@ class MultiPurposeLabelsController(BaseCrudController):
|
||||
|
||||
@router.post("", response_model=MultiPurposeLabelOut)
|
||||
def create_one(self, data: MultiPurposeLabelCreate):
|
||||
try:
|
||||
new_label = self.service.create_one(data)
|
||||
except Exception as ex:
|
||||
self.mixins.handle_exception(ex)
|
||||
raise # handle_exception always raises; this satisfies static analysis
|
||||
new_label = self.service.create_one(data)
|
||||
self.publish_event(
|
||||
event_type=EventTypes.label_created,
|
||||
document_data=EventLabelData(operation=EventOperation.create, label_id=new_label.id),
|
||||
|
||||
@@ -58,6 +58,6 @@ async def get_recipe_asset(recipe_id: UUID4, file_name: str):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if file.exists():
|
||||
return FileResponse(file, filename=file.name, content_disposition_type="attachment")
|
||||
return FileResponse(file)
|
||||
else:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@@ -52,7 +52,7 @@ class TagController(BaseCrudController):
|
||||
def create_one(self, tag: TagIn):
|
||||
"""Creates a Tag in the database"""
|
||||
save_data = mapper.cast(tag, TagSave, group_id=self.group_id)
|
||||
new_tag = self.mixins.create_one(save_data)
|
||||
new_tag = self.repo.create(save_data)
|
||||
|
||||
if new_tag:
|
||||
self.publish_event(
|
||||
|
||||
@@ -80,8 +80,6 @@ from mealie.services.scraper.scraper_strategies import (
|
||||
|
||||
from ._base import BaseRecipeController, JSONBytes
|
||||
|
||||
ASSET_ALLOWED_EXTENSIONS = {"pdf", "jpg", "jpeg", "png", "gif", "webp", "bmp", "avif", "txt", "md", "csv", "json"}
|
||||
|
||||
router = UserAPIRouter(prefix="/recipes", route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@@ -662,10 +660,6 @@ class RecipeController(BaseRecipeController):
|
||||
if "." in extension:
|
||||
extension = extension.split(".")[-1]
|
||||
|
||||
extension = extension.lower()
|
||||
if extension not in ASSET_ALLOWED_EXTENSIONS:
|
||||
raise HTTPException(status_code=400, detail="Unsupported file extension")
|
||||
|
||||
file_slug = slugify(name)
|
||||
if not extension or not file_slug:
|
||||
raise HTTPException(status_code=400, detail="Missing required fields")
|
||||
|
||||
@@ -4,6 +4,7 @@ from functools import cached_property
|
||||
from fastapi import Depends, File, Form, HTTPException
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.lang.providers import get_locale_provider
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.routes._base import BaseCrudController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
@@ -44,6 +45,21 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
self.registered_exceptions,
|
||||
)
|
||||
|
||||
def _translate_event_subject(self, event: RecipeTimelineEventOut) -> None:
|
||||
"""Translate auto-generated event subjects stored as i18n key references.
|
||||
|
||||
Subjects are stored as ``<i18n-key>|<name>`` (e.g. ``recipe.made-this-for-dinner|Alice``).
|
||||
Falls back to en-US when the requested locale has not yet been translated.
|
||||
"""
|
||||
if event.event_type == TimelineEventType.info.value and "|" in event.subject:
|
||||
key, _, name = event.subject.partition("|")
|
||||
if key.startswith("recipe."):
|
||||
translated = self.t(key, name=name)
|
||||
if translated == key:
|
||||
translated = get_locale_provider("en-US").t(key, name=name)
|
||||
if translated != key:
|
||||
event.subject = translated
|
||||
|
||||
@router.get("", response_model=RecipeTimelineEventPagination)
|
||||
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
||||
response = self.repo.page_all(
|
||||
@@ -52,8 +68,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
)
|
||||
|
||||
for event in response.items:
|
||||
if event.event_type == TimelineEventType.system.value:
|
||||
event.subject = self.t(event.subject)
|
||||
self._translate_event_subject(event)
|
||||
|
||||
response.set_pagination_guides(router.url_path_for("get_all"), q.model_dump())
|
||||
return response
|
||||
@@ -89,8 +104,7 @@ class RecipeTimelineEventsController(BaseCrudController):
|
||||
@router.get("/{item_id}", response_model=RecipeTimelineEventOut)
|
||||
def get_one(self, item_id: UUID4):
|
||||
event = self.mixins.get_one(item_id)
|
||||
if event.event_type == TimelineEventType.system.value:
|
||||
event.subject = self.t(event.subject)
|
||||
self._translate_event_subject(event)
|
||||
return event
|
||||
|
||||
@router.put("/{item_id}", response_model=RecipeTimelineEventOut)
|
||||
|
||||
@@ -38,8 +38,6 @@ from mealie.services.scraper import cleaner
|
||||
|
||||
from .template_service import TemplateService
|
||||
|
||||
RECIPE_CREATED_EVENT_SUBJECT = "recipe.recipe-created"
|
||||
|
||||
|
||||
class RecipeServiceBase(BaseService):
|
||||
def __init__(self, repos: AllRepositories, user: PrivateUser, household: HouseholdInDB, translator: Translator):
|
||||
@@ -71,19 +69,8 @@ class RecipeService(RecipeServiceBase):
|
||||
def can_delete(self, recipe_slugs: list[str]) -> bool:
|
||||
if self.user.admin:
|
||||
return True
|
||||
|
||||
# Deletion requires ownership; collaborative editing rules (can_update) do not apply
|
||||
model = self.group_recipes.model
|
||||
owned_count = self.group_recipes.session.scalar(
|
||||
sa.select(sa.func.count())
|
||||
.select_from(model)
|
||||
.where(
|
||||
model.slug.in_(recipe_slugs),
|
||||
model.group_id == self.user.group_id,
|
||||
model.user_id == self.user.id,
|
||||
)
|
||||
)
|
||||
return owned_count == len(recipe_slugs)
|
||||
else:
|
||||
return self.can_update(recipe_slugs)
|
||||
|
||||
def can_update(self, recipe_slugs: list[str]) -> bool:
|
||||
sql = dedent(
|
||||
@@ -237,7 +224,7 @@ class RecipeService(RecipeServiceBase):
|
||||
timeline_event_data = RecipeTimelineEventCreate(
|
||||
user_id=new_recipe.user_id,
|
||||
recipe_id=new_recipe.id,
|
||||
subject=RECIPE_CREATED_EVENT_SUBJECT,
|
||||
subject=self.t("recipe.recipe-created"),
|
||||
event_type=TimelineEventType.system,
|
||||
timestamp=new_recipe.created_at or datetime.now(UTC),
|
||||
)
|
||||
|
||||
@@ -43,12 +43,10 @@ def _create_mealplan_timeline_events_for_household(
|
||||
if not user:
|
||||
continue
|
||||
|
||||
# TODO: make this translatable
|
||||
if mealplan.entry_type == PlanEntryType.side:
|
||||
event_subject = f"{user.full_name} made this as a side"
|
||||
|
||||
event_subject = f"recipe.made-this-as-side|{user.full_name}"
|
||||
else:
|
||||
event_subject = f"{user.full_name} made this for {mealplan.entry_type.value}"
|
||||
event_subject = f"recipe.made-this-for-{mealplan.entry_type.value}|{user.full_name}"
|
||||
|
||||
query_start_time = datetime.combine(datetime.now(UTC).date(), time.min)
|
||||
query_end_time = query_start_time + timedelta(days=1)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mealie"
|
||||
version = "3.18.0"
|
||||
version = "3.17.0"
|
||||
description = "A Recipe Manager"
|
||||
authors = [{ name = "Hayden", email = "hay-kot@pm.me" }]
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
@@ -191,24 +191,6 @@ def test_organizer_association(
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", organizer_routes, ids=test_ids)
|
||||
def test_organizer_create_duplicate_name_returns_400(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
route: RoutesBase,
|
||||
):
|
||||
# Regression test for #7582: POSTing a duplicate name to organizer endpoints
|
||||
# leaked the sqlalchemy IntegrityError as an HTTP 500. The expected behavior,
|
||||
# matching other organizer endpoints (foods, units, tools), is HTTP 400.
|
||||
data = {"name": random_string(10)}
|
||||
|
||||
response = api_client.post(route.base, json=data, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.post(route.base, json=data, headers=unique_user.token)
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route, recipe_key", association_data, ids=test_ids)
|
||||
def test_organizer_get_by_slug(
|
||||
api_client: TestClient,
|
||||
|
||||
@@ -20,21 +20,6 @@ def create_labels(api_client: TestClient, unique_user: TestUser, count: int = 10
|
||||
return labels
|
||||
|
||||
|
||||
def test_label_create_duplicate_name_returns_400(api_client: TestClient, unique_user_fn_scoped: TestUser):
|
||||
# Regression test for #7582: POSTing a duplicate label name leaked the
|
||||
# sqlalchemy IntegrityError as an HTTP 500. The expected behavior, matching
|
||||
# the other organizer endpoints (foods, units, tools, tags, categories),
|
||||
# is HTTP 400. The function-scoped fixture avoids leaking the created label
|
||||
# into the module-scoped `unique_user` group state used by sibling tests.
|
||||
payload = {"name": random_string(), "color": "#ff0000"}
|
||||
|
||||
response = api_client.post(api_routes.groups_labels, json=payload, headers=unique_user_fn_scoped.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.post(api_routes.groups_labels, json=payload, headers=unique_user_fn_scoped.token)
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_new_list_creates_list_labels(api_client: TestClient, unique_user: TestUser):
|
||||
labels = create_labels(api_client, unique_user)
|
||||
response = api_client.post(
|
||||
|
||||
@@ -201,12 +201,19 @@ def test_delete_recipes_from_other_households(
|
||||
assert recipe_json["id"] == h2_recipe_id
|
||||
|
||||
response = api_client.delete(api_routes.recipes_slug(recipe_json["slug"]), headers=unique_user.token)
|
||||
assert response.status_code == 403
|
||||
if household_lock_recipe_edits:
|
||||
assert response.status_code == 403
|
||||
|
||||
# confirm the recipe still exists
|
||||
response = api_client.get(api_routes.recipes_slug(h2_recipe_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == h2_recipe_id
|
||||
# confirm the recipe still exists
|
||||
response = api_client.get(api_routes.recipes_slug(h2_recipe_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == h2_recipe_id
|
||||
else:
|
||||
assert response.status_code == 200
|
||||
|
||||
# confirm the recipe was deleted
|
||||
response = api_client.get(api_routes.recipes_slug(h2_recipe_id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_private_household", [True, False])
|
||||
|
||||
@@ -87,23 +87,6 @@ def test_recipe_asset_exploit(api_client: TestClient, unique_user: TestUser, rec
|
||||
assert not (recipe.asset_dir / "test.txt").exists()
|
||||
|
||||
|
||||
def test_recipe_asset_dangerous_extension_blocked(
|
||||
api_client: TestClient, unique_user: TestUser, recipe_ingredient_only: Recipe
|
||||
):
|
||||
"""Ensure scriptable extensions are rejected to prevent stored XSS (GHSA-gfwc-pjx4-mg9p)."""
|
||||
recipe = recipe_ingredient_only
|
||||
for ext in ("html", "svg", "js", "htm", "xhtml"):
|
||||
payload = {"name": random_string(10), "icon": "mdi-file", "extension": ext}
|
||||
file_payload = {"file": b"<script>alert(1)</script>"}
|
||||
response = api_client.post(
|
||||
f"/api/recipes/{recipe.slug}/assets",
|
||||
data=payload,
|
||||
files=file_payload,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert response.status_code == 400, f"expected 400 for extension={ext}, got {response.status_code}"
|
||||
|
||||
|
||||
def test_recipe_image_upload(api_client: TestClient, unique_user: TestUser, recipe_ingredient_only: Recipe):
|
||||
data_payload = {"extension": "jpg"}
|
||||
file_payload = {"image": data.images_test_image_1.read_bytes()}
|
||||
|
||||
@@ -160,24 +160,6 @@ def test_other_user_cant_delete_recipe(api_client: TestClient, user_tuple: list[
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_other_user_cant_delete_unlocked_recipe(api_client: TestClient, user_tuple: list[TestUser]):
|
||||
"""Non-owner must not delete an unlocked recipe — BOLA regression (GHSA-x5v9-9jvh-7c7q)."""
|
||||
slug = random_string(10)
|
||||
unique_user, other_user = user_tuple
|
||||
|
||||
unique_user.repos.recipes.create(
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=slug,
|
||||
settings=RecipeSettings(locked=False),
|
||||
)
|
||||
)
|
||||
|
||||
response = api_client.delete(api_routes.recipes_slug(slug), headers=other_user.token)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_other_user_bulk_delete(api_client: TestClient, user_tuple: list[TestUser]):
|
||||
slug_locked = random_string(10)
|
||||
slug_unlocked = random_string(10)
|
||||
@@ -208,30 +190,6 @@ def test_other_user_bulk_delete(api_client: TestClient, user_tuple: list[TestUse
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_other_user_cant_bulk_delete_unlocked_recipes(api_client: TestClient, user_tuple: list[TestUser]):
|
||||
"""Non-owner must not bulk-delete unlocked recipes — BOLA regression (GHSA-x5v9-9jvh-7c7q)."""
|
||||
slug_1 = random_string(10)
|
||||
slug_2 = random_string(10)
|
||||
unique_user, other_user = user_tuple
|
||||
|
||||
for slug in (slug_1, slug_2):
|
||||
unique_user.repos.recipes.create(
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=slug,
|
||||
settings=RecipeSettings(locked=False),
|
||||
)
|
||||
)
|
||||
|
||||
response = api_client.post(
|
||||
api_routes.recipes_bulk_actions_delete,
|
||||
json={"recipes": [slug_1, slug_2]},
|
||||
headers=other_user.token,
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_admin_can_delete_locked_recipe_owned_by_another_user(
|
||||
api_client: TestClient, unfiltered_database: AllRepositories, unique_user: TestUser, admin_user: TestUser
|
||||
):
|
||||
|
||||
@@ -3,24 +3,18 @@ from uuid import uuid4
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.lang.providers import get_all_translations
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe_timeline_events import (
|
||||
RecipeTimelineEventOut,
|
||||
RecipeTimelineEventPagination,
|
||||
TimelineEventImage,
|
||||
TimelineEventType,
|
||||
)
|
||||
from mealie.schema.recipe.request_helpers import UpdateImageResponse
|
||||
from mealie.services.recipe.recipe_service import RECIPE_CREATED_EVENT_SUBJECT
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
PERSISTED_TRANSLATION_KEYS = [RECIPE_CREATED_EVENT_SUBJECT]
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def recipes(api_client: TestClient, unique_user: TestUser):
|
||||
recipes = []
|
||||
@@ -347,50 +341,6 @@ def test_create_recipe_with_timeline_event(
|
||||
assert events_pagination.items
|
||||
|
||||
|
||||
@pytest.mark.parametrize("translation_key", PERSISTED_TRANSLATION_KEYS)
|
||||
def test_persisted_translation_keys_have_translations(translation_key: str):
|
||||
translations = get_all_translations(translation_key)
|
||||
missing_translations = [locale for locale, translation in translations.items() if translation == translation_key]
|
||||
|
||||
assert missing_translations == []
|
||||
|
||||
|
||||
def test_recipe_created_system_event_is_translated(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
recipes: list[Recipe],
|
||||
):
|
||||
recipe = recipes[0]
|
||||
params = {"queryFilter": f"recipe_id={recipe.id}"}
|
||||
|
||||
# fetch events in French — the system "recipe created" event should be translated
|
||||
fr_headers = {**unique_user.token, "Accept-Language": "fr-FR"}
|
||||
events_response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=fr_headers)
|
||||
assert events_response.status_code == 200
|
||||
events_pagination = RecipeTimelineEventPagination.model_validate(events_response.json())
|
||||
|
||||
system_events = [e for e in events_pagination.items if e.event_type == TimelineEventType.system.value]
|
||||
assert system_events, "expected at least one system event for a newly created recipe"
|
||||
|
||||
for event in system_events:
|
||||
assert event.subject == "Recette créée", f"expected French translation, got: {event.subject!r}"
|
||||
|
||||
# also verify the individual GET endpoint translates correctly
|
||||
single_response = api_client.get(api_routes.recipes_timeline_events_item_id(event.id), headers=fr_headers)
|
||||
assert single_response.status_code == 200
|
||||
single_event = RecipeTimelineEventOut.model_validate(single_response.json())
|
||||
assert single_event.subject == "Recette créée"
|
||||
|
||||
# fetch the same events in English — subject should be the English string
|
||||
en_headers = {**unique_user.token, "Accept-Language": "en-US"}
|
||||
events_response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=en_headers)
|
||||
events_pagination = RecipeTimelineEventPagination.model_validate(events_response.json())
|
||||
|
||||
system_events = [e for e in events_pagination.items if e.event_type == TimelineEventType.system.value]
|
||||
for event in system_events:
|
||||
assert event.subject == "Recipe Created", f"expected English string, got: {event.subject!r}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_other_household_user", [True, False])
|
||||
def test_invalid_recipe_id(
|
||||
api_client: TestClient, unique_user: TestUser, h2_user: TestUser, use_other_household_user: bool
|
||||
|
||||
@@ -13,6 +13,49 @@ from tests.utils.factories import random_int, random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def _create_recipe_and_mealplan(api_client: TestClient, user: TestUser, entry_type: str) -> tuple[RecipeSummary, int]:
|
||||
recipe_name = random_string(length=25)
|
||||
response = api_client.post(api_routes.recipes, json={"name": recipe_name}, headers=user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.get(api_routes.recipes_slug(recipe_name), headers=user.token)
|
||||
recipe = RecipeSummary.model_validate(response.json())
|
||||
|
||||
params = {"queryFilter": f"recipe_id={recipe.id}"}
|
||||
response = api_client.get(api_routes.recipes_timeline_events, params=params, headers=user.token)
|
||||
initial_event_count = len(response.json()["items"])
|
||||
|
||||
new_plan = CreatePlanEntry(date=datetime.now(UTC).date(), entry_type=entry_type, recipe_id=recipe.id).model_dump(
|
||||
by_alias=True
|
||||
)
|
||||
new_plan["date"] = datetime.now(UTC).date().isoformat()
|
||||
new_plan["recipeId"] = str(recipe.id)
|
||||
response = api_client.post(api_routes.households_mealplans, json=new_plan, headers=user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
return recipe, initial_event_count
|
||||
|
||||
|
||||
def _get_mealplan_event(
|
||||
api_client: TestClient, user: TestUser, recipe: RecipeSummary, initial_count: int, extra_headers: dict
|
||||
) -> dict:
|
||||
create_mealplan_timeline_events()
|
||||
|
||||
params = {
|
||||
"page": "1",
|
||||
"perPage": "-1",
|
||||
"orderBy": "created_at",
|
||||
"orderDirection": "desc",
|
||||
"queryFilter": f"recipe_id={recipe.id}",
|
||||
}
|
||||
response = api_client.get(
|
||||
api_routes.recipes_timeline_events, headers={**user.token, **extra_headers}, params=params
|
||||
)
|
||||
items = response.json()["items"]
|
||||
assert len(items) == initial_count + 1
|
||||
return items[0]
|
||||
|
||||
|
||||
def test_no_mealplans():
|
||||
# make sure this task runs successfully even if it doesn't do anything
|
||||
create_mealplan_timeline_events()
|
||||
@@ -251,3 +294,27 @@ def test_preserve_future_made_date(api_client: TestClient, unique_user: TestUser
|
||||
response = api_client.get(api_routes.households_self_recipes_recipe_slug(recipe.slug), headers=h2_user.token)
|
||||
household_recipe = HouseholdRecipeSummary.model_validate(response.json())
|
||||
assert household_recipe.last_made is None
|
||||
|
||||
|
||||
def test_mealplan_event_subject_is_translated(api_client: TestClient, unique_user: TestUser):
|
||||
"""Mealplan timeline event subjects are stored as i18n keys and translated at serve time."""
|
||||
# --- dinner entry type ---
|
||||
recipe, initial_count = _create_recipe_and_mealplan(api_client, unique_user, "dinner")
|
||||
event = _get_mealplan_event(api_client, unique_user, recipe, initial_count, {"Accept-Language": "en-US"})
|
||||
|
||||
expected = f"{unique_user.full_name} made this for dinner"
|
||||
assert event["subject"] == expected, f"expected {expected!r}, got {event['subject']!r}"
|
||||
|
||||
# --- side entry type uses a distinct phrase ---
|
||||
recipe2, initial_count2 = _create_recipe_and_mealplan(api_client, unique_user, "side")
|
||||
event2 = _get_mealplan_event(api_client, unique_user, recipe2, initial_count2, {"Accept-Language": "en-US"})
|
||||
|
||||
expected2 = f"{unique_user.full_name} made this as a side"
|
||||
assert event2["subject"] == expected2, f"expected {expected2!r}, got {event2['subject']!r}"
|
||||
|
||||
# --- locale fallback: fr-FR doesn't have these keys yet, should fall back to en-US ---
|
||||
recipe3, initial_count3 = _create_recipe_and_mealplan(api_client, unique_user, "lunch")
|
||||
event3 = _get_mealplan_event(api_client, unique_user, recipe3, initial_count3, {"Accept-Language": "fr-FR"})
|
||||
|
||||
expected3 = f"{unique_user.full_name} made this for lunch"
|
||||
assert event3["subject"] == expected3, f"expected en-US fallback {expected3!r}, got {event3['subject']!r}"
|
||||
|
||||
Reference in New Issue
Block a user