Compare commits

...

11 Commits

Author SHA1 Message Date
mealie-commit-bot[bot]
70b5865dce chore: bump version to v3.9.2 2026-01-02 19:40:19 +00:00
Michael Genson
3be7056f2c fix: Exception handling for recipe image reprocessing (#6822) 2026-01-02 13:17:07 -06:00
Arsène Reymond
1b57310535 fix: allow start attribute on ordered lists (SafeMarkdown) (#6820)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2026-01-02 19:02:11 +00:00
Patrick Lehner (he/him)
2b15d9a515 fix: Make quantity input in shopping list item editor visually consistent with other inputs (#6810) 2026-01-02 12:51:53 -06:00
Hayden
adc9c0b970 chore(l10n): New Crowdin updates (#6804) 2025-12-31 00:36:01 +00:00
renovate[bot]
bec1708891 chore(deps): update node.js to b52a8d1 (#6800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-30 10:33:30 -06:00
Patrick Lehner (he/him)
66bb545454 dev: Small .gitignore fixes (#6796) 2025-12-30 15:42:38 +00:00
renovate[bot]
c1ebf04291 chore(deps): update node.js to 33587cf (#6795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-30 05:20:34 +00:00
mealie-commit-bot[bot]
3166060644 chore: bump version to v3.9.1 2025-12-30 01:56:10 +00:00
Michael Genson
bde7cf6f9d fix: Revert extended touch on shopping list (#6794) 2025-12-29 19:24:03 -06:00
Hayden
8ea9bb19f6 chore(l10n): New Crowdin updates (#6792) 2025-12-29 17:11:05 -06:00
17 changed files with 223 additions and 228 deletions

5
.gitignore vendored
View File

@@ -70,8 +70,11 @@ wheels/
.installed.cfg
*.egg
# packaged output - temporarily written here by `uv build`
/mealie-*
# frontend copied into Python module for packaging purposes
/mealie/frontend/
/mealie/frontend
# PyInstaller
# Usually these files are written by a python script from a template

View File

@@ -1,7 +1,7 @@
###############################################
# Frontend Build
###############################################
FROM node:24@sha256:20988bcdc6dc76690023eb2505dd273bdeefddcd0bde4bfd1efe4ebf8707f747 \
FROM node:24@sha256:b52a8d1206132b36d60e51e413d9a81336e8a0206d3b648cabd6d5a49c4c0f54 \
AS frontend-builder
WORKDIR /frontend

View File

@@ -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.9.0`
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v3.9.2`
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

View File

@@ -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.9.0 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.9.2 # (3)
container_name: mealie
restart: always
ports:

View File

@@ -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.9.0 # (3)
image: ghcr.io/mealie-recipes/mealie:v3.9.2 # (3)
container_name: mealie
restart: always
ports:

View File

@@ -7,23 +7,18 @@
no-gutters
class="flex-nowrap align-center"
>
<v-card
flat
link
class="grow"
@click="() => {
listItem.checked = !listItem.checked
$emit('checked', listItem)
}"
>
<div class="d-flex align-center flex-nowrap grow">
<v-col :cols="itemLabelCols">
<div class="d-flex align-center flex-nowrap">
<v-checkbox
v-model="listItem.checked"
hide-details
density="compact"
class="mt-0 flex-shrink-0"
color="null"
@change="$emit('checked', listItem)"
@click="() => {
listItem.checked = !listItem.checked
$emit('checked', listItem)
}"
/>
<div
class="ml-2 text-truncate"
@@ -33,78 +28,84 @@
<RecipeIngredientListItem :ingredient="listItem" />
</div>
</div>
</v-card>
<div
v-if="!listItem.checked"
style="min-width: 72px"
</v-col>
<v-spacer />
<v-col
cols="auto"
class="text-right"
>
<v-menu
offset-x
start
min-width="125px"
<div
v-if="!listItem.checked"
style="min-width: 72px"
>
<template #activator="{ props }">
<v-tooltip
v-if="recipeList && recipeList.length"
open-delay="200"
transition="slide-x-reverse-transition"
density="compact"
location="end"
content-class="text-caption"
>
<template #activator="{ props: tooltipProps }">
<v-btn
size="small"
variant="text"
class="ml-2"
icon
v-bind="tooltipProps"
@click="displayRecipeRefs = !displayRecipeRefs"
>
<v-icon>
{{ $globals.icons.potSteam }}
</v-icon>
</v-btn>
</template>
<span>Toggle Recipes</span>
</v-tooltip>
<v-btn
size="small"
variant="text"
class="ml-2"
icon
@click="toggleEdit(true)"
>
<v-icon>
{{ $globals.icons.edit }}
</v-icon>
</v-btn>
<v-btn
size="small"
variant="text"
class="handle"
icon
v-bind="props"
>
<v-icon>
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item
v-for="action in contextMenu"
:key="action.event"
density="compact"
@click="contextHandler(action.event)"
>
<v-list-item-title>
{{ action.text }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
<v-menu
offset-x
start
min-width="125px"
>
<template #activator="{ props }">
<v-tooltip
v-if="recipeList && recipeList.length"
open-delay="200"
transition="slide-x-reverse-transition"
density="compact"
location="end"
content-class="text-caption"
>
<template #activator="{ props: tooltipProps }">
<v-btn
size="small"
variant="text"
class="ml-2"
icon
v-bind="tooltipProps"
@click="displayRecipeRefs = !displayRecipeRefs"
>
<v-icon>
{{ $globals.icons.potSteam }}
</v-icon>
</v-btn>
</template>
<span>Toggle Recipes</span>
</v-tooltip>
<v-btn
size="small"
variant="text"
class="ml-2"
icon
@click="toggleEdit(true)"
>
<v-icon>
{{ $globals.icons.edit }}
</v-icon>
</v-btn>
<v-btn
size="small"
variant="text"
class="handle"
icon
v-bind="props"
>
<v-icon>
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item
v-for="action in contextMenu"
:key="action.event"
density="compact"
@click="contextHandler(action.event)"
>
<v-list-item-title>
{{ action.text }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</v-col>
</v-row>
<v-row
v-if="!listItem.checked && recipeList && recipeList.length && displayRecipeRefs"
@@ -313,8 +314,4 @@ export default defineNuxtComponent({
.strike-through {
text-decoration: line-through !important;
}
.grow {
flex-grow: 1;
}
</style>

View File

@@ -10,11 +10,8 @@
:label="$t('form.quantity-label-abbreviated')"
:min="0"
:precision="null"
variant="plain"
control-variant="stacked"
inset
density="compact"
class="centered-number-input"
style="width: 100px;"
/>
</div>
@@ -242,10 +239,3 @@ export default defineNuxtComponent({
},
});
</script>
<style scoped>
.centered-number-input :deep(.v-field) {
display: flex;
align-items: center;
}
</style>

View File

@@ -31,7 +31,7 @@ export default defineNuxtComponent({
],
ALLOWED_ATTR: [
"href", "src", "alt", "height", "width", "class", "allow", "title", "allowfullscreen", "frameborder",
"scrolling", "cite", "datetime", "name", "abbr", "target", "border",
"scrolling", "cite", "datetime", "name", "abbr", "target", "border", "start",
],
});

View File

@@ -342,9 +342,9 @@
"breakfast": "Snídaně",
"lunch": "Oběd",
"dinner": "Večeře",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"snack": "Svačina",
"drink": "Nápoje",
"dessert": "Dezerty",
"type-any": "Libovolné",
"day-any": "Libovolný",
"editor": "Editor",
@@ -403,7 +403,7 @@
"title": "Recepty Tandoor"
},
"cookn": {
"description-long": "Mealie can import recipes from DVO Cook'n X3. Export a cookbook or menu in the \"Cook'n\" format, rename the export extension to .zip, then upload the .zip below.",
"description-long": "Mealie může importovat recept z DVO Cook'n X3. Exportujte kuchařku nebo menu ve formátu \"Cook'n\", přejmenujte rozšíření exportu na .zip, poté nahrajte .zip níže.",
"title": "DVO Cook'n X3"
},
"recipe-data-migrations": "Migrace dat receptů",
@@ -445,7 +445,7 @@
"upload-a-recipe": "Nahrát recept",
"upload-individual-zip-file": "Nahrát individuální .zip soubor exportovaný z jiné instance Mealie.",
"url-form-hint": "Zkopírujte a vložte odkaz z vaší oblíbené stránky s recepty",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"copy-and-paste-the-source-url-of-your-data-optional": "Zkopírujte a vložte zdrojovou adresu URL vašich dat (volitelné)",
"view-scraped-data": "Zobrazit scrapovaná data",
"trim-whitespace-description": "Oříznout počáteční a koncové mezery stejně jako prázdné řádky",
"trim-prefix-description": "Oříznout první znak z každé řádky",
@@ -453,8 +453,8 @@
"import-by-url": "Importovat recept podle URL",
"create-manually": "Vytvořit recept ručně",
"make-recipe-image": "Nastavit jako obrázek receptu",
"add-food": "Add Food",
"add-recipe": "Add Recipe"
"add-food": "Přidat jídlo",
"add-recipe": "Přidat recept"
},
"page": {
"404-page-not-found": "404 Stránka nebyla nalezena",
@@ -521,9 +521,9 @@
"recipe-deleted": "Recept smazán",
"recipe-image": "Obrázek receptu",
"recipe-image-updated": "Obrázek receptu aktualizován",
"delete-image": "Delete Recipe Image",
"delete-image-confirmation": "Are you sure you want to delete this recipe image?",
"recipe-image-deleted": "Recipe image deleted",
"delete-image": "Smazat recept",
"delete-image-confirmation": "Opravdu chcete smazat tento recept?",
"recipe-image-deleted": "Recept smazán",
"recipe-name": "Název receptu",
"recipe-settings": "Nastavení receptu",
"recipe-update-failed": "Aktualizace receptu se nezdařila",
@@ -569,7 +569,7 @@
"choose-unit": "Vybrat jednotku",
"press-enter-to-create": "Stiskněte enter pro vytvoření",
"choose-food": "Zvolte jídlo",
"choose-recipe": "Choose Recipe",
"choose-recipe": "Vybrat recept",
"notes": "Poznámky",
"toggle-section": "Přidat/odebrat název sekce",
"see-original-text": "Zobrazit původní text",
@@ -597,7 +597,7 @@
"made-this": "Toto jsem uvařil",
"how-did-it-turn-out": "Jak to dopadlo?",
"user-made-this": "{user} udělal toto",
"made-for-recipe": "Made for {recipe}",
"made-for-recipe": "Vytvořeno pro {recipe}",
"added-to-timeline": "Přidáno na časovou osu",
"failed-to-add-to-timeline": "Přidání na časovou osu selhalo",
"failed-to-update-recipe": "Aktualizace receptu selhala",
@@ -637,11 +637,11 @@
"scrape-recipe-suggest-bulk-importer": "Vyzkoušejte hromadný import",
"scrape-recipe-have-raw-html-or-json-data": "Máte surová data HTML nebo JSON?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Můžete importovat přímo ze surových dat",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"scrape-recipe-website-being-blocked": "Webové stránky jsou blokovány?",
"scrape-recipe-try-importing-raw-html-instead": "Zkuste namísto toho importovat raw HTML.",
"import-original-keywords-as-tags": "Importovat původní klíčová slova jako štítky",
"stay-in-edit-mode": "Zůstat v režimu úprav",
"parse-recipe-ingredients-after-import": "Parse recipe ingredients after import",
"parse-recipe-ingredients-after-import": "Po importu analyzovat ingredience receptu",
"import-from-zip": "Importovat ze zipu",
"import-from-zip-description": "Importovat jeden recept, který byl exportován z jiné instance Mealie.",
"import-from-html-or-json": "Importovat z HTML nebo JSON",
@@ -688,12 +688,12 @@
"this-unit-could-not-be-parsed-automatically": "Tuto jednotku nelze analyzovat automaticky",
"this-food-could-not-be-parsed-automatically": "Toto jídlo nelze analyzovat automaticky",
"no-food": "Žádné jídlo",
"review-parsed-ingredients": "Review parsed ingredients",
"confidence-score": "Confidence Score",
"ingredient-parser-description": "Your ingredients have been successfully parsed. Please review the ingredients we're not sure about.",
"ingredient-parser-final-review-description": "Once all ingredients have been reviewed, you'll have one more chance to review all ingredients before applying the changes to your recipe.",
"add-text-as-alias-for-item": "Add \"{text}\" as alias for {item}",
"delete-item": "Delete Item"
"review-parsed-ingredients": "Zkontrolovat analyzované ingredience",
"confidence-score": "Skóre spolehlivosti",
"ingredient-parser-description": "Vaše suroviny byly úspěšně analyzovány. Prosím zkontrolujte ingredience, o kterých si nejsme jisti.",
"ingredient-parser-final-review-description": "Jakmile budou všechny ingredience zkontrolovány, budete mít ještě jednu šanci zkontrolovat všechny ingredience před použitím změn ve vašem receptu.",
"add-text-as-alias-for-item": "Přidat \"{text}\" jako alias pro {item}",
"delete-item": "Odstranit položku"
},
"reset-servings-count": "Resetovat počet porcí",
"not-linked-ingredients": "Další ingredience",
@@ -702,9 +702,9 @@
"upload-more-images": "Nahrát více obrázků",
"set-as-cover-image": "Nastavit recept jako úvodní obrázek",
"cover-image": "Úvodní obrázek",
"include-linked-recipes": "Include Linked Recipes",
"include-linked-recipe-ingredients": "Include Linked Recipe Ingredients",
"toggle-recipe": "Toggle Recipe"
"include-linked-recipes": "Zahrnout připojené recepty",
"include-linked-recipe-ingredients": "Zahrnout připojené ingredience",
"toggle-recipe": "Přidat/odebrat název sekce"
},
"recipe-finder": {
"recipe-finder": "Vyhledávač receptů",
@@ -742,7 +742,7 @@
"advanced": "Pokročilé",
"auto-search": "Automatické vyhledávání",
"no-results": "Nebyly nalezeny žádné výsledky",
"type-to-search": "Type to search..."
"type-to-search": "Zadejte hledaný výraz..."
},
"settings": {
"add-a-new-theme": "Přidat nový motiv",
@@ -1081,8 +1081,8 @@
"forgot-password": "Zapomenuté heslo",
"forgot-password-text": "Zadejte prosím svou e-mailovou adresu a my vám zašleme odkaz pro obnovení hesla.",
"changes-reflected-immediately": "Změny tohoto uživatele budou okamžitě zohledněny.",
"default-activity": "Default Activity",
"default-activity-hint": "Select which page you'd like to navigate to upon logging in from this device"
"default-activity": "Výchozí aktivita",
"default-activity-hint": "Vyberte stránku, na kterou chcete přejít po přihlášení z tohoto zařízení"
},
"language-dialog": {
"translated": "přeloženo",
@@ -1430,11 +1430,11 @@
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
"required": "Toto pole je povinné",
"invalid-email": "E-mail musí být platný",
"invalid-url": "Musí být platná URL adresa",
"no-whitespace": "Prázdný prostor není povolen",
"min-length": "Musí být alespoň {min} znaků",
"max-length": "Musí být nejvíce {max} znaků"
}
}

View File

@@ -342,9 +342,9 @@
"breakfast": "Café da manhã",
"lunch": "Almoço",
"dinner": "Jantar",
"snack": "Snack",
"drink": "Drink",
"dessert": "Dessert",
"snack": "Lanche",
"drink": "Bebida",
"dessert": "Sobremesa",
"type-any": "Qualquer",
"day-any": "Qualquer",
"editor": "Editor",
@@ -445,7 +445,7 @@
"upload-a-recipe": "Enviar uma Receita",
"upload-individual-zip-file": "Enviar um arquivo .zip individual exportado a partir de outra instância do Mealie.",
"url-form-hint": "Copie e cole um link do seu site de receita favorito",
"copy-and-paste-the-source-url-of-your-data-optional": "Copy and paste the source URL of your data (optional)",
"copy-and-paste-the-source-url-of-your-data-optional": "Copie e cole o URL de origem dos seus dados (opcional)",
"view-scraped-data": "Visualizar Dados Rastreados",
"trim-whitespace-description": "Aparar o espaço em branco e à direita, bem como linhas em branco",
"trim-prefix-description": "Aparar primeiro caractere de cada linha",
@@ -637,8 +637,8 @@
"scrape-recipe-suggest-bulk-importer": "Tente o importador em massa",
"scrape-recipe-have-raw-html-or-json-data": "Tem dados HTML ou JSON brutos?",
"scrape-recipe-you-can-import-from-raw-data-directly": "Você pode importar diretamente de dados brutos",
"scrape-recipe-website-being-blocked": "Website being blocked?",
"scrape-recipe-try-importing-raw-html-instead": "Try importing the raw HTML instead.",
"scrape-recipe-website-being-blocked": "Site sendo bloqueado?",
"scrape-recipe-try-importing-raw-html-instead": "Tente importar o HTML ao invés disso.",
"import-original-keywords-as-tags": "Importar palavras-chave originais como marcadores",
"stay-in-edit-mode": "Permanecer no modo de edição",
"parse-recipe-ingredients-after-import": "Interpretar os ingredientes da receita após importar",
@@ -1430,11 +1430,11 @@
}
},
"validators": {
"required": "This Field is Required",
"invalid-email": "Email Must Be Valid",
"invalid-url": "Must Be A Valid URL",
"no-whitespace": "No Whitespace Allowed",
"min-length": "Must Be At Least {min} Characters",
"max-length": "Must Be At Most {max} Characters"
"required": "Este campo é obrigatório",
"invalid-email": "O e-mail deve ser válido",
"invalid-url": "Precisa ser uma URL válida",
"no-whitespace": "Nenhum espaço em branco é permitido",
"min-length": "Precisa ter pelo menos {min} caracteres",
"max-length": "Pode ter até {max} caracteres"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "mealie",
"version": "3.9.0",
"version": "3.9.2",
"private": true,
"scripts": {
"dev": "nuxt dev",

View File

@@ -5,7 +5,7 @@
"recipe": {
"unique-name-error": "Názvy receptů musí být jedinečné",
"recipe-created": "Recept byl vytvořen",
"recipe-image-deleted": "Recipe image deleted",
"recipe-image-deleted": "Recept smazán",
"recipe-defaults": {
"ingredient-note": "1 hrnek mouky",
"step-text": "Kroky receptu stejně jako další pole v receptu podporují syntaxi markdown.\n\n**Přidat odkaz**\n\n[Můj odkaz](https://demo.mealie.io)\n"
@@ -33,7 +33,7 @@
},
"exceptions": {
"permission_denied": "Nemáte oprávnění k provedení této akce",
"recursive-recipe-link": "A recipe cannot reference itself, either directly or indirectly",
"recursive-recipe-link": "Recept se nemůže sám sebe přímo ani nepřímo odkazovat",
"no-entry-found": "Požadovaný prostředek nebyl nalezen",
"integrity-error": "Chyba integrity databáze",
"username-conflict-error": "Toto uživatelské jméno je již používáno",
@@ -46,7 +46,7 @@
"generic-updated-with-url": "{name} byl aktualizován, {url}",
"generic-duplicated": "{name} byl duplikován",
"generic-deleted": "{name} byl odstraněn",
"logged-out": "Logged out"
"logged-out": "Odhlášení"
},
"datetime": {
"year": "rok|roky",

View File

@@ -340,7 +340,7 @@
"aliases": [],
"description": "",
"name": "chard",
"plural_name": "chards"
"plural_name": "chard"
},
"pimiento": {
"aliases": [],
@@ -2626,7 +2626,7 @@
"aliases": [],
"description": "",
"name": "milk",
"plural_name": "milks"
"plural_name": "milk"
},
"heavy cream": {
"aliases": [],
@@ -2644,7 +2644,7 @@
"aliases": [],
"description": "",
"name": "buttermilk",
"plural_name": "buttermilks"
"plural_name": "buttermilk"
},
"yogurt": {
"aliases": [],
@@ -2688,7 +2688,7 @@
"aliases": [],
"description": "",
"name": "condensed milk",
"plural_name": "condensed milks"
"plural_name": "condensed milk"
},
"half and half": {
"aliases": [],
@@ -2700,7 +2700,7 @@
"aliases": [],
"description": "",
"name": "sweetened condensed milk",
"plural_name": "sweetened condensed milks"
"plural_name": "sweetened condensed milk"
},
"ice cream": {
"aliases": [],
@@ -2730,7 +2730,7 @@
"aliases": [],
"description": "",
"name": "milk powder",
"plural_name": "milk powders"
"plural_name": "milk powder"
},
"curd": {
"aliases": [],
@@ -2784,7 +2784,7 @@
"aliases": [],
"description": "",
"name": "chocolate milk",
"plural_name": "chocolate milks"
"plural_name": "chocolate milk"
},
"liquid egg substitute": {
"aliases": [],
@@ -2814,7 +2814,7 @@
"aliases": [],
"description": "",
"name": "buttermilk powder",
"plural_name": "buttermilk powders"
"plural_name": "buttermilk powder"
},
"frozen yogurt": {
"aliases": [],
@@ -2832,7 +2832,7 @@
"aliases": [],
"description": "",
"name": "milk cream",
"plural_name": "milk creams"
"plural_name": "milk cream"
},
"coffee creamer": {
"aliases": [],
@@ -2850,7 +2850,7 @@
"aliases": [],
"description": "",
"name": "goat milk",
"plural_name": "goat milks"
"plural_name": "goat milk"
},
"cheese curd": {
"aliases": [],
@@ -2862,7 +2862,7 @@
"aliases": [],
"description": "",
"name": "sour milk",
"plural_name": "sour milks"
"plural_name": "sour milk"
},
"ganache": {
"aliases": [],
@@ -2904,7 +2904,7 @@
"aliases": [],
"description": "",
"name": "raw milk",
"plural_name": "raw milks"
"plural_name": "raw milk"
},
"lime curd": {
"aliases": [],
@@ -3024,7 +3024,7 @@
"aliases": [],
"description": "",
"name": "chocolate milk powder",
"plural_name": "chocolate milk powders"
"plural_name": "chocolate milk powder"
},
"liquid rennet": {
"aliases": [],
@@ -3035,14 +3035,14 @@
"sheeps milk yoghurt": {
"aliases": [],
"description": "",
"name": "sheeps milk yoghurt",
"plural_name": "sheeps milk yoghurts"
"name": "sheep's milk yoghurt",
"plural_name": "sheep's milk yoghurt"
},
"strawberry milk": {
"aliases": [],
"description": "",
"name": "strawberry milk",
"plural_name": "strawberry milks"
"plural_name": "strawberry milk"
},
"ayran": {
"aliases": [],
@@ -3078,7 +3078,7 @@
"aliases": [],
"description": "",
"name": "sheep milk",
"plural_name": "sheep milks"
"plural_name": "sheep milk"
},
"starter culture": {
"aliases": [],
@@ -3102,7 +3102,7 @@
"aliases": [],
"description": "",
"name": "vanilla milk",
"plural_name": "vanilla milks"
"plural_name": "vanilla milk"
},
"yoplait whip": {
"aliases": [],
@@ -3114,7 +3114,7 @@
"aliases": [],
"description": "",
"name": "buffalo milk",
"plural_name": "buffalo milks"
"plural_name": "buffalo milk"
},
"goat kefir": {
"aliases": [],
@@ -3136,13 +3136,13 @@
"aliases": [],
"description": "",
"name": "coconut milk",
"plural_name": "coconut milks"
"plural_name": "coconut milk"
},
"almond milk": {
"aliases": [],
"description": "",
"name": "almond milk",
"plural_name": "almond milks"
"plural_name": "almond milk"
},
"almond butter": {
"aliases": [],
@@ -3172,13 +3172,13 @@
"aliases": [],
"description": "",
"name": "non-dairy milk",
"plural_name": "non-dairy milks"
"plural_name": "non-dairy milk"
},
"soy milk": {
"aliases": [],
"description": "",
"name": "soy milk",
"plural_name": "soy milks"
"plural_name": "soy milk"
},
"extra firm tofu": {
"aliases": [],
@@ -3262,13 +3262,13 @@
"aliases": [],
"description": "",
"name": "cashew milk",
"plural_name": "cashew milks"
"plural_name": "cashew milk"
},
"oat milk": {
"aliases": [],
"description": "",
"name": "oat milk",
"plural_name": "oat milks"
"plural_name": "oat milk"
},
"nut butter": {
"aliases": [],
@@ -3358,7 +3358,7 @@
"aliases": [],
"description": "",
"name": "coconut milk powder",
"plural_name": "coconut milk powders"
"plural_name": "coconut milk powder"
},
"non-dairy whipped topping": {
"aliases": [],
@@ -3370,7 +3370,7 @@
"aliases": [],
"description": "",
"name": "nut milk",
"plural_name": "nut milks"
"plural_name": "nut milk"
},
"non-dairy cream": {
"aliases": [],
@@ -3388,7 +3388,7 @@
"aliases": [],
"description": "",
"name": "condensed coconut milk",
"plural_name": "condensed coconut milks"
"plural_name": "condensed coconut milk"
},
"vegan ground beef": {
"aliases": [],
@@ -3442,7 +3442,7 @@
"aliases": [],
"description": "",
"name": "hemp milk",
"plural_name": "hemp milks"
"plural_name": "hemp milk"
},
"vegan beef": {
"aliases": [],
@@ -3490,7 +3490,7 @@
"aliases": [],
"description": "",
"name": "almond-coconut milk",
"plural_name": "almond-coconut milks"
"plural_name": "almond-coconut milk"
},
"banana blossom": {
"aliases": [],
@@ -3514,7 +3514,7 @@
"aliases": [],
"description": "",
"name": "hazelnut milk",
"plural_name": "hazelnut milks"
"plural_name": "hazelnut milk"
},
"maple almond butter": {
"aliases": [],
@@ -3531,8 +3531,8 @@
"almond-milk yogurt": {
"aliases": [],
"description": "",
"name": "almond-milk yogurt",
"plural_name": "almond-milk yogurts"
"name": "almond-milk yoghurt",
"plural_name": "almond-milk yoghurt"
},
"almond creamer": {
"aliases": [],
@@ -3544,7 +3544,7 @@
"aliases": [],
"description": "",
"name": "soy milk powder",
"plural_name": "soy milk powders"
"plural_name": "soy milk powder"
},
"vegan cream cheese frosting": {
"aliases": [],
@@ -3592,7 +3592,7 @@
"aliases": [],
"description": "",
"name": "flax milk",
"plural_name": "flax milks"
"plural_name": "flax milk"
},
"hazelnut creamer": {
"aliases": [],
@@ -3670,7 +3670,7 @@
"aliases": [],
"description": "",
"name": "macadamia milk",
"plural_name": "macadamia milks"
"plural_name": "macadamia milk"
},
"vegan taco meat": {
"aliases": [],
@@ -3694,7 +3694,7 @@
"aliases": [],
"description": "",
"name": "banana milk",
"plural_name": "banana milks"
"plural_name": "banana milk"
},
"soy quark": {
"aliases": [],
@@ -3718,7 +3718,7 @@
"aliases": [],
"description": "",
"name": "walnut milk",
"plural_name": "walnut milks"
"plural_name": "walnut milk"
},
"latik": {
"aliases": [],
@@ -6995,7 +6995,7 @@
"aliases": [],
"description": "",
"name": "buttermilk syrup",
"plural_name": "buttermilk syrups"
"plural_name": "buttermilk syrup"
},
"chocolate sugar": {
"aliases": [],
@@ -7275,13 +7275,13 @@
"aliases": [],
"description": "",
"name": "garlic & herb seasoning",
"plural_name": "garlic & herb seasonings"
"plural_name": "garlic & herb seasoning"
},
"garlic-pepper seasoning": {
"aliases": [],
"description": "",
"name": "garlic-pepper seasoning",
"plural_name": "garlic-pepper seasonings"
"plural_name": "garlic-pepper seasoning"
},
"pickling salt": {
"aliases": [],
@@ -11331,7 +11331,7 @@
"aliases": [],
"description": "",
"name": "garlic mayonnaise",
"plural_name": "garlic mayonnaises"
"plural_name": "garlic mayonnaise"
},
"brown rice vinegar": {
"aliases": [],
@@ -11355,7 +11355,7 @@
"aliases": [],
"description": "",
"name": "buttermilk ranch dressing",
"plural_name": "buttermilk ranch dressings"
"plural_name": "buttermilk ranch dressing"
},
"cilantro dressing": {
"aliases": [],
@@ -11575,7 +11575,7 @@
"aliases": [],
"description": "",
"name": "ginger-garlic paste",
"plural_name": "ginger-garlic pastes"
"plural_name": "ginger-garlic paste"
},
"brown mustard": {
"aliases": [],
@@ -11641,7 +11641,7 @@
"aliases": [],
"description": "",
"name": "chili-garlic sauce",
"plural_name": "chili-garlic sauces"
"plural_name": "chili-garlic sauce"
},
"tamarind paste": {
"aliases": [],
@@ -11755,7 +11755,7 @@
"aliases": [],
"description": "",
"name": "chili-garlic paste",
"plural_name": "chili-garlic pastes"
"plural_name": "chili-garlic paste"
},
"bonito flake": {
"aliases": [],
@@ -13187,7 +13187,7 @@
"aliases": [],
"description": "",
"name": "garlic spread",
"plural_name": "garlic spreads"
"plural_name": "garlic spread"
},
"black pepper sauce": {
"aliases": [],
@@ -15264,7 +15264,7 @@
"aliases": [],
"description": "",
"name": "malted milk powder",
"plural_name": "malted milk powders"
"plural_name": "malted milk powder"
},
"mango juice": {
"aliases": [],
@@ -16018,7 +16018,7 @@
"aliases": [],
"description": "",
"name": "cannabis milk",
"plural_name": "cannabis milks"
"plural_name": "cannabis milk"
},
"malt extract": {
"aliases": [],

View File

@@ -5043,14 +5043,14 @@
"hake": {
"aliases": [],
"description": "",
"name": "hake",
"plural_name": "hakes"
"name": "merlu",
"plural_name": "merlus"
},
"pollock": {
"aliases": [],
"description": "",
"name": "pollock",
"plural_name": "pollocks"
"name": "lieu noir",
"plural_name": "lieu noir"
},
"salt cod": {
"aliases": [],
@@ -5217,8 +5217,8 @@
"pike": {
"aliases": [],
"description": "",
"name": "pike",
"plural_name": "pikes"
"name": "brochet",
"plural_name": "brochets"
},
"pickled herring": {
"aliases": [],
@@ -5469,8 +5469,8 @@
"tiny fish": {
"aliases": [],
"description": "",
"name": "tiny fish",
"plural_name": "tiny fish"
"name": "petit poisson",
"plural_name": "petits poissons"
},
"tuna belly": {
"aliases": [],
@@ -5569,8 +5569,8 @@
"crawfish": {
"aliases": [],
"description": "",
"name": "crawfish",
"plural_name": "crawfish"
"name": "écrevisse",
"plural_name": "écrevisses"
},
"octopu": {
"aliases": [],
@@ -5635,8 +5635,8 @@
"squid ink": {
"aliases": [],
"description": "",
"name": "squid ink",
"plural_name": "squid inks"
"name": "encre de seiche",
"plural_name": "encres de seiches"
},
"dried prawn": {
"aliases": [],
@@ -5719,8 +5719,8 @@
"sea urchin": {
"aliases": [],
"description": "",
"name": "sea urchin",
"plural_name": "sea urchins"
"name": "oursin",
"plural_name": "oursins"
},
"abalone": {
"aliases": [],
@@ -5743,8 +5743,8 @@
"smoked mussel": {
"aliases": [],
"description": "",
"name": "smoked mussel",
"plural_name": "smoked mussels"
"name": "moule fumée",
"plural_name": "moules fumées"
},
"sea snail": {
"aliases": [],
@@ -5761,8 +5761,8 @@
"prepared crab cake": {
"aliases": [],
"description": "",
"name": "prepared crab cake",
"plural_name": "prepared crab cakes"
"name": "gâteau de crabe",
"plural_name": "gâteaux de crabe"
},
"sea lettuce": {
"aliases": [],
@@ -6095,8 +6095,8 @@
"chipotle": {
"aliases": [],
"description": "",
"name": "chipotle",
"plural_name": "chipotles"
"name": "piment chipotle",
"plural_name": "piments chipotles"
},
"fenugreek": {
"aliases": [],
@@ -6107,8 +6107,8 @@
"onion flake": {
"aliases": [],
"description": "",
"name": "onion flake",
"plural_name": "onion flakes"
"name": "oignon émincé",
"plural_name": "oignons émincés"
},
"matcha powder": {
"aliases": [],
@@ -6263,8 +6263,8 @@
"garlic flake": {
"aliases": [],
"description": "",
"name": "garlic flake",
"plural_name": "garlic flakes"
"name": "ail émincé",
"plural_name": "aulx émincés"
},
"dried cilantro": {
"aliases": [],
@@ -15705,7 +15705,7 @@
"almond extract": {
"aliases": [],
"description": "",
"name": "extrait d'amandes",
"name": "extrait d'amande",
"plural_name": "extraits d'amandes"
},
"food coloring": {

View File

@@ -4,7 +4,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import sqlalchemy as sa
from PIL import Image
from PIL import Image, UnidentifiedImageError
from pydantic import UUID4
from mealie.core import root_logger
@@ -84,7 +84,12 @@ def reprocess_recipe_images(recipe_id: UUID4, force_all: bool = False) -> None:
image_file = service.dir_image / image_filename
image_file.unlink(missing_ok=True)
service.minifier.minify(original_image, force=True)
try:
service.minifier.minify(original_image, force=True)
except UnidentifiedImageError:
pass # source image is corrupted or invalid; skip
except Exception:
logger.exception(f"Failed to reprocess images for recipe {recipe_id}")
# Reprocess timeline event images
timeline_dir = service.dir_image_timeline

View File

@@ -1,6 +1,6 @@
[project]
name = "mealie"
version = "3.9.0"
version = "3.9.2"
description = "A Recipe Manager"
authors = [{ name = "Hayden", email = "hay-kot@pm.me" }]
license = "AGPL-3.0-only"

2
uv.lock generated
View File

@@ -811,7 +811,7 @@ wheels = [
[[package]]
name = "mealie"
version = "3.9.0"
version = "3.9.2"
source = { editable = "." }
dependencies = [
{ name = "aiofiles" },